Skip to main content

oxml_drawing/
theme.rs

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 &amp; 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 &amp; 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    /// Returns whether this style preserves an opaque effect DAG.
1272    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    /// Reports a placeholder colour inside this style's opaque effect DAG.
1277    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>"###;