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 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 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 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 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 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 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 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 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 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 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 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}