1use super::base::{ContentType, Part, PartType};
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
125 .colors
126 .iter()
127 .map(|c| {
128 format!(
129 r#"<a:{} val="{}"><a:srgbClr val="{}"/></a:{}>"#,
130 c.name, c.name, c.value, c.name
131 )
132 })
133 .collect::<Vec<_>>()
134 .join("\n ");
135
136 format!(
137 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
138<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="{}">
139 <a:themeElements>
140 <a:clrScheme name="Office">
141 {}
142 </a:clrScheme>
143 <a:fontScheme name="Office">
144 <a:majorFont>
145 <a:latin typeface="{}"/>
146 <a:ea typeface=""/>
147 <a:cs typeface=""/>
148 </a:majorFont>
149 <a:minorFont>
150 <a:latin typeface="{}"/>
151 <a:ea typeface=""/>
152 <a:cs typeface=""/>
153 </a:minorFont>
154 </a:fontScheme>
155 <a:fmtScheme name="Office">
156 <a:fillStyleLst>
157 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
158 <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>
159 <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>
160 </a:fillStyleLst>
161 <a:lnStyleLst>
162 <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>
163 <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>
164 <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>
165 </a:lnStyleLst>
166 <a:effectStyleLst>
167 <a:effectStyle><a:effectLst/></a:effectStyle>
168 <a:effectStyle><a:effectLst/></a:effectStyle>
169 <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>
170 </a:effectStyleLst>
171 <a:bgFillStyleLst>
172 <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
173 <a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill>
174 <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>
175 </a:bgFillStyleLst>
176 </a:fmtScheme>
177 </a:themeElements>
178 <a:objectDefaults/>
179 <a:extraClrSchemeLst/>
180</a:theme>"#,
181 self.name, colors_xml, self.major_font.typeface, self.minor_font.typeface
182 )
183 }
184}
185
186impl Part for ThemePart {
187 fn path(&self) -> &str {
188 &self.path
189 }
190
191 fn part_type(&self) -> PartType {
192 PartType::Theme
193 }
194
195 fn content_type(&self) -> ContentType {
196 ContentType::Theme
197 }
198
199 fn to_xml(&self) -> Result<String, PptxError> {
200 if let Some(ref xml) = self.xml_content {
201 return Ok(xml.clone());
202 }
203 Ok(self.generate_xml())
204 }
205
206 fn from_xml(xml: &str) -> Result<Self, PptxError> {
207 Ok(ThemePart {
208 path: "ppt/theme/theme1.xml".to_string(),
209 theme_number: 1,
210 name: "Office Theme".to_string(),
211 major_font: ThemeFont::new("Calibri Light"),
212 minor_font: ThemeFont::new("Calibri"),
213 colors: Self::default_colors(),
214 xml_content: Some(xml.to_string()),
215 })
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_theme_new() {
225 let theme = ThemePart::new(1);
226 assert_eq!(theme.theme_number(), 1);
227 assert_eq!(theme.path(), "ppt/theme/theme1.xml");
228 assert_eq!(theme.name(), "Office Theme");
229 }
230
231 #[test]
232 fn test_theme_set_fonts() {
233 let mut theme = ThemePart::new(1);
234 theme.set_major_font("Arial");
235 theme.set_minor_font("Times New Roman");
236 let xml = theme.to_xml().unwrap();
237 assert!(xml.contains("Arial"));
238 assert!(xml.contains("Times New Roman"));
239 }
240
241 #[test]
242 fn test_theme_set_color() {
243 let mut theme = ThemePart::new(1);
244 theme.set_color("accent1", "FF0000");
245 let xml = theme.to_xml().unwrap();
246 assert!(xml.contains("FF0000"));
247 }
248
249 #[test]
250 fn test_theme_to_xml() {
251 let theme = ThemePart::new(1);
252 let xml = theme.to_xml().unwrap();
253 assert!(xml.contains("a:theme"));
254 assert!(xml.contains("a:clrScheme"));
255 assert!(xml.contains("a:fontScheme"));
256 }
257
258 #[test]
259 fn test_theme_rel_target() {
260 let theme = ThemePart::new(1);
261 assert_eq!(theme.rel_target(), "../theme/theme1.xml");
262 }
263}