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 to_xml(&self) -> Result<Vec<u8>, GeometryError> {
807 let mut writer = Writer::new(Vec::new());
808 let mut start = BytesStart::new("a:prstGeom");
809 start.push_attribute(("prst", self.preset.as_str()));
810 if self.adjust_values.is_none() && self.raw_children.is_empty() {
811 writer
812 .write_event(Event::Empty(start))
813 .map_err(|error| GeometryError::Xml(error.to_string()))?;
814 return Ok(writer.into_inner());
815 }
816 writer
817 .write_event(Event::Start(start))
818 .map_err(|error| GeometryError::Xml(error.to_string()))?;
819 emit_raw(&mut writer, self.raw_children.at(0))?;
820 if let Some(list) = &self.adjust_values {
821 write_guide_list(&mut writer, "a:avLst", list)?;
822 }
823 emit_raw(&mut writer, self.raw_children.at(1))?;
824 writer
825 .write_event(Event::End(BytesEnd::new("a:prstGeom")))
826 .map_err(|error| GeometryError::Xml(error.to_string()))?;
827 Ok(writer.into_inner())
828 }
829
830 pub fn evaluate(
832 &self,
833 size: (f64, f64),
834 ) -> Result<Option<EvaluatedCustomGeometry>, GeometryError> {
835 let Some(xml) = preset_shape_definition(&self.preset) else {
836 return Ok(None);
837 };
838 let definition = CT_CustomGeometry2D::from_xml(xml)?;
839 let mut override_evaluator = GuideEvaluator::new(size.0, size.1)?;
840 override_evaluator.apply_adjust_values(self.adjust_values(), &BTreeMap::new())?;
841 let overrides = self
842 .adjust_values()
843 .iter()
844 .map(|guide| Ok((guide.name.clone(), override_evaluator.value(&guide.name)?)))
845 .collect::<Result<BTreeMap<_, _>, GeometryError>>()?;
846 let mut evaluated =
847 definition.evaluate_with_default_dimensions(&overrides, Some(size), Some(size))?;
848 for (commands, path) in evaluated.paths.iter_mut().zip(definition.paths()) {
849 let scale_x = path.width.map_or(1.0, |width| size.0 / width);
850 let scale_y = path.height.map_or(1.0, |height| size.1 / height);
851 for command in commands {
852 scale_evaluated_path_command(command, scale_x, scale_y);
853 }
854 }
855 Ok(Some(evaluated))
856 }
857}
858
859fn scale_evaluated_path_command(command: &mut EvaluatedPathCommand, scale_x: f64, scale_y: f64) {
860 match command {
861 EvaluatedPathCommand::MoveTo { x, y } | EvaluatedPathCommand::LineTo { x, y } => {
862 *x *= scale_x;
863 *y *= scale_y;
864 }
865 EvaluatedPathCommand::CubicTo {
866 x1,
867 y1,
868 x2,
869 y2,
870 x,
871 y,
872 } => {
873 *x1 *= scale_x;
874 *y1 *= scale_y;
875 *x2 *= scale_x;
876 *y2 *= scale_y;
877 *x *= scale_x;
878 *y *= scale_y;
879 }
880 EvaluatedPathCommand::Close => {}
881 }
882}
883
884impl CT_GeomRect {
885 fn evaluate(
886 &self,
887 evaluator: &GuideEvaluator,
888 ) -> Result<EvaluatedTextRectangle, GeometryError> {
889 Ok(EvaluatedTextRectangle {
890 left: evaluator.resolve(&self.left)?,
891 top: evaluator.resolve(&self.top)?,
892 right: evaluator.resolve(&self.right)?,
893 bottom: evaluator.resolve(&self.bottom)?,
894 })
895 }
896}
897
898impl CT_Path2DCommand {
899 fn to_evaluator_command(&self) -> PathCommand {
900 match self {
901 Self::MoveTo(point) => PathCommand::MoveTo {
902 x: point.x.clone(),
903 y: point.y.clone(),
904 },
905 Self::LineTo(point) => PathCommand::LineTo {
906 x: point.x.clone(),
907 y: point.y.clone(),
908 },
909 Self::CubicTo {
910 control_1,
911 control_2,
912 end,
913 } => PathCommand::CubicTo {
914 x1: control_1.x.clone(),
915 y1: control_1.y.clone(),
916 x2: control_2.x.clone(),
917 y2: control_2.y.clone(),
918 x: end.x.clone(),
919 y: end.y.clone(),
920 },
921 Self::ArcTo {
922 width_radius,
923 height_radius,
924 start_angle,
925 sweep_angle,
926 } => PathCommand::ArcTo {
927 width_radius: width_radius.clone(),
928 height_radius: height_radius.clone(),
929 start_angle: start_angle.clone(),
930 sweep_angle: sweep_angle.clone(),
931 },
932 Self::Close => PathCommand::Close,
933 }
934 }
935}
936
937fn parse_guide_list(
938 reader: &mut Reader<&[u8]>,
939 _start: &BytesStart<'_>,
940 end_name: &[u8],
941) -> Result<GuideList, GeometryError> {
942 let mut guides = Vec::new();
943 let mut guide_raw_children = Vec::new();
944 let mut raw_children = OrderedRawChildren::default();
945 let mut buffer = Vec::new();
946 loop {
947 match reader
948 .read_event_into(&mut buffer)
949 .map_err(|error| GeometryError::Xml(error.to_string()))?
950 {
951 Event::Empty(element) if matches_local_name(element.name().as_ref(), b"gd") => {
952 guides.push(parse_guide(&element)?);
953 guide_raw_children.push(OrderedRawChildren::default());
954 }
955 Event::Start(element) if matches_local_name(element.name().as_ref(), b"gd") => {
956 let guide = parse_guide(&element)?;
957 let mut children = OrderedRawChildren::default();
958 consume_leaf_children(reader, b"gd", &mut children, 0)?;
959 guides.push(guide);
960 guide_raw_children.push(children);
961 }
962 Event::Start(element) => {
963 raw_children.push(guides.len(), capture_element(reader, &element)?)
964 }
965 Event::Empty(element) => {
966 raw_children.push(guides.len(), capture_empty_element(&element)?)
967 }
968 Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
969 Event::Eof => {
970 return Err(GeometryError::Xml(format!(
971 "missing closing a:{}",
972 String::from_utf8_lossy(end_name)
973 )));
974 }
975 _ => {}
976 }
977 buffer.clear();
978 }
979 Ok(GuideList {
980 guides,
981 guide_raw_children,
982 raw_children,
983 })
984}
985
986fn consume_leaf_children(
987 reader: &mut Reader<&[u8]>,
988 end_name: &[u8],
989 raw_children: &mut OrderedRawChildren,
990 boundary: usize,
991) -> Result<(), GeometryError> {
992 let mut buffer = Vec::new();
993 loop {
994 match reader
995 .read_event_into(&mut buffer)
996 .map_err(|error| GeometryError::Xml(error.to_string()))?
997 {
998 Event::Start(element) => {
999 raw_children.push(boundary, capture_element(reader, &element)?)
1000 }
1001 Event::Empty(element) => raw_children.push(boundary, capture_empty_element(&element)?),
1002 Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
1003 Event::Eof => {
1004 return Err(GeometryError::Xml(format!(
1005 "missing closing a:{}",
1006 String::from_utf8_lossy(end_name)
1007 )));
1008 }
1009 _ => {}
1010 }
1011 buffer.clear();
1012 }
1013 Ok(())
1014}
1015
1016fn parse_guide(element: &BytesStart<'_>) -> Result<Guide, GeometryError> {
1017 Guide::parse(
1018 required_attr(element, b"name")?,
1019 &required_attr(element, b"fmla")?,
1020 )
1021}
1022
1023fn parse_empty_rect(element: &BytesStart<'_>) -> Result<CT_GeomRect, GeometryError> {
1024 Ok(CT_GeomRect {
1025 left: required_operand(element, b"l")?,
1026 top: required_operand(element, b"t")?,
1027 right: required_operand(element, b"r")?,
1028 bottom: required_operand(element, b"b")?,
1029 raw_children: OrderedRawChildren::default(),
1030 })
1031}
1032
1033fn parse_rect(
1034 reader: &mut Reader<&[u8]>,
1035 element: &BytesStart<'_>,
1036) -> Result<CT_GeomRect, GeometryError> {
1037 let mut rectangle = parse_empty_rect(element)?;
1038 consume_leaf_children(reader, b"rect", &mut rectangle.raw_children, 0)?;
1039 Ok(rectangle)
1040}
1041
1042fn parse_path_list(
1043 reader: &mut Reader<&[u8]>,
1044 _start: &BytesStart<'_>,
1045) -> Result<PathList, GeometryError> {
1046 let mut paths = Vec::new();
1047 let mut raw_children = OrderedRawChildren::default();
1048 let mut buffer = Vec::new();
1049 loop {
1050 match reader
1051 .read_event_into(&mut buffer)
1052 .map_err(|error| GeometryError::Xml(error.to_string()))?
1053 {
1054 Event::Start(element) if matches_local_name(element.name().as_ref(), b"path") => {
1055 paths.push(parse_path(reader, &element)?);
1056 }
1057 Event::Empty(element) if matches_local_name(element.name().as_ref(), b"path") => {
1058 paths.push(parse_empty_path(&element)?);
1059 }
1060 Event::Start(element) => {
1061 raw_children.push(paths.len(), capture_element(reader, &element)?)
1062 }
1063 Event::Empty(element) => {
1064 raw_children.push(paths.len(), capture_empty_element(&element)?)
1065 }
1066 Event::End(element) if matches_local_name(element.name().as_ref(), b"pathLst") => {
1067 break;
1068 }
1069 Event::Eof => {
1070 return Err(GeometryError::Xml("missing closing a:pathLst".to_owned()));
1071 }
1072 _ => {}
1073 }
1074 buffer.clear();
1075 }
1076 Ok(PathList {
1077 paths,
1078 raw_children,
1079 })
1080}
1081
1082fn parse_empty_path(element: &BytesStart<'_>) -> Result<CT_Path2D, GeometryError> {
1083 Ok(CT_Path2D {
1084 width: optional_f64(element, b"w")?,
1085 height: optional_f64(element, b"h")?,
1086 fill: get_attr(element, b"fill"),
1087 stroke: optional_bool(element, b"stroke")?,
1088 extrusion_ok: optional_bool(element, b"extrusionOk")?,
1089 commands: Vec::new(),
1090 raw_children: OrderedRawChildren::default(),
1091 })
1092}
1093
1094fn parse_path(
1095 reader: &mut Reader<&[u8]>,
1096 element: &BytesStart<'_>,
1097) -> Result<CT_Path2D, GeometryError> {
1098 let mut path = parse_empty_path(element)?;
1099 let mut buffer = Vec::new();
1100 loop {
1101 match reader
1102 .read_event_into(&mut buffer)
1103 .map_err(|error| GeometryError::Xml(error.to_string()))?
1104 {
1105 Event::Start(element) => {
1106 if let Some(command) = parse_path_command(reader, &element)? {
1107 path.commands.push(command);
1108 } else {
1109 path.raw_children
1110 .push(path.commands.len(), capture_element(reader, &element)?);
1111 }
1112 }
1113 Event::Empty(element) => {
1114 if let Some(command) = parse_empty_path_command(&element)? {
1115 path.commands.push(command);
1116 } else {
1117 path.raw_children
1118 .push(path.commands.len(), capture_empty_element(&element)?);
1119 }
1120 }
1121 Event::End(element) if matches_local_name(element.name().as_ref(), b"path") => break,
1122 Event::Eof => {
1123 return Err(GeometryError::Xml("missing closing a:path".to_owned()));
1124 }
1125 _ => {}
1126 }
1127 buffer.clear();
1128 }
1129 Ok(path)
1130}
1131
1132fn parse_path_command(
1133 reader: &mut Reader<&[u8]>,
1134 element: &BytesStart<'_>,
1135) -> Result<Option<PathCommandRecord>, GeometryError> {
1136 match local_name(element.name().as_ref()) {
1137 b"moveTo" => parse_point_command(reader, b"moveTo", 1, |mut points| {
1138 CT_Path2DCommand::MoveTo(points.remove(0))
1139 })
1140 .map(Some),
1141 b"lnTo" => parse_point_command(reader, b"lnTo", 1, |mut points| {
1142 CT_Path2DCommand::LineTo(points.remove(0))
1143 })
1144 .map(Some),
1145 b"cubicBezTo" => parse_point_command(reader, b"cubicBezTo", 3, |mut points| {
1146 CT_Path2DCommand::CubicTo {
1147 control_1: points.remove(0),
1148 control_2: points.remove(0),
1149 end: points.remove(0),
1150 }
1151 })
1152 .map(Some),
1153 b"arcTo" => {
1154 let command = parse_arc(element)?;
1155 let mut raw_children = OrderedRawChildren::default();
1156 consume_leaf_children(reader, b"arcTo", &mut raw_children, 0)?;
1157 Ok(Some(PathCommandRecord {
1158 command,
1159 raw_children,
1160 }))
1161 }
1162 b"close" => {
1163 let mut raw_children = OrderedRawChildren::default();
1164 consume_leaf_children(reader, b"close", &mut raw_children, 0)?;
1165 Ok(Some(PathCommandRecord {
1166 command: CT_Path2DCommand::Close,
1167 raw_children,
1168 }))
1169 }
1170 _ => Ok(None),
1171 }
1172}
1173
1174fn parse_empty_path_command(
1175 element: &BytesStart<'_>,
1176) -> Result<Option<PathCommandRecord>, GeometryError> {
1177 match local_name(element.name().as_ref()) {
1178 b"arcTo" => Ok(Some(PathCommandRecord {
1179 command: parse_arc(element)?,
1180 raw_children: OrderedRawChildren::default(),
1181 })),
1182 b"close" => Ok(Some(PathCommandRecord {
1183 command: CT_Path2DCommand::Close,
1184 raw_children: OrderedRawChildren::default(),
1185 })),
1186 b"moveTo" | b"lnTo" | b"cubicBezTo" => Err(GeometryError::Xml(format!(
1187 "DrawingML {} requires point children",
1188 element_name(element)
1189 ))),
1190 _ => Ok(None),
1191 }
1192}
1193
1194fn parse_point_command(
1195 reader: &mut Reader<&[u8]>,
1196 end_name: &[u8],
1197 expected_points: usize,
1198 make_command: impl FnOnce(Vec<CT_AdjPoint2D>) -> CT_Path2DCommand,
1199) -> Result<PathCommandRecord, GeometryError> {
1200 let mut points = Vec::new();
1201 let mut raw_children = OrderedRawChildren::default();
1202 let mut buffer = Vec::new();
1203 loop {
1204 match reader
1205 .read_event_into(&mut buffer)
1206 .map_err(|error| GeometryError::Xml(error.to_string()))?
1207 {
1208 Event::Empty(element)
1209 if matches_local_name(element.name().as_ref(), b"pt")
1210 && points.len() < expected_points =>
1211 {
1212 points.push(parse_point(&element)?);
1213 }
1214 Event::Start(element)
1215 if matches_local_name(element.name().as_ref(), b"pt")
1216 && points.len() < expected_points =>
1217 {
1218 let mut point = parse_point(&element)?;
1219 consume_leaf_children(reader, b"pt", &mut point.raw_children, 0)?;
1220 points.push(point);
1221 }
1222 Event::Start(element) => {
1223 raw_children.push(points.len(), capture_element(reader, &element)?)
1224 }
1225 Event::Empty(element) => {
1226 raw_children.push(points.len(), capture_empty_element(&element)?)
1227 }
1228 Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
1229 Event::Eof => {
1230 return Err(GeometryError::Xml(format!(
1231 "missing closing a:{}",
1232 String::from_utf8_lossy(end_name)
1233 )));
1234 }
1235 _ => {}
1236 }
1237 buffer.clear();
1238 }
1239 if points.len() != expected_points {
1240 return Err(GeometryError::Xml(format!(
1241 "DrawingML {} requires {expected_points} point children",
1242 String::from_utf8_lossy(end_name)
1243 )));
1244 }
1245 Ok(PathCommandRecord {
1246 command: make_command(points),
1247 raw_children,
1248 })
1249}
1250
1251fn parse_point(element: &BytesStart<'_>) -> Result<CT_AdjPoint2D, GeometryError> {
1252 Ok(CT_AdjPoint2D {
1253 x: required_operand(element, b"x")?,
1254 y: required_operand(element, b"y")?,
1255 raw_children: OrderedRawChildren::default(),
1256 })
1257}
1258
1259fn parse_arc(element: &BytesStart<'_>) -> Result<CT_Path2DCommand, GeometryError> {
1260 Ok(CT_Path2DCommand::ArcTo {
1261 width_radius: required_operand(element, b"wR")?,
1262 height_radius: required_operand(element, b"hR")?,
1263 start_angle: required_operand(element, b"stAng")?,
1264 sweep_angle: required_operand(element, b"swAng")?,
1265 })
1266}
1267
1268fn write_guide_list<W: Write>(
1269 writer: &mut Writer<W>,
1270 tag: &str,
1271 list: &GuideList,
1272) -> Result<(), GeometryError> {
1273 if list.guides.is_empty() && list.raw_children.is_empty() {
1274 writer
1275 .write_event(Event::Empty(BytesStart::new(tag)))
1276 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1277 return Ok(());
1278 }
1279 writer
1280 .write_event(Event::Start(BytesStart::new(tag)))
1281 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1282 for (index, guide) in list.guides.iter().enumerate() {
1283 emit_raw(writer, list.raw_children.at(index))?;
1284 let formula = guide.formula();
1285 let mut element = BytesStart::new("a:gd");
1286 element.push_attribute(("name", guide.name.as_str()));
1287 element.push_attribute(("fmla", formula.as_str()));
1288 let children = &list.guide_raw_children[index];
1289 write_leaf_with_raw(writer, element, "a:gd", children)?;
1290 }
1291 emit_raw(writer, list.raw_children.at(list.guides.len()))?;
1292 writer
1293 .write_event(Event::End(BytesEnd::new(tag)))
1294 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1295 Ok(())
1296}
1297
1298fn write_rect<W: Write>(
1299 writer: &mut Writer<W>,
1300 rectangle: &CT_GeomRect,
1301) -> Result<(), GeometryError> {
1302 let values = [
1303 operand_text(&rectangle.left),
1304 operand_text(&rectangle.top),
1305 operand_text(&rectangle.right),
1306 operand_text(&rectangle.bottom),
1307 ];
1308 let mut element = BytesStart::new("a:rect");
1309 element.push_attribute(("l", values[0].as_str()));
1310 element.push_attribute(("t", values[1].as_str()));
1311 element.push_attribute(("r", values[2].as_str()));
1312 element.push_attribute(("b", values[3].as_str()));
1313 if rectangle.raw_children.is_empty() {
1314 writer
1315 .write_event(Event::Empty(element))
1316 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1317 } else {
1318 writer
1319 .write_event(Event::Start(element))
1320 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1321 emit_raw(writer, rectangle.raw_children.at(0))?;
1322 writer
1323 .write_event(Event::End(BytesEnd::new("a:rect")))
1324 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1325 }
1326 Ok(())
1327}
1328
1329fn write_path_list<W: Write>(writer: &mut Writer<W>, list: &PathList) -> Result<(), GeometryError> {
1330 writer
1331 .write_event(Event::Start(BytesStart::new("a:pathLst")))
1332 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1333 for (index, path) in list.paths.iter().enumerate() {
1334 emit_raw(writer, list.raw_children.at(index))?;
1335 write_path(writer, path)?;
1336 }
1337 emit_raw(writer, list.raw_children.at(list.paths.len()))?;
1338 writer
1339 .write_event(Event::End(BytesEnd::new("a:pathLst")))
1340 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1341 Ok(())
1342}
1343
1344fn write_path<W: Write>(writer: &mut Writer<W>, path: &CT_Path2D) -> Result<(), GeometryError> {
1345 let width = path.width.map(|value| value.to_string());
1346 let height = path.height.map(|value| value.to_string());
1347 let mut element = BytesStart::new("a:path");
1348 if let Some(value) = width.as_deref() {
1349 element.push_attribute(("w", value));
1350 }
1351 if let Some(value) = height.as_deref() {
1352 element.push_attribute(("h", value));
1353 }
1354 if let Some(value) = path.fill.as_deref() {
1355 element.push_attribute(("fill", value));
1356 }
1357 if let Some(value) = path.stroke {
1358 element.push_attribute(("stroke", if value { "1" } else { "0" }));
1359 }
1360 if let Some(value) = path.extrusion_ok {
1361 element.push_attribute(("extrusionOk", if value { "1" } else { "0" }));
1362 }
1363 if path.commands.is_empty() && path.raw_children.is_empty() {
1364 writer
1365 .write_event(Event::Empty(element))
1366 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1367 return Ok(());
1368 }
1369 writer
1370 .write_event(Event::Start(element))
1371 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1372 for (index, command) in path.commands.iter().enumerate() {
1373 emit_raw(writer, path.raw_children.at(index))?;
1374 write_path_command(writer, command)?;
1375 }
1376 emit_raw(writer, path.raw_children.at(path.commands.len()))?;
1377 writer
1378 .write_event(Event::End(BytesEnd::new("a:path")))
1379 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1380 Ok(())
1381}
1382
1383fn write_path_command<W: Write>(
1384 writer: &mut Writer<W>,
1385 record: &PathCommandRecord,
1386) -> Result<(), GeometryError> {
1387 match &record.command {
1388 CT_Path2DCommand::MoveTo(point) => {
1389 write_point_command(writer, "a:moveTo", std::slice::from_ref(point), record)?
1390 }
1391 CT_Path2DCommand::LineTo(point) => {
1392 write_point_command(writer, "a:lnTo", std::slice::from_ref(point), record)?
1393 }
1394 CT_Path2DCommand::CubicTo {
1395 control_1,
1396 control_2,
1397 end,
1398 } => write_point_command(
1399 writer,
1400 "a:cubicBezTo",
1401 &[control_1.clone(), control_2.clone(), end.clone()],
1402 record,
1403 )?,
1404 CT_Path2DCommand::ArcTo {
1405 width_radius,
1406 height_radius,
1407 start_angle,
1408 sweep_angle,
1409 } => {
1410 let values = [
1411 operand_text(width_radius),
1412 operand_text(height_radius),
1413 operand_text(start_angle),
1414 operand_text(sweep_angle),
1415 ];
1416 let mut element = BytesStart::new("a:arcTo");
1417 element.push_attribute(("wR", values[0].as_str()));
1418 element.push_attribute(("hR", values[1].as_str()));
1419 element.push_attribute(("stAng", values[2].as_str()));
1420 element.push_attribute(("swAng", values[3].as_str()));
1421 write_leaf_with_raw(writer, element, "a:arcTo", &record.raw_children)?;
1422 }
1423 CT_Path2DCommand::Close => {
1424 write_leaf_with_raw(
1425 writer,
1426 BytesStart::new("a:close"),
1427 "a:close",
1428 &record.raw_children,
1429 )?;
1430 }
1431 }
1432 Ok(())
1433}
1434
1435fn write_point_command<W: Write>(
1436 writer: &mut Writer<W>,
1437 tag: &str,
1438 points: &[CT_AdjPoint2D],
1439 record: &PathCommandRecord,
1440) -> Result<(), GeometryError> {
1441 writer
1442 .write_event(Event::Start(BytesStart::new(tag)))
1443 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1444 for (index, point) in points.iter().enumerate() {
1445 emit_raw(writer, record.raw_children.at(index))?;
1446 write_point(writer, point)?;
1447 }
1448 emit_raw(writer, record.raw_children.at(points.len()))?;
1449 writer
1450 .write_event(Event::End(BytesEnd::new(tag)))
1451 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1452 Ok(())
1453}
1454
1455fn write_point<W: Write>(
1456 writer: &mut Writer<W>,
1457 point: &CT_AdjPoint2D,
1458) -> Result<(), GeometryError> {
1459 let x = operand_text(&point.x);
1460 let y = operand_text(&point.y);
1461 let mut element = BytesStart::new("a:pt");
1462 element.push_attribute(("x", x.as_str()));
1463 element.push_attribute(("y", y.as_str()));
1464 write_leaf_with_raw(writer, element, "a:pt", &point.raw_children)?;
1465 Ok(())
1466}
1467
1468fn write_leaf_with_raw<W: Write>(
1469 writer: &mut Writer<W>,
1470 element: BytesStart<'_>,
1471 tag: &str,
1472 raw_children: &OrderedRawChildren,
1473) -> Result<(), GeometryError> {
1474 if raw_children.is_empty() {
1475 writer
1476 .write_event(Event::Empty(element))
1477 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1478 } else {
1479 writer
1480 .write_event(Event::Start(element))
1481 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1482 emit_raw(writer, raw_children.at(0))?;
1483 writer
1484 .write_event(Event::End(BytesEnd::new(tag)))
1485 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1486 }
1487 Ok(())
1488}
1489
1490fn emit_raw<'a, W: Write>(
1491 writer: &mut Writer<W>,
1492 children: impl Iterator<Item = &'a [u8]>,
1493) -> Result<(), GeometryError> {
1494 for child in children {
1495 writer
1496 .get_mut()
1497 .write_all(child)
1498 .map_err(|error| GeometryError::Xml(error.to_string()))?;
1499 }
1500 Ok(())
1501}
1502
1503fn required_operand(
1504 element: &BytesStart<'_>,
1505 attribute: &[u8],
1506) -> Result<GuideOperand, GeometryError> {
1507 GuideOperand::parse(&required_attr(element, attribute)?)
1508}
1509
1510fn required_attr(element: &BytesStart<'_>, attribute: &[u8]) -> Result<String, GeometryError> {
1511 get_attr(element, attribute).ok_or_else(|| GeometryError::MissingAttribute {
1512 element: element_name(element),
1513 attribute: String::from_utf8_lossy(attribute).into_owned(),
1514 })
1515}
1516
1517fn optional_f64(element: &BytesStart<'_>, attribute: &[u8]) -> Result<Option<f64>, GeometryError> {
1518 get_attr(element, attribute)
1519 .map(|value| {
1520 value
1521 .parse::<f64>()
1522 .ok()
1523 .filter(|value| value.is_finite() && *value >= 0.0)
1524 .ok_or_else(|| invalid_attribute(element, attribute, value))
1525 })
1526 .transpose()
1527}
1528
1529fn optional_bool(
1530 element: &BytesStart<'_>,
1531 attribute: &[u8],
1532) -> Result<Option<bool>, GeometryError> {
1533 get_attr(element, attribute)
1534 .map(|value| match value.as_str() {
1535 "1" | "true" => Ok(true),
1536 "0" | "false" => Ok(false),
1537 _ => Err(invalid_attribute(element, attribute, value)),
1538 })
1539 .transpose()
1540}
1541
1542fn invalid_attribute(element: &BytesStart<'_>, attribute: &[u8], value: String) -> GeometryError {
1543 GeometryError::InvalidAttribute {
1544 element: element_name(element),
1545 attribute: String::from_utf8_lossy(attribute).into_owned(),
1546 value,
1547 }
1548}
1549
1550fn element_name(element: &BytesStart<'_>) -> String {
1551 String::from_utf8_lossy(local_name(element.name().as_ref())).into_owned()
1552}
1553
1554fn operand_text(operand: &GuideOperand) -> String {
1555 match operand {
1556 GuideOperand::Literal(value) => value.to_string(),
1557 GuideOperand::Guide(name) => name.clone(),
1558 }
1559}
1560
1561#[derive(Clone, Debug)]
1562pub struct GuideEvaluator {
1563 values: BTreeMap<String, f64>,
1564}
1565
1566impl GuideEvaluator {
1567 pub fn new(width: f64, height: f64) -> Result<Self, GeometryError> {
1568 ensure_finite(width, "shape width")?;
1569 ensure_finite(height, "shape height")?;
1570
1571 let mut evaluator = Self {
1572 values: BTreeMap::new(),
1573 };
1574 evaluator.seed("w", width);
1575 evaluator.seed("h", height);
1576 evaluator.seed("l", 0.0);
1577 evaluator.seed("t", 0.0);
1578 evaluator.seed("r", width);
1579 evaluator.seed("b", height);
1580 evaluator.seed("hc", width / 2.0);
1581 evaluator.seed("vc", height / 2.0);
1582 evaluator.seed("ss", width.min(height));
1583 evaluator.seed("ls", width.max(height));
1584
1585 for divisor in [2_u32, 3, 4, 5, 6, 8, 10, 12, 32] {
1586 evaluator.seed(&format!("wd{divisor}"), width / f64::from(divisor));
1587 }
1588 for divisor in [2_u32, 3, 4, 5, 6, 8, 10] {
1589 evaluator.seed(&format!("hd{divisor}"), height / f64::from(divisor));
1590 }
1591 for divisor in [2_u32, 4, 6, 8, 16, 32] {
1592 evaluator.seed(
1593 &format!("ssd{divisor}"),
1594 width.min(height) / f64::from(divisor),
1595 );
1596 }
1597 evaluator.seed("cd2", 180.0 * ANGLE_UNITS_PER_DEGREE);
1598 evaluator.seed("cd4", 90.0 * ANGLE_UNITS_PER_DEGREE);
1599 evaluator.seed("cd8", 45.0 * ANGLE_UNITS_PER_DEGREE);
1600 evaluator.seed("3cd4", 270.0 * ANGLE_UNITS_PER_DEGREE);
1601 evaluator.seed("3cd8", 135.0 * ANGLE_UNITS_PER_DEGREE);
1602 evaluator.seed("5cd8", 225.0 * ANGLE_UNITS_PER_DEGREE);
1603 evaluator.seed("7cd8", 315.0 * ANGLE_UNITS_PER_DEGREE);
1604 Ok(evaluator)
1605 }
1606
1607 pub fn value(&self, name: &str) -> Result<f64, GeometryError> {
1608 self.values
1609 .get(name)
1610 .copied()
1611 .ok_or_else(|| GeometryError::UnknownGuide(name.to_owned()))
1612 }
1613
1614 pub fn apply_adjust_values(
1615 &mut self,
1616 adjustments: &[Guide],
1617 overrides: &BTreeMap<String, f64>,
1618 ) -> Result<(), GeometryError> {
1619 let declared = adjustments
1620 .iter()
1621 .map(|guide| guide.name.as_str())
1622 .collect::<BTreeSet<_>>();
1623 if let Some(name) = overrides
1624 .keys()
1625 .find(|name| !declared.contains(name.as_str()))
1626 {
1627 return Err(GeometryError::UnknownAdjustOverride(name.clone()));
1628 }
1629
1630 for adjustment in adjustments {
1631 let value = match overrides.get(&adjustment.name) {
1632 Some(value) => {
1633 ensure_finite(*value, &adjustment.name)?;
1634 *value
1635 }
1636 None => self.evaluate_operation(adjustment.op, &adjustment.args)?,
1637 };
1638 self.insert_named(&adjustment.name, value)?;
1639 }
1640 Ok(())
1641 }
1642
1643 pub fn evaluate_guides(&mut self, guides: &[Guide]) -> Result<(), GeometryError> {
1644 let mut evaluated = BTreeSet::new();
1645 for guide in guides {
1646 let value = self.evaluate_operation(guide.op, &guide.args)?;
1647 if evaluated.insert(guide.name.clone()) {
1648 self.insert_named(&guide.name, value)?;
1649 } else {
1650 ensure_finite(value, &guide.name)?;
1651 self.values.insert(guide.name.clone(), value);
1652 }
1653 }
1654 Ok(())
1655 }
1656
1657 pub fn evaluate_path(
1658 &self,
1659 commands: &[PathCommand],
1660 ) -> Result<Vec<EvaluatedPathCommand>, GeometryError> {
1661 let mut output = Vec::with_capacity(commands.len());
1662 let mut current = None;
1663 let mut subpath_start = None;
1664
1665 for command in commands {
1666 match command {
1667 PathCommand::MoveTo { x, y } => {
1668 let point = (self.resolve(x)?, self.resolve(y)?);
1669 output.push(EvaluatedPathCommand::MoveTo {
1670 x: point.0,
1671 y: point.1,
1672 });
1673 current = Some(point);
1674 subpath_start = Some(point);
1675 }
1676 PathCommand::LineTo { x, y } => {
1677 current.ok_or(GeometryError::PathHasNoCurrentPoint)?;
1678 let point = (self.resolve(x)?, self.resolve(y)?);
1679 output.push(EvaluatedPathCommand::LineTo {
1680 x: point.0,
1681 y: point.1,
1682 });
1683 current = Some(point);
1684 }
1685 PathCommand::CubicTo {
1686 x1,
1687 y1,
1688 x2,
1689 y2,
1690 x,
1691 y,
1692 } => {
1693 current.ok_or(GeometryError::PathHasNoCurrentPoint)?;
1694 let command = EvaluatedPathCommand::CubicTo {
1695 x1: self.resolve(x1)?,
1696 y1: self.resolve(y1)?,
1697 x2: self.resolve(x2)?,
1698 y2: self.resolve(y2)?,
1699 x: self.resolve(x)?,
1700 y: self.resolve(y)?,
1701 };
1702 if let EvaluatedPathCommand::CubicTo { x, y, .. } = command {
1703 current = Some((x, y));
1704 }
1705 output.push(command);
1706 }
1707 PathCommand::ArcTo {
1708 width_radius,
1709 height_radius,
1710 start_angle,
1711 sweep_angle,
1712 } => {
1713 let start = current.ok_or(GeometryError::PathHasNoCurrentPoint)?;
1714 let cubics = flatten_arc(
1715 start,
1716 self.resolve(width_radius)?,
1717 self.resolve(height_radius)?,
1718 self.resolve(start_angle)?,
1719 self.resolve(sweep_angle)?,
1720 )?;
1721 if let Some(EvaluatedPathCommand::CubicTo { x, y, .. }) = cubics.last() {
1722 current = Some((*x, *y));
1723 }
1724 output.extend(cubics);
1725 }
1726 PathCommand::Close => {
1727 current.ok_or(GeometryError::PathHasNoCurrentPoint)?;
1728 output.push(EvaluatedPathCommand::Close);
1729 current = subpath_start;
1730 }
1731 }
1732 }
1733 Ok(output)
1734 }
1735
1736 fn seed(&mut self, name: &str, value: f64) {
1737 self.values.insert(name.to_owned(), value);
1738 }
1739
1740 fn insert_named(&mut self, name: &str, value: f64) -> Result<(), GeometryError> {
1741 ensure_finite(value, name)?;
1742 if self.values.contains_key(name) {
1743 return Err(GeometryError::DuplicateGuide(name.to_owned()));
1744 }
1745 self.values.insert(name.to_owned(), value);
1746 Ok(())
1747 }
1748
1749 fn resolve(&self, operand: &GuideOperand) -> Result<f64, GeometryError> {
1750 match operand {
1751 GuideOperand::Literal(value) => {
1752 ensure_finite(*value, "literal")?;
1753 Ok(*value)
1754 }
1755 GuideOperand::Guide(name) => self.value(name),
1756 }
1757 }
1758
1759 fn evaluate_operation(
1760 &self,
1761 op: GuideOp,
1762 operands: &[GuideOperand],
1763 ) -> Result<f64, GeometryError> {
1764 let expected = op.argument_count();
1765 if operands.len() != expected {
1766 return Err(GeometryError::WrongArgumentCount {
1767 operation: format!("{op:?}"),
1768 expected,
1769 actual: operands.len(),
1770 });
1771 }
1772 let args = operands
1773 .iter()
1774 .map(|operand| self.resolve(operand))
1775 .collect::<Result<Vec<_>, _>>()?;
1776
1777 let value = match op {
1778 GuideOp::MulDiv => checked_div(args[0] * args[1], args[2])?,
1779 GuideOp::AddSub => args[0] + args[1] - args[2],
1780 GuideOp::AddDiv => checked_div(args[0] + args[1], args[2])?,
1781 GuideOp::IfElse => {
1782 if args[0] > 0.0 {
1783 args[1]
1784 } else {
1785 args[2]
1786 }
1787 }
1788 GuideOp::Abs => args[0].abs(),
1789 GuideOp::At2 => radians_to_angle(args[1].atan2(args[0])),
1790 GuideOp::Cat2 => args[0] * args[2].atan2(args[1]).cos(),
1791 GuideOp::Cos => args[0] * angle_to_radians(args[1]).cos(),
1792 GuideOp::Max => args[0].max(args[1]),
1793 GuideOp::Min => args[0].min(args[1]),
1794 GuideOp::Mod => args[0].hypot(args[1]).hypot(args[2]),
1795 GuideOp::Pin => {
1796 if args[1] < args[0] {
1797 args[0]
1798 } else if args[1] > args[2] {
1799 args[2]
1800 } else {
1801 args[1]
1802 }
1803 }
1804 GuideOp::Sat2 => args[0] * args[2].atan2(args[1]).sin(),
1805 GuideOp::Sin => args[0] * angle_to_radians(args[1]).sin(),
1806 GuideOp::Sqrt => args[0].abs().sqrt(),
1807 GuideOp::Tan => args[0] * angle_to_radians(args[1]).tan(),
1808 GuideOp::Val => args[0],
1809 };
1810 ensure_finite(value, "guide result")?;
1811 Ok(value)
1812 }
1813}
1814
1815fn checked_div(numerator: f64, denominator: f64) -> Result<f64, GeometryError> {
1816 if denominator == 0.0 {
1817 return Err(GeometryError::DivisionByZero);
1818 }
1819 Ok(numerator / denominator)
1820}
1821
1822fn angle_to_radians(angle: f64) -> f64 {
1823 angle / ANGLE_UNITS_PER_DEGREE * PI / 180.0
1824}
1825
1826fn radians_to_angle(radians: f64) -> f64 {
1827 radians * 180.0 / PI * ANGLE_UNITS_PER_DEGREE
1828}
1829
1830fn ensure_finite(value: f64, context: &str) -> Result<(), GeometryError> {
1831 if value.is_finite() {
1832 Ok(())
1833 } else {
1834 Err(GeometryError::NonFiniteValue(context.to_owned()))
1835 }
1836}
1837
1838fn flatten_arc(
1839 current: (f64, f64),
1840 width_radius: f64,
1841 height_radius: f64,
1842 start_angle: f64,
1843 sweep_angle: f64,
1844) -> Result<Vec<EvaluatedPathCommand>, GeometryError> {
1845 for (value, context) in [
1846 (current.0, "arc start x"),
1847 (current.1, "arc start y"),
1848 (width_radius, "arc width radius"),
1849 (height_radius, "arc height radius"),
1850 (start_angle, "arc start angle"),
1851 (sweep_angle, "arc sweep angle"),
1852 ] {
1853 ensure_finite(value, context)?;
1854 }
1855 if width_radius <= 0.0 || height_radius <= 0.0 {
1856 return Err(GeometryError::InvalidArcRadius);
1857 }
1858 if sweep_angle == 0.0 {
1859 return Ok(Vec::new());
1860 }
1861
1862 let segment_count = (sweep_angle.abs() / QUARTER_CIRCLE).ceil();
1863 if segment_count > MAX_ARC_SEGMENTS as f64 {
1864 return Err(GeometryError::ArcSweepTooLarge);
1865 }
1866 let segment_count = segment_count as usize;
1867 let segment_sweep = sweep_angle / segment_count as f64;
1868 let start_radians = angle_to_radians(start_angle);
1869 let center = (
1870 current.0 - width_radius * start_radians.cos(),
1871 current.1 - height_radius * start_radians.sin(),
1872 );
1873 let mut cubics = Vec::with_capacity(segment_count);
1874
1875 for index in 0..segment_count {
1876 let angle_1 = angle_to_radians(start_angle + segment_sweep * index as f64);
1877 let angle_2 = angle_to_radians(start_angle + segment_sweep * (index + 1) as f64);
1878 let alpha = 4.0 / 3.0 * ((angle_2 - angle_1) / 4.0).tan();
1879 let point_1 = (
1880 center.0 + width_radius * angle_1.cos(),
1881 center.1 + height_radius * angle_1.sin(),
1882 );
1883 let point_2 = (
1884 center.0 + width_radius * angle_2.cos(),
1885 center.1 + height_radius * angle_2.sin(),
1886 );
1887 let tangent_1 = (-width_radius * angle_1.sin(), height_radius * angle_1.cos());
1888 let tangent_2 = (-width_radius * angle_2.sin(), height_radius * angle_2.cos());
1889 let command = EvaluatedPathCommand::CubicTo {
1890 x1: point_1.0 + alpha * tangent_1.0,
1891 y1: point_1.1 + alpha * tangent_1.1,
1892 x2: point_2.0 - alpha * tangent_2.0,
1893 y2: point_2.1 - alpha * tangent_2.1,
1894 x: point_2.0,
1895 y: point_2.1,
1896 };
1897 if let EvaluatedPathCommand::CubicTo {
1898 x1,
1899 y1,
1900 x2,
1901 y2,
1902 x,
1903 y,
1904 } = command
1905 {
1906 for value in [x1, y1, x2, y2, x, y] {
1907 ensure_finite(value, "arc cubic coordinate")?;
1908 }
1909 }
1910 cubics.push(command);
1911 }
1912 Ok(cubics)
1913}
1914
1915#[cfg(test)]
1916mod tests {
1917 use super::*;
1918
1919 #[test]
1920 fn preset_constructor_accepts_generated_names_and_rejects_unknown_names() {
1921 assert_eq!(
1922 CT_PresetGeometry2D::new("triangle").unwrap().preset,
1923 "triangle"
1924 );
1925 assert_eq!(
1926 CT_PresetGeometry2D::new("not-a-preset").unwrap_err(),
1927 GeometryError::UnknownPreset("not-a-preset".to_owned())
1928 );
1929 }
1930
1931 #[test]
1932 fn preset_adjustment_setter_inserts_and_replaces_named_values() {
1933 let mut geometry = CT_PresetGeometry2D::from_xml(
1934 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>"#,
1935 )
1936 .unwrap();
1937
1938 geometry.set_adjust_value("adj", 25_000.0).unwrap();
1939 geometry.set_adjust_value("adj2", 7_500.5).unwrap();
1940 let xml = String::from_utf8(geometry.to_xml().unwrap()).unwrap();
1941
1942 assert_eq!(geometry.adjust_values().len(), 2);
1943 assert_eq!(geometry.adjust_values()[0].name, "adj");
1944 assert_eq!(geometry.adjust_values()[0].args, vec![literal(25_000.0)]);
1945 assert_eq!(geometry.adjust_values()[1].name, "adj2");
1946 assert_eq!(geometry.adjust_values()[1].args, vec![literal(7_500.5)]);
1947 assert_eq!(xml.matches("name=\"adj\"").count(), 1);
1948 assert_eq!(xml.matches("name=\"adj2\"").count(), 1);
1949 assert!(xml.contains("<ext:raw xmlns:ext=\"urn:ext\"/>"));
1950 assert!(xml.contains("<a:gd name=\"adj2\" fmla=\"val 7500.5\"/><ext:tail"));
1951 assert!(geometry.set_adjust_value("bad", f64::INFINITY).is_err());
1952 }
1953
1954 fn literal(value: f64) -> GuideOperand {
1955 GuideOperand::Literal(value)
1956 }
1957
1958 fn guide(name: &str) -> GuideOperand {
1959 GuideOperand::Guide(name.to_owned())
1960 }
1961
1962 fn assert_close(actual: f64, expected: f64) {
1963 assert!((actual - expected).abs() < 1.0e-9, "{actual} != {expected}");
1964 }
1965
1966 #[test]
1967 fn hand_written_custom_geometry_guides_produce_expected_path_coordinates() {
1968 let adjustments = [Guide::parse("adj1", "val 25000").unwrap()];
1969 let guides = [
1970 Guide::parse("x1", "*/ w adj1 100000").unwrap(),
1971 Guide::parse("y1", "+/ hd2 0 2").unwrap(),
1972 Guide::parse("x2", "+- r 0 x1").unwrap(),
1973 Guide::parse("y2", "?: adj1 75 25").unwrap(),
1974 ];
1975 let overrides = BTreeMap::from([("adj1".to_owned(), 20_000.0)]);
1976 let mut evaluator = GuideEvaluator::new(100.0, 100.0).unwrap();
1977 assert_eq!(evaluator.value("wd32").unwrap(), 3.125);
1978 assert_eq!(evaluator.value("wd12").unwrap(), 100.0 / 12.0);
1979 assert_eq!(evaluator.value("hd8").unwrap(), 12.5);
1980 assert_eq!(evaluator.value("hd10").unwrap(), 10.0);
1981 assert_eq!(evaluator.value("ssd32").unwrap(), 3.125);
1982 assert_eq!(evaluator.value("3cd8").unwrap(), 8_100_000.0);
1983 evaluator
1984 .apply_adjust_values(&adjustments, &overrides)
1985 .unwrap();
1986 evaluator.evaluate_guides(&guides).unwrap();
1987
1988 let path = evaluator
1989 .evaluate_path(&[
1990 PathCommand::MoveTo {
1991 x: guide("x1"),
1992 y: guide("y1"),
1993 },
1994 PathCommand::LineTo {
1995 x: guide("x2"),
1996 y: guide("y2"),
1997 },
1998 ])
1999 .unwrap();
2000 assert_eq!(
2001 path,
2002 [
2003 EvaluatedPathCommand::MoveTo { x: 20.0, y: 25.0 },
2004 EvaluatedPathCommand::LineTo { x: 80.0, y: 75.0 },
2005 ]
2006 );
2007 }
2008
2009 #[test]
2010 fn ordinary_guides_replace_in_order_without_relaxing_adjustment_validation() {
2011 let mut evaluator = GuideEvaluator::new(100.0, 100.0).unwrap();
2012 evaluator
2013 .evaluate_guides(&[
2014 Guide::parse("connsiteX0", "val 10").unwrap(),
2015 Guide::parse("connsiteX0", "val 20").unwrap(),
2016 ])
2017 .unwrap();
2018 assert_eq!(evaluator.value("connsiteX0").unwrap(), 20.0);
2019
2020 let duplicate_adjustments = [
2021 Guide::parse("adj", "val 10").unwrap(),
2022 Guide::parse("adj", "val 20").unwrap(),
2023 ];
2024 let error = GuideEvaluator::new(100.0, 100.0)
2025 .unwrap()
2026 .apply_adjust_values(&duplicate_adjustments, &BTreeMap::new())
2027 .unwrap_err();
2028 assert_eq!(error, GeometryError::DuplicateGuide("adj".to_owned()));
2029
2030 let error = GuideEvaluator::new(100.0, 100.0)
2031 .unwrap()
2032 .apply_adjust_values(
2033 &[Guide::parse("adj", "val 10").unwrap()],
2034 &BTreeMap::from([("unknown".to_owned(), 20.0)]),
2035 )
2036 .unwrap_err();
2037 assert_eq!(
2038 error,
2039 GeometryError::UnknownAdjustOverride("unknown".to_owned())
2040 );
2041 }
2042
2043 #[test]
2044 fn all_seventeen_formula_tokens_parse_and_evaluate_with_drawingml_argument_order() {
2045 let cases = [
2046 ("*/ 6 7 2", GuideOp::MulDiv, 21.0),
2047 ("+- 10 4 3", GuideOp::AddSub, 11.0),
2048 ("+/ 10 4 2", GuideOp::AddDiv, 7.0),
2049 ("?: 1 5 6", GuideOp::IfElse, 5.0),
2050 ("abs -7", GuideOp::Abs, 7.0),
2051 ("at2 1 1", GuideOp::At2, 2_700_000.0),
2052 ("cat2 10 3 4", GuideOp::Cat2, 6.0),
2053 ("cos 10 3600000", GuideOp::Cos, 5.0),
2054 ("max 3 7", GuideOp::Max, 7.0),
2055 ("min 3 7", GuideOp::Min, 3.0),
2056 ("mod 3 4 12", GuideOp::Mod, 13.0),
2057 ("pin 0 15 10", GuideOp::Pin, 10.0),
2058 ("sat2 10 3 4", GuideOp::Sat2, 8.0),
2059 ("sin 10 1800000", GuideOp::Sin, 5.0),
2060 ("sqrt -9", GuideOp::Sqrt, 3.0),
2061 ("tan 10 2700000", GuideOp::Tan, 10.0),
2062 ("val 42", GuideOp::Val, 42.0),
2063 ];
2064 let guides = cases
2065 .iter()
2066 .enumerate()
2067 .map(|(index, (formula, expected_op, _))| {
2068 let guide = Guide::parse(format!("g{index}"), formula).unwrap();
2069 assert_eq!(guide.op, *expected_op);
2070 guide
2071 })
2072 .collect::<Vec<_>>();
2073 let mut evaluator = GuideEvaluator::new(100.0, 80.0).unwrap();
2074 evaluator.evaluate_guides(&guides).unwrap();
2075
2076 for (index, (_, _, expected)) in cases.iter().enumerate() {
2077 assert_close(evaluator.value(&format!("g{index}")).unwrap(), *expected);
2078 }
2079 }
2080
2081 #[test]
2082 fn arc_to_is_flattened_to_finite_cubics_with_matching_endpoints() {
2083 let evaluator = GuideEvaluator::new(100.0, 100.0).unwrap();
2084 let path = evaluator
2085 .evaluate_path(&[
2086 PathCommand::MoveTo {
2087 x: literal(10.0),
2088 y: literal(0.0),
2089 },
2090 PathCommand::ArcTo {
2091 width_radius: literal(10.0),
2092 height_radius: literal(5.0),
2093 start_angle: literal(0.0),
2094 sweep_angle: literal(27_000_000.0),
2095 },
2096 ])
2097 .unwrap();
2098 assert_eq!(path.len(), 6);
2099 assert!(path.iter().skip(1).all(|command| matches!(
2100 command,
2101 EvaluatedPathCommand::CubicTo {
2102 x1,
2103 y1,
2104 x2,
2105 y2,
2106 x,
2107 y
2108 } if [x1, y1, x2, y2, x, y].iter().all(|value| value.is_finite())
2109 )));
2110 let EvaluatedPathCommand::CubicTo { x, y, .. } = path[5] else {
2111 panic!("arc did not end in a cubic command")
2112 };
2113 assert_close(x, 0.0);
2114 assert_close(y, 5.0);
2115 let EvaluatedPathCommand::CubicTo { x, y, .. } = path[1] else {
2116 panic!("arc did not start with a cubic command")
2117 };
2118 assert_close(x, 0.0);
2119 assert_close(y, 5.0);
2120 }
2121
2122 #[test]
2123 fn office_mod_and_negative_sqrt_semantics_produce_finite_values() {
2124 let guides = [
2125 Guide::parse("norm", "mod 3 4 12").unwrap(),
2126 Guide::parse("root", "sqrt -9").unwrap(),
2127 ];
2128 let mut evaluator = GuideEvaluator::new(10.0, 10.0).unwrap();
2129 evaluator.evaluate_guides(&guides).unwrap();
2130 assert_eq!(evaluator.value("norm").unwrap(), 13.0);
2131 assert_eq!(evaluator.value("root").unwrap(), 3.0);
2132 }
2133
2134 #[test]
2135 fn division_by_zero_returns_an_error_instead_of_non_finite_coordinates() {
2136 let mut evaluator = GuideEvaluator::new(10.0, 10.0).unwrap();
2137 let error = evaluator
2138 .evaluate_guides(&[Guide::parse("bad", "*/ 1 2 0").unwrap()])
2139 .unwrap_err();
2140 assert_eq!(error, GeometryError::DivisionByZero);
2141 assert_eq!(error.to_string(), "division by zero");
2142 }
2143
2144 #[test]
2145 fn corpus_custom_geometry_round_trips_and_evaluates_to_a_closed_path() {
2146 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>"#;
2147 let geometry = CT_CustomGeometry2D::from_xml(xml).unwrap();
2148 let evaluated = geometry.evaluate(&BTreeMap::new()).unwrap();
2149
2150 assert_eq!(geometry.adjust_values().len(), 1);
2151 assert_eq!(geometry.guides().len(), 2);
2152 assert_eq!(geometry.paths().len(), 1);
2153 assert_eq!(evaluated.paths.len(), 1);
2154 assert_eq!(
2155 evaluated.paths[0].last(),
2156 Some(&EvaluatedPathCommand::Close)
2157 );
2158 assert_eq!(
2159 evaluated.text_rectangle,
2160 Some(EvaluatedTextRectangle {
2161 left: 25.0,
2162 top: 0.0,
2163 right: 75.0,
2164 bottom: 100.0,
2165 })
2166 );
2167
2168 let written = geometry.to_xml().unwrap();
2169 let reparsed = CT_CustomGeometry2D::from_xml(&written).unwrap();
2170 assert_eq!(reparsed, geometry);
2171 }
2172
2173 #[test]
2174 fn custom_geometry_reads_any_prefix_and_writes_fixed_a_prefix_in_schema_order() {
2175 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>"#;
2176 let geometry = CT_CustomGeometry2D::from_xml(xml).unwrap();
2177 let written = String::from_utf8(geometry.to_xml().unwrap()).unwrap();
2178
2179 assert!(written.starts_with("<a:custGeom>"));
2180 assert!(written.contains("<a:avLst/>"));
2181 assert!(written.contains("<a:gdLst/>"));
2182 assert!(written.contains("<a:rect l=\"l\" t=\"t\" r=\"r\" b=\"b\"/>"));
2183 assert!(written.contains("<a:arcTo wR=\"10\" hR=\"5\" stAng=\"0\" swAng=\"5400000\"/>"));
2184 assert!(written.contains("<a:close/>"));
2185 let av = written.find("<a:avLst").unwrap();
2186 let gd = written.find("<a:gdLst").unwrap();
2187 let ah = written.find("<q:ahLst/>").unwrap();
2188 let cxn = written.find("<q:cxnLst/>").unwrap();
2189 let rect = written.find("<a:rect").unwrap();
2190 let paths = written.find("<a:pathLst").unwrap();
2191 assert!(av < gd && gd < ah && ah < cxn && cxn < rect && rect < paths);
2192 }
2193
2194 #[test]
2195 fn empty_custom_geometry_path_list_from_theme_defaults_round_trips() {
2196 let geometry = CT_CustomGeometry2D::from_xml(
2197 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>"#,
2198 )
2199 .unwrap();
2200 assert!(geometry.paths().is_empty());
2201 let written = geometry.to_xml().unwrap();
2202 assert_eq!(CT_CustomGeometry2D::from_xml(&written).unwrap(), geometry);
2203 }
2204
2205 #[test]
2206 fn unknown_custom_geometry_children_round_trip_byte_for_byte_in_place() {
2207 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>"#;
2208 let written = String::from_utf8(
2209 CT_CustomGeometry2D::from_xml(xml)
2210 .unwrap()
2211 .to_xml()
2212 .unwrap(),
2213 )
2214 .unwrap();
2215
2216 for raw in [
2217 "<u:before/>",
2218 "<u:avBefore/>",
2219 "<u:insideGuide/>",
2220 "<u:avAfter/>",
2221 "<u:middle u:id=\"7\"><u:child/></u:middle>",
2222 "<u:pathBefore/>",
2223 "<u:insidePoint/>",
2224 "<u:insideMove/>",
2225 "<u:between/>",
2226 "<u:pathAfter/>",
2227 "<u:after/>",
2228 ] {
2229 assert!(written.contains(raw), "missing raw subtree {raw}");
2230 }
2231 assert!(written.find("<u:before/>").unwrap() < written.find("<a:avLst").unwrap());
2232 assert!(written.find("<u:avBefore/>").unwrap() < written.find("<a:gd ").unwrap());
2233 assert!(written.find("<a:gd ").unwrap() < written.find("<u:avAfter/>").unwrap());
2234 assert!(written.find("<u:insideGuide/>").unwrap() < written.find("</a:gd>").unwrap());
2235 assert!(written.find("<u:insidePoint/>").unwrap() < written.find("</a:pt>").unwrap());
2236 assert!(written.find("<u:insideMove/>").unwrap() < written.find("</a:moveTo>").unwrap());
2237 assert!(written.find("</a:moveTo>").unwrap() < written.find("<u:between/>").unwrap());
2238 }
2239
2240 #[test]
2241 fn malformed_custom_geometry_returns_an_error_without_panicking() {
2242 let malformed: [&[u8]; 3] = [
2243 br#"<a:custGeom><a:avLst><a:gd fmla="val 1"/></a:avLst><a:pathLst><a:path w="1" h="1"/></a:pathLst></a:custGeom>"#,
2244 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>"#,
2245 br#"<a:custGeom><a:pathLst><a:path w="1" h="1">"#,
2246 ];
2247
2248 for xml in malformed {
2249 let result = std::panic::catch_unwind(|| CT_CustomGeometry2D::from_xml(xml));
2250 assert!(result.is_ok(), "malformed XML panicked");
2251 assert!(result.unwrap().is_err(), "malformed XML was accepted");
2252 }
2253 }
2254
2255 #[test]
2256 fn rectangle_preset_evaluates_to_expected_bounds_and_text_rect() {
2257 let preset = CT_PresetGeometry2D::from_xml(br#"<q:prstGeom prst="rect"/>"#).unwrap();
2258 let evaluated = preset.evaluate((120.0, 80.0)).unwrap().unwrap();
2259
2260 assert_eq!(
2261 evaluated.paths[0],
2262 [
2263 EvaluatedPathCommand::MoveTo { x: 0.0, y: 0.0 },
2264 EvaluatedPathCommand::LineTo { x: 120.0, y: 0.0 },
2265 EvaluatedPathCommand::LineTo { x: 120.0, y: 80.0 },
2266 EvaluatedPathCommand::LineTo { x: 0.0, y: 80.0 },
2267 EvaluatedPathCommand::Close,
2268 ]
2269 );
2270 assert_eq!(
2271 evaluated.text_rectangle,
2272 Some(EvaluatedTextRectangle {
2273 left: 0.0,
2274 top: 0.0,
2275 right: 120.0,
2276 bottom: 80.0,
2277 })
2278 );
2279 }
2280
2281 #[test]
2282 fn preset_adjustments_override_generated_defaults() {
2283 let default = CT_PresetGeometry2D::from_xml(
2284 br#"<a:prstGeom prst="trapezoid"><a:avLst/></a:prstGeom>"#,
2285 )
2286 .unwrap()
2287 .evaluate((200.0, 100.0))
2288 .unwrap()
2289 .unwrap();
2290 let adjusted = CT_PresetGeometry2D::from_xml(
2291 br#"<a:prstGeom prst="trapezoid"><a:avLst><a:gd name="adj" fmla="val 50000"/></a:avLst></a:prstGeom>"#,
2292 )
2293 .unwrap()
2294 .evaluate((200.0, 100.0))
2295 .unwrap()
2296 .unwrap();
2297
2298 assert_eq!(
2299 default.paths[0][1],
2300 EvaluatedPathCommand::LineTo { x: 25.0, y: 0.0 }
2301 );
2302 assert_eq!(
2303 adjusted.paths[0][1],
2304 EvaluatedPathCommand::LineTo { x: 50.0, y: 0.0 }
2305 );
2306 }
2307}