1use oxml_core::OxmlError;
2use oxml_core::raw_xml::{capture_element, capture_empty_element};
3use oxml_core::xml::{local_name, matches_local_name};
4use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
5use quick_xml::{Reader, Writer, XmlVersion};
6use std::fmt;
7
8use crate::color::{ColorChoice, ColorError, ThemeColorSlot};
9use crate::effect::{
10 CT_EffectList, EffectError, raw_contains_placeholder_color, raw_is_effect_dag,
11};
12use crate::fill::{Fill, FillError};
13use crate::line::{CT_LineProperties, LineError};
14use crate::namespace::A_NS;
15use crate::order::OrderedRawChildren;
16
17#[cfg(test)]
18mod tests {
19 use std::fs;
20 use std::path::{Path, PathBuf};
21 use std::process::Command;
22
23 use oxml_opc::OpcPackage;
24
25 use crate::color::{ColorChoice, RgbColor};
26 use crate::order::OrderedRawChildren;
27
28 use super::{CT_OfficeStyleSheet, OFFICE_DEFAULT_XML};
29
30 const POWERPOINT_VERSION: &str = "16.104";
31 const POWERPOINT_BUILD: &str = "16.104.25121423";
32 const POWERPOINT_APP_BUILD: &str = "1214";
33
34 #[test]
35 fn powerpoint_office_theme_round_trips_structurally() {
36 let theme = CT_OfficeStyleSheet::from_xml(OFFICE_DEFAULT_XML.as_bytes()).unwrap();
37 let reparsed = CT_OfficeStyleSheet::from_xml(&theme.to_xml().unwrap()).unwrap();
38
39 assert_eq!(reparsed, theme);
40 }
41
42 #[test]
43 #[ignore = "requires RDOCX_POWERPOINT_ORACLE_SHELL and pinned Microsoft PowerPoint"]
44 fn office_default_theme_is_accepted_by_powerpoint() {
45 assert_powerpoint_build();
46 let shell = PathBuf::from(
47 std::env::var_os("RDOCX_POWERPOINT_ORACLE_SHELL")
48 .expect("RDOCX_POWERPOINT_ORACLE_SHELL must name a PowerPoint-authored PPTX"),
49 );
50 let output = std::env::temp_dir().join(format!(
51 "rdocx-f065-office-default-{}.pptx",
52 std::process::id()
53 ));
54 let mut package = OpcPackage::open(&shell).unwrap();
55 package.set_part(
56 "/ppt/theme/theme1.xml",
57 CT_OfficeStyleSheet::office_default().to_xml().unwrap(),
58 );
59 package.save(&output).unwrap();
60 open_without_repair(&output);
61 fs::remove_file(output).unwrap();
62 }
63
64 #[test]
65 fn theme_reads_any_prefix_and_writes_fixed_a_prefix_in_schema_order() {
66 let xml = minimal_theme()
67 .replace("name=\"Test\" custom", "name=\"Test & Co\" custom")
68 .replace("typeface=\"Major\"", "typeface=\"Major & Display\"");
69 let theme = CT_OfficeStyleSheet::from_xml(xml.as_bytes()).unwrap();
70 assert_eq!(theme.name.as_deref(), Some("Test & Co"));
71 assert_eq!(
72 theme.theme_elements.font_scheme.major_font.latin.typeface,
73 "Major & Display"
74 );
75 let written = String::from_utf8(theme.to_xml().unwrap()).unwrap();
76 let reparsed = CT_OfficeStyleSheet::from_xml(written.as_bytes()).unwrap();
77
78 assert_eq!(reparsed, theme);
79 assert!(
80 written.starts_with(
81 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><a:theme"
82 )
83 );
84 assert!(written.contains("<a:themeElements><a:clrScheme"));
85 assert!(written.find("<a:clrScheme").unwrap() < written.find("<a:fontScheme").unwrap());
86 assert!(written.find("<a:fontScheme").unwrap() < written.find("<a:fmtScheme").unwrap());
87 assert!(!written.contains("<z:themeElements"));
88 }
89
90 #[test]
91 fn format_scheme_preserves_all_four_style_lists_in_order() {
92 let theme = CT_OfficeStyleSheet::office_default();
93 let matrix = &theme.theme_elements.format_scheme;
94
95 assert_eq!(matrix.fill_styles.len(), 3);
96 assert_eq!(matrix.line_styles.len(), 3);
97 assert_eq!(matrix.effect_styles.len(), 3);
98 assert_eq!(matrix.background_fill_styles.len(), 3);
99 let written = String::from_utf8(theme.to_xml().unwrap()).unwrap();
100 assert!(written.find("<a:fillStyleLst").unwrap() < written.find("<a:lnStyleLst").unwrap());
101 assert!(
102 written.find("<a:lnStyleLst").unwrap() < written.find("<a:effectStyleLst").unwrap()
103 );
104 assert!(
105 written.find("<a:effectStyleLst").unwrap() < written.find("<a:bgFillStyleLst").unwrap()
106 );
107 }
108
109 #[test]
110 fn format_scheme_without_name_round_trips_without_inventing_attribute() {
111 let xml = minimal_theme().replace(
112 "<z:fmtScheme name=\"Test\">",
113 "<z:fmtScheme custom=\"preserved\">",
114 );
115 let theme = CT_OfficeStyleSheet::from_xml(xml.as_bytes()).unwrap();
116 assert_eq!(theme.theme_elements.format_scheme.name, None);
117
118 let written = String::from_utf8(theme.to_xml().unwrap()).unwrap();
119 let format_scheme = written
120 .split_once("<a:fmtScheme")
121 .unwrap()
122 .1
123 .split_once('>')
124 .unwrap()
125 .0;
126 assert!(!format_scheme.contains("name="));
127 assert!(format_scheme.contains("custom=\"preserved\""));
128 assert_eq!(
129 CT_OfficeStyleSheet::from_xml(written.as_bytes()).unwrap(),
130 theme
131 );
132 }
133
134 #[test]
135 fn unknown_theme_children_round_trip_byte_for_byte_in_place() {
136 let xml = minimal_theme()
137 .replace(
138 "<z:clrScheme",
139 "<x:before id=\"1\"><x:child>one & two</x:child><!--note--></x:before><z:clrScheme",
140 )
141 .replace("</z:clrScheme>", "</z:clrScheme><x:between x:id=\"2\"/>")
142 .replace("</z:fmtScheme>", "</z:fmtScheme><x:after>kept</x:after>");
143 let written = CT_OfficeStyleSheet::from_xml(xml.as_bytes())
144 .unwrap()
145 .to_xml()
146 .unwrap();
147
148 assert!(contains(
149 &written,
150 br#"<x:before id="1"><x:child>one & two</x:child><!--note--></x:before>"#
151 ));
152 assert!(contains(&written, br#"<x:between x:id="2"/>"#));
153 assert!(contains(&written, br#"<x:after>kept</x:after>"#));
154 assert!(contains(&written, br#"custom="root""#));
155 }
156
157 #[test]
158 fn qualified_extension_attributes_do_not_replace_unqualified_theme_attributes() {
159 let xml = minimal_theme()
160 .replace(
161 "name=\"Test\" custom=\"root\"",
162 "name=\"Test\" x:name=\"extension\" custom=\"root\"",
163 )
164 .replace(
165 "<z:themeElements>",
166 "<x:extension x:name=\"child\"/><z:themeElements>",
167 );
168 let theme = CT_OfficeStyleSheet::from_xml(xml.as_bytes()).unwrap();
169 let written = theme.to_xml().unwrap();
170 let reparsed = CT_OfficeStyleSheet::from_xml(&written).unwrap();
171
172 assert_eq!(reparsed, theme);
173 assert!(contains(&written, br#"xmlns:x="urn:test""#));
174 assert!(contains(&written, br#"x:name="extension""#));
175 assert!(contains(&written, br#"<x:extension x:name="child"/>"#));
176
177 let missing_unqualified = minimal_theme().replace(
178 "<z:clrScheme name=\"Test\">",
179 "<z:clrScheme x:name=\"extension\">",
180 );
181 assert!(CT_OfficeStyleSheet::from_xml(missing_unqualified.as_bytes()).is_err());
182 }
183
184 #[test]
185 fn office_default_contains_the_standard_colour_font_and_style_contract() {
186 let theme = CT_OfficeStyleSheet::office_default();
187 let elements = &theme.theme_elements;
188
189 assert_eq!(elements.color_scheme.iter().count(), 12);
190 assert_eq!(
191 elements.font_scheme.major_font.latin.typeface,
192 "Aptos Display"
193 );
194 assert_eq!(elements.font_scheme.minor_font.latin.typeface, "Aptos");
195 assert!(elements.font_scheme.major_font.supplemental_fonts.len() > 40);
196 assert!(elements.font_scheme.minor_font.supplemental_fonts.len() > 40);
197 assert_eq!(elements.format_scheme.fill_styles.len(), 3);
198 assert_eq!(elements.format_scheme.line_styles.len(), 3);
199 assert_eq!(elements.format_scheme.effect_styles.len(), 3);
200 assert_eq!(elements.format_scheme.background_fill_styles.len(), 3);
201 }
202
203 #[test]
204 fn shared_theme_adapter_matches_the_legacy_theme_projection() {
205 let shared = CT_OfficeStyleSheet::from_xml(OFFICE_DEFAULT_XML.as_bytes()).unwrap();
206 let legacy = rdocx_oxml::theme::Theme::from_xml(OFFICE_DEFAULT_XML.as_bytes()).unwrap();
207 let projected = rdocx_oxml::theme::Theme::from(&shared);
208
209 for slot in [
210 "dk1", "dk2", "lt1", "lt2", "accent1", "accent2", "accent3", "accent4", "accent5",
211 "accent6", "hlink", "folHlink",
212 ] {
213 assert_eq!(projected.colors.get(slot), legacy.colors.get(slot));
214 }
215 assert_eq!(projected.major_font, legacy.major_font);
216 assert_eq!(projected.minor_font, legacy.minor_font);
217 }
218
219 #[test]
220 fn shared_theme_adapter_does_not_project_unresolved_colour_forms() {
221 let mut shared = CT_OfficeStyleSheet::office_default();
222 shared.theme_elements.color_scheme.accent1 = ColorChoice::Scheme {
223 value: "accent2".to_owned(),
224 transforms: Vec::new(),
225 raw_children: OrderedRawChildren::default(),
226 };
227 shared.theme_elements.color_scheme.accent2 = ColorChoice::Preset {
228 value: "red".to_owned(),
229 transforms: Vec::new(),
230 raw_children: OrderedRawChildren::default(),
231 };
232 shared.theme_elements.color_scheme.accent3 = ColorChoice::System {
233 value: "windowText".to_owned(),
234 last_color: None,
235 transforms: Vec::new(),
236 raw_children: OrderedRawChildren::default(),
237 };
238 shared.theme_elements.color_scheme.accent4 = ColorChoice::System {
239 value: "window".to_owned(),
240 last_color: Some(RgbColor::new(0xab, 0xcd, 0xef)),
241 transforms: Vec::new(),
242 raw_children: OrderedRawChildren::default(),
243 };
244
245 let projected = rdocx_oxml::theme::Theme::from(&shared);
246
247 assert_eq!(projected.colors.accent1, None);
248 assert_eq!(projected.colors.accent2, None);
249 assert_eq!(projected.colors.accent3.as_deref(), Some("windowText"));
250 assert_eq!(projected.colors.accent4.as_deref(), Some("ABCDEF"));
251 }
252
253 #[test]
254 fn malformed_theme_returns_an_error_without_panicking() {
255 assert!(CT_OfficeStyleSheet::from_xml(b"<a:theme/>").is_err());
256 assert!(CT_OfficeStyleSheet::from_xml(b"<a:theme><a:themeElements/></a:theme>").is_err());
257 assert!(CT_OfficeStyleSheet::from_xml(b"<a:theme><a:themeElements>").is_err());
258 assert!(CT_OfficeStyleSheet::from_xml(b"<a:theme><a:themeElements><a:clrScheme name=\"x\"><a:dk1><a:srgbClr val=\"not-rgb\"/></a:dk1></a:clrScheme></a:themeElements></a:theme>").is_err());
259 let duplicate = minimal_theme().replacen(
260 "</z:clrScheme>",
261 "<z:dk1><z:srgbClr val=\"000000\"/></z:dk1></z:clrScheme>",
262 1,
263 );
264 assert!(CT_OfficeStyleSheet::from_xml(duplicate.as_bytes()).is_err());
265 }
266
267 fn minimal_theme() -> String {
268 r#"<z:theme xmlns:z="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:test" name="Test" custom="root"><z:themeElements><z:clrScheme name="Test"><z:dk1><z:srgbClr val="000000"/></z:dk1><z:lt1><z:srgbClr val="FFFFFF"/></z:lt1><z:dk2><z:srgbClr val="111111"/></z:dk2><z:lt2><z:srgbClr val="EEEEEE"/></z:lt2><z:accent1><z:srgbClr val="100001"/></z:accent1><z:accent2><z:srgbClr val="200002"/></z:accent2><z:accent3><z:srgbClr val="300003"/></z:accent3><z:accent4><z:srgbClr val="400004"/></z:accent4><z:accent5><z:srgbClr val="500005"/></z:accent5><z:accent6><z:srgbClr val="600006"/></z:accent6><z:hlink><z:srgbClr val="0000FF"/></z:hlink><z:folHlink><z:srgbClr val="800080"/></z:folHlink></z:clrScheme><z:fontScheme name="Test"><z:majorFont><z:latin typeface="Major"/><z:ea typeface=""/><z:cs typeface=""/></z:majorFont><z:minorFont><z:latin typeface="Minor"/><z:ea typeface=""/><z:cs typeface=""/></z:minorFont></z:fontScheme><z:fmtScheme name="Test"><z:fillStyleLst><z:noFill/></z:fillStyleLst><z:lnStyleLst><z:ln/></z:lnStyleLst><z:effectStyleLst><z:effectStyle><z:effectLst/></z:effectStyle></z:effectStyleLst><z:bgFillStyleLst><z:noFill/></z:bgFillStyleLst></z:fmtScheme></z:themeElements></z:theme>"#.to_owned()
269 }
270
271 fn contains(haystack: &[u8], needle: &[u8]) -> bool {
272 haystack.windows(needle.len()).any(|part| part == needle)
273 }
274
275 fn assert_powerpoint_build() {
276 let app = "/Applications/Microsoft PowerPoint.app/Contents/Info.plist";
277 let version = Command::new("/usr/libexec/PlistBuddy")
278 .args(["-c", "Print :CFBundleShortVersionString", app])
279 .output()
280 .unwrap();
281 let build = Command::new("/usr/libexec/PlistBuddy")
282 .args(["-c", "Print :CFBundleVersion", app])
283 .output()
284 .unwrap();
285 assert!(version.status.success());
286 assert!(build.status.success());
287 assert_eq!(
288 String::from_utf8(version.stdout).unwrap().trim(),
289 POWERPOINT_VERSION
290 );
291 assert_eq!(
292 String::from_utf8(build.stdout).unwrap().trim(),
293 POWERPOINT_BUILD
294 );
295 }
296
297 fn open_without_repair(path: &Path) {
298 let path = path.to_string_lossy();
299 let name = Path::new(path.as_ref())
300 .file_name()
301 .unwrap()
302 .to_string_lossy();
303 let script = format!(
304 "with timeout of 120 seconds\ntell application \"Microsoft PowerPoint\"\nset previousStartUpDialog to start up dialog\ntry\nif (Version as text) is not \"{POWERPOINT_VERSION}\" then error \"PowerPoint version mismatch\"\nif (build as text) is not \"{POWERPOINT_APP_BUILD}\" then error \"PowerPoint build mismatch\"\nset start up dialog to false\nset deckPath to \"{path}\"\nopen my POSIX file deckPath\nset checkedDeck to presentation \"{name}\"\nif (full name of checkedDeck) is not deckPath then error \"exact path mismatch\"\nclose checkedDeck saving no\nset start up dialog to previousStartUpDialog\non error errorMessage number errorNumber\ntry\nclose checkedDeck saving no\nend try\nset start up dialog to previousStartUpDialog\nerror errorMessage number errorNumber\nend try\nend tell\nend timeout\n"
305 );
306 let result = Command::new("osascript")
307 .args(["-e", &script])
308 .output()
309 .unwrap();
310 assert!(
311 result.status.success(),
312 "PowerPoint no-repair open failed: {}",
313 String::from_utf8_lossy(&result.stderr)
314 );
315 }
316}
317
318#[derive(Debug)]
319pub enum ThemeError {
320 Xml(OxmlError),
321 Color(ColorError),
322 Fill(FillError),
323 Line(LineError),
324 Effect(EffectError),
325 UnexpectedElement(String),
326 MissingElement(String),
327 DuplicateElement(String),
328 MissingAttribute { element: String, attribute: String },
329 ChildOutOfOrder { parent: String, child: String },
330}
331
332impl fmt::Display for ThemeError {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 match self {
335 Self::Xml(e) => e.fmt(f),
336 Self::Color(e) => e.fmt(f),
337 Self::Fill(e) => e.fmt(f),
338 Self::Line(e) => e.fmt(f),
339 Self::Effect(e) => e.fmt(f),
340 Self::UnexpectedElement(e) => write!(f, "unexpected DrawingML theme element: {e}"),
341 Self::MissingElement(e) => write!(f, "DrawingML theme requires {e}"),
342 Self::DuplicateElement(e) => write!(f, "DrawingML theme contains duplicate {e}"),
343 Self::MissingAttribute { element, attribute } => {
344 write!(f, "DrawingML {element} requires @{attribute}")
345 }
346 Self::ChildOutOfOrder { parent, child } => {
347 write!(f, "DrawingML {child} is out of schema order in {parent}")
348 }
349 }
350 }
351}
352
353impl std::error::Error for ThemeError {
354 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
355 match self {
356 Self::Xml(e) => Some(e),
357 Self::Color(e) => Some(e),
358 Self::Fill(e) => Some(e),
359 Self::Line(e) => Some(e),
360 Self::Effect(e) => Some(e),
361 _ => None,
362 }
363 }
364}
365
366impl From<OxmlError> for ThemeError {
367 fn from(error: OxmlError) -> Self {
368 Self::Xml(error)
369 }
370}
371
372impl From<ColorError> for ThemeError {
373 fn from(error: ColorError) -> Self {
374 Self::Color(error)
375 }
376}
377
378impl From<FillError> for ThemeError {
379 fn from(error: FillError) -> Self {
380 Self::Fill(error)
381 }
382}
383
384impl From<LineError> for ThemeError {
385 fn from(error: LineError) -> Self {
386 Self::Line(error)
387 }
388}
389
390impl From<EffectError> for ThemeError {
391 fn from(error: EffectError) -> Self {
392 Self::Effect(error)
393 }
394}
395
396pub type Result<T> = std::result::Result<T, ThemeError>;
397
398type RawAttributes = Vec<(String, String)>;
399
400#[allow(non_camel_case_types)]
401#[derive(Clone, Debug, Eq, PartialEq)]
402pub struct CT_OfficeStyleSheet {
403 pub name: Option<String>,
404 pub theme_elements: CT_ThemeElements,
405 raw_attributes: RawAttributes,
406 raw_children: OrderedRawChildren,
407}
408
409#[allow(non_camel_case_types)]
410#[derive(Clone, Debug, Eq, PartialEq)]
411pub struct CT_ThemeElements {
412 pub color_scheme: CT_ColorScheme,
413 pub font_scheme: CT_FontScheme,
414 pub format_scheme: CT_StyleMatrix,
415 raw_attributes: RawAttributes,
416 raw_children: OrderedRawChildren,
417}
418
419#[allow(non_camel_case_types)]
420#[derive(Clone, Debug, Eq, PartialEq)]
421pub struct CT_ColorScheme {
422 pub name: String,
423 pub dark1: ColorChoice,
424 pub light1: ColorChoice,
425 pub dark2: ColorChoice,
426 pub light2: ColorChoice,
427 pub accent1: ColorChoice,
428 pub accent2: ColorChoice,
429 pub accent3: ColorChoice,
430 pub accent4: ColorChoice,
431 pub accent5: ColorChoice,
432 pub accent6: ColorChoice,
433 pub hyperlink: ColorChoice,
434 pub followed_hyperlink: ColorChoice,
435 raw_attributes: RawAttributes,
436 raw_children: OrderedRawChildren,
437 slot_attributes: [RawAttributes; 12],
438 slot_children: [OrderedRawChildren; 12],
439}
440
441impl CT_ColorScheme {
442 pub const SLOTS: [ThemeColorSlot; 12] = [
443 ThemeColorSlot::Dark1,
444 ThemeColorSlot::Light1,
445 ThemeColorSlot::Dark2,
446 ThemeColorSlot::Light2,
447 ThemeColorSlot::Accent1,
448 ThemeColorSlot::Accent2,
449 ThemeColorSlot::Accent3,
450 ThemeColorSlot::Accent4,
451 ThemeColorSlot::Accent5,
452 ThemeColorSlot::Accent6,
453 ThemeColorSlot::Hyperlink,
454 ThemeColorSlot::FollowedHyperlink,
455 ];
456
457 pub fn color(&self, slot: ThemeColorSlot) -> &ColorChoice {
458 match slot {
459 ThemeColorSlot::Dark1 => &self.dark1,
460 ThemeColorSlot::Light1 => &self.light1,
461 ThemeColorSlot::Dark2 => &self.dark2,
462 ThemeColorSlot::Light2 => &self.light2,
463 ThemeColorSlot::Accent1 => &self.accent1,
464 ThemeColorSlot::Accent2 => &self.accent2,
465 ThemeColorSlot::Accent3 => &self.accent3,
466 ThemeColorSlot::Accent4 => &self.accent4,
467 ThemeColorSlot::Accent5 => &self.accent5,
468 ThemeColorSlot::Accent6 => &self.accent6,
469 ThemeColorSlot::Hyperlink => &self.hyperlink,
470 ThemeColorSlot::FollowedHyperlink => &self.followed_hyperlink,
471 }
472 }
473
474 pub fn iter(&self) -> impl Iterator<Item = (ThemeColorSlot, &ColorChoice)> {
475 Self::SLOTS.into_iter().map(|slot| (slot, self.color(slot)))
476 }
477}
478
479#[allow(non_camel_case_types)]
480#[derive(Clone, Debug, Eq, PartialEq)]
481pub struct CT_FontScheme {
482 pub name: String,
483 pub major_font: CT_FontCollection,
484 pub minor_font: CT_FontCollection,
485 raw_attributes: RawAttributes,
486 raw_children: OrderedRawChildren,
487}
488
489#[allow(non_camel_case_types)]
490#[derive(Clone, Debug, Eq, PartialEq)]
491pub struct CT_FontCollection {
492 pub latin: ThemeTypeface,
493 pub east_asian: ThemeTypeface,
494 pub complex_script: ThemeTypeface,
495 pub supplemental_fonts: Vec<SupplementalFont>,
496 raw_attributes: RawAttributes,
497 raw_children: OrderedRawChildren,
498}
499
500#[derive(Clone, Debug, Eq, PartialEq)]
501pub struct ThemeTypeface {
502 pub typeface: String,
503 raw_attributes: RawAttributes,
504 raw_children: OrderedRawChildren,
505}
506
507#[derive(Clone, Debug, Eq, PartialEq)]
508pub struct SupplementalFont {
509 pub script: String,
510 pub typeface: String,
511 raw_attributes: RawAttributes,
512 raw_children: OrderedRawChildren,
513}
514
515impl From<&CT_OfficeStyleSheet> for rdocx_oxml::theme::Theme {
516 fn from(theme: &CT_OfficeStyleSheet) -> Self {
517 let colours = &theme.theme_elements.color_scheme;
518 let fonts = &theme.theme_elements.font_scheme;
519 Self {
520 colors: rdocx_oxml::theme::ThemeColors {
521 dk1: legacy_colour(colours.color(ThemeColorSlot::Dark1)),
522 dk2: legacy_colour(colours.color(ThemeColorSlot::Dark2)),
523 lt1: legacy_colour(colours.color(ThemeColorSlot::Light1)),
524 lt2: legacy_colour(colours.color(ThemeColorSlot::Light2)),
525 accent1: legacy_colour(colours.color(ThemeColorSlot::Accent1)),
526 accent2: legacy_colour(colours.color(ThemeColorSlot::Accent2)),
527 accent3: legacy_colour(colours.color(ThemeColorSlot::Accent3)),
528 accent4: legacy_colour(colours.color(ThemeColorSlot::Accent4)),
529 accent5: legacy_colour(colours.color(ThemeColorSlot::Accent5)),
530 accent6: legacy_colour(colours.color(ThemeColorSlot::Accent6)),
531 hlink: legacy_colour(colours.color(ThemeColorSlot::Hyperlink)),
532 fol_hlink: legacy_colour(colours.color(ThemeColorSlot::FollowedHyperlink)),
533 },
534 major_font: Some(fonts.major_font.latin.typeface.clone()),
535 minor_font: Some(fonts.minor_font.latin.typeface.clone()),
536 }
537 }
538}
539
540fn legacy_colour(colour: &ColorChoice) -> Option<String> {
541 match colour {
542 ColorChoice::Srgb { value, .. } => Some(value.to_string()),
543 ColorChoice::System {
544 value, last_color, ..
545 } => Some(
546 last_color
547 .map(|resolved| resolved.to_string())
548 .unwrap_or_else(|| value.clone()),
549 ),
550 ColorChoice::Scheme { .. } | ColorChoice::Preset { .. } => None,
551 }
552}
553
554#[derive(Clone, Debug, Default, Eq, PartialEq)]
555struct StyleListRaw {
556 attributes: RawAttributes,
557 children: OrderedRawChildren,
558}
559
560#[allow(non_camel_case_types)]
561#[derive(Clone, Debug, Eq, PartialEq)]
562pub struct CT_StyleMatrix {
563 pub name: Option<String>,
564 pub fill_styles: Vec<Fill>,
565 pub line_styles: Vec<CT_LineProperties>,
566 pub effect_styles: Vec<CT_EffectStyle>,
567 pub background_fill_styles: Vec<Fill>,
568 raw_attributes: RawAttributes,
569 raw_children: OrderedRawChildren,
570 fill_list_raw: StyleListRaw,
571 line_list_raw: StyleListRaw,
572 effect_list_raw: StyleListRaw,
573 background_fill_list_raw: StyleListRaw,
574}
575
576#[allow(non_camel_case_types)]
577#[derive(Clone, Debug, Eq, PartialEq)]
578pub struct CT_EffectStyle {
579 pub effect_list: Option<CT_EffectList>,
580 raw_attributes: RawAttributes,
581 raw_children: OrderedRawChildren,
582}
583
584impl CT_OfficeStyleSheet {
585 pub fn from_xml(xml: &[u8]) -> Result<Self> {
586 let mut reader = Reader::from_reader(xml);
587 let mut buf = Vec::new();
588 loop {
589 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
590 Event::Start(e) if matches_local_name(e.name().as_ref(), b"theme") => {
591 return Self::read(&mut reader, &e);
592 }
593 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"theme") => {
594 return Err(missing("themeElements"));
595 }
596 Event::Start(e) | Event::Empty(e) => return Err(unexpected(&e)),
597 Event::Eof => return Err(missing("theme")),
598 _ => {}
599 }
600 buf.clear();
601 }
602 }
603
604 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
605 let mut elements = None;
606 let mut raw_children = OrderedRawChildren::default();
607 let mut boundary = 0;
608 let mut buf = Vec::new();
609 loop {
610 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
611 Event::Start(e) if matches_local_name(e.name().as_ref(), b"themeElements") => {
612 reject_duplicate(&elements, "themeElements")?;
613 elements = Some(CT_ThemeElements::read(reader, &e)?);
614 boundary = 1;
615 }
616 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"themeElements") => {
617 return Err(missing("themeElements children"));
618 }
619 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
620 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
621 Event::End(e) if matches_local_name(e.name().as_ref(), b"theme") => break,
622 Event::Eof => return Err(missing_end("theme")),
623 _ => {}
624 }
625 buf.clear();
626 }
627 Ok(Self {
628 name: decoded_attr(start, b"name")?,
629 theme_elements: elements.ok_or_else(|| missing("themeElements"))?,
630 raw_attributes: raw_attributes(start, &[b"name"], true)?,
631 raw_children,
632 })
633 }
634
635 pub fn to_xml(&self) -> Result<Vec<u8>> {
636 let mut writer = Writer::new(Vec::new());
637 writer
638 .write_event(Event::Decl(BytesDecl::new(
639 "1.0",
640 Some("UTF-8"),
641 Some("yes"),
642 )))
643 .map_err(OxmlError::from)?;
644 let mut start = BytesStart::new("a:theme");
645 start.push_attribute(("xmlns:a", A_NS));
646 if let Some(name) = self.name.as_deref() {
647 start.push_attribute(("name", name));
648 }
649 push_attributes(&mut start, &self.raw_attributes);
650 write_start(&mut writer, start)?;
651 emit_raw(&mut writer, self.raw_children.at(0))?;
652 self.theme_elements.write(&mut writer)?;
653 emit_raw(&mut writer, self.raw_children.at(1))?;
654 write_end(&mut writer, "a:theme")?;
655 Ok(writer.into_inner())
656 }
657
658 pub fn office_default() -> Self {
659 Self::from_xml(OFFICE_DEFAULT_XML.as_bytes())
660 .unwrap_or_else(|error| panic!("built-in Office theme is invalid: {error}"))
661 }
662}
663
664impl CT_ThemeElements {
665 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
666 let mut colors = None;
667 let mut fonts = None;
668 let mut formats = None;
669 let mut raw_children = OrderedRawChildren::default();
670 let mut boundary = 0;
671 let mut buf = Vec::new();
672 loop {
673 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
674 Event::Start(e) if matches_local_name(e.name().as_ref(), b"clrScheme") => {
675 require_order("themeElements", "clrScheme", boundary, 0)?;
676 reject_duplicate(&colors, "clrScheme")?;
677 colors = Some(CT_ColorScheme::read(reader, &e)?);
678 boundary = 1;
679 }
680 Event::Start(e) if matches_local_name(e.name().as_ref(), b"fontScheme") => {
681 require_order("themeElements", "fontScheme", boundary, 1)?;
682 reject_duplicate(&fonts, "fontScheme")?;
683 fonts = Some(CT_FontScheme::read(reader, &e)?);
684 boundary = 2;
685 }
686 Event::Start(e) if matches_local_name(e.name().as_ref(), b"fmtScheme") => {
687 require_order("themeElements", "fmtScheme", boundary, 2)?;
688 reject_duplicate(&formats, "fmtScheme")?;
689 formats = Some(CT_StyleMatrix::read(reader, &e)?);
690 boundary = 3;
691 }
692 Event::Empty(e) if is_theme_elements_child(e.name().as_ref()) => {
693 return Err(missing(format!("{} children", element_name(&e))));
694 }
695 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
696 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
697 Event::End(e) if matches_local_name(e.name().as_ref(), b"themeElements") => break,
698 Event::Eof => return Err(missing_end("themeElements")),
699 _ => {}
700 }
701 buf.clear();
702 }
703 Ok(Self {
704 color_scheme: colors.ok_or_else(|| missing("clrScheme"))?,
705 font_scheme: fonts.ok_or_else(|| missing("fontScheme"))?,
706 format_scheme: formats.ok_or_else(|| missing("fmtScheme"))?,
707 raw_attributes: raw_attributes(start, &[], false)?,
708 raw_children,
709 })
710 }
711
712 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
713 let mut start = BytesStart::new("a:themeElements");
714 push_attributes(&mut start, &self.raw_attributes);
715 write_start(writer, start)?;
716 emit_raw(writer, self.raw_children.at(0))?;
717 self.color_scheme.write(writer)?;
718 emit_raw(writer, self.raw_children.at(1))?;
719 self.font_scheme.write(writer)?;
720 emit_raw(writer, self.raw_children.at(2))?;
721 self.format_scheme.write(writer)?;
722 emit_raw(writer, self.raw_children.at(3))?;
723 write_end(writer, "a:themeElements")
724 }
725}
726
727#[derive(Default)]
728struct ParsedColors {
729 values: [Option<ColorChoice>; 12],
730 attributes: [RawAttributes; 12],
731 children: [OrderedRawChildren; 12],
732}
733
734struct ParsedColorSlot {
735 value: ColorChoice,
736 attributes: RawAttributes,
737 children: OrderedRawChildren,
738}
739
740impl CT_ColorScheme {
741 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
742 let mut parsed = ParsedColors::default();
743 let mut raw_children = OrderedRawChildren::default();
744 let mut boundary = 0;
745 let mut buf = Vec::new();
746 loop {
747 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
748 Event::Start(e) if color_slot_index(e.name().as_ref()).is_some() => {
749 let index = color_slot_index(e.name().as_ref()).unwrap();
750 let name = element_name(&e);
751 require_order("clrScheme", &name, boundary, index)?;
752 if parsed.values[index].is_some() {
753 return Err(duplicate(name));
754 }
755 let slot = read_color_slot(reader, &e)?;
756 parsed.values[index] = Some(slot.value);
757 parsed.attributes[index] = slot.attributes;
758 parsed.children[index] = slot.children;
759 boundary = index + 1;
760 }
761 Event::Empty(e) if color_slot_index(e.name().as_ref()).is_some() => {
762 return Err(missing(format!("{} colour", element_name(&e))));
763 }
764 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
765 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
766 Event::End(e) if matches_local_name(e.name().as_ref(), b"clrScheme") => break,
767 Event::Eof => return Err(missing_end("clrScheme")),
768 _ => {}
769 }
770 buf.clear();
771 }
772 let [
773 dark1,
774 light1,
775 dark2,
776 light2,
777 accent1,
778 accent2,
779 accent3,
780 accent4,
781 accent5,
782 accent6,
783 hyperlink,
784 followed_hyperlink,
785 ] = parsed.values;
786 Ok(Self {
787 name: required_attr(start, b"name")?,
788 dark1: dark1.ok_or_else(|| missing("dk1"))?,
789 light1: light1.ok_or_else(|| missing("lt1"))?,
790 dark2: dark2.ok_or_else(|| missing("dk2"))?,
791 light2: light2.ok_or_else(|| missing("lt2"))?,
792 accent1: accent1.ok_or_else(|| missing("accent1"))?,
793 accent2: accent2.ok_or_else(|| missing("accent2"))?,
794 accent3: accent3.ok_or_else(|| missing("accent3"))?,
795 accent4: accent4.ok_or_else(|| missing("accent4"))?,
796 accent5: accent5.ok_or_else(|| missing("accent5"))?,
797 accent6: accent6.ok_or_else(|| missing("accent6"))?,
798 hyperlink: hyperlink.ok_or_else(|| missing("hlink"))?,
799 followed_hyperlink: followed_hyperlink.ok_or_else(|| missing("folHlink"))?,
800 raw_attributes: raw_attributes(start, &[b"name"], false)?,
801 raw_children,
802 slot_attributes: parsed.attributes,
803 slot_children: parsed.children,
804 })
805 }
806
807 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
808 let mut start = BytesStart::new("a:clrScheme");
809 start.push_attribute(("name", self.name.as_str()));
810 push_attributes(&mut start, &self.raw_attributes);
811 write_start(writer, start)?;
812 for (index, (slot, color)) in self.iter().enumerate() {
813 emit_raw(writer, self.raw_children.at(index))?;
814 let tag = format!("a:{}", slot.as_str());
815 let mut slot_start = BytesStart::new(tag.as_str());
816 push_attributes(&mut slot_start, &self.slot_attributes[index]);
817 write_start(writer, slot_start)?;
818 emit_raw(writer, self.slot_children[index].at(0))?;
819 color.to_xml(writer)?;
820 emit_raw(writer, self.slot_children[index].at(1))?;
821 write_end(writer, &tag)?;
822 }
823 emit_raw(writer, self.raw_children.at(12))?;
824 write_end(writer, "a:clrScheme")
825 }
826}
827
828fn read_color_slot(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<ParsedColorSlot> {
829 let end = local_name(start.name().as_ref()).to_vec();
830 let mut value = None;
831 let mut children = OrderedRawChildren::default();
832 let mut boundary = 0;
833 let mut buf = Vec::new();
834 loop {
835 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
836 Event::Start(e) if is_color(e.name().as_ref()) => {
837 reject_duplicate(&value, "theme colour choice")?;
838 value = Some(ColorChoice::from_xml(reader, &e)?);
839 boundary = 1;
840 }
841 Event::Empty(e) if is_color(e.name().as_ref()) => {
842 reject_duplicate(&value, "theme colour choice")?;
843 value = Some(ColorChoice::from_empty_xml(&e)?);
844 boundary = 1;
845 }
846 Event::Start(e) => children.push(boundary, capture_element(reader, &e)?),
847 Event::Empty(e) => children.push(boundary, capture_empty_element(&e)?),
848 Event::End(e) if local_name(e.name().as_ref()) == end => break,
849 Event::Eof => return Err(missing_end(String::from_utf8_lossy(&end))),
850 _ => {}
851 }
852 buf.clear();
853 }
854 Ok(ParsedColorSlot {
855 value: value.ok_or_else(|| missing("theme colour choice"))?,
856 attributes: raw_attributes(start, &[], false)?,
857 children,
858 })
859}
860
861impl CT_FontScheme {
862 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
863 let mut major = None;
864 let mut minor = None;
865 let mut raw_children = OrderedRawChildren::default();
866 let mut boundary = 0;
867 let mut buf = Vec::new();
868 loop {
869 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
870 Event::Start(e) if matches_local_name(e.name().as_ref(), b"majorFont") => {
871 require_order("fontScheme", "majorFont", boundary, 0)?;
872 reject_duplicate(&major, "majorFont")?;
873 major = Some(CT_FontCollection::read(reader, &e)?);
874 boundary = 1;
875 }
876 Event::Start(e) if matches_local_name(e.name().as_ref(), b"minorFont") => {
877 require_order("fontScheme", "minorFont", boundary, 1)?;
878 reject_duplicate(&minor, "minorFont")?;
879 minor = Some(CT_FontCollection::read(reader, &e)?);
880 boundary = 2;
881 }
882 Event::Empty(e)
883 if matches_local_name(e.name().as_ref(), b"majorFont")
884 || matches_local_name(e.name().as_ref(), b"minorFont") =>
885 {
886 return Err(missing(format!("{} children", element_name(&e))));
887 }
888 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
889 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
890 Event::End(e) if matches_local_name(e.name().as_ref(), b"fontScheme") => break,
891 Event::Eof => return Err(missing_end("fontScheme")),
892 _ => {}
893 }
894 buf.clear();
895 }
896 Ok(Self {
897 name: required_attr(start, b"name")?,
898 major_font: major.ok_or_else(|| missing("majorFont"))?,
899 minor_font: minor.ok_or_else(|| missing("minorFont"))?,
900 raw_attributes: raw_attributes(start, &[b"name"], false)?,
901 raw_children,
902 })
903 }
904
905 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
906 let mut start = BytesStart::new("a:fontScheme");
907 start.push_attribute(("name", self.name.as_str()));
908 push_attributes(&mut start, &self.raw_attributes);
909 write_start(writer, start)?;
910 emit_raw(writer, self.raw_children.at(0))?;
911 self.major_font.write(writer, "a:majorFont")?;
912 emit_raw(writer, self.raw_children.at(1))?;
913 self.minor_font.write(writer, "a:minorFont")?;
914 emit_raw(writer, self.raw_children.at(2))?;
915 write_end(writer, "a:fontScheme")
916 }
917}
918
919impl CT_FontCollection {
920 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
921 let end = local_name(start.name().as_ref()).to_vec();
922 let mut latin = None;
923 let mut east_asian = None;
924 let mut complex_script = None;
925 let mut supplemental_fonts = Vec::new();
926 let mut raw_children = OrderedRawChildren::default();
927 let mut boundary = 0;
928 let mut buf = Vec::new();
929 loop {
930 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
931 Event::Start(e) if matches_local_name(e.name().as_ref(), b"latin") => {
932 require_order("font collection", "latin", boundary, 0)?;
933 reject_duplicate(&latin, "latin")?;
934 latin = Some(read_typeface(reader, &e, false)?);
935 boundary = 1;
936 }
937 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"latin") => {
938 require_order("font collection", "latin", boundary, 0)?;
939 reject_duplicate(&latin, "latin")?;
940 latin = Some(read_typeface(reader, &e, true)?);
941 boundary = 1;
942 }
943 Event::Start(e) if matches_local_name(e.name().as_ref(), b"ea") => {
944 require_order("font collection", "ea", boundary, 1)?;
945 reject_duplicate(&east_asian, "ea")?;
946 east_asian = Some(read_typeface(reader, &e, false)?);
947 boundary = 2;
948 }
949 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"ea") => {
950 require_order("font collection", "ea", boundary, 1)?;
951 reject_duplicate(&east_asian, "ea")?;
952 east_asian = Some(read_typeface(reader, &e, true)?);
953 boundary = 2;
954 }
955 Event::Start(e) if matches_local_name(e.name().as_ref(), b"cs") => {
956 require_order("font collection", "cs", boundary, 2)?;
957 reject_duplicate(&complex_script, "cs")?;
958 complex_script = Some(read_typeface(reader, &e, false)?);
959 boundary = 3;
960 }
961 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"cs") => {
962 require_order("font collection", "cs", boundary, 2)?;
963 reject_duplicate(&complex_script, "cs")?;
964 complex_script = Some(read_typeface(reader, &e, true)?);
965 boundary = 3;
966 }
967 Event::Start(e) if matches_local_name(e.name().as_ref(), b"font") => {
968 if boundary < 3 {
969 return Err(out_of_order("font collection", "font"));
970 }
971 supplemental_fonts.push(read_supplemental_font(reader, &e, false)?);
972 boundary = 3 + supplemental_fonts.len();
973 }
974 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"font") => {
975 if boundary < 3 {
976 return Err(out_of_order("font collection", "font"));
977 }
978 supplemental_fonts.push(read_supplemental_font(reader, &e, true)?);
979 boundary = 3 + supplemental_fonts.len();
980 }
981 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
982 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
983 Event::End(e) if local_name(e.name().as_ref()) == end => break,
984 Event::Eof => return Err(missing_end(String::from_utf8_lossy(&end))),
985 _ => {}
986 }
987 buf.clear();
988 }
989 Ok(Self {
990 latin: latin.ok_or_else(|| missing("latin"))?,
991 east_asian: east_asian.ok_or_else(|| missing("ea"))?,
992 complex_script: complex_script.ok_or_else(|| missing("cs"))?,
993 supplemental_fonts,
994 raw_attributes: raw_attributes(start, &[], false)?,
995 raw_children,
996 })
997 }
998
999 fn write(&self, writer: &mut Writer<Vec<u8>>, tag: &str) -> Result<()> {
1000 let mut start = BytesStart::new(tag);
1001 push_attributes(&mut start, &self.raw_attributes);
1002 write_start(writer, start)?;
1003 emit_raw(writer, self.raw_children.at(0))?;
1004 self.latin.write(writer, "a:latin")?;
1005 emit_raw(writer, self.raw_children.at(1))?;
1006 self.east_asian.write(writer, "a:ea")?;
1007 emit_raw(writer, self.raw_children.at(2))?;
1008 self.complex_script.write(writer, "a:cs")?;
1009 for (index, font) in self.supplemental_fonts.iter().enumerate() {
1010 emit_raw(writer, self.raw_children.at(3 + index))?;
1011 font.write(writer)?;
1012 }
1013 emit_raw(
1014 writer,
1015 self.raw_children.at(3 + self.supplemental_fonts.len()),
1016 )?;
1017 write_end(writer, tag)
1018 }
1019}
1020
1021fn read_typeface(
1022 reader: &mut Reader<&[u8]>,
1023 start: &BytesStart<'_>,
1024 empty: bool,
1025) -> Result<ThemeTypeface> {
1026 Ok(ThemeTypeface {
1027 typeface: required_attr(start, b"typeface")?,
1028 raw_attributes: raw_attributes(start, &[b"typeface"], false)?,
1029 raw_children: read_leaf_children(reader, start, empty)?,
1030 })
1031}
1032
1033impl ThemeTypeface {
1034 fn write(&self, writer: &mut Writer<Vec<u8>>, tag: &str) -> Result<()> {
1035 let mut start = BytesStart::new(tag);
1036 start.push_attribute(("typeface", self.typeface.as_str()));
1037 push_attributes(&mut start, &self.raw_attributes);
1038 write_leaf(writer, start, tag, &self.raw_children)
1039 }
1040}
1041
1042fn read_supplemental_font(
1043 reader: &mut Reader<&[u8]>,
1044 start: &BytesStart<'_>,
1045 empty: bool,
1046) -> Result<SupplementalFont> {
1047 Ok(SupplementalFont {
1048 script: required_attr(start, b"script")?,
1049 typeface: required_attr(start, b"typeface")?,
1050 raw_attributes: raw_attributes(start, &[b"script", b"typeface"], false)?,
1051 raw_children: read_leaf_children(reader, start, empty)?,
1052 })
1053}
1054
1055impl SupplementalFont {
1056 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
1057 let mut start = BytesStart::new("a:font");
1058 start.push_attribute(("script", self.script.as_str()));
1059 start.push_attribute(("typeface", self.typeface.as_str()));
1060 push_attributes(&mut start, &self.raw_attributes);
1061 write_leaf(writer, start, "a:font", &self.raw_children)
1062 }
1063}
1064
1065impl CT_StyleMatrix {
1066 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1067 let mut fills = None;
1068 let mut lines = None;
1069 let mut effects = None;
1070 let mut background_fills = None;
1071 let mut fill_list_raw = StyleListRaw::default();
1072 let mut line_list_raw = StyleListRaw::default();
1073 let mut effect_list_raw = StyleListRaw::default();
1074 let mut background_fill_list_raw = StyleListRaw::default();
1075 let mut raw_children = OrderedRawChildren::default();
1076 let mut boundary = 0;
1077 let mut buf = Vec::new();
1078 loop {
1079 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1080 Event::Start(e) if matches_local_name(e.name().as_ref(), b"fillStyleLst") => {
1081 require_order("fmtScheme", "fillStyleLst", boundary, 0)?;
1082 reject_duplicate(&fills, "fillStyleLst")?;
1083 let (values, raw) = read_fill_list(reader, &e, b"fillStyleLst")?;
1084 fills = Some(values);
1085 fill_list_raw = raw;
1086 boundary = 1;
1087 }
1088 Event::Start(e) if matches_local_name(e.name().as_ref(), b"lnStyleLst") => {
1089 require_order("fmtScheme", "lnStyleLst", boundary, 1)?;
1090 reject_duplicate(&lines, "lnStyleLst")?;
1091 let (values, raw) = read_line_list(reader, &e)?;
1092 lines = Some(values);
1093 line_list_raw = raw;
1094 boundary = 2;
1095 }
1096 Event::Start(e) if matches_local_name(e.name().as_ref(), b"effectStyleLst") => {
1097 require_order("fmtScheme", "effectStyleLst", boundary, 2)?;
1098 reject_duplicate(&effects, "effectStyleLst")?;
1099 let (values, raw) = read_effect_style_list(reader, &e)?;
1100 effects = Some(values);
1101 effect_list_raw = raw;
1102 boundary = 3;
1103 }
1104 Event::Start(e) if matches_local_name(e.name().as_ref(), b"bgFillStyleLst") => {
1105 require_order("fmtScheme", "bgFillStyleLst", boundary, 3)?;
1106 reject_duplicate(&background_fills, "bgFillStyleLst")?;
1107 let (values, raw) = read_fill_list(reader, &e, b"bgFillStyleLst")?;
1108 background_fills = Some(values);
1109 background_fill_list_raw = raw;
1110 boundary = 4;
1111 }
1112 Event::Empty(e) if is_style_list(e.name().as_ref()) => {
1113 return Err(missing(format!("{} entries", element_name(&e))));
1114 }
1115 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
1116 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
1117 Event::End(e) if matches_local_name(e.name().as_ref(), b"fmtScheme") => break,
1118 Event::Eof => return Err(missing_end("fmtScheme")),
1119 _ => {}
1120 }
1121 buf.clear();
1122 }
1123 Ok(Self {
1124 name: decoded_attr(start, b"name")?,
1125 fill_styles: fills.ok_or_else(|| missing("fillStyleLst"))?,
1126 line_styles: lines.ok_or_else(|| missing("lnStyleLst"))?,
1127 effect_styles: effects.ok_or_else(|| missing("effectStyleLst"))?,
1128 background_fill_styles: background_fills.ok_or_else(|| missing("bgFillStyleLst"))?,
1129 raw_attributes: raw_attributes(start, &[b"name"], false)?,
1130 raw_children,
1131 fill_list_raw,
1132 line_list_raw,
1133 effect_list_raw,
1134 background_fill_list_raw,
1135 })
1136 }
1137
1138 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
1139 let mut start = BytesStart::new("a:fmtScheme");
1140 if let Some(name) = self.name.as_deref() {
1141 start.push_attribute(("name", name));
1142 }
1143 push_attributes(&mut start, &self.raw_attributes);
1144 write_start(writer, start)?;
1145 emit_raw(writer, self.raw_children.at(0))?;
1146 write_fill_list(
1147 writer,
1148 "a:fillStyleLst",
1149 &self.fill_styles,
1150 &self.fill_list_raw,
1151 )?;
1152 emit_raw(writer, self.raw_children.at(1))?;
1153 write_line_list(writer, &self.line_styles, &self.line_list_raw)?;
1154 emit_raw(writer, self.raw_children.at(2))?;
1155 write_effect_style_list(writer, &self.effect_styles, &self.effect_list_raw)?;
1156 emit_raw(writer, self.raw_children.at(3))?;
1157 write_fill_list(
1158 writer,
1159 "a:bgFillStyleLst",
1160 &self.background_fill_styles,
1161 &self.background_fill_list_raw,
1162 )?;
1163 emit_raw(writer, self.raw_children.at(4))?;
1164 write_end(writer, "a:fmtScheme")
1165 }
1166}
1167
1168fn read_fill_list(
1169 reader: &mut Reader<&[u8]>,
1170 start: &BytesStart<'_>,
1171 end: &[u8],
1172) -> Result<(Vec<Fill>, StyleListRaw)> {
1173 let mut values = Vec::new();
1174 let mut raw = StyleListRaw {
1175 attributes: raw_attributes(start, &[], false)?,
1176 ..StyleListRaw::default()
1177 };
1178 let mut buf = Vec::new();
1179 loop {
1180 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1181 Event::Start(e) if is_fill(e.name().as_ref()) => {
1182 let xml = capture_element(reader, &e)?;
1183 values.push(Fill::from_xml(&xml)?);
1184 }
1185 Event::Empty(e) if is_fill(e.name().as_ref()) => {
1186 let xml = capture_empty_element(&e)?;
1187 values.push(Fill::from_xml(&xml)?);
1188 }
1189 Event::Start(e) => raw
1190 .children
1191 .push(values.len(), capture_element(reader, &e)?),
1192 Event::Empty(e) => raw.children.push(values.len(), capture_empty_element(&e)?),
1193 Event::End(e) if local_name(e.name().as_ref()) == end => break,
1194 Event::Eof => return Err(missing_end(String::from_utf8_lossy(end))),
1195 _ => {}
1196 }
1197 buf.clear();
1198 }
1199 Ok((values, raw))
1200}
1201
1202fn read_line_list(
1203 reader: &mut Reader<&[u8]>,
1204 start: &BytesStart<'_>,
1205) -> Result<(Vec<CT_LineProperties>, StyleListRaw)> {
1206 let mut values = Vec::new();
1207 let mut raw = StyleListRaw {
1208 attributes: raw_attributes(start, &[], false)?,
1209 ..StyleListRaw::default()
1210 };
1211 let mut buf = Vec::new();
1212 loop {
1213 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1214 Event::Start(e) if matches_local_name(e.name().as_ref(), b"ln") => {
1215 let xml = capture_element(reader, &e)?;
1216 values.push(CT_LineProperties::from_xml(&xml)?);
1217 }
1218 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"ln") => {
1219 let xml = capture_empty_element(&e)?;
1220 values.push(CT_LineProperties::from_xml(&xml)?);
1221 }
1222 Event::Start(e) => raw
1223 .children
1224 .push(values.len(), capture_element(reader, &e)?),
1225 Event::Empty(e) => raw.children.push(values.len(), capture_empty_element(&e)?),
1226 Event::End(e) if matches_local_name(e.name().as_ref(), b"lnStyleLst") => break,
1227 Event::Eof => return Err(missing_end("lnStyleLst")),
1228 _ => {}
1229 }
1230 buf.clear();
1231 }
1232 Ok((values, raw))
1233}
1234
1235fn read_effect_style_list(
1236 reader: &mut Reader<&[u8]>,
1237 start: &BytesStart<'_>,
1238) -> Result<(Vec<CT_EffectStyle>, StyleListRaw)> {
1239 let mut values = Vec::new();
1240 let mut raw = StyleListRaw {
1241 attributes: raw_attributes(start, &[], false)?,
1242 ..StyleListRaw::default()
1243 };
1244 let mut buf = Vec::new();
1245 loop {
1246 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1247 Event::Start(e) if matches_local_name(e.name().as_ref(), b"effectStyle") => {
1248 values.push(CT_EffectStyle::read(reader, &e)?);
1249 }
1250 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"effectStyle") => {
1251 values.push(CT_EffectStyle {
1252 effect_list: None,
1253 raw_attributes: raw_attributes(&e, &[], false)?,
1254 raw_children: OrderedRawChildren::default(),
1255 });
1256 }
1257 Event::Start(e) => raw
1258 .children
1259 .push(values.len(), capture_element(reader, &e)?),
1260 Event::Empty(e) => raw.children.push(values.len(), capture_empty_element(&e)?),
1261 Event::End(e) if matches_local_name(e.name().as_ref(), b"effectStyleLst") => break,
1262 Event::Eof => return Err(missing_end("effectStyleLst")),
1263 _ => {}
1264 }
1265 buf.clear();
1266 }
1267 Ok((values, raw))
1268}
1269
1270impl CT_EffectStyle {
1271 pub fn has_unmodelled_effect(&self) -> bool {
1273 (0..=1).any(|boundary| self.raw_children.at(boundary).any(raw_is_effect_dag))
1274 }
1275
1276 pub fn has_unmodelled_effect_placeholder_color(&self) -> bool {
1278 (0..=1).any(|boundary| {
1279 self.raw_children
1280 .at(boundary)
1281 .any(|xml| raw_is_effect_dag(xml) && raw_contains_placeholder_color(xml))
1282 })
1283 }
1284
1285 fn read(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1286 let mut effect_list = None;
1287 let mut raw_children = OrderedRawChildren::default();
1288 let mut boundary = 0;
1289 let mut buf = Vec::new();
1290 loop {
1291 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1292 Event::Start(e) if matches_local_name(e.name().as_ref(), b"effectLst") => {
1293 reject_duplicate(&effect_list, "effectLst")?;
1294 effect_list = Some(CT_EffectList::from_xml(&capture_element(reader, &e)?)?);
1295 boundary = 1;
1296 }
1297 Event::Empty(e) if matches_local_name(e.name().as_ref(), b"effectLst") => {
1298 reject_duplicate(&effect_list, "effectLst")?;
1299 effect_list = Some(CT_EffectList::from_xml(&capture_empty_element(&e)?)?);
1300 boundary = 1;
1301 }
1302 Event::Start(e) => raw_children.push(boundary, capture_element(reader, &e)?),
1303 Event::Empty(e) => raw_children.push(boundary, capture_empty_element(&e)?),
1304 Event::End(e) if matches_local_name(e.name().as_ref(), b"effectStyle") => break,
1305 Event::Eof => return Err(missing_end("effectStyle")),
1306 _ => {}
1307 }
1308 buf.clear();
1309 }
1310 Ok(Self {
1311 effect_list,
1312 raw_attributes: raw_attributes(start, &[], false)?,
1313 raw_children,
1314 })
1315 }
1316
1317 fn write(&self, writer: &mut Writer<Vec<u8>>) -> Result<()> {
1318 let mut start = BytesStart::new("a:effectStyle");
1319 push_attributes(&mut start, &self.raw_attributes);
1320 write_start(writer, start)?;
1321 emit_raw(writer, self.raw_children.at(0))?;
1322 if let Some(effects) = &self.effect_list {
1323 effects.write_xml(writer)?;
1324 }
1325 emit_raw(writer, self.raw_children.at(1))?;
1326 write_end(writer, "a:effectStyle")
1327 }
1328}
1329
1330fn write_fill_list(
1331 writer: &mut Writer<Vec<u8>>,
1332 tag: &str,
1333 values: &[Fill],
1334 raw: &StyleListRaw,
1335) -> Result<()> {
1336 let mut start = BytesStart::new(tag);
1337 push_attributes(&mut start, &raw.attributes);
1338 write_start(writer, start)?;
1339 for (index, value) in values.iter().enumerate() {
1340 emit_raw(writer, raw.children.at(index))?;
1341 value.write_xml(writer)?;
1342 }
1343 emit_raw(writer, raw.children.at(values.len()))?;
1344 write_end(writer, tag)
1345}
1346
1347fn write_line_list(
1348 writer: &mut Writer<Vec<u8>>,
1349 values: &[CT_LineProperties],
1350 raw: &StyleListRaw,
1351) -> Result<()> {
1352 let mut start = BytesStart::new("a:lnStyleLst");
1353 push_attributes(&mut start, &raw.attributes);
1354 write_start(writer, start)?;
1355 for (index, value) in values.iter().enumerate() {
1356 emit_raw(writer, raw.children.at(index))?;
1357 value.write_xml(writer)?;
1358 }
1359 emit_raw(writer, raw.children.at(values.len()))?;
1360 write_end(writer, "a:lnStyleLst")
1361}
1362
1363fn write_effect_style_list(
1364 writer: &mut Writer<Vec<u8>>,
1365 values: &[CT_EffectStyle],
1366 raw: &StyleListRaw,
1367) -> Result<()> {
1368 let mut start = BytesStart::new("a:effectStyleLst");
1369 push_attributes(&mut start, &raw.attributes);
1370 write_start(writer, start)?;
1371 for (index, value) in values.iter().enumerate() {
1372 emit_raw(writer, raw.children.at(index))?;
1373 value.write(writer)?;
1374 }
1375 emit_raw(writer, raw.children.at(values.len()))?;
1376 write_end(writer, "a:effectStyleLst")
1377}
1378
1379fn read_leaf_children(
1380 reader: &mut Reader<&[u8]>,
1381 start: &BytesStart<'_>,
1382 empty: bool,
1383) -> Result<OrderedRawChildren> {
1384 if empty {
1385 return Ok(OrderedRawChildren::default());
1386 }
1387 let end = local_name(start.name().as_ref()).to_vec();
1388 let mut raw = OrderedRawChildren::default();
1389 let mut buf = Vec::new();
1390 loop {
1391 match reader.read_event_into(&mut buf).map_err(OxmlError::from)? {
1392 Event::Start(e) => raw.push(0, capture_element(reader, &e)?),
1393 Event::Empty(e) => raw.push(0, capture_empty_element(&e)?),
1394 Event::End(e) if local_name(e.name().as_ref()) == end => break,
1395 Event::Eof => return Err(missing_end(String::from_utf8_lossy(&end))),
1396 _ => {}
1397 }
1398 buf.clear();
1399 }
1400 Ok(raw)
1401}
1402
1403fn write_leaf(
1404 writer: &mut Writer<Vec<u8>>,
1405 start: BytesStart<'_>,
1406 tag: &str,
1407 raw: &OrderedRawChildren,
1408) -> Result<()> {
1409 if raw.is_empty() {
1410 writer
1411 .write_event(Event::Empty(start))
1412 .map_err(OxmlError::from)?;
1413 return Ok(());
1414 }
1415 write_start(writer, start)?;
1416 emit_raw(writer, raw.at(0))?;
1417 write_end(writer, tag)
1418}
1419
1420fn raw_attributes(
1421 start: &BytesStart<'_>,
1422 modelled: &[&[u8]],
1423 is_root: bool,
1424) -> Result<RawAttributes> {
1425 let mut raw = Vec::new();
1426 for attribute in start.attributes() {
1427 let attribute = attribute.map_err(OxmlError::from)?;
1428 let key = attribute.key.as_ref();
1429 if modelled.contains(&key) {
1430 continue;
1431 }
1432 if is_root && key == b"xmlns:a" {
1433 continue;
1434 }
1435 let name = std::str::from_utf8(key)
1436 .map_err(OxmlError::from)?
1437 .to_owned();
1438 let value = attribute
1439 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
1440 .map_err(OxmlError::from)?
1441 .into_owned();
1442 raw.push((name, value));
1443 }
1444 Ok(raw)
1445}
1446
1447fn required_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<String> {
1448 decoded_attr(start, name)?.ok_or_else(|| ThemeError::MissingAttribute {
1449 element: element_name(start),
1450 attribute: String::from_utf8_lossy(name).into_owned(),
1451 })
1452}
1453
1454fn decoded_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<String>> {
1455 for attribute in start.attributes() {
1456 let attribute = attribute.map_err(OxmlError::from)?;
1457 if attribute.key.as_ref() == name {
1458 return Ok(Some(
1459 attribute
1460 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
1461 .map_err(OxmlError::from)?
1462 .into_owned(),
1463 ));
1464 }
1465 }
1466 Ok(None)
1467}
1468
1469fn push_attributes(start: &mut BytesStart<'_>, attributes: &RawAttributes) {
1470 for (name, value) in attributes {
1471 start.push_attribute((name.as_str(), value.as_str()));
1472 }
1473}
1474
1475fn reject_duplicate<T>(value: &Option<T>, name: impl Into<String>) -> Result<()> {
1476 if value.is_some() {
1477 Err(duplicate(name))
1478 } else {
1479 Ok(())
1480 }
1481}
1482
1483fn require_order(parent: &str, child: &str, boundary: usize, index: usize) -> Result<()> {
1484 if boundary > index {
1485 Err(out_of_order(parent, child))
1486 } else {
1487 Ok(())
1488 }
1489}
1490
1491fn color_slot_index(name: &[u8]) -> Option<usize> {
1492 match local_name(name) {
1493 b"dk1" => Some(0),
1494 b"lt1" => Some(1),
1495 b"dk2" => Some(2),
1496 b"lt2" => Some(3),
1497 b"accent1" => Some(4),
1498 b"accent2" => Some(5),
1499 b"accent3" => Some(6),
1500 b"accent4" => Some(7),
1501 b"accent5" => Some(8),
1502 b"accent6" => Some(9),
1503 b"hlink" => Some(10),
1504 b"folHlink" => Some(11),
1505 _ => None,
1506 }
1507}
1508
1509fn is_color(name: &[u8]) -> bool {
1510 matches!(
1511 local_name(name),
1512 b"srgbClr" | b"schemeClr" | b"sysClr" | b"prstClr"
1513 )
1514}
1515
1516fn is_fill(name: &[u8]) -> bool {
1517 matches!(
1518 local_name(name),
1519 b"noFill" | b"solidFill" | b"gradFill" | b"pattFill" | b"blipFill"
1520 )
1521}
1522
1523fn is_theme_elements_child(name: &[u8]) -> bool {
1524 matches!(
1525 local_name(name),
1526 b"clrScheme" | b"fontScheme" | b"fmtScheme"
1527 )
1528}
1529
1530fn is_style_list(name: &[u8]) -> bool {
1531 matches!(
1532 local_name(name),
1533 b"fillStyleLst" | b"lnStyleLst" | b"effectStyleLst" | b"bgFillStyleLst"
1534 )
1535}
1536
1537fn write_start(writer: &mut Writer<Vec<u8>>, start: BytesStart<'_>) -> Result<()> {
1538 writer
1539 .write_event(Event::Start(start))
1540 .map_err(OxmlError::from)?;
1541 Ok(())
1542}
1543
1544fn write_end(writer: &mut Writer<Vec<u8>>, name: &str) -> Result<()> {
1545 writer
1546 .write_event(Event::End(BytesEnd::new(name)))
1547 .map_err(OxmlError::from)?;
1548 Ok(())
1549}
1550
1551fn emit_raw<'a>(
1552 writer: &mut Writer<Vec<u8>>,
1553 values: impl Iterator<Item = &'a [u8]>,
1554) -> Result<()> {
1555 for value in values {
1556 writer.get_mut().extend_from_slice(value);
1557 }
1558 Ok(())
1559}
1560
1561fn element_name(start: &BytesStart<'_>) -> String {
1562 String::from_utf8_lossy(local_name(start.name().as_ref())).into_owned()
1563}
1564
1565fn unexpected(start: &BytesStart<'_>) -> ThemeError {
1566 ThemeError::UnexpectedElement(String::from_utf8_lossy(start.name().as_ref()).into_owned())
1567}
1568
1569fn missing(name: impl Into<String>) -> ThemeError {
1570 ThemeError::MissingElement(name.into())
1571}
1572
1573fn missing_end(name: impl fmt::Display) -> ThemeError {
1574 ThemeError::Xml(OxmlError::MissingElement(format!(
1575 "closing DrawingML {name}"
1576 )))
1577}
1578
1579fn duplicate(name: impl Into<String>) -> ThemeError {
1580 ThemeError::DuplicateElement(name.into())
1581}
1582
1583fn out_of_order(parent: impl Into<String>, child: impl Into<String>) -> ThemeError {
1584 ThemeError::ChildOutOfOrder {
1585 parent: parent.into(),
1586 child: child.into(),
1587 }
1588}
1589
1590const OFFICE_DEFAULT_XML: &str = r###"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="0E2841"/></a:dk2><a:lt2><a:srgbClr val="E8E8E8"/></a:lt2><a:accent1><a:srgbClr val="156082"/></a:accent1><a:accent2><a:srgbClr val="E97132"/></a:accent2><a:accent3><a:srgbClr val="196B24"/></a:accent3><a:accent4><a:srgbClr val="0F9ED5"/></a:accent4><a:accent5><a:srgbClr val="A02B93"/></a:accent5><a:accent6><a:srgbClr val="4EA72E"/></a:accent6><a:hlink><a:srgbClr val="467886"/></a:hlink><a:folHlink><a:srgbClr val="96607D"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin typeface="Aptos Display" panose="02110004020202020204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Angsana New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:majorFont><a:minorFont><a:latin typeface="Aptos" panose="02110004020202020204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Cordia New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><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><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><a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><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></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><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></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults><a:lnDef><a:spPr/><a:bodyPr/><a:lstStyle/><a:style><a:lnRef idx="2"><a:schemeClr val="accent1"/></a:lnRef><a:fillRef idx="0"><a:schemeClr val="accent1"/></a:fillRef><a:effectRef idx="1"><a:schemeClr val="accent1"/></a:effectRef><a:fontRef idx="minor"><a:schemeClr val="tx1"/></a:fontRef></a:style></a:lnDef></a:objectDefaults><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{2E142A2C-CD16-42D6-873A-C26D2A0506FA}" vid="{1BDDFF52-6CD6-40A5-AB3C-68EB2F1E4D0A}"/></a:ext></a:extLst></a:theme>"###;