Skip to main content

oxml_drawing/
geometry.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::error::Error;
3use std::f64::consts::PI;
4use std::fmt;
5use std::io::Write;
6
7use oxml_core::OxmlError;
8use oxml_core::raw_xml::{capture_element, capture_empty_element};
9use oxml_core::xml::{get_attr, local_name, matches_local_name};
10use quick_xml::events::{BytesEnd, BytesStart, Event};
11use quick_xml::{Reader, Writer};
12
13use crate::order::OrderedRawChildren;
14use crate::preset_shape_data::preset_shape_definition;
15
16const ANGLE_UNITS_PER_DEGREE: f64 = 60_000.0;
17const QUARTER_CIRCLE: f64 = 90.0 * ANGLE_UNITS_PER_DEGREE;
18const MAX_ARC_SEGMENTS: usize = 4_096;
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum GuideOp {
22    MulDiv,
23    AddSub,
24    AddDiv,
25    IfElse,
26    Abs,
27    At2,
28    Cat2,
29    Cos,
30    Max,
31    Min,
32    Mod,
33    Pin,
34    Sat2,
35    Sin,
36    Sqrt,
37    Tan,
38    Val,
39}
40
41impl GuideOp {
42    pub fn parse(token: &str) -> Result<Self, GeometryError> {
43        match token {
44            "*/" => Ok(Self::MulDiv),
45            "+-" => Ok(Self::AddSub),
46            "+/" => Ok(Self::AddDiv),
47            "?:" => Ok(Self::IfElse),
48            "abs" => Ok(Self::Abs),
49            "at2" => Ok(Self::At2),
50            "cat2" => Ok(Self::Cat2),
51            "cos" => Ok(Self::Cos),
52            "max" => Ok(Self::Max),
53            "min" => Ok(Self::Min),
54            "mod" => Ok(Self::Mod),
55            "pin" => Ok(Self::Pin),
56            "sat2" => Ok(Self::Sat2),
57            "sin" => Ok(Self::Sin),
58            "sqrt" => Ok(Self::Sqrt),
59            "tan" => Ok(Self::Tan),
60            "val" => Ok(Self::Val),
61            _ => Err(GeometryError::UnknownGuideOperation(token.to_owned())),
62        }
63    }
64
65    fn argument_count(self) -> usize {
66        match self {
67            Self::Abs | Self::Sqrt | Self::Val => 1,
68            Self::At2 | Self::Cos | Self::Max | Self::Min | Self::Sin | Self::Tan => 2,
69            Self::MulDiv
70            | Self::AddSub
71            | Self::AddDiv
72            | Self::IfElse
73            | Self::Cat2
74            | Self::Mod
75            | Self::Pin
76            | Self::Sat2 => 3,
77        }
78    }
79
80    fn token(self) -> &'static str {
81        match self {
82            Self::MulDiv => "*/",
83            Self::AddSub => "+-",
84            Self::AddDiv => "+/",
85            Self::IfElse => "?:",
86            Self::Abs => "abs",
87            Self::At2 => "at2",
88            Self::Cat2 => "cat2",
89            Self::Cos => "cos",
90            Self::Max => "max",
91            Self::Min => "min",
92            Self::Mod => "mod",
93            Self::Pin => "pin",
94            Self::Sat2 => "sat2",
95            Self::Sin => "sin",
96            Self::Sqrt => "sqrt",
97            Self::Tan => "tan",
98            Self::Val => "val",
99        }
100    }
101}
102
103#[derive(Clone, Debug, PartialEq)]
104pub enum GuideOperand {
105    Literal(f64),
106    Guide(String),
107}
108
109impl GuideOperand {
110    pub fn parse(value: &str) -> Result<Self, GeometryError> {
111        match value.parse::<f64>() {
112            Ok(value) if value.is_finite() => Ok(Self::Literal(value)),
113            Ok(_) => Err(GeometryError::NonFiniteValue(value.to_owned())),
114            Err(_) if value.is_empty() => Err(GeometryError::EmptyGuideOperand),
115            Err(_) => Ok(Self::Guide(value.to_owned())),
116        }
117    }
118}
119
120impl From<f64> for GuideOperand {
121    fn from(value: f64) -> Self {
122        Self::Literal(value)
123    }
124}
125
126impl From<&str> for GuideOperand {
127    fn from(value: &str) -> Self {
128        Self::Guide(value.to_owned())
129    }
130}
131
132#[derive(Clone, Debug, PartialEq)]
133pub struct Guide {
134    pub name: String,
135    pub op: GuideOp,
136    pub args: Vec<GuideOperand>,
137}
138
139impl Guide {
140    pub fn parse(name: impl Into<String>, formula: &str) -> Result<Self, GeometryError> {
141        let mut parts = formula.split_ascii_whitespace();
142        let token = parts.next().ok_or(GeometryError::EmptyGuideFormula)?;
143        let op = GuideOp::parse(token)?;
144        let args = parts
145            .map(GuideOperand::parse)
146            .collect::<Result<Vec<_>, _>>()?;
147        let expected = op.argument_count();
148        if args.len() != expected {
149            return Err(GeometryError::WrongArgumentCount {
150                operation: token.to_owned(),
151                expected,
152                actual: args.len(),
153            });
154        }
155        Ok(Self {
156            name: name.into(),
157            op,
158            args,
159        })
160    }
161
162    fn formula(&self) -> String {
163        let mut formula = self.op.token().to_owned();
164        for argument in &self.args {
165            formula.push(' ');
166            match argument {
167                GuideOperand::Literal(value) => formula.push_str(&value.to_string()),
168                GuideOperand::Guide(name) => formula.push_str(name),
169            }
170        }
171        formula
172    }
173}
174
175#[derive(Clone, Debug, PartialEq)]
176pub enum PathCommand {
177    MoveTo {
178        x: GuideOperand,
179        y: GuideOperand,
180    },
181    LineTo {
182        x: GuideOperand,
183        y: GuideOperand,
184    },
185    CubicTo {
186        x1: GuideOperand,
187        y1: GuideOperand,
188        x2: GuideOperand,
189        y2: GuideOperand,
190        x: GuideOperand,
191        y: GuideOperand,
192    },
193    ArcTo {
194        width_radius: GuideOperand,
195        height_radius: GuideOperand,
196        start_angle: GuideOperand,
197        sweep_angle: GuideOperand,
198    },
199    Close,
200}
201
202#[derive(Clone, Copy, Debug, PartialEq)]
203pub enum EvaluatedPathCommand {
204    MoveTo {
205        x: f64,
206        y: f64,
207    },
208    LineTo {
209        x: f64,
210        y: f64,
211    },
212    CubicTo {
213        x1: f64,
214        y1: f64,
215        x2: f64,
216        y2: f64,
217        x: f64,
218        y: f64,
219    },
220    Close,
221}
222
223#[derive(Clone, Debug, PartialEq)]
224pub enum GeometryError {
225    Xml(String),
226    UnexpectedElement(String),
227    MissingAttribute {
228        element: String,
229        attribute: String,
230    },
231    InvalidAttribute {
232        element: String,
233        attribute: String,
234        value: String,
235    },
236    MissingPathList,
237    MissingPathDimensions,
238    EmptyGuideFormula,
239    EmptyGuideOperand,
240    UnknownPreset(String),
241    UnknownGuideOperation(String),
242    WrongArgumentCount {
243        operation: String,
244        expected: usize,
245        actual: usize,
246    },
247    UnknownGuide(String),
248    DuplicateGuide(String),
249    UnknownAdjustOverride(String),
250    DivisionByZero,
251    NonFiniteValue(String),
252    PathHasNoCurrentPoint,
253    InvalidArcRadius,
254    ArcSweepTooLarge,
255}
256
257impl fmt::Display for GeometryError {
258    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
259        match self {
260            Self::Xml(error) => formatter.write_str(error),
261            Self::UnexpectedElement(element) => {
262                write!(formatter, "unexpected custom geometry element: {element}")
263            }
264            Self::MissingAttribute { element, attribute } => {
265                write!(formatter, "DrawingML {element} requires @{attribute}")
266            }
267            Self::InvalidAttribute {
268                element,
269                attribute,
270                value,
271            } => write!(
272                formatter,
273                "DrawingML {element} has invalid @{attribute}: {value}"
274            ),
275            Self::MissingPathList => formatter.write_str("custom geometry requires a path list"),
276            Self::MissingPathDimensions => {
277                formatter.write_str("custom geometry path requires width and height")
278            }
279            Self::EmptyGuideFormula => formatter.write_str("empty guide formula"),
280            Self::EmptyGuideOperand => formatter.write_str("empty guide operand"),
281            Self::UnknownPreset(preset) => {
282                write!(formatter, "unknown DrawingML preset geometry: {preset}")
283            }
284            Self::UnknownGuideOperation(operation) => {
285                write!(formatter, "unknown guide operation: {operation}")
286            }
287            Self::WrongArgumentCount {
288                operation,
289                expected,
290                actual,
291            } => write!(
292                formatter,
293                "guide operation {operation} expects {expected} arguments, got {actual}"
294            ),
295            Self::UnknownGuide(name) => write!(formatter, "unknown guide: {name}"),
296            Self::DuplicateGuide(name) => write!(formatter, "duplicate guide: {name}"),
297            Self::UnknownAdjustOverride(name) => {
298                write!(formatter, "unknown adjust override: {name}")
299            }
300            Self::DivisionByZero => formatter.write_str("division by zero"),
301            Self::NonFiniteValue(context) => write!(formatter, "non-finite value: {context}"),
302            Self::PathHasNoCurrentPoint => {
303                formatter.write_str("path command requires a current point")
304            }
305            Self::InvalidArcRadius => formatter.write_str("arc radii must be positive"),
306            Self::ArcSweepTooLarge => formatter.write_str("arc sweep requires too many segments"),
307        }
308    }
309}
310
311impl Error for GeometryError {}
312
313impl From<OxmlError> for GeometryError {
314    fn from(error: OxmlError) -> Self {
315        Self::Xml(error.to_string())
316    }
317}
318
319#[allow(non_camel_case_types)]
320#[derive(Clone, Debug, PartialEq)]
321pub struct CT_AdjPoint2D {
322    pub x: GuideOperand,
323    pub y: GuideOperand,
324    raw_children: OrderedRawChildren,
325}
326
327#[allow(non_camel_case_types)]
328#[derive(Clone, Debug, PartialEq)]
329pub struct CT_GeomRect {
330    pub left: GuideOperand,
331    pub top: GuideOperand,
332    pub right: GuideOperand,
333    pub bottom: GuideOperand,
334    raw_children: OrderedRawChildren,
335}
336
337#[allow(non_camel_case_types)]
338#[derive(Clone, Debug, PartialEq)]
339pub enum CT_Path2DCommand {
340    MoveTo(CT_AdjPoint2D),
341    LineTo(CT_AdjPoint2D),
342    CubicTo {
343        control_1: CT_AdjPoint2D,
344        control_2: CT_AdjPoint2D,
345        end: CT_AdjPoint2D,
346    },
347    ArcTo {
348        width_radius: GuideOperand,
349        height_radius: GuideOperand,
350        start_angle: GuideOperand,
351        sweep_angle: GuideOperand,
352    },
353    Close,
354}
355
356#[derive(Clone, Debug, PartialEq)]
357struct PathCommandRecord {
358    command: CT_Path2DCommand,
359    raw_children: OrderedRawChildren,
360}
361
362#[allow(non_camel_case_types)]
363#[derive(Clone, Debug, PartialEq)]
364pub struct CT_Path2D {
365    pub width: Option<f64>,
366    pub height: Option<f64>,
367    pub fill: Option<String>,
368    pub stroke: Option<bool>,
369    pub extrusion_ok: Option<bool>,
370    commands: Vec<PathCommandRecord>,
371    raw_children: OrderedRawChildren,
372}
373
374impl CT_Path2D {
375    pub fn commands(&self) -> impl Iterator<Item = &CT_Path2DCommand> {
376        self.commands.iter().map(|record| &record.command)
377    }
378}
379
380#[derive(Clone, Debug, PartialEq)]
381struct GuideList {
382    guides: Vec<Guide>,
383    guide_raw_children: Vec<OrderedRawChildren>,
384    raw_children: OrderedRawChildren,
385}
386
387#[derive(Clone, Debug, PartialEq)]
388struct PathList {
389    paths: Vec<CT_Path2D>,
390    raw_children: OrderedRawChildren,
391}
392
393#[allow(non_camel_case_types)]
394#[derive(Clone, Debug, PartialEq)]
395pub struct CT_CustomGeometry2D {
396    adjust_values: Option<GuideList>,
397    guides: Option<GuideList>,
398    pub text_rectangle: Option<CT_GeomRect>,
399    path_list: PathList,
400    raw_children: OrderedRawChildren,
401}
402
403#[allow(non_camel_case_types)]
404#[derive(Clone, Debug, PartialEq)]
405pub struct CT_PresetGeometry2D {
406    pub preset: String,
407    adjust_values: Option<GuideList>,
408    raw_children: OrderedRawChildren,
409}
410
411#[derive(Clone, Copy, Debug, PartialEq)]
412pub struct EvaluatedTextRectangle {
413    pub left: f64,
414    pub top: f64,
415    pub right: f64,
416    pub bottom: f64,
417}
418
419#[derive(Clone, Debug, PartialEq)]
420pub struct EvaluatedCustomGeometry {
421    pub paths: Vec<Vec<EvaluatedPathCommand>>,
422    pub text_rectangle: Option<EvaluatedTextRectangle>,
423}
424
425impl CT_CustomGeometry2D {
426    /// Parses one complete `a:custGeom` element with any namespace prefix.
427    pub fn from_xml(xml: &[u8]) -> Result<Self, GeometryError> {
428        let mut reader = Reader::from_reader(xml);
429        let mut buffer = Vec::new();
430        loop {
431            match reader
432                .read_event_into(&mut buffer)
433                .map_err(|error| GeometryError::Xml(error.to_string()))?
434            {
435                Event::Start(element)
436                    if matches_local_name(element.name().as_ref(), b"custGeom") =>
437                {
438                    return Self::from_element(&mut reader, &element);
439                }
440                Event::Empty(element)
441                    if matches_local_name(element.name().as_ref(), b"custGeom") =>
442                {
443                    return Err(GeometryError::MissingPathList);
444                }
445                Event::Start(element) | Event::Empty(element) => {
446                    return Err(GeometryError::UnexpectedElement(
447                        String::from_utf8_lossy(element.name().as_ref()).into_owned(),
448                    ));
449                }
450                Event::Eof => {
451                    return Err(GeometryError::UnexpectedElement("EOF".to_owned()));
452                }
453                _ => {}
454            }
455            buffer.clear();
456        }
457    }
458
459    /// Parses an `a:custGeom` after the caller consumed its start event.
460    pub fn from_element(
461        reader: &mut Reader<&[u8]>,
462        start: &BytesStart<'_>,
463    ) -> Result<Self, GeometryError> {
464        if !matches_local_name(start.name().as_ref(), b"custGeom") {
465            return Err(GeometryError::UnexpectedElement(element_name(start)));
466        }
467
468        let mut adjust_values = None;
469        let mut guides = None;
470        let mut text_rectangle = None;
471        let mut path_list = None;
472        let mut raw_children = OrderedRawChildren::default();
473        let mut boundary = 0;
474        let mut buffer = Vec::new();
475
476        loop {
477            match reader
478                .read_event_into(&mut buffer)
479                .map_err(|error| GeometryError::Xml(error.to_string()))?
480            {
481                Event::Start(element) => match local_name(element.name().as_ref()) {
482                    b"avLst" if adjust_values.is_none() => {
483                        adjust_values = Some(parse_guide_list(reader, &element, b"avLst")?);
484                        boundary = boundary.max(1);
485                    }
486                    b"gdLst" if guides.is_none() => {
487                        guides = Some(parse_guide_list(reader, &element, b"gdLst")?);
488                        boundary = boundary.max(2);
489                    }
490                    b"rect" if text_rectangle.is_none() => {
491                        text_rectangle = Some(parse_rect(reader, &element)?);
492                        boundary = boundary.max(5);
493                    }
494                    b"pathLst" if path_list.is_none() => {
495                        path_list = Some(parse_path_list(reader, &element)?);
496                        boundary = boundary.max(6);
497                    }
498                    _ => raw_children.push(boundary, capture_element(reader, &element)?),
499                },
500                Event::Empty(element) => match local_name(element.name().as_ref()) {
501                    b"avLst" if adjust_values.is_none() => {
502                        adjust_values = Some(GuideList {
503                            guides: Vec::new(),
504                            guide_raw_children: Vec::new(),
505                            raw_children: OrderedRawChildren::default(),
506                        });
507                        boundary = boundary.max(1);
508                    }
509                    b"gdLst" if guides.is_none() => {
510                        guides = Some(GuideList {
511                            guides: Vec::new(),
512                            guide_raw_children: Vec::new(),
513                            raw_children: OrderedRawChildren::default(),
514                        });
515                        boundary = boundary.max(2);
516                    }
517                    b"rect" if text_rectangle.is_none() => {
518                        text_rectangle = Some(parse_empty_rect(&element)?);
519                        boundary = boundary.max(5);
520                    }
521                    b"pathLst" if path_list.is_none() => {
522                        path_list = Some(PathList {
523                            paths: Vec::new(),
524                            raw_children: OrderedRawChildren::default(),
525                        });
526                        boundary = boundary.max(6);
527                    }
528                    _ => raw_children.push(boundary, capture_empty_element(&element)?),
529                },
530                Event::End(element) if matches_local_name(element.name().as_ref(), b"custGeom") => {
531                    break;
532                }
533                Event::Eof => {
534                    return Err(GeometryError::Xml("missing closing a:custGeom".to_owned()));
535                }
536                _ => {}
537            }
538            buffer.clear();
539        }
540
541        Ok(Self {
542            adjust_values,
543            guides,
544            text_rectangle,
545            path_list: path_list.ok_or(GeometryError::MissingPathList)?,
546            raw_children,
547        })
548    }
549
550    pub fn adjust_values(&self) -> &[Guide] {
551        self.adjust_values
552            .as_ref()
553            .map_or(&[], |list| list.guides.as_slice())
554    }
555
556    pub fn guides(&self) -> &[Guide] {
557        self.guides
558            .as_ref()
559            .map_or(&[], |list| list.guides.as_slice())
560    }
561
562    pub fn paths(&self) -> &[CT_Path2D] {
563        &self.path_list.paths
564    }
565
566    /// Writes with the canonical `a:` prefix and DrawingML schema order.
567    pub fn to_xml(&self) -> Result<Vec<u8>, GeometryError> {
568        let mut writer = Writer::new(Vec::new());
569        writer
570            .write_event(Event::Start(BytesStart::new("a:custGeom")))
571            .map_err(|error| GeometryError::Xml(error.to_string()))?;
572        emit_raw(&mut writer, self.raw_children.at(0))?;
573        if let Some(list) = &self.adjust_values {
574            write_guide_list(&mut writer, "a:avLst", list)?;
575        }
576        emit_raw(&mut writer, self.raw_children.at(1))?;
577        if let Some(list) = &self.guides {
578            write_guide_list(&mut writer, "a:gdLst", list)?;
579        }
580        emit_raw(&mut writer, self.raw_children.at(2))?;
581        emit_raw(&mut writer, self.raw_children.at(3))?;
582        emit_raw(&mut writer, self.raw_children.at(4))?;
583        if let Some(rectangle) = &self.text_rectangle {
584            write_rect(&mut writer, rectangle)?;
585        }
586        emit_raw(&mut writer, self.raw_children.at(5))?;
587        write_path_list(&mut writer, &self.path_list)?;
588        emit_raw(&mut writer, self.raw_children.at(6))?;
589        writer
590            .write_event(Event::End(BytesEnd::new("a:custGeom")))
591            .map_err(|error| GeometryError::Xml(error.to_string()))?;
592        Ok(writer.into_inner())
593    }
594
595    /// Evaluates all paths and the text rectangle in each path's coordinate space.
596    pub fn evaluate(
597        &self,
598        overrides: &BTreeMap<String, f64>,
599    ) -> Result<EvaluatedCustomGeometry, GeometryError> {
600        self.evaluate_with_default_dimensions(overrides, None, None)
601    }
602
603    /// Evaluates paths with the containing shape size on omitted coordinate axes.
604    pub fn evaluate_with_size(
605        &self,
606        overrides: &BTreeMap<String, f64>,
607        size: (f64, f64),
608    ) -> Result<EvaluatedCustomGeometry, GeometryError> {
609        let text_dimensions = self.paths().first().map_or(size, |path| {
610            (path.width.unwrap_or(size.0), path.height.unwrap_or(size.1))
611        });
612        self.evaluate_with_default_dimensions(overrides, Some(size), Some(text_dimensions))
613    }
614
615    fn evaluate_with_default_dimensions(
616        &self,
617        overrides: &BTreeMap<String, f64>,
618        default_dimensions: Option<(f64, f64)>,
619        text_dimensions: Option<(f64, f64)>,
620    ) -> Result<EvaluatedCustomGeometry, GeometryError> {
621        let mut paths = Vec::with_capacity(self.path_list.paths.len());
622        let mut text_rectangle = None;
623        for (index, path) in self.path_list.paths.iter().enumerate() {
624            let width = path
625                .width
626                .or(default_dimensions.map(|dimensions| dimensions.0))
627                .ok_or(GeometryError::MissingPathDimensions)?;
628            let height = path
629                .height
630                .or(default_dimensions.map(|dimensions| dimensions.1))
631                .ok_or(GeometryError::MissingPathDimensions)?;
632            let mut evaluator = GuideEvaluator::new(width, height)?;
633            evaluator.apply_adjust_values(self.adjust_values(), overrides)?;
634            evaluator.evaluate_guides(self.guides())?;
635            let commands = path
636                .commands
637                .iter()
638                .map(|record| record.command.to_evaluator_command())
639                .collect::<Vec<_>>();
640            paths.push(evaluator.evaluate_path(&commands)?);
641            if index == 0 && default_dimensions.is_none() {
642                text_rectangle = self
643                    .text_rectangle
644                    .as_ref()
645                    .map(|rectangle| rectangle.evaluate(&evaluator))
646                    .transpose()?;
647            }
648        }
649        if let Some((width, height)) = text_dimensions {
650            let mut evaluator = GuideEvaluator::new(width, height)?;
651            evaluator.apply_adjust_values(self.adjust_values(), overrides)?;
652            evaluator.evaluate_guides(self.guides())?;
653            text_rectangle = self
654                .text_rectangle
655                .as_ref()
656                .map(|rectangle| rectangle.evaluate(&evaluator))
657                .transpose()?;
658        }
659        Ok(EvaluatedCustomGeometry {
660            paths,
661            text_rectangle,
662        })
663    }
664}
665
666impl CT_PresetGeometry2D {
667    /// Creates preset geometry with a canonical empty adjustment list.
668    pub fn new(preset: &str) -> Result<Self, GeometryError> {
669        if preset_shape_definition(preset).is_none() {
670            return Err(GeometryError::UnknownPreset(preset.to_owned()));
671        }
672        Ok(Self {
673            preset: preset.to_owned(),
674            adjust_values: Some(GuideList {
675                guides: Vec::new(),
676                guide_raw_children: Vec::new(),
677                raw_children: OrderedRawChildren::default(),
678            }),
679            raw_children: OrderedRawChildren::default(),
680        })
681    }
682
683    /// Parses one complete `a:prstGeom` element with any namespace prefix.
684    pub fn from_xml(xml: &[u8]) -> Result<Self, GeometryError> {
685        let mut reader = Reader::from_reader(xml);
686        let mut buffer = Vec::new();
687        loop {
688            match reader
689                .read_event_into(&mut buffer)
690                .map_err(|error| GeometryError::Xml(error.to_string()))?
691            {
692                Event::Start(element)
693                    if matches_local_name(element.name().as_ref(), b"prstGeom") =>
694                {
695                    return Self::from_element(&mut reader, &element);
696                }
697                Event::Empty(element)
698                    if matches_local_name(element.name().as_ref(), b"prstGeom") =>
699                {
700                    return Ok(Self {
701                        preset: required_attr(&element, b"prst")?,
702                        adjust_values: None,
703                        raw_children: OrderedRawChildren::default(),
704                    });
705                }
706                Event::Start(element) | Event::Empty(element) => {
707                    return Err(GeometryError::UnexpectedElement(element_name(&element)));
708                }
709                Event::Eof => {
710                    return Err(GeometryError::UnexpectedElement("EOF".to_owned()));
711                }
712                _ => {}
713            }
714            buffer.clear();
715        }
716    }
717
718    fn from_element(
719        reader: &mut Reader<&[u8]>,
720        start: &BytesStart<'_>,
721    ) -> Result<Self, GeometryError> {
722        let preset = required_attr(start, b"prst")?;
723        let mut adjust_values = None;
724        let mut raw_children = OrderedRawChildren::default();
725        let mut boundary = 0;
726        let mut buffer = Vec::new();
727        loop {
728            match reader
729                .read_event_into(&mut buffer)
730                .map_err(|error| GeometryError::Xml(error.to_string()))?
731            {
732                Event::Start(element)
733                    if matches_local_name(element.name().as_ref(), b"avLst")
734                        && adjust_values.is_none() =>
735                {
736                    adjust_values = Some(parse_guide_list(reader, &element, b"avLst")?);
737                    boundary = 1;
738                }
739                Event::Empty(element)
740                    if matches_local_name(element.name().as_ref(), b"avLst")
741                        && adjust_values.is_none() =>
742                {
743                    adjust_values = Some(GuideList {
744                        guides: Vec::new(),
745                        guide_raw_children: Vec::new(),
746                        raw_children: OrderedRawChildren::default(),
747                    });
748                    boundary = 1;
749                }
750                Event::Start(element) => {
751                    raw_children.push(boundary, capture_element(reader, &element)?)
752                }
753                Event::Empty(element) => {
754                    raw_children.push(boundary, capture_empty_element(&element)?)
755                }
756                Event::End(element) if matches_local_name(element.name().as_ref(), b"prstGeom") => {
757                    break;
758                }
759                Event::Eof => {
760                    return Err(GeometryError::Xml("missing closing a:prstGeom".to_owned()));
761                }
762                _ => {}
763            }
764            buffer.clear();
765        }
766        Ok(Self {
767            preset,
768            adjust_values,
769            raw_children,
770        })
771    }
772
773    pub fn adjust_values(&self) -> &[Guide] {
774        self.adjust_values
775            .as_ref()
776            .map_or(&[], |list| list.guides.as_slice())
777    }
778
779    /// Inserts or replaces one named `val` adjustment guide.
780    pub fn set_adjust_value(&mut self, name: &str, value: f64) -> Result<(), GeometryError> {
781        if !value.is_finite() {
782            return Err(GeometryError::NonFiniteValue(name.to_owned()));
783        }
784        let guide = Guide {
785            name: name.to_owned(),
786            op: GuideOp::Val,
787            args: vec![GuideOperand::Literal(value)],
788        };
789        let list = self.adjust_values.get_or_insert_with(|| GuideList {
790            guides: Vec::new(),
791            guide_raw_children: Vec::new(),
792            raw_children: OrderedRawChildren::default(),
793        });
794        if let Some(index) = list.guides.iter().position(|guide| guide.name == name) {
795            list.guides[index] = guide;
796        } else {
797            let trailing_boundary = list.guides.len();
798            list.raw_children.shift_boundaries_from(trailing_boundary);
799            list.guides.push(guide);
800            list.guide_raw_children.push(OrderedRawChildren::default());
801        }
802        Ok(())
803    }
804
805    /// Returns the preset definition's own `avLst` guides in definition order.
806    ///
807    /// Only the `avLst` is read, so a preset whose other guides do not parse
808    /// still reports its defaults. An unknown preset has none.
809    pub fn default_adjust_values(&self) -> Result<Vec<Guide>, GeometryError> {
810        let Some(xml) = preset_shape_definition(&self.preset) else {
811            return Ok(Vec::new());
812        };
813        let mut reader = Reader::from_reader(xml);
814        let mut buffer = Vec::new();
815        loop {
816            match reader
817                .read_event_into(&mut buffer)
818                .map_err(|error| GeometryError::Xml(error.to_string()))?
819            {
820                Event::Start(element) if matches_local_name(element.name().as_ref(), b"avLst") => {
821                    return Ok(parse_guide_list(&mut reader, &element, b"avLst")?.guides);
822                }
823                Event::Eof => return Ok(Vec::new()),
824                _ => {}
825            }
826            buffer.clear();
827        }
828    }
829
830    /// Writes with the canonical `a:` prefix and DrawingML schema order.
831    pub fn to_xml(&self) -> Result<Vec<u8>, GeometryError> {
832        let mut writer = Writer::new(Vec::new());
833        let mut start = BytesStart::new("a:prstGeom");
834        start.push_attribute(("prst", self.preset.as_str()));
835        if self.adjust_values.is_none() && self.raw_children.is_empty() {
836            writer
837                .write_event(Event::Empty(start))
838                .map_err(|error| GeometryError::Xml(error.to_string()))?;
839            return Ok(writer.into_inner());
840        }
841        writer
842            .write_event(Event::Start(start))
843            .map_err(|error| GeometryError::Xml(error.to_string()))?;
844        emit_raw(&mut writer, self.raw_children.at(0))?;
845        if let Some(list) = &self.adjust_values {
846            write_guide_list(&mut writer, "a:avLst", list)?;
847        }
848        emit_raw(&mut writer, self.raw_children.at(1))?;
849        writer
850            .write_event(Event::End(BytesEnd::new("a:prstGeom")))
851            .map_err(|error| GeometryError::Xml(error.to_string()))?;
852        Ok(writer.into_inner())
853    }
854
855    /// Evaluates a known preset in the shape's coordinate space.
856    pub fn evaluate(
857        &self,
858        size: (f64, f64),
859    ) -> Result<Option<EvaluatedCustomGeometry>, GeometryError> {
860        let Some(xml) = preset_shape_definition(&self.preset) else {
861            return Ok(None);
862        };
863        let definition = CT_CustomGeometry2D::from_xml(xml)?;
864        let mut override_evaluator = GuideEvaluator::new(size.0, size.1)?;
865        override_evaluator.apply_adjust_values(self.adjust_values(), &BTreeMap::new())?;
866        let overrides = self
867            .adjust_values()
868            .iter()
869            .map(|guide| Ok((guide.name.clone(), override_evaluator.value(&guide.name)?)))
870            .collect::<Result<BTreeMap<_, _>, GeometryError>>()?;
871        let mut evaluated =
872            definition.evaluate_with_default_dimensions(&overrides, Some(size), Some(size))?;
873        for (commands, path) in evaluated.paths.iter_mut().zip(definition.paths()) {
874            let scale_x = path.width.map_or(1.0, |width| size.0 / width);
875            let scale_y = path.height.map_or(1.0, |height| size.1 / height);
876            for command in commands {
877                scale_evaluated_path_command(command, scale_x, scale_y);
878            }
879        }
880        Ok(Some(evaluated))
881    }
882}
883
884fn scale_evaluated_path_command(command: &mut EvaluatedPathCommand, scale_x: f64, scale_y: f64) {
885    match command {
886        EvaluatedPathCommand::MoveTo { x, y } | EvaluatedPathCommand::LineTo { x, y } => {
887            *x *= scale_x;
888            *y *= scale_y;
889        }
890        EvaluatedPathCommand::CubicTo {
891            x1,
892            y1,
893            x2,
894            y2,
895            x,
896            y,
897        } => {
898            *x1 *= scale_x;
899            *y1 *= scale_y;
900            *x2 *= scale_x;
901            *y2 *= scale_y;
902            *x *= scale_x;
903            *y *= scale_y;
904        }
905        EvaluatedPathCommand::Close => {}
906    }
907}
908
909impl CT_GeomRect {
910    fn evaluate(
911        &self,
912        evaluator: &GuideEvaluator,
913    ) -> Result<EvaluatedTextRectangle, GeometryError> {
914        Ok(EvaluatedTextRectangle {
915            left: evaluator.resolve(&self.left)?,
916            top: evaluator.resolve(&self.top)?,
917            right: evaluator.resolve(&self.right)?,
918            bottom: evaluator.resolve(&self.bottom)?,
919        })
920    }
921}
922
923impl CT_Path2DCommand {
924    fn to_evaluator_command(&self) -> PathCommand {
925        match self {
926            Self::MoveTo(point) => PathCommand::MoveTo {
927                x: point.x.clone(),
928                y: point.y.clone(),
929            },
930            Self::LineTo(point) => PathCommand::LineTo {
931                x: point.x.clone(),
932                y: point.y.clone(),
933            },
934            Self::CubicTo {
935                control_1,
936                control_2,
937                end,
938            } => PathCommand::CubicTo {
939                x1: control_1.x.clone(),
940                y1: control_1.y.clone(),
941                x2: control_2.x.clone(),
942                y2: control_2.y.clone(),
943                x: end.x.clone(),
944                y: end.y.clone(),
945            },
946            Self::ArcTo {
947                width_radius,
948                height_radius,
949                start_angle,
950                sweep_angle,
951            } => PathCommand::ArcTo {
952                width_radius: width_radius.clone(),
953                height_radius: height_radius.clone(),
954                start_angle: start_angle.clone(),
955                sweep_angle: sweep_angle.clone(),
956            },
957            Self::Close => PathCommand::Close,
958        }
959    }
960}
961
962fn parse_guide_list(
963    reader: &mut Reader<&[u8]>,
964    _start: &BytesStart<'_>,
965    end_name: &[u8],
966) -> Result<GuideList, GeometryError> {
967    let mut guides = Vec::new();
968    let mut guide_raw_children = Vec::new();
969    let mut raw_children = OrderedRawChildren::default();
970    let mut buffer = Vec::new();
971    loop {
972        match reader
973            .read_event_into(&mut buffer)
974            .map_err(|error| GeometryError::Xml(error.to_string()))?
975        {
976            Event::Empty(element) if matches_local_name(element.name().as_ref(), b"gd") => {
977                guides.push(parse_guide(&element)?);
978                guide_raw_children.push(OrderedRawChildren::default());
979            }
980            Event::Start(element) if matches_local_name(element.name().as_ref(), b"gd") => {
981                let guide = parse_guide(&element)?;
982                let mut children = OrderedRawChildren::default();
983                consume_leaf_children(reader, b"gd", &mut children, 0)?;
984                guides.push(guide);
985                guide_raw_children.push(children);
986            }
987            Event::Start(element) => {
988                raw_children.push(guides.len(), capture_element(reader, &element)?)
989            }
990            Event::Empty(element) => {
991                raw_children.push(guides.len(), capture_empty_element(&element)?)
992            }
993            Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
994            Event::Eof => {
995                return Err(GeometryError::Xml(format!(
996                    "missing closing a:{}",
997                    String::from_utf8_lossy(end_name)
998                )));
999            }
1000            _ => {}
1001        }
1002        buffer.clear();
1003    }
1004    Ok(GuideList {
1005        guides,
1006        guide_raw_children,
1007        raw_children,
1008    })
1009}
1010
1011fn consume_leaf_children(
1012    reader: &mut Reader<&[u8]>,
1013    end_name: &[u8],
1014    raw_children: &mut OrderedRawChildren,
1015    boundary: usize,
1016) -> Result<(), GeometryError> {
1017    let mut buffer = Vec::new();
1018    loop {
1019        match reader
1020            .read_event_into(&mut buffer)
1021            .map_err(|error| GeometryError::Xml(error.to_string()))?
1022        {
1023            Event::Start(element) => {
1024                raw_children.push(boundary, capture_element(reader, &element)?)
1025            }
1026            Event::Empty(element) => raw_children.push(boundary, capture_empty_element(&element)?),
1027            Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
1028            Event::Eof => {
1029                return Err(GeometryError::Xml(format!(
1030                    "missing closing a:{}",
1031                    String::from_utf8_lossy(end_name)
1032                )));
1033            }
1034            _ => {}
1035        }
1036        buffer.clear();
1037    }
1038    Ok(())
1039}
1040
1041fn parse_guide(element: &BytesStart<'_>) -> Result<Guide, GeometryError> {
1042    Guide::parse(
1043        required_attr(element, b"name")?,
1044        &required_attr(element, b"fmla")?,
1045    )
1046}
1047
1048fn parse_empty_rect(element: &BytesStart<'_>) -> Result<CT_GeomRect, GeometryError> {
1049    Ok(CT_GeomRect {
1050        left: required_operand(element, b"l")?,
1051        top: required_operand(element, b"t")?,
1052        right: required_operand(element, b"r")?,
1053        bottom: required_operand(element, b"b")?,
1054        raw_children: OrderedRawChildren::default(),
1055    })
1056}
1057
1058fn parse_rect(
1059    reader: &mut Reader<&[u8]>,
1060    element: &BytesStart<'_>,
1061) -> Result<CT_GeomRect, GeometryError> {
1062    let mut rectangle = parse_empty_rect(element)?;
1063    consume_leaf_children(reader, b"rect", &mut rectangle.raw_children, 0)?;
1064    Ok(rectangle)
1065}
1066
1067fn parse_path_list(
1068    reader: &mut Reader<&[u8]>,
1069    _start: &BytesStart<'_>,
1070) -> Result<PathList, GeometryError> {
1071    let mut paths = Vec::new();
1072    let mut raw_children = OrderedRawChildren::default();
1073    let mut buffer = Vec::new();
1074    loop {
1075        match reader
1076            .read_event_into(&mut buffer)
1077            .map_err(|error| GeometryError::Xml(error.to_string()))?
1078        {
1079            Event::Start(element) if matches_local_name(element.name().as_ref(), b"path") => {
1080                paths.push(parse_path(reader, &element)?);
1081            }
1082            Event::Empty(element) if matches_local_name(element.name().as_ref(), b"path") => {
1083                paths.push(parse_empty_path(&element)?);
1084            }
1085            Event::Start(element) => {
1086                raw_children.push(paths.len(), capture_element(reader, &element)?)
1087            }
1088            Event::Empty(element) => {
1089                raw_children.push(paths.len(), capture_empty_element(&element)?)
1090            }
1091            Event::End(element) if matches_local_name(element.name().as_ref(), b"pathLst") => {
1092                break;
1093            }
1094            Event::Eof => {
1095                return Err(GeometryError::Xml("missing closing a:pathLst".to_owned()));
1096            }
1097            _ => {}
1098        }
1099        buffer.clear();
1100    }
1101    Ok(PathList {
1102        paths,
1103        raw_children,
1104    })
1105}
1106
1107fn parse_empty_path(element: &BytesStart<'_>) -> Result<CT_Path2D, GeometryError> {
1108    Ok(CT_Path2D {
1109        width: optional_f64(element, b"w")?,
1110        height: optional_f64(element, b"h")?,
1111        fill: get_attr(element, b"fill"),
1112        stroke: optional_bool(element, b"stroke")?,
1113        extrusion_ok: optional_bool(element, b"extrusionOk")?,
1114        commands: Vec::new(),
1115        raw_children: OrderedRawChildren::default(),
1116    })
1117}
1118
1119fn parse_path(
1120    reader: &mut Reader<&[u8]>,
1121    element: &BytesStart<'_>,
1122) -> Result<CT_Path2D, GeometryError> {
1123    let mut path = parse_empty_path(element)?;
1124    let mut buffer = Vec::new();
1125    loop {
1126        match reader
1127            .read_event_into(&mut buffer)
1128            .map_err(|error| GeometryError::Xml(error.to_string()))?
1129        {
1130            Event::Start(element) => {
1131                if let Some(command) = parse_path_command(reader, &element)? {
1132                    path.commands.push(command);
1133                } else {
1134                    path.raw_children
1135                        .push(path.commands.len(), capture_element(reader, &element)?);
1136                }
1137            }
1138            Event::Empty(element) => {
1139                if let Some(command) = parse_empty_path_command(&element)? {
1140                    path.commands.push(command);
1141                } else {
1142                    path.raw_children
1143                        .push(path.commands.len(), capture_empty_element(&element)?);
1144                }
1145            }
1146            Event::End(element) if matches_local_name(element.name().as_ref(), b"path") => break,
1147            Event::Eof => {
1148                return Err(GeometryError::Xml("missing closing a:path".to_owned()));
1149            }
1150            _ => {}
1151        }
1152        buffer.clear();
1153    }
1154    Ok(path)
1155}
1156
1157fn parse_path_command(
1158    reader: &mut Reader<&[u8]>,
1159    element: &BytesStart<'_>,
1160) -> Result<Option<PathCommandRecord>, GeometryError> {
1161    match local_name(element.name().as_ref()) {
1162        b"moveTo" => parse_point_command(reader, b"moveTo", 1, |mut points| {
1163            CT_Path2DCommand::MoveTo(points.remove(0))
1164        })
1165        .map(Some),
1166        b"lnTo" => parse_point_command(reader, b"lnTo", 1, |mut points| {
1167            CT_Path2DCommand::LineTo(points.remove(0))
1168        })
1169        .map(Some),
1170        b"cubicBezTo" => parse_point_command(reader, b"cubicBezTo", 3, |mut points| {
1171            CT_Path2DCommand::CubicTo {
1172                control_1: points.remove(0),
1173                control_2: points.remove(0),
1174                end: points.remove(0),
1175            }
1176        })
1177        .map(Some),
1178        b"arcTo" => {
1179            let command = parse_arc(element)?;
1180            let mut raw_children = OrderedRawChildren::default();
1181            consume_leaf_children(reader, b"arcTo", &mut raw_children, 0)?;
1182            Ok(Some(PathCommandRecord {
1183                command,
1184                raw_children,
1185            }))
1186        }
1187        b"close" => {
1188            let mut raw_children = OrderedRawChildren::default();
1189            consume_leaf_children(reader, b"close", &mut raw_children, 0)?;
1190            Ok(Some(PathCommandRecord {
1191                command: CT_Path2DCommand::Close,
1192                raw_children,
1193            }))
1194        }
1195        _ => Ok(None),
1196    }
1197}
1198
1199fn parse_empty_path_command(
1200    element: &BytesStart<'_>,
1201) -> Result<Option<PathCommandRecord>, GeometryError> {
1202    match local_name(element.name().as_ref()) {
1203        b"arcTo" => Ok(Some(PathCommandRecord {
1204            command: parse_arc(element)?,
1205            raw_children: OrderedRawChildren::default(),
1206        })),
1207        b"close" => Ok(Some(PathCommandRecord {
1208            command: CT_Path2DCommand::Close,
1209            raw_children: OrderedRawChildren::default(),
1210        })),
1211        b"moveTo" | b"lnTo" | b"cubicBezTo" => Err(GeometryError::Xml(format!(
1212            "DrawingML {} requires point children",
1213            element_name(element)
1214        ))),
1215        _ => Ok(None),
1216    }
1217}
1218
1219fn parse_point_command(
1220    reader: &mut Reader<&[u8]>,
1221    end_name: &[u8],
1222    expected_points: usize,
1223    make_command: impl FnOnce(Vec<CT_AdjPoint2D>) -> CT_Path2DCommand,
1224) -> Result<PathCommandRecord, GeometryError> {
1225    let mut points = Vec::new();
1226    let mut raw_children = OrderedRawChildren::default();
1227    let mut buffer = Vec::new();
1228    loop {
1229        match reader
1230            .read_event_into(&mut buffer)
1231            .map_err(|error| GeometryError::Xml(error.to_string()))?
1232        {
1233            Event::Empty(element)
1234                if matches_local_name(element.name().as_ref(), b"pt")
1235                    && points.len() < expected_points =>
1236            {
1237                points.push(parse_point(&element)?);
1238            }
1239            Event::Start(element)
1240                if matches_local_name(element.name().as_ref(), b"pt")
1241                    && points.len() < expected_points =>
1242            {
1243                let mut point = parse_point(&element)?;
1244                consume_leaf_children(reader, b"pt", &mut point.raw_children, 0)?;
1245                points.push(point);
1246            }
1247            Event::Start(element) => {
1248                raw_children.push(points.len(), capture_element(reader, &element)?)
1249            }
1250            Event::Empty(element) => {
1251                raw_children.push(points.len(), capture_empty_element(&element)?)
1252            }
1253            Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
1254            Event::Eof => {
1255                return Err(GeometryError::Xml(format!(
1256                    "missing closing a:{}",
1257                    String::from_utf8_lossy(end_name)
1258                )));
1259            }
1260            _ => {}
1261        }
1262        buffer.clear();
1263    }
1264    if points.len() != expected_points {
1265        return Err(GeometryError::Xml(format!(
1266            "DrawingML {} requires {expected_points} point children",
1267            String::from_utf8_lossy(end_name)
1268        )));
1269    }
1270    Ok(PathCommandRecord {
1271        command: make_command(points),
1272        raw_children,
1273    })
1274}
1275
1276fn parse_point(element: &BytesStart<'_>) -> Result<CT_AdjPoint2D, GeometryError> {
1277    Ok(CT_AdjPoint2D {
1278        x: required_operand(element, b"x")?,
1279        y: required_operand(element, b"y")?,
1280        raw_children: OrderedRawChildren::default(),
1281    })
1282}
1283
1284fn parse_arc(element: &BytesStart<'_>) -> Result<CT_Path2DCommand, GeometryError> {
1285    Ok(CT_Path2DCommand::ArcTo {
1286        width_radius: required_operand(element, b"wR")?,
1287        height_radius: required_operand(element, b"hR")?,
1288        start_angle: required_operand(element, b"stAng")?,
1289        sweep_angle: required_operand(element, b"swAng")?,
1290    })
1291}
1292
1293fn write_guide_list<W: Write>(
1294    writer: &mut Writer<W>,
1295    tag: &str,
1296    list: &GuideList,
1297) -> Result<(), GeometryError> {
1298    if list.guides.is_empty() && list.raw_children.is_empty() {
1299        writer
1300            .write_event(Event::Empty(BytesStart::new(tag)))
1301            .map_err(|error| GeometryError::Xml(error.to_string()))?;
1302        return Ok(());
1303    }
1304    writer
1305        .write_event(Event::Start(BytesStart::new(tag)))
1306        .map_err(|error| GeometryError::Xml(error.to_string()))?;
1307    for (index, guide) in list.guides.iter().enumerate() {
1308        emit_raw(writer, list.raw_children.at(index))?;
1309        let formula = guide.formula();
1310        let mut element = BytesStart::new("a:gd");
1311        element.push_attribute(("name", guide.name.as_str()));
1312        element.push_attribute(("fmla", formula.as_str()));
1313        let children = &list.guide_raw_children[index];
1314        write_leaf_with_raw(writer, element, "a:gd", children)?;
1315    }
1316    emit_raw(writer, list.raw_children.at(list.guides.len()))?;
1317    writer
1318        .write_event(Event::End(BytesEnd::new(tag)))
1319        .map_err(|error| GeometryError::Xml(error.to_string()))?;
1320    Ok(())
1321}
1322
1323fn write_rect<W: Write>(
1324    writer: &mut Writer<W>,
1325    rectangle: &CT_GeomRect,
1326) -> Result<(), GeometryError> {
1327    let values = [
1328        operand_text(&rectangle.left),
1329        operand_text(&rectangle.top),
1330        operand_text(&rectangle.right),
1331        operand_text(&rectangle.bottom),
1332    ];
1333    let mut element = BytesStart::new("a:rect");
1334    element.push_attribute(("l", values[0].as_str()));
1335    element.push_attribute(("t", values[1].as_str()));
1336    element.push_attribute(("r", values[2].as_str()));
1337    element.push_attribute(("b", values[3].as_str()));
1338    if rectangle.raw_children.is_empty() {
1339        writer
1340            .write_event(Event::Empty(element))
1341            .map_err(|error| GeometryError::Xml(error.to_string()))?;
1342    } else {
1343        writer
1344            .write_event(Event::Start(element))
1345            .map_err(|error| GeometryError::Xml(error.to_string()))?;
1346        emit_raw(writer, rectangle.raw_children.at(0))?;
1347        writer
1348            .write_event(Event::End(BytesEnd::new("a:rect")))
1349            .map_err(|error| GeometryError::Xml(error.to_string()))?;
1350    }
1351    Ok(())
1352}
1353
1354fn write_path_list<W: Write>(writer: &mut Writer<W>, list: &PathList) -> Result<(), GeometryError> {
1355    writer
1356        .write_event(Event::Start(BytesStart::new("a:pathLst")))
1357        .map_err(|error| GeometryError::Xml(error.to_string()))?;
1358    for (index, path) in list.paths.iter().enumerate() {
1359        emit_raw(writer, list.raw_children.at(index))?;
1360        write_path(writer, path)?;
1361    }
1362    emit_raw(writer, list.raw_children.at(list.paths.len()))?;
1363    writer
1364        .write_event(Event::End(BytesEnd::new("a:pathLst")))
1365        .map_err(|error| GeometryError::Xml(error.to_string()))?;
1366    Ok(())
1367}
1368
1369fn write_path<W: Write>(writer: &mut Writer<W>, path: &CT_Path2D) -> Result<(), GeometryError> {
1370    let width = path.width.map(|value| value.to_string());
1371    let height = path.height.map(|value| value.to_string());
1372    let mut element = BytesStart::new("a:path");
1373    if let Some(value) = width.as_deref() {
1374        element.push_attribute(("w", value));
1375    }
1376    if let Some(value) = height.as_deref() {
1377        element.push_attribute(("h", value));
1378    }
1379    if let Some(value) = path.fill.as_deref() {
1380        element.push_attribute(("fill", value));
1381    }
1382    if let Some(value) = path.stroke {
1383        element.push_attribute(("stroke", if value { "1" } else { "0" }));
1384    }
1385    if let Some(value) = path.extrusion_ok {
1386        element.push_attribute(("extrusionOk", if value { "1" } else { "0" }));
1387    }
1388    if path.commands.is_empty() && path.raw_children.is_empty() {
1389        writer
1390            .write_event(Event::Empty(element))
1391            .map_err(|error| GeometryError::Xml(error.to_string()))?;
1392        return Ok(());
1393    }
1394    writer
1395        .write_event(Event::Start(element))
1396        .map_err(|error| GeometryError::Xml(error.to_string()))?;
1397    for (index, command) in path.commands.iter().enumerate() {
1398        emit_raw(writer, path.raw_children.at(index))?;
1399        write_path_command(writer, command)?;
1400    }
1401    emit_raw(writer, path.raw_children.at(path.commands.len()))?;
1402    writer
1403        .write_event(Event::End(BytesEnd::new("a:path")))
1404        .map_err(|error| GeometryError::Xml(error.to_string()))?;
1405    Ok(())
1406}
1407
1408fn write_path_command<W: Write>(
1409    writer: &mut Writer<W>,
1410    record: &PathCommandRecord,
1411) -> Result<(), GeometryError> {
1412    match &record.command {
1413        CT_Path2DCommand::MoveTo(point) => {
1414            write_point_command(writer, "a:moveTo", std::slice::from_ref(point), record)?
1415        }
1416        CT_Path2DCommand::LineTo(point) => {
1417            write_point_command(writer, "a:lnTo", std::slice::from_ref(point), record)?
1418        }
1419        CT_Path2DCommand::CubicTo {
1420            control_1,
1421            control_2,
1422            end,
1423        } => write_point_command(
1424            writer,
1425            "a:cubicBezTo",
1426            &[control_1.clone(), control_2.clone(), end.clone()],
1427            record,
1428        )?,
1429        CT_Path2DCommand::ArcTo {
1430            width_radius,
1431            height_radius,
1432            start_angle,
1433            sweep_angle,
1434        } => {
1435            let values = [
1436                operand_text(width_radius),
1437                operand_text(height_radius),
1438                operand_text(start_angle),
1439                operand_text(sweep_angle),
1440            ];
1441            let mut element = BytesStart::new("a:arcTo");
1442            element.push_attribute(("wR", values[0].as_str()));
1443            element.push_attribute(("hR", values[1].as_str()));
1444            element.push_attribute(("stAng", values[2].as_str()));
1445            element.push_attribute(("swAng", values[3].as_str()));
1446            write_leaf_with_raw(writer, element, "a:arcTo", &record.raw_children)?;
1447        }
1448        CT_Path2DCommand::Close => {
1449            write_leaf_with_raw(
1450                writer,
1451                BytesStart::new("a:close"),
1452                "a:close",
1453                &record.raw_children,
1454            )?;
1455        }
1456    }
1457    Ok(())
1458}
1459
1460fn write_point_command<W: Write>(
1461    writer: &mut Writer<W>,
1462    tag: &str,
1463    points: &[CT_AdjPoint2D],
1464    record: &PathCommandRecord,
1465) -> Result<(), GeometryError> {
1466    writer
1467        .write_event(Event::Start(BytesStart::new(tag)))
1468        .map_err(|error| GeometryError::Xml(error.to_string()))?;
1469    for (index, point) in points.iter().enumerate() {
1470        emit_raw(writer, record.raw_children.at(index))?;
1471        write_point(writer, point)?;
1472    }
1473    emit_raw(writer, record.raw_children.at(points.len()))?;
1474    writer
1475        .write_event(Event::End(BytesEnd::new(tag)))
1476        .map_err(|error| GeometryError::Xml(error.to_string()))?;
1477    Ok(())
1478}
1479
1480fn write_point<W: Write>(
1481    writer: &mut Writer<W>,
1482    point: &CT_AdjPoint2D,
1483) -> Result<(), GeometryError> {
1484    let x = operand_text(&point.x);
1485    let y = operand_text(&point.y);
1486    let mut element = BytesStart::new("a:pt");
1487    element.push_attribute(("x", x.as_str()));
1488    element.push_attribute(("y", y.as_str()));
1489    write_leaf_with_raw(writer, element, "a:pt", &point.raw_children)?;
1490    Ok(())
1491}
1492
1493fn write_leaf_with_raw<W: Write>(
1494    writer: &mut Writer<W>,
1495    element: BytesStart<'_>,
1496    tag: &str,
1497    raw_children: &OrderedRawChildren,
1498) -> Result<(), GeometryError> {
1499    if raw_children.is_empty() {
1500        writer
1501            .write_event(Event::Empty(element))
1502            .map_err(|error| GeometryError::Xml(error.to_string()))?;
1503    } else {
1504        writer
1505            .write_event(Event::Start(element))
1506            .map_err(|error| GeometryError::Xml(error.to_string()))?;
1507        emit_raw(writer, raw_children.at(0))?;
1508        writer
1509            .write_event(Event::End(BytesEnd::new(tag)))
1510            .map_err(|error| GeometryError::Xml(error.to_string()))?;
1511    }
1512    Ok(())
1513}
1514
1515fn emit_raw<'a, W: Write>(
1516    writer: &mut Writer<W>,
1517    children: impl Iterator<Item = &'a [u8]>,
1518) -> Result<(), GeometryError> {
1519    for child in children {
1520        writer
1521            .get_mut()
1522            .write_all(child)
1523            .map_err(|error| GeometryError::Xml(error.to_string()))?;
1524    }
1525    Ok(())
1526}
1527
1528fn required_operand(
1529    element: &BytesStart<'_>,
1530    attribute: &[u8],
1531) -> Result<GuideOperand, GeometryError> {
1532    GuideOperand::parse(&required_attr(element, attribute)?)
1533}
1534
1535fn required_attr(element: &BytesStart<'_>, attribute: &[u8]) -> Result<String, GeometryError> {
1536    get_attr(element, attribute).ok_or_else(|| GeometryError::MissingAttribute {
1537        element: element_name(element),
1538        attribute: String::from_utf8_lossy(attribute).into_owned(),
1539    })
1540}
1541
1542fn optional_f64(element: &BytesStart<'_>, attribute: &[u8]) -> Result<Option<f64>, GeometryError> {
1543    get_attr(element, attribute)
1544        .map(|value| {
1545            value
1546                .parse::<f64>()
1547                .ok()
1548                .filter(|value| value.is_finite() && *value >= 0.0)
1549                .ok_or_else(|| invalid_attribute(element, attribute, value))
1550        })
1551        .transpose()
1552}
1553
1554fn optional_bool(
1555    element: &BytesStart<'_>,
1556    attribute: &[u8],
1557) -> Result<Option<bool>, GeometryError> {
1558    get_attr(element, attribute)
1559        .map(|value| match value.as_str() {
1560            "1" | "true" => Ok(true),
1561            "0" | "false" => Ok(false),
1562            _ => Err(invalid_attribute(element, attribute, value)),
1563        })
1564        .transpose()
1565}
1566
1567fn invalid_attribute(element: &BytesStart<'_>, attribute: &[u8], value: String) -> GeometryError {
1568    GeometryError::InvalidAttribute {
1569        element: element_name(element),
1570        attribute: String::from_utf8_lossy(attribute).into_owned(),
1571        value,
1572    }
1573}
1574
1575fn element_name(element: &BytesStart<'_>) -> String {
1576    String::from_utf8_lossy(local_name(element.name().as_ref())).into_owned()
1577}
1578
1579fn operand_text(operand: &GuideOperand) -> String {
1580    match operand {
1581        GuideOperand::Literal(value) => value.to_string(),
1582        GuideOperand::Guide(name) => name.clone(),
1583    }
1584}
1585
1586#[derive(Clone, Debug)]
1587pub struct GuideEvaluator {
1588    values: BTreeMap<String, f64>,
1589}
1590
1591impl GuideEvaluator {
1592    pub fn new(width: f64, height: f64) -> Result<Self, GeometryError> {
1593        ensure_finite(width, "shape width")?;
1594        ensure_finite(height, "shape height")?;
1595
1596        let mut evaluator = Self {
1597            values: BTreeMap::new(),
1598        };
1599        evaluator.seed("w", width);
1600        evaluator.seed("h", height);
1601        evaluator.seed("l", 0.0);
1602        evaluator.seed("t", 0.0);
1603        evaluator.seed("r", width);
1604        evaluator.seed("b", height);
1605        evaluator.seed("hc", width / 2.0);
1606        evaluator.seed("vc", height / 2.0);
1607        evaluator.seed("ss", width.min(height));
1608        evaluator.seed("ls", width.max(height));
1609
1610        for divisor in [2_u32, 3, 4, 5, 6, 8, 10, 12, 32] {
1611            evaluator.seed(&format!("wd{divisor}"), width / f64::from(divisor));
1612        }
1613        for divisor in [2_u32, 3, 4, 5, 6, 8, 10] {
1614            evaluator.seed(&format!("hd{divisor}"), height / f64::from(divisor));
1615        }
1616        for divisor in [2_u32, 4, 6, 8, 16, 32] {
1617            evaluator.seed(
1618                &format!("ssd{divisor}"),
1619                width.min(height) / f64::from(divisor),
1620            );
1621        }
1622        evaluator.seed("cd2", 180.0 * ANGLE_UNITS_PER_DEGREE);
1623        evaluator.seed("cd4", 90.0 * ANGLE_UNITS_PER_DEGREE);
1624        evaluator.seed("cd8", 45.0 * ANGLE_UNITS_PER_DEGREE);
1625        evaluator.seed("3cd4", 270.0 * ANGLE_UNITS_PER_DEGREE);
1626        evaluator.seed("3cd8", 135.0 * ANGLE_UNITS_PER_DEGREE);
1627        evaluator.seed("5cd8", 225.0 * ANGLE_UNITS_PER_DEGREE);
1628        evaluator.seed("7cd8", 315.0 * ANGLE_UNITS_PER_DEGREE);
1629        Ok(evaluator)
1630    }
1631
1632    pub fn value(&self, name: &str) -> Result<f64, GeometryError> {
1633        self.values
1634            .get(name)
1635            .copied()
1636            .ok_or_else(|| GeometryError::UnknownGuide(name.to_owned()))
1637    }
1638
1639    pub fn apply_adjust_values(
1640        &mut self,
1641        adjustments: &[Guide],
1642        overrides: &BTreeMap<String, f64>,
1643    ) -> Result<(), GeometryError> {
1644        let declared = adjustments
1645            .iter()
1646            .map(|guide| guide.name.as_str())
1647            .collect::<BTreeSet<_>>();
1648        if let Some(name) = overrides
1649            .keys()
1650            .find(|name| !declared.contains(name.as_str()))
1651        {
1652            return Err(GeometryError::UnknownAdjustOverride(name.clone()));
1653        }
1654
1655        for adjustment in adjustments {
1656            let value = match overrides.get(&adjustment.name) {
1657                Some(value) => {
1658                    ensure_finite(*value, &adjustment.name)?;
1659                    *value
1660                }
1661                None => self.evaluate_operation(adjustment.op, &adjustment.args)?,
1662            };
1663            self.insert_named(&adjustment.name, value)?;
1664        }
1665        Ok(())
1666    }
1667
1668    pub fn evaluate_guides(&mut self, guides: &[Guide]) -> Result<(), GeometryError> {
1669        let mut evaluated = BTreeSet::new();
1670        for guide in guides {
1671            let value = self.evaluate_operation(guide.op, &guide.args)?;
1672            if evaluated.insert(guide.name.clone()) {
1673                self.insert_named(&guide.name, value)?;
1674            } else {
1675                ensure_finite(value, &guide.name)?;
1676                self.values.insert(guide.name.clone(), value);
1677            }
1678        }
1679        Ok(())
1680    }
1681
1682    pub fn evaluate_path(
1683        &self,
1684        commands: &[PathCommand],
1685    ) -> Result<Vec<EvaluatedPathCommand>, GeometryError> {
1686        let mut output = Vec::with_capacity(commands.len());
1687        let mut current = None;
1688        let mut subpath_start = None;
1689
1690        for command in commands {
1691            match command {
1692                PathCommand::MoveTo { x, y } => {
1693                    let point = (self.resolve(x)?, self.resolve(y)?);
1694                    output.push(EvaluatedPathCommand::MoveTo {
1695                        x: point.0,
1696                        y: point.1,
1697                    });
1698                    current = Some(point);
1699                    subpath_start = Some(point);
1700                }
1701                PathCommand::LineTo { x, y } => {
1702                    current.ok_or(GeometryError::PathHasNoCurrentPoint)?;
1703                    let point = (self.resolve(x)?, self.resolve(y)?);
1704                    output.push(EvaluatedPathCommand::LineTo {
1705                        x: point.0,
1706                        y: point.1,
1707                    });
1708                    current = Some(point);
1709                }
1710                PathCommand::CubicTo {
1711                    x1,
1712                    y1,
1713                    x2,
1714                    y2,
1715                    x,
1716                    y,
1717                } => {
1718                    current.ok_or(GeometryError::PathHasNoCurrentPoint)?;
1719                    let command = EvaluatedPathCommand::CubicTo {
1720                        x1: self.resolve(x1)?,
1721                        y1: self.resolve(y1)?,
1722                        x2: self.resolve(x2)?,
1723                        y2: self.resolve(y2)?,
1724                        x: self.resolve(x)?,
1725                        y: self.resolve(y)?,
1726                    };
1727                    if let EvaluatedPathCommand::CubicTo { x, y, .. } = command {
1728                        current = Some((x, y));
1729                    }
1730                    output.push(command);
1731                }
1732                PathCommand::ArcTo {
1733                    width_radius,
1734                    height_radius,
1735                    start_angle,
1736                    sweep_angle,
1737                } => {
1738                    let start = current.ok_or(GeometryError::PathHasNoCurrentPoint)?;
1739                    let cubics = flatten_arc(
1740                        start,
1741                        self.resolve(width_radius)?,
1742                        self.resolve(height_radius)?,
1743                        self.resolve(start_angle)?,
1744                        self.resolve(sweep_angle)?,
1745                    )?;
1746                    if let Some(EvaluatedPathCommand::CubicTo { x, y, .. }) = cubics.last() {
1747                        current = Some((*x, *y));
1748                    }
1749                    output.extend(cubics);
1750                }
1751                PathCommand::Close => {
1752                    current.ok_or(GeometryError::PathHasNoCurrentPoint)?;
1753                    output.push(EvaluatedPathCommand::Close);
1754                    current = subpath_start;
1755                }
1756            }
1757        }
1758        Ok(output)
1759    }
1760
1761    fn seed(&mut self, name: &str, value: f64) {
1762        self.values.insert(name.to_owned(), value);
1763    }
1764
1765    fn insert_named(&mut self, name: &str, value: f64) -> Result<(), GeometryError> {
1766        ensure_finite(value, name)?;
1767        if self.values.contains_key(name) {
1768            return Err(GeometryError::DuplicateGuide(name.to_owned()));
1769        }
1770        self.values.insert(name.to_owned(), value);
1771        Ok(())
1772    }
1773
1774    fn resolve(&self, operand: &GuideOperand) -> Result<f64, GeometryError> {
1775        match operand {
1776            GuideOperand::Literal(value) => {
1777                ensure_finite(*value, "literal")?;
1778                Ok(*value)
1779            }
1780            GuideOperand::Guide(name) => self.value(name),
1781        }
1782    }
1783
1784    fn evaluate_operation(
1785        &self,
1786        op: GuideOp,
1787        operands: &[GuideOperand],
1788    ) -> Result<f64, GeometryError> {
1789        let expected = op.argument_count();
1790        if operands.len() != expected {
1791            return Err(GeometryError::WrongArgumentCount {
1792                operation: format!("{op:?}"),
1793                expected,
1794                actual: operands.len(),
1795            });
1796        }
1797        let args = operands
1798            .iter()
1799            .map(|operand| self.resolve(operand))
1800            .collect::<Result<Vec<_>, _>>()?;
1801
1802        let value = match op {
1803            GuideOp::MulDiv => checked_div(args[0] * args[1], args[2])?,
1804            GuideOp::AddSub => args[0] + args[1] - args[2],
1805            GuideOp::AddDiv => checked_div(args[0] + args[1], args[2])?,
1806            GuideOp::IfElse => {
1807                if args[0] > 0.0 {
1808                    args[1]
1809                } else {
1810                    args[2]
1811                }
1812            }
1813            GuideOp::Abs => args[0].abs(),
1814            GuideOp::At2 => radians_to_angle(args[1].atan2(args[0])),
1815            GuideOp::Cat2 => args[0] * args[2].atan2(args[1]).cos(),
1816            GuideOp::Cos => args[0] * angle_to_radians(args[1]).cos(),
1817            GuideOp::Max => args[0].max(args[1]),
1818            GuideOp::Min => args[0].min(args[1]),
1819            GuideOp::Mod => args[0].hypot(args[1]).hypot(args[2]),
1820            GuideOp::Pin => {
1821                if args[1] < args[0] {
1822                    args[0]
1823                } else if args[1] > args[2] {
1824                    args[2]
1825                } else {
1826                    args[1]
1827                }
1828            }
1829            GuideOp::Sat2 => args[0] * args[2].atan2(args[1]).sin(),
1830            GuideOp::Sin => args[0] * angle_to_radians(args[1]).sin(),
1831            GuideOp::Sqrt => args[0].abs().sqrt(),
1832            GuideOp::Tan => args[0] * angle_to_radians(args[1]).tan(),
1833            GuideOp::Val => args[0],
1834        };
1835        ensure_finite(value, "guide result")?;
1836        Ok(value)
1837    }
1838}
1839
1840fn checked_div(numerator: f64, denominator: f64) -> Result<f64, GeometryError> {
1841    if denominator == 0.0 {
1842        return Err(GeometryError::DivisionByZero);
1843    }
1844    Ok(numerator / denominator)
1845}
1846
1847fn angle_to_radians(angle: f64) -> f64 {
1848    angle / ANGLE_UNITS_PER_DEGREE * PI / 180.0
1849}
1850
1851fn radians_to_angle(radians: f64) -> f64 {
1852    radians * 180.0 / PI * ANGLE_UNITS_PER_DEGREE
1853}
1854
1855fn ensure_finite(value: f64, context: &str) -> Result<(), GeometryError> {
1856    if value.is_finite() {
1857        Ok(())
1858    } else {
1859        Err(GeometryError::NonFiniteValue(context.to_owned()))
1860    }
1861}
1862
1863fn flatten_arc(
1864    current: (f64, f64),
1865    width_radius: f64,
1866    height_radius: f64,
1867    start_angle: f64,
1868    sweep_angle: f64,
1869) -> Result<Vec<EvaluatedPathCommand>, GeometryError> {
1870    for (value, context) in [
1871        (current.0, "arc start x"),
1872        (current.1, "arc start y"),
1873        (width_radius, "arc width radius"),
1874        (height_radius, "arc height radius"),
1875        (start_angle, "arc start angle"),
1876        (sweep_angle, "arc sweep angle"),
1877    ] {
1878        ensure_finite(value, context)?;
1879    }
1880    if width_radius <= 0.0 || height_radius <= 0.0 {
1881        return Err(GeometryError::InvalidArcRadius);
1882    }
1883    if sweep_angle == 0.0 {
1884        return Ok(Vec::new());
1885    }
1886
1887    let segment_count = (sweep_angle.abs() / QUARTER_CIRCLE).ceil();
1888    if segment_count > MAX_ARC_SEGMENTS as f64 {
1889        return Err(GeometryError::ArcSweepTooLarge);
1890    }
1891    let segment_count = segment_count as usize;
1892    let segment_sweep = sweep_angle / segment_count as f64;
1893    let start_radians = angle_to_radians(start_angle);
1894    let center = (
1895        current.0 - width_radius * start_radians.cos(),
1896        current.1 - height_radius * start_radians.sin(),
1897    );
1898    let mut cubics = Vec::with_capacity(segment_count);
1899
1900    for index in 0..segment_count {
1901        let angle_1 = angle_to_radians(start_angle + segment_sweep * index as f64);
1902        let angle_2 = angle_to_radians(start_angle + segment_sweep * (index + 1) as f64);
1903        let alpha = 4.0 / 3.0 * ((angle_2 - angle_1) / 4.0).tan();
1904        let point_1 = (
1905            center.0 + width_radius * angle_1.cos(),
1906            center.1 + height_radius * angle_1.sin(),
1907        );
1908        let point_2 = (
1909            center.0 + width_radius * angle_2.cos(),
1910            center.1 + height_radius * angle_2.sin(),
1911        );
1912        let tangent_1 = (-width_radius * angle_1.sin(), height_radius * angle_1.cos());
1913        let tangent_2 = (-width_radius * angle_2.sin(), height_radius * angle_2.cos());
1914        let command = EvaluatedPathCommand::CubicTo {
1915            x1: point_1.0 + alpha * tangent_1.0,
1916            y1: point_1.1 + alpha * tangent_1.1,
1917            x2: point_2.0 - alpha * tangent_2.0,
1918            y2: point_2.1 - alpha * tangent_2.1,
1919            x: point_2.0,
1920            y: point_2.1,
1921        };
1922        if let EvaluatedPathCommand::CubicTo {
1923            x1,
1924            y1,
1925            x2,
1926            y2,
1927            x,
1928            y,
1929        } = command
1930        {
1931            for value in [x1, y1, x2, y2, x, y] {
1932                ensure_finite(value, "arc cubic coordinate")?;
1933            }
1934        }
1935        cubics.push(command);
1936    }
1937    Ok(cubics)
1938}
1939
1940#[cfg(test)]
1941mod tests {
1942    use super::*;
1943
1944    #[test]
1945    fn preset_constructor_accepts_generated_names_and_rejects_unknown_names() {
1946        assert_eq!(
1947            CT_PresetGeometry2D::new("triangle").unwrap().preset,
1948            "triangle"
1949        );
1950        assert_eq!(
1951            CT_PresetGeometry2D::new("not-a-preset").unwrap_err(),
1952            GeometryError::UnknownPreset("not-a-preset".to_owned())
1953        );
1954    }
1955
1956    #[test]
1957    fn preset_adjustment_setter_inserts_and_replaces_named_values() {
1958        let mut geometry = CT_PresetGeometry2D::from_xml(
1959            br#"<x:prstGeom xmlns:x="urn:a" prst="roundRect"><x:avLst><x:gd name="adj" fmla="val 12000"><ext:raw xmlns:ext="urn:ext"/></x:gd><ext:tail xmlns:ext="urn:ext"/></x:avLst><ext:after xmlns:ext="urn:ext"/></x:prstGeom>"#,
1960        )
1961        .unwrap();
1962
1963        geometry.set_adjust_value("adj", 25_000.0).unwrap();
1964        geometry.set_adjust_value("adj2", 7_500.5).unwrap();
1965        let xml = String::from_utf8(geometry.to_xml().unwrap()).unwrap();
1966
1967        assert_eq!(geometry.adjust_values().len(), 2);
1968        assert_eq!(geometry.adjust_values()[0].name, "adj");
1969        assert_eq!(geometry.adjust_values()[0].args, vec![literal(25_000.0)]);
1970        assert_eq!(geometry.adjust_values()[1].name, "adj2");
1971        assert_eq!(geometry.adjust_values()[1].args, vec![literal(7_500.5)]);
1972        assert_eq!(xml.matches("name=\"adj\"").count(), 1);
1973        assert_eq!(xml.matches("name=\"adj2\"").count(), 1);
1974        assert!(xml.contains("<ext:raw xmlns:ext=\"urn:ext\"/>"));
1975        assert!(xml.contains("<a:gd name=\"adj2\" fmla=\"val 7500.5\"/><ext:tail"));
1976        assert!(geometry.set_adjust_value("bad", f64::INFINITY).is_err());
1977    }
1978
1979    fn literal(value: f64) -> GuideOperand {
1980        GuideOperand::Literal(value)
1981    }
1982
1983    fn guide(name: &str) -> GuideOperand {
1984        GuideOperand::Guide(name.to_owned())
1985    }
1986
1987    fn assert_close(actual: f64, expected: f64) {
1988        assert!((actual - expected).abs() < 1.0e-9, "{actual} != {expected}");
1989    }
1990
1991    #[test]
1992    fn hand_written_custom_geometry_guides_produce_expected_path_coordinates() {
1993        let adjustments = [Guide::parse("adj1", "val 25000").unwrap()];
1994        let guides = [
1995            Guide::parse("x1", "*/ w adj1 100000").unwrap(),
1996            Guide::parse("y1", "+/ hd2 0 2").unwrap(),
1997            Guide::parse("x2", "+- r 0 x1").unwrap(),
1998            Guide::parse("y2", "?: adj1 75 25").unwrap(),
1999        ];
2000        let overrides = BTreeMap::from([("adj1".to_owned(), 20_000.0)]);
2001        let mut evaluator = GuideEvaluator::new(100.0, 100.0).unwrap();
2002        assert_eq!(evaluator.value("wd32").unwrap(), 3.125);
2003        assert_eq!(evaluator.value("wd12").unwrap(), 100.0 / 12.0);
2004        assert_eq!(evaluator.value("hd8").unwrap(), 12.5);
2005        assert_eq!(evaluator.value("hd10").unwrap(), 10.0);
2006        assert_eq!(evaluator.value("ssd32").unwrap(), 3.125);
2007        assert_eq!(evaluator.value("3cd8").unwrap(), 8_100_000.0);
2008        evaluator
2009            .apply_adjust_values(&adjustments, &overrides)
2010            .unwrap();
2011        evaluator.evaluate_guides(&guides).unwrap();
2012
2013        let path = evaluator
2014            .evaluate_path(&[
2015                PathCommand::MoveTo {
2016                    x: guide("x1"),
2017                    y: guide("y1"),
2018                },
2019                PathCommand::LineTo {
2020                    x: guide("x2"),
2021                    y: guide("y2"),
2022                },
2023            ])
2024            .unwrap();
2025        assert_eq!(
2026            path,
2027            [
2028                EvaluatedPathCommand::MoveTo { x: 20.0, y: 25.0 },
2029                EvaluatedPathCommand::LineTo { x: 80.0, y: 75.0 },
2030            ]
2031        );
2032    }
2033
2034    #[test]
2035    fn ordinary_guides_replace_in_order_without_relaxing_adjustment_validation() {
2036        let mut evaluator = GuideEvaluator::new(100.0, 100.0).unwrap();
2037        evaluator
2038            .evaluate_guides(&[
2039                Guide::parse("connsiteX0", "val 10").unwrap(),
2040                Guide::parse("connsiteX0", "val 20").unwrap(),
2041            ])
2042            .unwrap();
2043        assert_eq!(evaluator.value("connsiteX0").unwrap(), 20.0);
2044
2045        let duplicate_adjustments = [
2046            Guide::parse("adj", "val 10").unwrap(),
2047            Guide::parse("adj", "val 20").unwrap(),
2048        ];
2049        let error = GuideEvaluator::new(100.0, 100.0)
2050            .unwrap()
2051            .apply_adjust_values(&duplicate_adjustments, &BTreeMap::new())
2052            .unwrap_err();
2053        assert_eq!(error, GeometryError::DuplicateGuide("adj".to_owned()));
2054
2055        let error = GuideEvaluator::new(100.0, 100.0)
2056            .unwrap()
2057            .apply_adjust_values(
2058                &[Guide::parse("adj", "val 10").unwrap()],
2059                &BTreeMap::from([("unknown".to_owned(), 20.0)]),
2060            )
2061            .unwrap_err();
2062        assert_eq!(
2063            error,
2064            GeometryError::UnknownAdjustOverride("unknown".to_owned())
2065        );
2066    }
2067
2068    #[test]
2069    fn all_seventeen_formula_tokens_parse_and_evaluate_with_drawingml_argument_order() {
2070        let cases = [
2071            ("*/ 6 7 2", GuideOp::MulDiv, 21.0),
2072            ("+- 10 4 3", GuideOp::AddSub, 11.0),
2073            ("+/ 10 4 2", GuideOp::AddDiv, 7.0),
2074            ("?: 1 5 6", GuideOp::IfElse, 5.0),
2075            ("abs -7", GuideOp::Abs, 7.0),
2076            ("at2 1 1", GuideOp::At2, 2_700_000.0),
2077            ("cat2 10 3 4", GuideOp::Cat2, 6.0),
2078            ("cos 10 3600000", GuideOp::Cos, 5.0),
2079            ("max 3 7", GuideOp::Max, 7.0),
2080            ("min 3 7", GuideOp::Min, 3.0),
2081            ("mod 3 4 12", GuideOp::Mod, 13.0),
2082            ("pin 0 15 10", GuideOp::Pin, 10.0),
2083            ("sat2 10 3 4", GuideOp::Sat2, 8.0),
2084            ("sin 10 1800000", GuideOp::Sin, 5.0),
2085            ("sqrt -9", GuideOp::Sqrt, 3.0),
2086            ("tan 10 2700000", GuideOp::Tan, 10.0),
2087            ("val 42", GuideOp::Val, 42.0),
2088        ];
2089        let guides = cases
2090            .iter()
2091            .enumerate()
2092            .map(|(index, (formula, expected_op, _))| {
2093                let guide = Guide::parse(format!("g{index}"), formula).unwrap();
2094                assert_eq!(guide.op, *expected_op);
2095                guide
2096            })
2097            .collect::<Vec<_>>();
2098        let mut evaluator = GuideEvaluator::new(100.0, 80.0).unwrap();
2099        evaluator.evaluate_guides(&guides).unwrap();
2100
2101        for (index, (_, _, expected)) in cases.iter().enumerate() {
2102            assert_close(evaluator.value(&format!("g{index}")).unwrap(), *expected);
2103        }
2104    }
2105
2106    #[test]
2107    fn arc_to_is_flattened_to_finite_cubics_with_matching_endpoints() {
2108        let evaluator = GuideEvaluator::new(100.0, 100.0).unwrap();
2109        let path = evaluator
2110            .evaluate_path(&[
2111                PathCommand::MoveTo {
2112                    x: literal(10.0),
2113                    y: literal(0.0),
2114                },
2115                PathCommand::ArcTo {
2116                    width_radius: literal(10.0),
2117                    height_radius: literal(5.0),
2118                    start_angle: literal(0.0),
2119                    sweep_angle: literal(27_000_000.0),
2120                },
2121            ])
2122            .unwrap();
2123        assert_eq!(path.len(), 6);
2124        assert!(path.iter().skip(1).all(|command| matches!(
2125            command,
2126            EvaluatedPathCommand::CubicTo {
2127                x1,
2128                y1,
2129                x2,
2130                y2,
2131                x,
2132                y
2133            } if [x1, y1, x2, y2, x, y].iter().all(|value| value.is_finite())
2134        )));
2135        let EvaluatedPathCommand::CubicTo { x, y, .. } = path[5] else {
2136            panic!("arc did not end in a cubic command")
2137        };
2138        assert_close(x, 0.0);
2139        assert_close(y, 5.0);
2140        let EvaluatedPathCommand::CubicTo { x, y, .. } = path[1] else {
2141            panic!("arc did not start with a cubic command")
2142        };
2143        assert_close(x, 0.0);
2144        assert_close(y, 5.0);
2145    }
2146
2147    #[test]
2148    fn office_mod_and_negative_sqrt_semantics_produce_finite_values() {
2149        let guides = [
2150            Guide::parse("norm", "mod 3 4 12").unwrap(),
2151            Guide::parse("root", "sqrt -9").unwrap(),
2152        ];
2153        let mut evaluator = GuideEvaluator::new(10.0, 10.0).unwrap();
2154        evaluator.evaluate_guides(&guides).unwrap();
2155        assert_eq!(evaluator.value("norm").unwrap(), 13.0);
2156        assert_eq!(evaluator.value("root").unwrap(), 3.0);
2157    }
2158
2159    #[test]
2160    fn division_by_zero_returns_an_error_instead_of_non_finite_coordinates() {
2161        let mut evaluator = GuideEvaluator::new(10.0, 10.0).unwrap();
2162        let error = evaluator
2163            .evaluate_guides(&[Guide::parse("bad", "*/ 1 2 0").unwrap()])
2164            .unwrap_err();
2165        assert_eq!(error, GeometryError::DivisionByZero);
2166        assert_eq!(error.to_string(), "division by zero");
2167    }
2168
2169    #[test]
2170    fn corpus_custom_geometry_round_trips_and_evaluates_to_a_closed_path() {
2171        let xml = br#"<z:custGeom xmlns:z="http://schemas.openxmlformats.org/drawingml/2006/main"><z:avLst><z:gd name="adj" fmla="val 25000"/></z:avLst><z:gdLst><z:gd name="x1" fmla="*/ w adj 100000"/><z:gd name="x2" fmla="+- r 0 x1"/></z:gdLst><z:rect l="x1" t="t" r="x2" b="b"/><z:pathLst><z:path w="100" h="100"><z:moveTo><z:pt x="l" y="t"/></z:moveTo><z:lnTo><z:pt x="r" y="t"/></z:lnTo><z:cubicBezTo><z:pt x="r" y="t"/><z:pt x="r" y="b"/><z:pt x="x2" y="b"/></z:cubicBezTo><z:close/></z:path></z:pathLst></z:custGeom>"#;
2172        let geometry = CT_CustomGeometry2D::from_xml(xml).unwrap();
2173        let evaluated = geometry.evaluate(&BTreeMap::new()).unwrap();
2174
2175        assert_eq!(geometry.adjust_values().len(), 1);
2176        assert_eq!(geometry.guides().len(), 2);
2177        assert_eq!(geometry.paths().len(), 1);
2178        assert_eq!(evaluated.paths.len(), 1);
2179        assert_eq!(
2180            evaluated.paths[0].last(),
2181            Some(&EvaluatedPathCommand::Close)
2182        );
2183        assert_eq!(
2184            evaluated.text_rectangle,
2185            Some(EvaluatedTextRectangle {
2186                left: 25.0,
2187                top: 0.0,
2188                right: 75.0,
2189                bottom: 100.0,
2190            })
2191        );
2192
2193        let written = geometry.to_xml().unwrap();
2194        let reparsed = CT_CustomGeometry2D::from_xml(&written).unwrap();
2195        assert_eq!(reparsed, geometry);
2196    }
2197
2198    #[test]
2199    fn custom_geometry_reads_any_prefix_and_writes_fixed_a_prefix_in_schema_order() {
2200        let xml = br#"<q:custGeom><q:avLst/><q:gdLst/><q:ahLst/><q:cxnLst/><q:rect l="l" t="t" r="r" b="b"/><q:pathLst><q:path w="100" h="80" fill="none" stroke="false" extrusionOk="true"><q:moveTo><q:pt x="0" y="0"/></q:moveTo><q:arcTo wR="10" hR="5" stAng="0" swAng="5400000"/><q:close/></q:path></q:pathLst></q:custGeom>"#;
2201        let geometry = CT_CustomGeometry2D::from_xml(xml).unwrap();
2202        let written = String::from_utf8(geometry.to_xml().unwrap()).unwrap();
2203
2204        assert!(written.starts_with("<a:custGeom>"));
2205        assert!(written.contains("<a:avLst/>"));
2206        assert!(written.contains("<a:gdLst/>"));
2207        assert!(written.contains("<a:rect l=\"l\" t=\"t\" r=\"r\" b=\"b\"/>"));
2208        assert!(written.contains("<a:arcTo wR=\"10\" hR=\"5\" stAng=\"0\" swAng=\"5400000\"/>"));
2209        assert!(written.contains("<a:close/>"));
2210        let av = written.find("<a:avLst").unwrap();
2211        let gd = written.find("<a:gdLst").unwrap();
2212        let ah = written.find("<q:ahLst/>").unwrap();
2213        let cxn = written.find("<q:cxnLst/>").unwrap();
2214        let rect = written.find("<a:rect").unwrap();
2215        let paths = written.find("<a:pathLst").unwrap();
2216        assert!(av < gd && gd < ah && ah < cxn && cxn < rect && rect < paths);
2217    }
2218
2219    #[test]
2220    fn empty_custom_geometry_path_list_from_theme_defaults_round_trips() {
2221        let geometry = CT_CustomGeometry2D::from_xml(
2222            br#"<q:custGeom><q:avLst/><q:gdLst/><q:ahLst/><q:cxnLst/><q:rect l="0" t="0" r="0" b="0"/><q:pathLst/></q:custGeom>"#,
2223        )
2224        .unwrap();
2225        assert!(geometry.paths().is_empty());
2226        let written = geometry.to_xml().unwrap();
2227        assert_eq!(CT_CustomGeometry2D::from_xml(&written).unwrap(), geometry);
2228    }
2229
2230    #[test]
2231    fn unknown_custom_geometry_children_round_trip_byte_for_byte_in_place() {
2232        let xml = br#"<a:custGeom><u:before/><a:avLst><u:avBefore/><a:gd name="adj" fmla="val 25000"><u:insideGuide/></a:gd><u:avAfter/></a:avLst><u:middle u:id="7"><u:child/></u:middle><a:pathLst><u:pathBefore/><a:path w="100" h="100"><a:moveTo><a:pt x="0" y="0"><u:insidePoint/></a:pt><u:insideMove/></a:moveTo><u:between/><a:lnTo><a:pt x="100" y="100"/></a:lnTo><a:close/></a:path><u:pathAfter/></a:pathLst><u:after/></a:custGeom>"#;
2233        let written = String::from_utf8(
2234            CT_CustomGeometry2D::from_xml(xml)
2235                .unwrap()
2236                .to_xml()
2237                .unwrap(),
2238        )
2239        .unwrap();
2240
2241        for raw in [
2242            "<u:before/>",
2243            "<u:avBefore/>",
2244            "<u:insideGuide/>",
2245            "<u:avAfter/>",
2246            "<u:middle u:id=\"7\"><u:child/></u:middle>",
2247            "<u:pathBefore/>",
2248            "<u:insidePoint/>",
2249            "<u:insideMove/>",
2250            "<u:between/>",
2251            "<u:pathAfter/>",
2252            "<u:after/>",
2253        ] {
2254            assert!(written.contains(raw), "missing raw subtree {raw}");
2255        }
2256        assert!(written.find("<u:before/>").unwrap() < written.find("<a:avLst").unwrap());
2257        assert!(written.find("<u:avBefore/>").unwrap() < written.find("<a:gd ").unwrap());
2258        assert!(written.find("<a:gd ").unwrap() < written.find("<u:avAfter/>").unwrap());
2259        assert!(written.find("<u:insideGuide/>").unwrap() < written.find("</a:gd>").unwrap());
2260        assert!(written.find("<u:insidePoint/>").unwrap() < written.find("</a:pt>").unwrap());
2261        assert!(written.find("<u:insideMove/>").unwrap() < written.find("</a:moveTo>").unwrap());
2262        assert!(written.find("</a:moveTo>").unwrap() < written.find("<u:between/>").unwrap());
2263    }
2264
2265    #[test]
2266    fn malformed_custom_geometry_returns_an_error_without_panicking() {
2267        let malformed: [&[u8]; 3] = [
2268            br#"<a:custGeom><a:avLst><a:gd fmla="val 1"/></a:avLst><a:pathLst><a:path w="1" h="1"/></a:pathLst></a:custGeom>"#,
2269            br#"<a:custGeom><a:gdLst><a:gd name="bad" fmla="nope 1"/></a:gdLst><a:pathLst><a:path w="1" h="1"/></a:pathLst></a:custGeom>"#,
2270            br#"<a:custGeom><a:pathLst><a:path w="1" h="1">"#,
2271        ];
2272
2273        for xml in malformed {
2274            let result = std::panic::catch_unwind(|| CT_CustomGeometry2D::from_xml(xml));
2275            assert!(result.is_ok(), "malformed XML panicked");
2276            assert!(result.unwrap().is_err(), "malformed XML was accepted");
2277        }
2278    }
2279
2280    #[test]
2281    fn rectangle_preset_evaluates_to_expected_bounds_and_text_rect() {
2282        let preset = CT_PresetGeometry2D::from_xml(br#"<q:prstGeom prst="rect"/>"#).unwrap();
2283        let evaluated = preset.evaluate((120.0, 80.0)).unwrap().unwrap();
2284
2285        assert_eq!(
2286            evaluated.paths[0],
2287            [
2288                EvaluatedPathCommand::MoveTo { x: 0.0, y: 0.0 },
2289                EvaluatedPathCommand::LineTo { x: 120.0, y: 0.0 },
2290                EvaluatedPathCommand::LineTo { x: 120.0, y: 80.0 },
2291                EvaluatedPathCommand::LineTo { x: 0.0, y: 80.0 },
2292                EvaluatedPathCommand::Close,
2293            ]
2294        );
2295        assert_eq!(
2296            evaluated.text_rectangle,
2297            Some(EvaluatedTextRectangle {
2298                left: 0.0,
2299                top: 0.0,
2300                right: 120.0,
2301                bottom: 80.0,
2302            })
2303        );
2304    }
2305
2306    #[test]
2307    fn preset_adjustments_override_generated_defaults() {
2308        let default = CT_PresetGeometry2D::from_xml(
2309            br#"<a:prstGeom prst="trapezoid"><a:avLst/></a:prstGeom>"#,
2310        )
2311        .unwrap()
2312        .evaluate((200.0, 100.0))
2313        .unwrap()
2314        .unwrap();
2315        let adjusted = CT_PresetGeometry2D::from_xml(
2316            br#"<a:prstGeom prst="trapezoid"><a:avLst><a:gd name="adj" fmla="val 50000"/></a:avLst></a:prstGeom>"#,
2317        )
2318        .unwrap()
2319        .evaluate((200.0, 100.0))
2320        .unwrap()
2321        .unwrap();
2322
2323        assert_eq!(
2324            default.paths[0][1],
2325            EvaluatedPathCommand::LineTo { x: 25.0, y: 0.0 }
2326        );
2327        assert_eq!(
2328            adjusted.paths[0][1],
2329            EvaluatedPathCommand::LineTo { x: 50.0, y: 0.0 }
2330        );
2331    }
2332}