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