1use super::base::{Part, PartType, ContentType};
6use crate::exc::PptxError;
7
8#[derive(Debug, Clone)]
10pub struct ThemeColor {
11 pub name: String,
12 pub value: String, }
14
15impl ThemeColor {
16 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
17 ThemeColor {
18 name: name.into(),
19 value: value.into(),
20 }
21 }
22}
23
24#[derive(Debug, Clone)]
26pub struct ThemeFont {
27 pub typeface: String,
28 pub panose: Option<String>,
29}
30
31impl ThemeFont {
32 pub fn new(typeface: impl Into<String>) -> Self {
33 ThemeFont {
34 typeface: typeface.into(),
35 panose: None,
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct ThemePart {
43 path: String,
44 theme_number: usize,
45 name: String,
46 major_font: ThemeFont,
47 minor_font: ThemeFont,
48 colors: Vec<ThemeColor>,
49 xml_content: Option<String>,
50}
51
52impl ThemePart {
53 pub fn new(theme_number: usize) -> Self {
55 ThemePart {
56 path: format!("ppt/theme/theme{}.xml", theme_number),
57 theme_number,
58 name: "Office Theme".to_string(),
59 major_font: ThemeFont::new("Calibri Light"),
60 minor_font: ThemeFont::new("Calibri"),
61 colors: Self::default_colors(),
62 xml_content: None,
63 }
64 }
65
66 fn default_colors() -> Vec<ThemeColor> {
67 vec![
68 ThemeColor::new("dk1", "000000"),
69 ThemeColor::new("lt1", "FFFFFF"),
70 ThemeColor::new("dk2", "44546A"),
71 ThemeColor::new("lt2", "E7E6E6"),
72 ThemeColor::new("accent1", "4472C4"),
73 ThemeColor::new("accent2", "ED7D31"),
74 ThemeColor::new("accent3", "A5A5A5"),
75 ThemeColor::new("accent4", "FFC000"),
76 ThemeColor::new("accent5", "5B9BD5"),
77 ThemeColor::new("accent6", "70AD47"),
78 ThemeColor::new("hlink", "0563C1"),
79 ThemeColor::new("folHlink", "954F72"),
80 ]
81 }
82
83 pub fn theme_number(&self) -> usize {
85 self.theme_number
86 }
87
88 pub fn name(&self) -> &str {
90 &self.name
91 }
92
93 pub fn set_name(&mut self, name: impl Into<String>) {
95 self.name = name.into();
96 }
97
98 pub fn set_major_font(&mut self, typeface: impl Into<String>) {
100 self.major_font = ThemeFont::new(typeface);
101 }
102
103 pub fn set_minor_font(&mut self, typeface: impl Into<String>) {
105 self.minor_font = ThemeFont::new(typeface);
106 }
107
108 pub fn set_color(&mut self, name: impl Into<String>, value: impl Into<String>) {
110 let name = name.into();
111 if let Some(color) = self.colors.iter_mut().find(|c| c.name == name) {
112 color.value = value.into();
113 } else {
114 self.colors.push(ThemeColor::new(name, value));
115 }
116 }
117
118 pub fn rel_target(&self) -> String {
120 format!("../theme/theme{}.xml", self.theme_number)
121 }
122
123 fn generate_xml(&self) -> String {
124 let colors_xml: String = self.colors.iter()
125 .map(|c| format!(r#"<a:{} val="{}"><a:srgbClr val="{}"/></a:{}>"#, c.name, c.name, c.value, c.name))
126 .collect::<Vec<_>>()
127 .join("\n ");
128
129 format!(
130 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
131<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="{}">
132 <a:themeElements>
133 <a:clrScheme name="Office">
134 {}
135 </a:clrScheme>
136 <a:fontScheme name="Office">
137 <a:majorFont>
138 <a:latin typeface="{}"/>
139 <a:ea typeface=""/>
140 <a:cs typeface=""/>
141 </a:majorFont>
142 <a:minorFont>
143 <a:latin typeface="{}"/>
144 <a:ea typeface=""/>
145 <a:cs typeface=""/>
146 </a:minorFont>
147 </a:fontScheme>
148 <a:fmtScheme name="Office">
149 <a:fillStyleLst>
150 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
151 <a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/><a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="16200000" scaled="1"/></a:gradFill>
152 <a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:shade val="51000"/><a:satMod val="130000"/></a:schemeClr></a:gs><a:gs pos="80000"><a:schemeClr val="phClr"><a:shade val="93000"/><a:satMod val="130000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="94000"/><a:satMod val="135000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="16200000" scaled="0"/></a:gradFill>
153 </a:fillStyleLst>
154 <a:lnStyleLst>
155 <a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln>
156 <a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln>
157 <a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln>
158 </a:lnStyleLst>
159 <a:effectStyleLst>
160 <a:effectStyle><a:effectLst/></a:effectStyle>
161 <a:effectStyle><a:effectLst/></a:effectStyle>
162 <a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle>
163 </a:effectStyleLst>
164 <a:bgFillStyleLst>
165 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
166 <a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill>
167 <a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill>
168 </a:bgFillStyleLst>
169 </a:fmtScheme>
170 </a:themeElements>
171 <a:objectDefaults/>
172 <a:extraClrSchemeLst/>
173</a:theme>"#,
174 self.name,
175 colors_xml,
176 self.major_font.typeface,
177 self.minor_font.typeface
178 )
179 }
180}
181
182impl Part for ThemePart {
183 fn path(&self) -> &str {
184 &self.path
185 }
186
187 fn part_type(&self) -> PartType {
188 PartType::Theme
189 }
190
191 fn content_type(&self) -> ContentType {
192 ContentType::Theme
193 }
194
195 fn to_xml(&self) -> Result<String, PptxError> {
196 if let Some(ref xml) = self.xml_content {
197 return Ok(xml.clone());
198 }
199 Ok(self.generate_xml())
200 }
201
202 fn from_xml(xml: &str) -> Result<Self, PptxError> {
203 Ok(ThemePart {
204 path: "ppt/theme/theme1.xml".to_string(),
205 theme_number: 1,
206 name: "Office Theme".to_string(),
207 major_font: ThemeFont::new("Calibri Light"),
208 minor_font: ThemeFont::new("Calibri"),
209 colors: Self::default_colors(),
210 xml_content: Some(xml.to_string()),
211 })
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_theme_new() {
221 let theme = ThemePart::new(1);
222 assert_eq!(theme.theme_number(), 1);
223 assert_eq!(theme.path(), "ppt/theme/theme1.xml");
224 assert_eq!(theme.name(), "Office Theme");
225 }
226
227 #[test]
228 fn test_theme_set_fonts() {
229 let mut theme = ThemePart::new(1);
230 theme.set_major_font("Arial");
231 theme.set_minor_font("Times New Roman");
232 let xml = theme.to_xml().unwrap();
233 assert!(xml.contains("Arial"));
234 assert!(xml.contains("Times New Roman"));
235 }
236
237 #[test]
238 fn test_theme_set_color() {
239 let mut theme = ThemePart::new(1);
240 theme.set_color("accent1", "FF0000");
241 let xml = theme.to_xml().unwrap();
242 assert!(xml.contains("FF0000"));
243 }
244
245 #[test]
246 fn test_theme_to_xml() {
247 let theme = ThemePart::new(1);
248 let xml = theme.to_xml().unwrap();
249 assert!(xml.contains("a:theme"));
250 assert!(xml.contains("a:clrScheme"));
251 assert!(xml.contains("a:fontScheme"));
252 }
253
254 #[test]
255 fn test_theme_rel_target() {
256 let theme = ThemePart::new(1);
257 assert_eq!(theme.rel_target(), "../theme/theme1.xml");
258 }
259}