Skip to main content

oxml_drawing/
fill.rs

1use std::fmt;
2use std::io::Write;
3
4use oxml_core::OxmlError;
5use oxml_core::raw_xml::{capture_element, capture_empty_element};
6use oxml_core::units::{Angle, Percent1000};
7use oxml_core::xml::{R_NS, get_attr, local_name, matches_local_name};
8use quick_xml::events::{BytesEnd, BytesStart, Event};
9use quick_xml::{Reader, Writer, XmlVersion};
10
11use crate::color::{ColorChoice, ColorError};
12use crate::namespace::{A_NS, reject_conflicting_a_prefix};
13use crate::order::OrderedRawChildren;
14
15/// Errors produced while parsing or writing DrawingML fills.
16#[derive(Debug)]
17pub enum FillError {
18    Xml(OxmlError),
19    Color(ColorError),
20    UnexpectedElement(String),
21    MissingAttribute {
22        element: String,
23        attribute: String,
24    },
25    InvalidAttribute {
26        element: String,
27        attribute: String,
28        value: String,
29    },
30    GradientStopOutOfRange(i32),
31}
32
33impl fmt::Display for FillError {
34    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            Self::Xml(error) => error.fmt(formatter),
37            Self::Color(error) => error.fmt(formatter),
38            Self::UnexpectedElement(element) => {
39                write!(formatter, "unexpected DrawingML fill element: {element}")
40            }
41            Self::MissingAttribute { element, attribute } => {
42                write!(formatter, "DrawingML {element} requires @{attribute}")
43            }
44            Self::InvalidAttribute {
45                element,
46                attribute,
47                value,
48            } => write!(
49                formatter,
50                "DrawingML {element} has invalid @{attribute}: {value}"
51            ),
52            Self::GradientStopOutOfRange(value) => {
53                write!(
54                    formatter,
55                    "DrawingML gradient stop is outside 0 to 100000: {value}"
56                )
57            }
58        }
59    }
60}
61
62impl std::error::Error for FillError {
63    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
64        match self {
65            Self::Xml(error) => Some(error),
66            Self::Color(error) => Some(error),
67            _ => None,
68        }
69    }
70}
71
72impl From<OxmlError> for FillError {
73    fn from(error: OxmlError) -> Self {
74        Self::Xml(error)
75    }
76}
77
78impl From<ColorError> for FillError {
79    fn from(error: ColorError) -> Self {
80        Self::Color(error)
81    }
82}
83
84pub type Result<T> = std::result::Result<T, FillError>;
85
86/// One of the five DrawingML fill families.
87#[derive(Clone, Debug, Eq, PartialEq)]
88pub enum Fill {
89    NoFill(NoFill),
90    Solid(SolidFill),
91    Gradient(GradientFill),
92    Pattern(PatternFill),
93    Blip(BlipFill),
94}
95
96impl Fill {
97    /// Parses one complete fill element with any namespace prefix.
98    pub fn from_xml(xml: &[u8]) -> Result<Self> {
99        let mut reader = Reader::from_reader(xml);
100        let mut buffer = Vec::new();
101        loop {
102            match reader
103                .read_event_into(&mut buffer)
104                .map_err(OxmlError::from)?
105            {
106                Event::Start(element) => return Self::from_element(&mut reader, &element),
107                Event::Empty(element) => return Self::from_empty_element(&element),
108                Event::Eof => {
109                    return Err(FillError::Xml(OxmlError::MissingElement(
110                        "DrawingML fill".to_owned(),
111                    )));
112                }
113                _ => {}
114            }
115            buffer.clear();
116        }
117    }
118
119    /// Parses a fill after the caller has consumed its start event.
120    pub fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
121        reject_conflicting_a_prefix(start)?;
122        match local_name(start.name().as_ref()) {
123            b"noFill" => Ok(Self::NoFill(NoFill::from_element(reader, start)?)),
124            b"solidFill" => Ok(Self::Solid(SolidFill::from_element(reader, start)?)),
125            b"gradFill" => Ok(Self::Gradient(GradientFill::from_element(reader, start)?)),
126            b"pattFill" => Ok(Self::Pattern(PatternFill::from_element(reader, start)?)),
127            b"blipFill" => Ok(Self::Blip(BlipFill::from_element(reader, start)?)),
128            _ => Err(unexpected(start)),
129        }
130    }
131
132    /// Parses a self-closing fill element.
133    pub fn from_empty_element(start: &BytesStart<'_>) -> Result<Self> {
134        reject_conflicting_a_prefix(start)?;
135        match local_name(start.name().as_ref()) {
136            b"noFill" => Ok(Self::NoFill(NoFill::default())),
137            b"solidFill" => Ok(Self::Solid(SolidFill::default())),
138            b"gradFill" => Ok(Self::Gradient(GradientFill::from_start(start)?)),
139            b"pattFill" => Ok(Self::Pattern(PatternFill::from_start(start)?)),
140            b"blipFill" => Ok(Self::Blip(BlipFill::from_start(start)?)),
141            _ => Err(unexpected(start)),
142        }
143    }
144
145    /// Writes this fill with canonical DrawingML prefixes and schema order.
146    pub fn to_xml(&self) -> Result<Vec<u8>> {
147        let mut writer = Writer::new(Vec::new());
148        self.write_xml(&mut writer)?;
149        Ok(writer.into_inner())
150    }
151
152    /// Writes this fill into an existing XML writer.
153    pub fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
154        match self {
155            Self::NoFill(fill) => fill.write_xml(writer),
156            Self::Solid(fill) => fill.write_xml(writer),
157            Self::Gradient(fill) => fill.write_xml(writer),
158            Self::Pattern(fill) => fill.write_xml(writer),
159            Self::Blip(fill) => fill.write_xml(writer),
160        }
161    }
162}
163
164/// An `a:noFill` value, including preserved extension children.
165#[derive(Clone, Debug, Default, Eq, PartialEq)]
166pub struct NoFill {
167    raw_children: OrderedRawChildren,
168}
169
170impl NoFill {
171    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
172        Ok(Self {
173            raw_children: capture_all_children(reader, local_name(start.name().as_ref()))?,
174        })
175    }
176
177    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
178        if self.raw_children.is_empty() {
179            write_empty(writer, BytesStart::new("a:noFill"))
180        } else {
181            write_start(writer, BytesStart::new("a:noFill"))?;
182            emit_raw(writer, self.raw_children.at(0))?;
183            write_end(writer, "a:noFill")
184        }
185    }
186
187    pub fn raw_children(&self) -> &OrderedRawChildren {
188        &self.raw_children
189    }
190}
191
192/// A solid fill with its DrawingML colour choice.
193#[derive(Clone, Debug, Default, Eq, PartialEq)]
194pub struct SolidFill {
195    pub color: Option<ColorChoice>,
196    raw_children: OrderedRawChildren,
197}
198
199impl SolidFill {
200    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
201        let mut value = Self::default();
202        read_color_children(
203            reader,
204            local_name(start.name().as_ref()),
205            &mut value.color,
206            &mut value.raw_children,
207        )?;
208        Ok(value)
209    }
210
211    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
212        if self.color.is_none() && self.raw_children.is_empty() {
213            return write_empty(writer, BytesStart::new("a:solidFill"));
214        }
215        write_start(writer, BytesStart::new("a:solidFill"))?;
216        emit_raw(writer, self.raw_children.at(0))?;
217        if let Some(color) = &self.color {
218            color.to_xml(writer)?;
219        }
220        emit_raw(writer, self.raw_children.at(1))?;
221        write_end(writer, "a:solidFill")
222    }
223
224    pub fn raw_children(&self) -> &OrderedRawChildren {
225        &self.raw_children
226    }
227}
228
229/// One gradient stop. Stops remain in document order.
230#[derive(Clone, Debug, Eq, PartialEq)]
231pub struct GradientStop {
232    pub position: Percent1000,
233    pub color: Option<ColorChoice>,
234    raw_children: OrderedRawChildren,
235}
236
237impl GradientStop {
238    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
239        reject_conflicting_a_prefix(start)?;
240        let position = required_i32(start, b"pos")?;
241        validate_gradient_position(position)?;
242        let mut stop = Self {
243            position: Percent1000(position),
244            color: None,
245            raw_children: OrderedRawChildren::default(),
246        };
247        read_color_children(reader, b"gs", &mut stop.color, &mut stop.raw_children)?;
248        Ok(stop)
249    }
250
251    fn from_empty(start: &BytesStart<'_>) -> Result<Self> {
252        reject_conflicting_a_prefix(start)?;
253        let position = required_i32(start, b"pos")?;
254        validate_gradient_position(position)?;
255        Ok(Self {
256            position: Percent1000(position),
257            color: None,
258            raw_children: OrderedRawChildren::default(),
259        })
260    }
261
262    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
263        validate_gradient_position(self.position.0)?;
264        let position = self.position.0.to_string();
265        let mut start = BytesStart::new("a:gs");
266        start.push_attribute(("pos", position.as_str()));
267        if self.color.is_none() && self.raw_children.is_empty() {
268            return write_empty(writer, start);
269        }
270        write_start(writer, start)?;
271        emit_raw(writer, self.raw_children.at(0))?;
272        if let Some(color) = &self.color {
273            color.to_xml(writer)?;
274        }
275        emit_raw(writer, self.raw_children.at(1))?;
276        write_end(writer, "a:gs")
277    }
278}
279
280/// Gradient geometry for either a linear or path gradient.
281#[derive(Clone, Debug, Eq, PartialEq)]
282pub enum GradientGeometry {
283    Linear(LinearGradient),
284    Path(PathGradient),
285}
286
287/// Linear gradient angle and scaling behavior.
288#[derive(Clone, Debug, Default, Eq, PartialEq)]
289pub struct LinearGradient {
290    /// `@ang`. The schema makes it optional without a default, and a missing
291    /// angle reads as 0, a left to right axis, as in LibreOffice's import.
292    pub angle: Angle,
293    pub scaled: Option<bool>,
294    /// The source had no `@ang`, so a zero angle is written without one.
295    angle_omitted: bool,
296    raw_children: OrderedRawChildren,
297}
298
299/// A DrawingML path-gradient shape.
300#[derive(Clone, Copy, Debug, Eq, PartialEq)]
301pub enum PathGradientKind {
302    Shape,
303    Circle,
304    Rectangle,
305}
306
307impl PathGradientKind {
308    fn parse(value: &str) -> Option<Self> {
309        match value {
310            "shape" => Some(Self::Shape),
311            "circle" => Some(Self::Circle),
312            "rect" => Some(Self::Rectangle),
313            _ => None,
314        }
315    }
316
317    const fn as_str(self) -> &'static str {
318        match self {
319            Self::Shape => "shape",
320            Self::Circle => "circle",
321            Self::Rectangle => "rect",
322        }
323    }
324}
325
326/// Path gradient shape and optional focal rectangle.
327#[derive(Clone, Debug, Eq, PartialEq)]
328pub struct PathGradient {
329    /// `@path`. The schema makes it optional without a default, and a missing
330    /// path reads as `rect`, as in LibreOffice's import.
331    pub kind: PathGradientKind,
332    pub fill_to_rect: Option<RelativeRect>,
333    /// The source had no `@path`, so a `rect` path is written without one.
334    kind_omitted: bool,
335    raw_children: OrderedRawChildren,
336}
337
338/// A rectangle expressed as optional percentages from each edge.
339#[derive(Clone, Debug, Default, Eq, PartialEq)]
340pub struct RelativeRect {
341    pub left: Option<Percent1000>,
342    pub top: Option<Percent1000>,
343    pub right: Option<Percent1000>,
344    pub bottom: Option<Percent1000>,
345    raw_children: OrderedRawChildren,
346}
347
348/// A gradient fill with stops in source order.
349#[derive(Clone, Debug, Default, Eq, PartialEq)]
350pub struct GradientFill {
351    pub flip: Option<String>,
352    pub rotate_with_shape: Option<bool>,
353    pub stops: Vec<GradientStop>,
354    pub geometry: Option<GradientGeometry>,
355    pub tile_rect: Option<RelativeRect>,
356    raw_children: OrderedRawChildren,
357    stop_list_raw_children: OrderedRawChildren,
358}
359
360impl GradientFill {
361    fn from_start(start: &BytesStart<'_>) -> Result<Self> {
362        reject_conflicting_a_prefix(start)?;
363        Ok(Self {
364            flip: optional_token(start, b"flip", &["none", "x", "y", "xy"])?,
365            rotate_with_shape: optional_bool(start, b"rotWithShape")?,
366            ..Self::default()
367        })
368    }
369
370    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
371        let mut fill = Self::from_start(start)?;
372        let mut buffer = Vec::new();
373        let mut boundary = 0;
374        loop {
375            match reader
376                .read_event_into(&mut buffer)
377                .map_err(OxmlError::from)?
378            {
379                Event::Start(element) if matches_local_name(element.name().as_ref(), b"gsLst") => {
380                    reject_conflicting_a_prefix(&element)?;
381                    fill.read_stop_list(reader)?;
382                    boundary = 1;
383                }
384                Event::Start(element) if matches_local_name(element.name().as_ref(), b"lin") => {
385                    fill.geometry = Some(GradientGeometry::Linear(parse_linear_element(
386                        reader, &element,
387                    )?));
388                    boundary = 2;
389                }
390                Event::Empty(element) if matches_local_name(element.name().as_ref(), b"lin") => {
391                    fill.geometry = Some(GradientGeometry::Linear(parse_linear(&element)?));
392                    boundary = 2;
393                }
394                Event::Start(element) if matches_local_name(element.name().as_ref(), b"path") => {
395                    fill.geometry = Some(GradientGeometry::Path(parse_path(reader, &element)?));
396                    boundary = 2;
397                }
398                Event::Empty(element) if matches_local_name(element.name().as_ref(), b"path") => {
399                    fill.geometry = Some(GradientGeometry::Path(parse_empty_path(&element)?));
400                    boundary = 2;
401                }
402                Event::Start(element)
403                    if matches_local_name(element.name().as_ref(), b"tileRect") =>
404                {
405                    fill.tile_rect = Some(parse_rect_element(reader, &element)?);
406                    boundary = 3;
407                }
408                Event::Empty(element)
409                    if matches_local_name(element.name().as_ref(), b"tileRect") =>
410                {
411                    fill.tile_rect = Some(parse_rect(&element)?);
412                    boundary = 3;
413                }
414                Event::Start(element) => fill
415                    .raw_children
416                    .push(boundary, capture_element(reader, &element)?),
417                Event::Empty(element) => fill
418                    .raw_children
419                    .push(boundary, capture_empty_element(&element)?),
420                Event::End(element) if matches_local_name(element.name().as_ref(), b"gradFill") => {
421                    break;
422                }
423                Event::Eof => return Err(missing_end("gradFill")),
424                _ => {}
425            }
426            buffer.clear();
427        }
428        Ok(fill)
429    }
430
431    fn read_stop_list(&mut self, reader: &mut Reader<&[u8]>) -> Result<()> {
432        let mut buffer = Vec::new();
433        loop {
434            match reader
435                .read_event_into(&mut buffer)
436                .map_err(OxmlError::from)?
437            {
438                Event::Start(element) if matches_local_name(element.name().as_ref(), b"gs") => {
439                    self.stops
440                        .push(GradientStop::from_element(reader, &element)?);
441                }
442                Event::Empty(element) if matches_local_name(element.name().as_ref(), b"gs") => {
443                    self.stops.push(GradientStop::from_empty(&element)?);
444                }
445                Event::Start(element) => self
446                    .stop_list_raw_children
447                    .push(self.stops.len(), capture_element(reader, &element)?),
448                Event::Empty(element) => self
449                    .stop_list_raw_children
450                    .push(self.stops.len(), capture_empty_element(&element)?),
451                Event::End(element) if matches_local_name(element.name().as_ref(), b"gsLst") => {
452                    break;
453                }
454                Event::Eof => return Err(missing_end("gsLst")),
455                _ => {}
456            }
457            buffer.clear();
458        }
459        Ok(())
460    }
461
462    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
463        let mut start = BytesStart::new("a:gradFill");
464        if let Some(flip) = self.flip.as_deref() {
465            start.push_attribute(("flip", flip));
466        }
467        let rotate = self.rotate_with_shape.map(bool_text);
468        if let Some(rotate) = rotate {
469            start.push_attribute(("rotWithShape", rotate));
470        }
471        if self.stops.is_empty()
472            && self.geometry.is_none()
473            && self.tile_rect.is_none()
474            && self.stop_list_raw_children.is_empty()
475            && self.raw_children.is_empty()
476        {
477            return write_empty(writer, start);
478        }
479        write_start(writer, start)?;
480        emit_raw(writer, self.raw_children.at(0))?;
481        if !self.stops.is_empty() || !self.stop_list_raw_children.is_empty() {
482            write_start(writer, BytesStart::new("a:gsLst"))?;
483            for boundary in 0..=self.stops.len() {
484                emit_raw(writer, self.stop_list_raw_children.at(boundary))?;
485                if let Some(stop) = self.stops.get(boundary) {
486                    stop.write_xml(writer)?;
487                }
488            }
489            write_end(writer, "a:gsLst")?;
490        }
491        emit_raw(writer, self.raw_children.at(1))?;
492        if let Some(geometry) = &self.geometry {
493            write_geometry(writer, geometry)?;
494        }
495        emit_raw(writer, self.raw_children.at(2))?;
496        if let Some(rect) = &self.tile_rect {
497            write_rect(writer, "a:tileRect", rect)?;
498        }
499        emit_raw(writer, self.raw_children.at(3))?;
500        write_end(writer, "a:gradFill")
501    }
502
503    pub fn raw_children(&self) -> &OrderedRawChildren {
504        &self.raw_children
505    }
506}
507
508/// Pattern fill foreground, background, and preset identifier.
509#[derive(Clone, Debug, Default, Eq, PartialEq)]
510pub struct PatternFill {
511    pub preset: Option<String>,
512    pub foreground: Option<ColorChoice>,
513    pub background: Option<ColorChoice>,
514    raw_children: OrderedRawChildren,
515    foreground_raw_children: OrderedRawChildren,
516    background_raw_children: OrderedRawChildren,
517}
518
519impl PatternFill {
520    fn from_start(start: &BytesStart<'_>) -> Result<Self> {
521        reject_conflicting_a_prefix(start)?;
522        Ok(Self {
523            preset: get_attr(start, b"prst"),
524            ..Self::default()
525        })
526    }
527
528    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
529        let mut fill = Self::from_start(start)?;
530        let mut buffer = Vec::new();
531        let mut boundary = 0;
532        loop {
533            match reader
534                .read_event_into(&mut buffer)
535                .map_err(OxmlError::from)?
536            {
537                Event::Start(element) if matches_local_name(element.name().as_ref(), b"fgClr") => {
538                    reject_conflicting_a_prefix(&element)?;
539                    (fill.foreground, fill.foreground_raw_children) =
540                        parse_color_container(reader, b"fgClr")?;
541                    boundary = 1;
542                }
543                Event::Start(element) if matches_local_name(element.name().as_ref(), b"bgClr") => {
544                    reject_conflicting_a_prefix(&element)?;
545                    (fill.background, fill.background_raw_children) =
546                        parse_color_container(reader, b"bgClr")?;
547                    boundary = 2;
548                }
549                Event::Empty(element) if matches_local_name(element.name().as_ref(), b"fgClr") => {
550                    reject_conflicting_a_prefix(&element)?;
551                    boundary = 1
552                }
553                Event::Empty(element) if matches_local_name(element.name().as_ref(), b"bgClr") => {
554                    reject_conflicting_a_prefix(&element)?;
555                    boundary = 2
556                }
557                Event::Start(element) => fill
558                    .raw_children
559                    .push(boundary, capture_element(reader, &element)?),
560                Event::Empty(element) => fill
561                    .raw_children
562                    .push(boundary, capture_empty_element(&element)?),
563                Event::End(element) if matches_local_name(element.name().as_ref(), b"pattFill") => {
564                    break;
565                }
566                Event::Eof => return Err(missing_end("pattFill")),
567                _ => {}
568            }
569            buffer.clear();
570        }
571        Ok(fill)
572    }
573
574    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
575        let mut start = BytesStart::new("a:pattFill");
576        if let Some(preset) = self.preset.as_deref() {
577            start.push_attribute(("prst", preset));
578        }
579        if self.foreground.is_none()
580            && self.background.is_none()
581            && self.foreground_raw_children.is_empty()
582            && self.background_raw_children.is_empty()
583            && self.raw_children.is_empty()
584        {
585            return write_empty(writer, start);
586        }
587        write_start(writer, start)?;
588        emit_raw(writer, self.raw_children.at(0))?;
589        if self.foreground.is_some() || !self.foreground_raw_children.is_empty() {
590            write_color_container(
591                writer,
592                "a:fgClr",
593                self.foreground.as_ref(),
594                &self.foreground_raw_children,
595            )?;
596        }
597        emit_raw(writer, self.raw_children.at(1))?;
598        if self.background.is_some() || !self.background_raw_children.is_empty() {
599            write_color_container(
600                writer,
601                "a:bgClr",
602                self.background.as_ref(),
603                &self.background_raw_children,
604            )?;
605        }
606        emit_raw(writer, self.raw_children.at(2))?;
607        write_end(writer, "a:pattFill")
608    }
609
610    pub fn raw_children(&self) -> &OrderedRawChildren {
611        &self.raw_children
612    }
613}
614
615/// Picture relationship identifiers and preserved image effects.
616#[derive(Clone, Debug, Default, Eq, PartialEq)]
617pub struct Blip {
618    pub embed: Option<String>,
619    pub link: Option<String>,
620    /// Fixed alpha modulation in thousandths of a percent.
621    pub alpha_modulation_fix: Option<Percent1000>,
622    alpha_modulation_fix_raw_attributes: Vec<(String, String)>,
623    raw_children: OrderedRawChildren,
624}
625
626impl Blip {
627    fn from_start(start: &BytesStart<'_>) -> Self {
628        Self {
629            embed: get_attr(start, b"embed"),
630            link: get_attr(start, b"link"),
631            alpha_modulation_fix: None,
632            alpha_modulation_fix_raw_attributes: Vec::new(),
633            raw_children: OrderedRawChildren::default(),
634        }
635    }
636
637    fn from_element(
638        reader: &mut Reader<&[u8]>,
639        start: &BytesStart<'_>,
640        ancestor: &BytesStart<'_>,
641    ) -> Result<Self> {
642        let mut blip = Self::from_start(start);
643        let mut buffer = Vec::new();
644        let mut boundary = 0;
645        loop {
646            match reader
647                .read_event_into(&mut buffer)
648                .map_err(OxmlError::from)?
649            {
650                Event::Start(element)
651                    if blip.alpha_modulation_fix.is_none()
652                        && is_drawingml_alpha_mod_fix(&element, start, ancestor)? =>
653                {
654                    let (amount, raw_attributes) = parse_alpha_modulation_fix(&element)?;
655                    read_empty_effect_body(reader, &element)?;
656                    blip.alpha_modulation_fix = Some(amount);
657                    blip.alpha_modulation_fix_raw_attributes = raw_attributes;
658                    boundary = 1;
659                }
660                Event::Empty(element)
661                    if blip.alpha_modulation_fix.is_none()
662                        && is_drawingml_alpha_mod_fix(&element, start, ancestor)? =>
663                {
664                    let (amount, raw_attributes) = parse_alpha_modulation_fix(&element)?;
665                    blip.alpha_modulation_fix = Some(amount);
666                    blip.alpha_modulation_fix_raw_attributes = raw_attributes;
667                    boundary = 1;
668                }
669                Event::Start(element) => blip
670                    .raw_children
671                    .push(boundary, capture_element(reader, &element)?),
672                Event::Empty(element) => blip
673                    .raw_children
674                    .push(boundary, capture_empty_element(&element)?),
675                Event::End(element) if matches_local_name(element.name().as_ref(), b"blip") => {
676                    break;
677                }
678                Event::Eof => return Err(missing_end("blip")),
679                _ => {}
680            }
681            buffer.clear();
682        }
683        Ok(blip)
684    }
685
686    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
687        if let Some(amount) = self.alpha_modulation_fix
688            && !(0..=100_000).contains(&amount.0)
689        {
690            return Err(FillError::InvalidAttribute {
691                element: "alphaModFix".to_owned(),
692                attribute: "amt".to_owned(),
693                value: amount.0.to_string(),
694            });
695        }
696        let mut start = BytesStart::new("a:blip");
697        if self.embed.is_some() || self.link.is_some() {
698            start.push_attribute(("xmlns:r", R_NS));
699        }
700        if let Some(embed) = self.embed.as_deref() {
701            start.push_attribute(("r:embed", embed));
702        }
703        if let Some(link) = self.link.as_deref() {
704            start.push_attribute(("r:link", link));
705        }
706        if self.alpha_modulation_fix.is_none() && self.raw_children.is_empty() {
707            return write_empty(writer, start);
708        }
709        write_start(writer, start)?;
710        emit_raw(writer, self.raw_children.at(0))?;
711        if let Some(amount) = self.alpha_modulation_fix {
712            let amount = amount.0.to_string();
713            let mut effect = BytesStart::new("a:alphaModFix");
714            effect.push_attribute(("amt", amount.as_str()));
715            push_raw_attributes(&mut effect, &self.alpha_modulation_fix_raw_attributes);
716            write_empty(writer, effect)?;
717        }
718        emit_raw(writer, self.raw_children.at(1))?;
719        write_end(writer, "a:blip")
720    }
721}
722
723fn is_drawingml_alpha_mod_fix(
724    element: &BytesStart<'_>,
725    parent: &BytesStart<'_>,
726    ancestor: &BytesStart<'_>,
727) -> Result<bool> {
728    if !matches_local_name(element.name().as_ref(), b"alphaModFix") {
729        return Ok(false);
730    }
731    let qualified = element.name();
732    let qualified = qualified.as_ref();
733    let prefix = qualified
734        .split(|byte| *byte == b':')
735        .next()
736        .filter(|prefix| prefix.len() != qualified.len());
737    let binding = namespace_binding(element, prefix)?
738        .or(namespace_binding(parent, prefix)?)
739        .or(namespace_binding(ancestor, prefix)?);
740    if prefix == Some(b"a") && binding.as_deref().is_some_and(|uri| uri != A_NS) {
741        return Err(OxmlError::InvalidValue(
742            "xmlns:a conflicts with the fixed DrawingML writer namespace".to_owned(),
743        )
744        .into());
745    }
746    Ok(binding.as_deref() == Some(A_NS) || (prefix == Some(b"a") && binding.is_none()))
747}
748
749fn namespace_binding(start: &BytesStart<'_>, prefix: Option<&[u8]>) -> Result<Option<String>> {
750    let name = prefix.map_or_else(
751        || b"xmlns".to_vec(),
752        |prefix| {
753            let mut name = b"xmlns:".to_vec();
754            name.extend_from_slice(prefix);
755            name
756        },
757    );
758    for attribute in start.attributes() {
759        let attribute = attribute.map_err(OxmlError::from)?;
760        if attribute.key.as_ref() == name {
761            return Ok(Some(
762                attribute
763                    .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
764                    .map_err(OxmlError::from)?
765                    .into_owned(),
766            ));
767        }
768    }
769    Ok(None)
770}
771
772fn parse_alpha_modulation_fix(
773    element: &BytesStart<'_>,
774) -> Result<(Percent1000, Vec<(String, String)>)> {
775    let mut amount = None;
776    for attribute in element.attributes() {
777        let attribute = attribute.map_err(OxmlError::from)?;
778        if attribute.key.as_ref() == b"amt" {
779            amount = Some(
780                attribute
781                    .decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())
782                    .map_err(OxmlError::from)?
783                    .into_owned(),
784            );
785        }
786    }
787    let amount = amount.unwrap_or_else(|| "100000".to_owned());
788    let parsed = amount
789        .parse::<i32>()
790        .map_err(|_| invalid(element, b"amt", amount.clone()))?;
791    if !(0..=100_000).contains(&parsed) {
792        return Err(invalid(element, b"amt", amount));
793    }
794    Ok((
795        Percent1000(parsed),
796        capture_unmodelled_attributes(element, &[b"amt"])?,
797    ))
798}
799
800fn read_empty_effect_body(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<()> {
801    let mut buffer = Vec::new();
802    loop {
803        match reader
804            .read_event_into(&mut buffer)
805            .map_err(OxmlError::from)?
806        {
807            Event::End(element) if element.name().as_ref() == start.name().as_ref() => {
808                return Ok(());
809            }
810            Event::Text(text) if text.iter().all(u8::is_ascii_whitespace) => {}
811            Event::Start(element) | Event::Empty(element) => return Err(unexpected(&element)),
812            Event::Eof => return Err(missing_end("alphaModFix")),
813            _ => {
814                return Err(OxmlError::InvalidValue(
815                    "a:alphaModFix must not contain content".to_owned(),
816                )
817                .into());
818            }
819        }
820        buffer.clear();
821    }
822}
823
824/// Picture-fill placement mode.
825#[derive(Clone, Debug, Eq, PartialEq)]
826pub enum BlipMode {
827    Stretch {
828        fill_rect: Option<RelativeRect>,
829        raw_children: OrderedRawChildren,
830    },
831    Tile(Tile),
832}
833
834/// Tiled picture-fill offsets, scales, flip, and alignment.
835#[derive(Clone, Debug, Default, Eq, PartialEq)]
836pub struct Tile {
837    pub translation_x: Option<i64>,
838    pub translation_y: Option<i64>,
839    pub scale_x: Option<Percent1000>,
840    pub scale_y: Option<Percent1000>,
841    pub flip: Option<String>,
842    pub alignment: Option<String>,
843    raw_children: OrderedRawChildren,
844}
845
846/// Picture fill with relationship ids but no package dependency.
847#[derive(Clone, Debug, Default, Eq, PartialEq)]
848pub struct BlipFill {
849    pub dpi: Option<u32>,
850    pub rotate_with_shape: Option<bool>,
851    pub blip: Option<Blip>,
852    pub source_rect: Option<RelativeRect>,
853    pub mode: Option<BlipMode>,
854    raw_attributes: Vec<(String, String)>,
855    raw_children: OrderedRawChildren,
856}
857
858impl BlipFill {
859    /// Parses one complete picture-fill element with any namespace prefix.
860    pub fn from_xml(xml: &[u8]) -> Result<Self> {
861        let mut reader = Reader::from_reader(xml);
862        let mut buffer = Vec::new();
863        loop {
864            match reader
865                .read_event_into(&mut buffer)
866                .map_err(OxmlError::from)?
867            {
868                Event::Start(element)
869                    if matches_local_name(element.name().as_ref(), b"blipFill") =>
870                {
871                    return Self::from_element(&mut reader, &element);
872                }
873                Event::Empty(element)
874                    if matches_local_name(element.name().as_ref(), b"blipFill") =>
875                {
876                    return Self::from_start(&element);
877                }
878                Event::Start(element) | Event::Empty(element) => {
879                    return Err(unexpected(&element));
880                }
881                Event::Eof => return Err(missing_end("blipFill")),
882                _ => {}
883            }
884            buffer.clear();
885        }
886    }
887
888    fn from_start(start: &BytesStart<'_>) -> Result<Self> {
889        reject_conflicting_a_prefix(start)?;
890        Ok(Self {
891            dpi: optional_parse(start, b"dpi")?,
892            rotate_with_shape: optional_bool(start, b"rotWithShape")?,
893            raw_attributes: capture_unmodelled_attributes(start, &[b"dpi", b"rotWithShape"])?,
894            ..Self::default()
895        })
896    }
897
898    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
899        let mut fill = Self::from_start(start)?;
900        let mut buffer = Vec::new();
901        let mut boundary = 0;
902        loop {
903            match reader
904                .read_event_into(&mut buffer)
905                .map_err(OxmlError::from)?
906            {
907                Event::Start(element) if matches_local_name(element.name().as_ref(), b"blip") => {
908                    reject_conflicting_a_prefix(&element)?;
909                    fill.blip = Some(Blip::from_element(reader, &element, start)?);
910                    boundary = 1;
911                }
912                Event::Empty(element) if matches_local_name(element.name().as_ref(), b"blip") => {
913                    reject_conflicting_a_prefix(&element)?;
914                    fill.blip = Some(Blip::from_start(&element));
915                    boundary = 1;
916                }
917                Event::Start(element)
918                    if matches_local_name(element.name().as_ref(), b"srcRect") =>
919                {
920                    fill.source_rect = Some(parse_rect_element(reader, &element)?);
921                    boundary = 2;
922                }
923                Event::Empty(element)
924                    if matches_local_name(element.name().as_ref(), b"srcRect") =>
925                {
926                    fill.source_rect = Some(parse_rect(&element)?);
927                    boundary = 2;
928                }
929                Event::Start(element)
930                    if matches_local_name(element.name().as_ref(), b"stretch") =>
931                {
932                    reject_conflicting_a_prefix(&element)?;
933                    fill.mode = Some(parse_stretch(reader)?);
934                    boundary = 3;
935                }
936                Event::Empty(element)
937                    if matches_local_name(element.name().as_ref(), b"stretch") =>
938                {
939                    reject_conflicting_a_prefix(&element)?;
940                    fill.mode = Some(BlipMode::Stretch {
941                        fill_rect: None,
942                        raw_children: OrderedRawChildren::default(),
943                    });
944                    boundary = 3;
945                }
946                Event::Start(element) if matches_local_name(element.name().as_ref(), b"tile") => {
947                    fill.mode = Some(BlipMode::Tile(parse_tile(reader, &element)?));
948                    boundary = 3;
949                }
950                Event::Empty(element) if matches_local_name(element.name().as_ref(), b"tile") => {
951                    fill.mode = Some(BlipMode::Tile(parse_empty_tile(&element)?));
952                    boundary = 3;
953                }
954                Event::Start(element) => fill
955                    .raw_children
956                    .push(boundary, capture_element(reader, &element)?),
957                Event::Empty(element) => fill
958                    .raw_children
959                    .push(boundary, capture_empty_element(&element)?),
960                Event::End(element) if matches_local_name(element.name().as_ref(), b"blipFill") => {
961                    break;
962                }
963                Event::Eof => return Err(missing_end("blipFill")),
964                _ => {}
965            }
966            buffer.clear();
967        }
968        Ok(fill)
969    }
970
971    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
972        self.write_xml_as(writer, "a:blipFill")
973    }
974
975    /// Writes the picture fill under the caller's required root name.
976    pub fn write_xml_as<W: Write>(&self, writer: &mut Writer<W>, name: &str) -> Result<()> {
977        let mut start = BytesStart::new(name);
978        let dpi = self.dpi.map(|value| value.to_string());
979        if let Some(dpi) = dpi.as_deref() {
980            start.push_attribute(("dpi", dpi));
981        }
982        if let Some(rotate) = self.rotate_with_shape.map(bool_text) {
983            start.push_attribute(("rotWithShape", rotate));
984        }
985        push_raw_attributes(&mut start, &self.raw_attributes);
986        if self.blip.is_none()
987            && self.source_rect.is_none()
988            && self.mode.is_none()
989            && self.raw_children.is_empty()
990        {
991            return write_empty(writer, start);
992        }
993        write_start(writer, start)?;
994        emit_raw(writer, self.raw_children.at(0))?;
995        if let Some(blip) = &self.blip {
996            blip.write_xml(writer)?;
997        }
998        emit_raw(writer, self.raw_children.at(1))?;
999        if let Some(rect) = &self.source_rect {
1000            write_rect(writer, "a:srcRect", rect)?;
1001        }
1002        emit_raw(writer, self.raw_children.at(2))?;
1003        if let Some(mode) = &self.mode {
1004            write_blip_mode(writer, mode)?;
1005        }
1006        emit_raw(writer, self.raw_children.at(3))?;
1007        write_end(writer, name)
1008    }
1009
1010    pub fn raw_children(&self) -> &OrderedRawChildren {
1011        &self.raw_children
1012    }
1013}
1014
1015fn capture_unmodelled_attributes(
1016    start: &BytesStart<'_>,
1017    modelled: &[&[u8]],
1018) -> Result<Vec<(String, String)>> {
1019    let mut raw = Vec::new();
1020    for attribute in start.attributes() {
1021        let attribute = attribute.map_err(OxmlError::from)?;
1022        let key = attribute.key.as_ref();
1023        if !key.contains(&b':') && modelled.contains(&key) {
1024            continue;
1025        }
1026        let name = std::str::from_utf8(key)
1027            .map_err(OxmlError::from)?
1028            .to_owned();
1029        let value = attribute
1030            .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
1031            .map_err(OxmlError::from)?
1032            .into_owned();
1033        raw.push((name, value));
1034    }
1035    Ok(raw)
1036}
1037
1038fn push_raw_attributes(start: &mut BytesStart<'_>, attributes: &[(String, String)]) {
1039    for (name, value) in attributes {
1040        start.push_attribute((name.as_str(), value.as_str()));
1041    }
1042}
1043
1044fn parse_linear(start: &BytesStart<'_>) -> Result<LinearGradient> {
1045    reject_conflicting_a_prefix(start)?;
1046    let angle = optional_parse::<i32>(start, b"ang")?;
1047    Ok(LinearGradient {
1048        angle: Angle(angle.unwrap_or(0)),
1049        scaled: optional_bool(start, b"scaled")?,
1050        angle_omitted: angle.is_none(),
1051        raw_children: OrderedRawChildren::default(),
1052    })
1053}
1054
1055fn parse_linear_element(
1056    reader: &mut Reader<&[u8]>,
1057    start: &BytesStart<'_>,
1058) -> Result<LinearGradient> {
1059    let mut linear = parse_linear(start)?;
1060    linear.raw_children = capture_all_children(reader, b"lin")?;
1061    Ok(linear)
1062}
1063
1064fn parse_empty_path(start: &BytesStart<'_>) -> Result<PathGradient> {
1065    reject_conflicting_a_prefix(start)?;
1066    let (kind, kind_omitted) = match get_attr(start, b"path") {
1067        Some(value) => (
1068            PathGradientKind::parse(&value).ok_or_else(|| invalid(start, b"path", value))?,
1069            false,
1070        ),
1071        None => (PathGradientKind::Rectangle, true),
1072    };
1073    Ok(PathGradient {
1074        kind,
1075        fill_to_rect: None,
1076        kind_omitted,
1077        raw_children: OrderedRawChildren::default(),
1078    })
1079}
1080
1081fn parse_path(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<PathGradient> {
1082    let mut path = parse_empty_path(start)?;
1083    let mut buffer = Vec::new();
1084    let mut boundary = 0;
1085    loop {
1086        match reader
1087            .read_event_into(&mut buffer)
1088            .map_err(OxmlError::from)?
1089        {
1090            Event::Start(element) if matches_local_name(element.name().as_ref(), b"fillToRect") => {
1091                path.fill_to_rect = Some(parse_rect_element(reader, &element)?);
1092                boundary = 1;
1093            }
1094            Event::Empty(element) if matches_local_name(element.name().as_ref(), b"fillToRect") => {
1095                path.fill_to_rect = Some(parse_rect(&element)?);
1096                boundary = 1;
1097            }
1098            Event::Start(element) => path
1099                .raw_children
1100                .push(boundary, capture_element(reader, &element)?),
1101            Event::Empty(element) => path
1102                .raw_children
1103                .push(boundary, capture_empty_element(&element)?),
1104            Event::End(element) if matches_local_name(element.name().as_ref(), b"path") => break,
1105            Event::Eof => return Err(missing_end("path")),
1106            _ => {}
1107        }
1108        buffer.clear();
1109    }
1110    Ok(path)
1111}
1112
1113fn write_geometry<W: Write>(writer: &mut Writer<W>, geometry: &GradientGeometry) -> Result<()> {
1114    match geometry {
1115        GradientGeometry::Linear(linear) => {
1116            let angle = linear.angle.0.to_string();
1117            let mut start = BytesStart::new("a:lin");
1118            if !linear.angle_omitted || linear.angle.0 != 0 {
1119                start.push_attribute(("ang", angle.as_str()));
1120            }
1121            if let Some(scaled) = linear.scaled.map(bool_text) {
1122                start.push_attribute(("scaled", scaled));
1123            }
1124            if linear.raw_children.is_empty() {
1125                return write_empty(writer, start);
1126            }
1127            write_start(writer, start)?;
1128            emit_raw(writer, linear.raw_children.at(0))?;
1129            write_end(writer, "a:lin")
1130        }
1131        GradientGeometry::Path(path) => {
1132            let mut start = BytesStart::new("a:path");
1133            if !path.kind_omitted || path.kind != PathGradientKind::Rectangle {
1134                start.push_attribute(("path", path.kind.as_str()));
1135            }
1136            if path.fill_to_rect.is_none() && path.raw_children.is_empty() {
1137                return write_empty(writer, start);
1138            }
1139            write_start(writer, start)?;
1140            emit_raw(writer, path.raw_children.at(0))?;
1141            if let Some(rect) = &path.fill_to_rect {
1142                write_rect(writer, "a:fillToRect", rect)?;
1143            }
1144            emit_raw(writer, path.raw_children.at(1))?;
1145            write_end(writer, "a:path")
1146        }
1147    }
1148}
1149
1150fn parse_stretch(reader: &mut Reader<&[u8]>) -> Result<BlipMode> {
1151    let mut fill_rect = None;
1152    let mut raw_children = OrderedRawChildren::default();
1153    let mut boundary = 0;
1154    let mut buffer = Vec::new();
1155    loop {
1156        match reader
1157            .read_event_into(&mut buffer)
1158            .map_err(OxmlError::from)?
1159        {
1160            Event::Start(element) if matches_local_name(element.name().as_ref(), b"fillRect") => {
1161                fill_rect = Some(parse_rect_element(reader, &element)?);
1162                boundary = 1;
1163            }
1164            Event::Empty(element) if matches_local_name(element.name().as_ref(), b"fillRect") => {
1165                fill_rect = Some(parse_rect(&element)?);
1166                boundary = 1;
1167            }
1168            Event::Start(element) => {
1169                raw_children.push(boundary, capture_element(reader, &element)?)
1170            }
1171            Event::Empty(element) => raw_children.push(boundary, capture_empty_element(&element)?),
1172            Event::End(element) if matches_local_name(element.name().as_ref(), b"stretch") => break,
1173            Event::Eof => return Err(missing_end("stretch")),
1174            _ => {}
1175        }
1176        buffer.clear();
1177    }
1178    Ok(BlipMode::Stretch {
1179        fill_rect,
1180        raw_children,
1181    })
1182}
1183
1184fn parse_empty_tile(start: &BytesStart<'_>) -> Result<Tile> {
1185    reject_conflicting_a_prefix(start)?;
1186    Ok(Tile {
1187        translation_x: optional_parse(start, b"tx")?,
1188        translation_y: optional_parse(start, b"ty")?,
1189        scale_x: optional_parse::<i32>(start, b"sx")?.map(Percent1000),
1190        scale_y: optional_parse::<i32>(start, b"sy")?.map(Percent1000),
1191        flip: optional_token(start, b"flip", &["none", "x", "y", "xy"])?,
1192        alignment: optional_token(
1193            start,
1194            b"algn",
1195            &["tl", "t", "tr", "l", "ctr", "r", "bl", "b", "br"],
1196        )?,
1197        raw_children: OrderedRawChildren::default(),
1198    })
1199}
1200
1201fn parse_tile(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Tile> {
1202    let mut tile = parse_empty_tile(start)?;
1203    tile.raw_children = capture_all_children(reader, b"tile")?;
1204    Ok(tile)
1205}
1206
1207fn write_blip_mode<W: Write>(writer: &mut Writer<W>, mode: &BlipMode) -> Result<()> {
1208    match mode {
1209        BlipMode::Stretch {
1210            fill_rect,
1211            raw_children,
1212        } => {
1213            if fill_rect.is_none() && raw_children.is_empty() {
1214                return write_empty(writer, BytesStart::new("a:stretch"));
1215            }
1216            write_start(writer, BytesStart::new("a:stretch"))?;
1217            emit_raw(writer, raw_children.at(0))?;
1218            if let Some(rect) = fill_rect {
1219                write_rect(writer, "a:fillRect", rect)?;
1220            }
1221            emit_raw(writer, raw_children.at(1))?;
1222            write_end(writer, "a:stretch")
1223        }
1224        BlipMode::Tile(tile) => {
1225            let mut start = BytesStart::new("a:tile");
1226            let tx = tile.translation_x.map(|value| value.to_string());
1227            let ty = tile.translation_y.map(|value| value.to_string());
1228            let sx = tile.scale_x.map(|value| value.0.to_string());
1229            let sy = tile.scale_y.map(|value| value.0.to_string());
1230            for (name, value) in [
1231                ("tx", tx.as_deref()),
1232                ("ty", ty.as_deref()),
1233                ("sx", sx.as_deref()),
1234                ("sy", sy.as_deref()),
1235            ] {
1236                if let Some(value) = value {
1237                    start.push_attribute((name, value));
1238                }
1239            }
1240            if let Some(flip) = tile.flip.as_deref() {
1241                start.push_attribute(("flip", flip));
1242            }
1243            if let Some(alignment) = tile.alignment.as_deref() {
1244                start.push_attribute(("algn", alignment));
1245            }
1246            if tile.raw_children.is_empty() {
1247                return write_empty(writer, start);
1248            }
1249            write_start(writer, start)?;
1250            emit_raw(writer, tile.raw_children.at(0))?;
1251            write_end(writer, "a:tile")
1252        }
1253    }
1254}
1255
1256fn parse_rect(start: &BytesStart<'_>) -> Result<RelativeRect> {
1257    reject_conflicting_a_prefix(start)?;
1258    Ok(RelativeRect {
1259        left: optional_parse::<i32>(start, b"l")?.map(Percent1000),
1260        top: optional_parse::<i32>(start, b"t")?.map(Percent1000),
1261        right: optional_parse::<i32>(start, b"r")?.map(Percent1000),
1262        bottom: optional_parse::<i32>(start, b"b")?.map(Percent1000),
1263        raw_children: OrderedRawChildren::default(),
1264    })
1265}
1266
1267fn parse_rect_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<RelativeRect> {
1268    let mut rect = parse_rect(start)?;
1269    rect.raw_children = capture_all_children(reader, local_name(start.name().as_ref()))?;
1270    Ok(rect)
1271}
1272
1273fn write_rect<W: Write>(writer: &mut Writer<W>, name: &str, rect: &RelativeRect) -> Result<()> {
1274    let left = rect.left.map(|value| value.0.to_string());
1275    let top = rect.top.map(|value| value.0.to_string());
1276    let right = rect.right.map(|value| value.0.to_string());
1277    let bottom = rect.bottom.map(|value| value.0.to_string());
1278    let mut start = BytesStart::new(name);
1279    for (name, value) in [
1280        ("l", left.as_deref()),
1281        ("t", top.as_deref()),
1282        ("r", right.as_deref()),
1283        ("b", bottom.as_deref()),
1284    ] {
1285        if let Some(value) = value {
1286            start.push_attribute((name, value));
1287        }
1288    }
1289    if rect.raw_children.is_empty() {
1290        return write_empty(writer, start);
1291    }
1292    write_start(writer, start)?;
1293    emit_raw(writer, rect.raw_children.at(0))?;
1294    write_end(writer, name)
1295}
1296
1297fn parse_color_container(
1298    reader: &mut Reader<&[u8]>,
1299    end_name: &[u8],
1300) -> Result<(Option<ColorChoice>, OrderedRawChildren)> {
1301    let mut color = None;
1302    let mut raw_children = OrderedRawChildren::default();
1303    let mut boundary = 0;
1304    let mut buffer = Vec::new();
1305    loop {
1306        match reader
1307            .read_event_into(&mut buffer)
1308            .map_err(OxmlError::from)?
1309        {
1310            Event::Start(element) if is_color(element.name().as_ref()) => {
1311                color = Some(ColorChoice::from_xml(reader, &element)?);
1312                boundary = 1;
1313            }
1314            Event::Empty(element) if is_color(element.name().as_ref()) => {
1315                color = Some(ColorChoice::from_empty_xml(&element)?);
1316                boundary = 1;
1317            }
1318            Event::Start(element) => {
1319                raw_children.push(boundary, capture_element(reader, &element)?);
1320            }
1321            Event::Empty(element) => {
1322                raw_children.push(boundary, capture_empty_element(&element)?);
1323            }
1324            Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
1325            Event::Eof => return Err(missing_end(&String::from_utf8_lossy(end_name))),
1326            _ => {}
1327        }
1328        buffer.clear();
1329    }
1330    Ok((color, raw_children))
1331}
1332
1333fn write_color_container<W: Write>(
1334    writer: &mut Writer<W>,
1335    name: &str,
1336    color: Option<&ColorChoice>,
1337    raw_children: &OrderedRawChildren,
1338) -> Result<()> {
1339    write_start(writer, BytesStart::new(name))?;
1340    emit_raw(writer, raw_children.at(0))?;
1341    if let Some(color) = color {
1342        color.to_xml(writer)?;
1343    }
1344    emit_raw(writer, raw_children.at(1))?;
1345    write_end(writer, name)
1346}
1347
1348fn read_color_children(
1349    reader: &mut Reader<&[u8]>,
1350    end_name: &[u8],
1351    color: &mut Option<ColorChoice>,
1352    raw_children: &mut OrderedRawChildren,
1353) -> Result<()> {
1354    let mut buffer = Vec::new();
1355    let mut boundary = 0;
1356    loop {
1357        match reader
1358            .read_event_into(&mut buffer)
1359            .map_err(OxmlError::from)?
1360        {
1361            Event::Start(element) if is_color(element.name().as_ref()) => {
1362                *color = Some(ColorChoice::from_xml(reader, &element)?);
1363                boundary = 1;
1364            }
1365            Event::Empty(element) if is_color(element.name().as_ref()) => {
1366                *color = Some(ColorChoice::from_empty_xml(&element)?);
1367                boundary = 1;
1368            }
1369            Event::Start(element) => {
1370                raw_children.push(boundary, capture_element(reader, &element)?)
1371            }
1372            Event::Empty(element) => raw_children.push(boundary, capture_empty_element(&element)?),
1373            Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
1374            Event::Eof => return Err(missing_end(&String::from_utf8_lossy(end_name))),
1375            _ => {}
1376        }
1377        buffer.clear();
1378    }
1379    Ok(())
1380}
1381
1382fn capture_all_children(reader: &mut Reader<&[u8]>, end_name: &[u8]) -> Result<OrderedRawChildren> {
1383    let mut raw_children = OrderedRawChildren::default();
1384    let mut buffer = Vec::new();
1385    loop {
1386        match reader
1387            .read_event_into(&mut buffer)
1388            .map_err(OxmlError::from)?
1389        {
1390            Event::Start(element) => raw_children.push(0, capture_element(reader, &element)?),
1391            Event::Empty(element) => raw_children.push(0, capture_empty_element(&element)?),
1392            Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
1393            Event::Eof => return Err(missing_end(&String::from_utf8_lossy(end_name))),
1394            _ => {}
1395        }
1396        buffer.clear();
1397    }
1398    Ok(raw_children)
1399}
1400
1401fn is_color(name: &[u8]) -> bool {
1402    matches!(
1403        local_name(name),
1404        b"srgbClr" | b"schemeClr" | b"sysClr" | b"prstClr"
1405    )
1406}
1407
1408fn validate_gradient_position(value: i32) -> Result<()> {
1409    if (0..=100_000).contains(&value) {
1410        Ok(())
1411    } else {
1412        Err(FillError::GradientStopOutOfRange(value))
1413    }
1414}
1415
1416fn required_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<String> {
1417    get_attr(start, name).ok_or_else(|| FillError::MissingAttribute {
1418        element: String::from_utf8_lossy(local_name(start.name().as_ref())).into_owned(),
1419        attribute: String::from_utf8_lossy(name).into_owned(),
1420    })
1421}
1422
1423fn required_i32(start: &BytesStart<'_>, name: &[u8]) -> Result<i32> {
1424    let value = required_attr(start, name)?;
1425    value.parse().map_err(|_| invalid(start, name, value))
1426}
1427
1428fn optional_parse<T: std::str::FromStr>(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<T>> {
1429    get_attr(start, name)
1430        .map(|value| value.parse().map_err(|_| invalid(start, name, value)))
1431        .transpose()
1432}
1433
1434fn optional_bool(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<bool>> {
1435    get_attr(start, name)
1436        .map(|value| match value.as_str() {
1437            "1" | "true" => Ok(true),
1438            "0" | "false" => Ok(false),
1439            _ => Err(invalid(start, name, value)),
1440        })
1441        .transpose()
1442}
1443
1444fn optional_token(start: &BytesStart<'_>, name: &[u8], allowed: &[&str]) -> Result<Option<String>> {
1445    get_attr(start, name)
1446        .map(|value| {
1447            if allowed.contains(&value.as_str()) {
1448                Ok(value)
1449            } else {
1450                Err(invalid(start, name, value))
1451            }
1452        })
1453        .transpose()
1454}
1455
1456fn invalid(start: &BytesStart<'_>, attribute: &[u8], value: String) -> FillError {
1457    FillError::InvalidAttribute {
1458        element: String::from_utf8_lossy(local_name(start.name().as_ref())).into_owned(),
1459        attribute: String::from_utf8_lossy(attribute).into_owned(),
1460        value,
1461    }
1462}
1463
1464fn unexpected(start: &BytesStart<'_>) -> FillError {
1465    FillError::UnexpectedElement(String::from_utf8_lossy(start.name().as_ref()).into_owned())
1466}
1467
1468fn missing_end(name: &str) -> FillError {
1469    FillError::Xml(OxmlError::MissingElement(format!("closing a:{name}")))
1470}
1471
1472const fn bool_text(value: bool) -> &'static str {
1473    if value { "1" } else { "0" }
1474}
1475
1476fn emit_raw<'a, W: Write>(
1477    writer: &mut Writer<W>,
1478    children: impl Iterator<Item = &'a [u8]>,
1479) -> Result<()> {
1480    for child in children {
1481        writer.get_mut().write_all(child).map_err(OxmlError::from)?;
1482    }
1483    Ok(())
1484}
1485
1486fn write_start<W: Write>(writer: &mut Writer<W>, start: BytesStart<'_>) -> Result<()> {
1487    writer
1488        .write_event(Event::Start(start))
1489        .map_err(OxmlError::from)?;
1490    Ok(())
1491}
1492
1493fn write_empty<W: Write>(writer: &mut Writer<W>, start: BytesStart<'_>) -> Result<()> {
1494    writer
1495        .write_event(Event::Empty(start))
1496        .map_err(OxmlError::from)?;
1497    Ok(())
1498}
1499
1500fn write_end<W: Write>(writer: &mut Writer<W>, name: &str) -> Result<()> {
1501    writer
1502        .write_event(Event::End(BytesEnd::new(name)))
1503        .map_err(OxmlError::from)?;
1504    Ok(())
1505}
1506
1507#[cfg(test)]
1508mod tests {
1509    use super::{
1510        Angle, Fill, FillError, GradientFill, GradientGeometry, LinearGradient, PathGradientKind,
1511    };
1512
1513    #[test]
1514    fn every_fill_form_round_trips_and_gradient_stops_keep_document_order() {
1515        let cases: &[&[u8]] = &[
1516            br#"<a:noFill/>"#,
1517            br#"<a:solidFill><a:schemeClr val="accent1"/></a:solidFill>"#,
1518            br#"<a:gradFill flip="xy" rotWithShape="1"><a:gsLst><a:gs pos="75000"><a:srgbClr val="FF0000"/></a:gs><a:gs pos="25000"><a:schemeClr val="accent2"/></a:gs></a:gsLst><a:lin ang="5400000" scaled="1"/><a:tileRect l="1000"/></a:gradFill>"#,
1519            br#"<a:gradFill><a:gsLst><a:gs pos="0"><a:srgbClr val="000000"/></a:gs><a:gs pos="100000"><a:srgbClr val="FFFFFF"/></a:gs></a:gsLst><a:path path="circle"><a:fillToRect l="10000" t="10000" r="10000" b="10000"/></a:path></a:gradFill>"#,
1520            br#"<a:pattFill prst="pct10"><a:fgClr><a:srgbClr val="112233"/></a:fgClr><a:bgClr><a:schemeClr val="bg1"/></a:bgClr></a:pattFill>"#,
1521            br#"<a:blipFill dpi="96" rotWithShape="0"><a:blip r:embed="rId7"/><a:srcRect l="1000" t="2000"/><a:stretch><a:fillRect r="3000"/></a:stretch></a:blipFill>"#,
1522            br#"<a:blipFill><a:blip r:link="rId8"/><a:tile tx="10" ty="20" sx="50000" sy="75000" flip="x" algn="ctr"/></a:blipFill>"#,
1523        ];
1524
1525        for xml in cases {
1526            let parsed = Fill::from_xml(xml).unwrap();
1527            let written = parsed.to_xml().unwrap();
1528            assert_eq!(Fill::from_xml(&written).unwrap(), parsed);
1529        }
1530
1531        let Fill::Gradient(gradient) = Fill::from_xml(cases[2]).unwrap() else {
1532            panic!("expected gradient")
1533        };
1534        assert_eq!(
1535            gradient
1536                .stops
1537                .iter()
1538                .map(|stop| stop.position.0)
1539                .collect::<Vec<_>>(),
1540            vec![75_000, 25_000]
1541        );
1542        assert!(matches!(
1543            gradient.geometry,
1544            Some(GradientGeometry::Linear(_))
1545        ));
1546    }
1547
1548    #[test]
1549    fn gradient_geometry_without_angle_or_path_round_trips_without_adding_them() {
1550        let cases: &[&[u8]] = &[
1551            br#"<a:gradFill><a:lin scaled="0"/></a:gradFill>"#,
1552            br#"<a:gradFill><a:lin/></a:gradFill>"#,
1553            br#"<a:gradFill><a:path/></a:gradFill>"#,
1554            br#"<a:gradFill><a:path><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path></a:gradFill>"#,
1555        ];
1556        for xml in cases {
1557            let parsed = Fill::from_xml(xml).unwrap();
1558            assert_eq!(parsed.to_xml().unwrap(), *xml);
1559        }
1560
1561        // A missing angle reads as 0 and a missing path as rect. Another value
1562        // assigned to either is written, since the default no longer holds.
1563        let Fill::Gradient(mut gradient) = Fill::from_xml(cases[0]).unwrap() else {
1564            panic!("expected gradient")
1565        };
1566        let Some(GradientGeometry::Linear(linear)) = &mut gradient.geometry else {
1567            panic!("expected linear geometry")
1568        };
1569        assert_eq!(linear.angle, Angle(0));
1570        assert_eq!(linear.scaled, Some(false));
1571        linear.angle = Angle(5_400_000);
1572        assert_eq!(
1573            Fill::Gradient(gradient).to_xml().unwrap(),
1574            br#"<a:gradFill><a:lin ang="5400000" scaled="0"/></a:gradFill>"#
1575        );
1576
1577        let Fill::Gradient(mut gradient) = Fill::from_xml(cases[3]).unwrap() else {
1578            panic!("expected gradient")
1579        };
1580        let Some(GradientGeometry::Path(path)) = &mut gradient.geometry else {
1581            panic!("expected path geometry")
1582        };
1583        assert_eq!(path.kind, PathGradientKind::Rectangle);
1584        assert!(path.fill_to_rect.is_some());
1585        path.kind = PathGradientKind::Circle;
1586        assert_eq!(
1587            Fill::Gradient(gradient).to_xml().unwrap(),
1588            br#"<a:gradFill><a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path></a:gradFill>"#
1589        );
1590
1591        // Explicit defaults, and a value built in code, keep their attribute.
1592        for xml in [
1593            br#"<a:gradFill><a:lin ang="0" scaled="0"/></a:gradFill>"#.as_slice(),
1594            br#"<a:gradFill><a:path path="rect"/></a:gradFill>"#,
1595        ] {
1596            assert_eq!(Fill::from_xml(xml).unwrap().to_xml().unwrap(), xml);
1597        }
1598        let built = GradientFill {
1599            geometry: Some(GradientGeometry::Linear(LinearGradient::default())),
1600            ..GradientFill::default()
1601        };
1602        assert_eq!(
1603            Fill::Gradient(built).to_xml().unwrap(),
1604            br#"<a:gradFill><a:lin ang="0"/></a:gradFill>"#
1605        );
1606    }
1607
1608    #[test]
1609    fn fill_forms_read_any_prefix_and_write_fixed_a_prefix_in_schema_order() {
1610        let fill = Fill::from_xml(br#"<z:blipFill><z:blip r:embed="rId4"/><z:srcRect l="10"/><z:stretch><z:fillRect t="20"/></z:stretch></z:blipFill>"#).unwrap();
1611        assert_eq!(fill.to_xml().unwrap(), br#"<a:blipFill><a:blip xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:embed="rId4"/><a:srcRect l="10"/><a:stretch><a:fillRect t="20"/></a:stretch></a:blipFill>"#);
1612
1613        let gradient = Fill::from_xml(br#"<q:gradFill><q:gsLst><q:gs pos="0"><q:srgbClr val="ABCDEF"/></q:gs></q:gsLst><q:path path="rect"/></q:gradFill>"#).unwrap();
1614        assert_eq!(gradient.to_xml().unwrap(), br#"<a:gradFill><a:gsLst><a:gs pos="0"><a:srgbClr val="ABCDEF"/></a:gs></a:gsLst><a:path path="rect"/></a:gradFill>"#);
1615    }
1616
1617    #[test]
1618    fn blip_relationship_prefix_is_declared_in_canonical_output() {
1619        let fill = Fill::from_xml(
1620            br#"<a:blipFill><a:blip xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:embed="rId1"/></a:blipFill>"#,
1621        )
1622        .unwrap();
1623        let written = String::from_utf8(fill.to_xml().unwrap()).unwrap();
1624
1625        assert!(written.contains(
1626            "<a:blip xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" r:embed=\"rId1\"/>"
1627        ));
1628    }
1629
1630    #[test]
1631    fn unknown_fill_children_round_trip_byte_for_byte_in_place() {
1632        let xml = br#"<z:gradFill><x:before x:id="1"><x:item>one &amp; two</x:item><!--note--></x:before><z:gsLst><y:first/><z:gs pos="0"><z:srgbClr val="000000"><x:transform value="kept"/></z:srgbClr></z:gs><y:last/></z:gsLst><x:middle/><z:lin ang="0"/><x:after/></z:gradFill>"#;
1633        let written = Fill::from_xml(xml).unwrap().to_xml().unwrap();
1634        assert_eq!(written, br#"<a:gradFill><x:before x:id="1"><x:item>one &amp; two</x:item><!--note--></x:before><a:gsLst><y:first/><a:gs pos="0"><a:srgbClr val="000000"><x:transform value="kept"/></a:srgbClr></a:gs><y:last/></a:gsLst><x:middle/><a:lin ang="0"/><x:after/></a:gradFill>"#);
1635
1636        let blip = br#"<z:blipFill><z:blip r:embed="rId1"><a:alphaModFix amt="50000"/><x:ext x:v="raw"/></z:blip><x:between/><z:tile><x:tileExt/></z:tile></z:blipFill>"#;
1637        assert_eq!(Fill::from_xml(blip).unwrap().to_xml().unwrap(), br#"<a:blipFill><a:blip xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:embed="rId1"><a:alphaModFix amt="50000"/><x:ext x:v="raw"/></a:blip><x:between/><a:tile><x:tileExt/></a:tile></a:blipFill>"#);
1638
1639        let pattern = br#"<z:pattFill><z:fgClr><x:before/><z:srgbClr val="102030"/><x:after x:v="kept"/></z:fgClr></z:pattFill>"#;
1640        assert_eq!(Fill::from_xml(pattern).unwrap().to_xml().unwrap(), br#"<a:pattFill><a:fgClr><x:before/><a:srgbClr val="102030"/><x:after x:v="kept"/></a:fgClr></a:pattFill>"#);
1641
1642        let extension_only_pattern =
1643            br#"<z:pattFill><z:fgClr><x:extension x:v="kept"/></z:fgClr></z:pattFill>"#;
1644        assert_eq!(
1645            Fill::from_xml(extension_only_pattern)
1646                .unwrap()
1647                .to_xml()
1648                .unwrap(),
1649            br#"<a:pattFill><a:fgClr><x:extension x:v="kept"/></a:fgClr></a:pattFill>"#
1650        );
1651
1652        let gradient_leaf_extensions = br#"<z:gradFill><z:lin ang="0"><x:linExt/></z:lin><z:tileRect l="10"><x:tileRectExt/></z:tileRect></z:gradFill>"#;
1653        assert_eq!(Fill::from_xml(gradient_leaf_extensions).unwrap().to_xml().unwrap(), br#"<a:gradFill><a:lin ang="0"><x:linExt/></a:lin><a:tileRect l="10"><x:tileRectExt/></a:tileRect></a:gradFill>"#);
1654
1655        let extension_only_stop_list =
1656            br#"<z:gradFill><z:gsLst><x:extension x:v="kept"/></z:gsLst></z:gradFill>"#;
1657        assert_eq!(
1658            Fill::from_xml(extension_only_stop_list)
1659                .unwrap()
1660                .to_xml()
1661                .unwrap(),
1662            br#"<a:gradFill><a:gsLst><x:extension x:v="kept"/></a:gsLst></a:gradFill>"#
1663        );
1664
1665        let path_leaf_extension = br#"<z:gradFill><z:path path="rect"><z:fillToRect><x:pathRectExt/></z:fillToRect></z:path></z:gradFill>"#;
1666        assert_eq!(Fill::from_xml(path_leaf_extension).unwrap().to_xml().unwrap(), br#"<a:gradFill><a:path path="rect"><a:fillToRect><x:pathRectExt/></a:fillToRect></a:path></a:gradFill>"#);
1667
1668        let blip_leaf_extensions = br#"<z:blipFill><z:srcRect><x:sourceExt/></z:srcRect><z:stretch><z:fillRect><x:fillExt/></z:fillRect></z:stretch></z:blipFill>"#;
1669        assert_eq!(Fill::from_xml(blip_leaf_extensions).unwrap().to_xml().unwrap(), br#"<a:blipFill><a:srcRect><x:sourceExt/></a:srcRect><a:stretch><a:fillRect><x:fillExt/></a:fillRect></a:stretch></a:blipFill>"#);
1670    }
1671
1672    #[test]
1673    fn blip_alpha_mod_fix_is_typed_bounded_and_schema_ordered() {
1674        let parse = |children: &str| {
1675            let xml = format!(
1676                r#"<a:blipFill xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><a:blip r:embed="rId1">{children}</a:blip></a:blipFill>"#
1677            );
1678            super::BlipFill::from_xml(xml.as_bytes())
1679        };
1680
1681        let fill = parse(
1682            r#"<x:before xmlns:x="urn:producer"/><q:alphaModFix xmlns:q="http://schemas.openxmlformats.org/drawingml/2006/main" amt="30000"/><x:after xmlns:x="urn:producer"/>"#,
1683        )
1684        .unwrap();
1685        let blip = fill.blip.unwrap();
1686        assert_eq!(blip.alpha_modulation_fix, Some(super::Percent1000(30_000)));
1687        let xml = super::Fill::Blip(super::BlipFill {
1688            blip: Some(blip),
1689            ..Default::default()
1690        })
1691        .to_xml()
1692        .unwrap();
1693        let xml = String::from_utf8(xml).unwrap();
1694        let before = xml.find("x:before").unwrap();
1695        let alpha = xml.find("a:alphaModFix").unwrap();
1696        let after = xml.find("x:after").unwrap();
1697        assert!(before < alpha && alpha < after, "{xml}");
1698        assert!(xml.contains("amt=\"30000\""), "{xml}");
1699
1700        let inherited_alias = super::BlipFill::from_xml(
1701            br#"<q:blipFill xmlns:q="http://schemas.openxmlformats.org/drawingml/2006/main"><q:blip><q:alphaModFix amt="25000"/></q:blip></q:blipFill>"#,
1702        )
1703        .unwrap();
1704        assert_eq!(
1705            inherited_alias.blip.unwrap().alpha_modulation_fix,
1706            Some(super::Percent1000(25_000))
1707        );
1708
1709        let qualified_amount =
1710            parse(r#"<a:alphaModFix xmlns:producer="urn:producer" producer:amt="20000"/>"#)
1711                .unwrap();
1712        assert_eq!(
1713            qualified_amount.blip.unwrap().alpha_modulation_fix,
1714            Some(super::Percent1000(100_000))
1715        );
1716
1717        let duplicate = parse(
1718            r#"<a:alphaModFix amt="40000"/><a:alphaModFix producer:keep="yes" xmlns:producer="urn:producer" amt="20000"/>"#,
1719        )
1720        .unwrap();
1721        assert_eq!(
1722            duplicate.blip.as_ref().unwrap().alpha_modulation_fix,
1723            Some(super::Percent1000(40_000))
1724        );
1725        let duplicate_xml = super::Fill::Blip(duplicate).to_xml().unwrap();
1726        let duplicate_xml = String::from_utf8(duplicate_xml).unwrap();
1727        assert_eq!(duplicate_xml.matches("alphaModFix").count(), 2);
1728        assert!(duplicate_xml.contains("producer:keep=\"yes\""));
1729
1730        for invalid in ["-1", "100001", "not-a-number"] {
1731            assert!(parse(&format!(r#"<a:alphaModFix amt="{invalid}"/>"#)).is_err());
1732        }
1733        assert!(parse(r#"<a:alphaModFix amt="30000">content</a:alphaModFix>"#).is_err());
1734        let mut invalid_write = parse(r#"<a:alphaModFix amt="30000"/>"#).unwrap();
1735        invalid_write.blip.as_mut().unwrap().alpha_modulation_fix = Some(super::Percent1000(-1));
1736        assert!(super::Fill::Blip(invalid_write).to_xml().is_err());
1737        let foreign = parse(
1738            r#"<x:alphaModFix xmlns:x="urn:producer" amt="30000" producer:keep="yes" xmlns:producer="urn:producer"/>"#,
1739        )
1740        .unwrap();
1741        assert_eq!(foreign.blip.as_ref().unwrap().alpha_modulation_fix, None);
1742        let foreign_xml = super::Fill::Blip(foreign).to_xml().unwrap();
1743        assert!(
1744            String::from_utf8(foreign_xml)
1745                .unwrap()
1746                .contains("producer:keep=\"yes\"")
1747        );
1748    }
1749
1750    #[test]
1751    fn malformed_fill_values_return_errors_without_panicking() {
1752        let cases: &[&[u8]] = &[
1753            br#"<a:gradFill rotWithShape="maybe"/>"#,
1754            br#"<a:gradFill><a:gsLst><a:gs pos="100001"/></a:gsLst></a:gradFill>"#,
1755            br#"<a:gradFill><a:gsLst><a:gs/></a:gsLst></a:gradFill>"#,
1756            br#"<a:gradFill><a:lin ang="ninety"/></a:gradFill>"#,
1757            br#"<a:gradFill><a:path path="triangle"/></a:gradFill>"#,
1758            br#"<a:blipFill dpi="many"/>"#,
1759            br#"<a:blipFill><a:srcRect l="half"/></a:blipFill>"#,
1760            br#"<a:blipFill><a:tile sx="large"/></a:blipFill>"#,
1761            br#"<a:gradFill flip="diagonal"/>"#,
1762            br#"<a:blipFill><a:tile algn="middle"/></a:blipFill>"#,
1763        ];
1764        for xml in cases {
1765            let result = std::panic::catch_unwind(|| Fill::from_xml(xml));
1766            assert!(
1767                result.is_ok(),
1768                "parser panicked for {}",
1769                String::from_utf8_lossy(xml)
1770            );
1771            assert!(
1772                result.unwrap().is_err(),
1773                "malformed fill parsed successfully"
1774            );
1775        }
1776        assert!(matches!(
1777            Fill::from_xml(cases[1]),
1778            Err(FillError::GradientStopOutOfRange(100_001))
1779        ));
1780    }
1781}