1use std::fmt;
2use std::io::Write;
3
4use oxml_core::OxmlError;
5use oxml_core::raw_xml::{capture_element, capture_empty_element};
6use oxml_core::units::{Angle, Emu};
7use oxml_core::xml::{get_attr, local_name, matches_local_name};
8use quick_xml::events::{BytesEnd, BytesStart, Event};
9use quick_xml::{Reader, Writer};
10
11use crate::order::OrderedRawChildren;
12
13#[derive(Debug)]
15pub enum TransformError {
16 Xml(OxmlError),
17 UnexpectedElement(String),
18 MissingAttribute {
19 element: String,
20 attribute: String,
21 },
22 InvalidAttribute {
23 element: String,
24 attribute: String,
25 value: String,
26 },
27 ZeroChildExtent {
28 axis: &'static str,
29 },
30 NonFiniteMatrix,
31}
32
33impl fmt::Display for TransformError {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::Xml(error) => error.fmt(formatter),
37 Self::UnexpectedElement(element) => {
38 write!(
39 formatter,
40 "unexpected DrawingML transform element: {element}"
41 )
42 }
43 Self::MissingAttribute { element, attribute } => {
44 write!(formatter, "DrawingML {element} requires @{attribute}")
45 }
46 Self::InvalidAttribute {
47 element,
48 attribute,
49 value,
50 } => write!(
51 formatter,
52 "DrawingML {element} has invalid @{attribute}: {value}"
53 ),
54 Self::ZeroChildExtent { axis } => {
55 write!(
56 formatter,
57 "DrawingML child extent is zero on the {axis} axis"
58 )
59 }
60 Self::NonFiniteMatrix => write!(formatter, "DrawingML transform matrix is not finite"),
61 }
62 }
63}
64
65impl std::error::Error for TransformError {
66 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
67 match self {
68 Self::Xml(error) => Some(error),
69 _ => None,
70 }
71 }
72}
73
74impl From<OxmlError> for TransformError {
75 fn from(error: OxmlError) -> Self {
76 Self::Xml(error)
77 }
78}
79
80pub type Result<T> = std::result::Result<T, TransformError>;
81
82#[allow(non_camel_case_types)]
84#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
85pub struct CT_Point2D {
86 pub x: Emu,
87 pub y: Emu,
88}
89
90#[allow(non_camel_case_types)]
92#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
93pub struct CT_PositiveSize2D {
94 pub cx: Emu,
95 pub cy: Emu,
96}
97
98#[allow(non_camel_case_types)]
100#[derive(Clone, Debug, Default, Eq, PartialEq)]
101pub struct CT_Transform2D {
102 pub offset: Option<CT_Point2D>,
103 pub extent: Option<CT_PositiveSize2D>,
104 pub child_offset: Option<CT_Point2D>,
105 pub child_extent: Option<CT_PositiveSize2D>,
106 pub rotation: Angle,
107 pub flip_horizontal: bool,
108 pub flip_vertical: bool,
109 raw_children: OrderedRawChildren,
110}
111
112impl CT_Transform2D {
113 pub fn from_xml(xml: &[u8]) -> Result<Self> {
115 let mut reader = Reader::from_reader(xml);
116 let mut buffer = Vec::new();
117 loop {
118 match reader
119 .read_event_into(&mut buffer)
120 .map_err(OxmlError::from)?
121 {
122 Event::Start(element) if matches_local_name(element.name().as_ref(), b"xfrm") => {
123 return Self::from_element(&mut reader, &element);
124 }
125 Event::Empty(element) if matches_local_name(element.name().as_ref(), b"xfrm") => {
126 return Self::from_empty_element(&element);
127 }
128 Event::Start(element) | Event::Empty(element) => {
129 return Err(TransformError::UnexpectedElement(
130 String::from_utf8_lossy(element.name().as_ref()).into_owned(),
131 ));
132 }
133 Event::Eof => {
134 return Err(TransformError::Xml(OxmlError::MissingElement(
135 "a:xfrm".to_owned(),
136 )));
137 }
138 _ => {}
139 }
140 buffer.clear();
141 }
142 }
143
144 pub fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
146 let mut transform = Self::from_empty_element(start)?;
147 transform.read_children(reader)?;
148 Ok(transform)
149 }
150
151 pub fn from_empty_element(start: &BytesStart<'_>) -> Result<Self> {
153 if !matches_local_name(start.name().as_ref(), b"xfrm") {
154 return Err(TransformError::UnexpectedElement(
155 String::from_utf8_lossy(start.name().as_ref()).into_owned(),
156 ));
157 }
158 Ok(Self {
159 rotation: optional_i32(start, b"rot")?.map_or(Angle::default(), Angle),
160 flip_horizontal: optional_bool(start, b"flipH")?.unwrap_or(false),
161 flip_vertical: optional_bool(start, b"flipV")?.unwrap_or(false),
162 ..Self::default()
163 })
164 }
165
166 pub fn to_xml(&self) -> Result<Vec<u8>> {
168 let mut writer = Writer::new(Vec::new());
169 self.write_xml(&mut writer)?;
170 Ok(writer.into_inner())
171 }
172
173 pub fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
175 self.write_xml_with_root(writer, "a:xfrm")
176 }
177
178 pub fn write_xml_with_root<W: Write>(
184 &self,
185 writer: &mut Writer<W>,
186 root_name: &str,
187 ) -> Result<()> {
188 let mut start = BytesStart::new(root_name);
189 let rotation = (self.rotation.0 != 0).then(|| self.rotation.0.to_string());
190 if let Some(rotation) = rotation.as_deref() {
191 start.push_attribute(("rot", rotation));
192 }
193 if self.flip_horizontal {
194 start.push_attribute(("flipH", "1"));
195 }
196 if self.flip_vertical {
197 start.push_attribute(("flipV", "1"));
198 }
199
200 if self.offset.is_none()
201 && self.extent.is_none()
202 && self.child_offset.is_none()
203 && self.child_extent.is_none()
204 && self.raw_children.is_empty()
205 {
206 writer
207 .write_event(Event::Empty(start))
208 .map_err(OxmlError::from)?;
209 return Ok(());
210 }
211
212 writer
213 .write_event(Event::Start(start))
214 .map_err(OxmlError::from)?;
215 emit_raw(writer, self.raw_children.at(0))?;
216 if let Some(offset) = self.offset {
217 write_point(writer, "a:off", offset)?;
218 }
219 emit_raw(writer, self.raw_children.at(1))?;
220 if let Some(extent) = self.extent {
221 write_size(writer, "a:ext", extent)?;
222 }
223 emit_raw(writer, self.raw_children.at(2))?;
224 if let Some(offset) = self.child_offset {
225 write_point(writer, "a:chOff", offset)?;
226 }
227 emit_raw(writer, self.raw_children.at(3))?;
228 if let Some(extent) = self.child_extent {
229 write_size(writer, "a:chExt", extent)?;
230 }
231 emit_raw(writer, self.raw_children.at(4))?;
232 writer
233 .write_event(Event::End(BytesEnd::new(root_name)))
234 .map_err(OxmlError::from)?;
235 Ok(())
236 }
237
238 pub fn matrix(&self) -> Result<[f64; 6]> {
240 let offset = self.offset.unwrap_or_default();
241 let extent = self.extent.unwrap_or_default();
242 let child_offset = self.child_offset.unwrap_or_default();
243 let scale = match self.child_extent {
244 Some(child_extent) => {
245 if child_extent.cx.0 == 0 {
246 return Err(TransformError::ZeroChildExtent { axis: "x" });
247 }
248 if child_extent.cy.0 == 0 {
249 return Err(TransformError::ZeroChildExtent { axis: "y" });
250 }
251 [
252 extent.cx.0 as f64 / child_extent.cx.0 as f64,
253 extent.cy.0 as f64 / child_extent.cy.0 as f64,
254 ]
255 }
256 None => [1.0, 1.0],
257 };
258
259 let centre_x = offset.x.0 as f64 + extent.cx.0 as f64 / 2.0;
260 let centre_y = offset.y.0 as f64 + extent.cy.0 as f64 / 2.0;
261 let mut matrix = affine_identity();
262 matrix = affine_then(
263 matrix,
264 affine_translation(-(child_offset.x.0 as f64), -(child_offset.y.0 as f64)),
265 );
266 matrix = affine_then(matrix, affine_scale(scale[0], scale[1]));
267 matrix = affine_then(
268 matrix,
269 affine_translation(offset.x.0 as f64, offset.y.0 as f64),
270 );
271 matrix = affine_then(
272 matrix,
273 affine_rotation_about(self.rotation.to_degrees(), centre_x, centre_y),
274 );
275 matrix = affine_then(
276 matrix,
277 affine_scale_about(
278 if self.flip_horizontal { -1.0 } else { 1.0 },
279 if self.flip_vertical { -1.0 } else { 1.0 },
280 centre_x,
281 centre_y,
282 ),
283 );
284
285 if matrix.into_iter().all(f64::is_finite) {
286 Ok(matrix)
287 } else {
288 Err(TransformError::NonFiniteMatrix)
289 }
290 }
291
292 pub fn raw_children(&self) -> &OrderedRawChildren {
294 &self.raw_children
295 }
296
297 fn read_children(&mut self, reader: &mut Reader<&[u8]>) -> Result<()> {
298 let mut boundary = 0;
299 let mut buffer = Vec::new();
300 loop {
301 match reader
302 .read_event_into(&mut buffer)
303 .map_err(OxmlError::from)?
304 {
305 Event::Start(element) => {
306 let slot = schema_slot(element.name().as_ref());
307 let raw = capture_element(reader, &element)?;
308 if let Some(slot) = slot.filter(|_| is_explicit_empty_element(&raw)) {
309 if self.set_modelled(slot, &element)? {
310 boundary = boundary.max(slot);
311 } else {
312 self.raw_children.push(boundary, raw);
313 }
314 } else {
315 self.raw_children.push(boundary, raw);
316 }
317 }
318 Event::Empty(element) => {
319 if let Some(slot) = schema_slot(element.name().as_ref()) {
320 if self.set_modelled(slot, &element)? {
321 boundary = boundary.max(slot);
322 } else {
323 self.raw_children
324 .push(boundary, capture_empty_element(&element)?);
325 }
326 } else {
327 self.raw_children
328 .push(boundary, capture_empty_element(&element)?);
329 }
330 }
331 Event::End(element) if matches_local_name(element.name().as_ref(), b"xfrm") => {
332 break;
333 }
334 Event::Eof => {
335 return Err(TransformError::Xml(OxmlError::MissingElement(
336 "closing a:xfrm".to_owned(),
337 )));
338 }
339 _ => {}
340 }
341 buffer.clear();
342 }
343 Ok(())
344 }
345
346 fn set_modelled(&mut self, slot: usize, element: &BytesStart<'_>) -> Result<bool> {
347 match slot {
348 1 if self.offset.is_none() => self.offset = Some(parse_point(element)?),
349 2 if self.extent.is_none() => self.extent = Some(parse_size(element)?),
350 3 if self.child_offset.is_none() => self.child_offset = Some(parse_point(element)?),
351 4 if self.child_extent.is_none() => self.child_extent = Some(parse_size(element)?),
352 1..=4 => return Ok(false),
353 _ => unreachable!("schema slots are limited to one through four"),
354 }
355 Ok(true)
356 }
357}
358
359fn schema_slot(name: &[u8]) -> Option<usize> {
360 match local_name(name) {
361 b"off" => Some(1),
362 b"ext" => Some(2),
363 b"chOff" => Some(3),
364 b"chExt" => Some(4),
365 _ => None,
366 }
367}
368
369fn parse_point(element: &BytesStart<'_>) -> Result<CT_Point2D> {
370 Ok(CT_Point2D {
371 x: Emu(required_i64(element, b"x")?),
372 y: Emu(required_i64(element, b"y")?),
373 })
374}
375
376fn parse_size(element: &BytesStart<'_>) -> Result<CT_PositiveSize2D> {
377 let cx = required_i64(element, b"cx")?;
378 let cy = required_i64(element, b"cy")?;
379 if cx < 0 {
380 return Err(invalid_attribute(element, b"cx", cx.to_string()));
381 }
382 if cy < 0 {
383 return Err(invalid_attribute(element, b"cy", cy.to_string()));
384 }
385 Ok(CT_PositiveSize2D {
386 cx: Emu(cx),
387 cy: Emu(cy),
388 })
389}
390
391fn required_i64(element: &BytesStart<'_>, attribute: &[u8]) -> Result<i64> {
392 let value = get_attr(element, attribute).ok_or_else(|| TransformError::MissingAttribute {
393 element: element_local_name(element),
394 attribute: String::from_utf8_lossy(attribute).into_owned(),
395 })?;
396 value
397 .parse()
398 .map_err(|_| invalid_attribute(element, attribute, value))
399}
400
401fn optional_i32(element: &BytesStart<'_>, attribute: &[u8]) -> Result<Option<i32>> {
402 get_attr(element, attribute)
403 .map(|value| {
404 value
405 .parse()
406 .map_err(|_| invalid_attribute(element, attribute, value))
407 })
408 .transpose()
409}
410
411fn optional_bool(element: &BytesStart<'_>, attribute: &[u8]) -> Result<Option<bool>> {
412 get_attr(element, attribute)
413 .map(|value| match value.as_str() {
414 "1" | "true" => Ok(true),
415 "0" | "false" => Ok(false),
416 _ => Err(invalid_attribute(element, attribute, value)),
417 })
418 .transpose()
419}
420
421fn invalid_attribute(element: &BytesStart<'_>, attribute: &[u8], value: String) -> TransformError {
422 TransformError::InvalidAttribute {
423 element: element_local_name(element),
424 attribute: String::from_utf8_lossy(attribute).into_owned(),
425 value,
426 }
427}
428
429fn element_local_name(element: &BytesStart<'_>) -> String {
430 String::from_utf8_lossy(local_name(element.name().as_ref())).into_owned()
431}
432
433fn write_point<W: Write>(writer: &mut Writer<W>, tag: &str, point: CT_Point2D) -> Result<()> {
434 let x = point.x.0.to_string();
435 let y = point.y.0.to_string();
436 let mut element = BytesStart::new(tag);
437 element.push_attribute(("x", x.as_str()));
438 element.push_attribute(("y", y.as_str()));
439 writer
440 .write_event(Event::Empty(element))
441 .map_err(OxmlError::from)?;
442 Ok(())
443}
444
445fn write_size<W: Write>(writer: &mut Writer<W>, tag: &str, size: CT_PositiveSize2D) -> Result<()> {
446 let cx = size.cx.0.to_string();
447 let cy = size.cy.0.to_string();
448 let mut element = BytesStart::new(tag);
449 element.push_attribute(("cx", cx.as_str()));
450 element.push_attribute(("cy", cy.as_str()));
451 writer
452 .write_event(Event::Empty(element))
453 .map_err(OxmlError::from)?;
454 Ok(())
455}
456
457fn emit_raw<'a, W: Write>(
458 writer: &mut Writer<W>,
459 children: impl Iterator<Item = &'a [u8]>,
460) -> Result<()> {
461 for child in children {
462 writer.get_mut().write_all(child).map_err(OxmlError::from)?;
463 }
464 Ok(())
465}
466
467fn is_explicit_empty_element(xml: &[u8]) -> bool {
468 let mut reader = Reader::from_reader(xml);
469 let mut buffer = Vec::new();
470 if !matches!(reader.read_event_into(&mut buffer), Ok(Event::Start(_))) {
471 return false;
472 }
473 loop {
474 buffer.clear();
475 match reader.read_event_into(&mut buffer) {
476 Ok(Event::Text(text)) if is_xml_whitespace(text.as_ref()) => {}
477 Ok(Event::CData(text)) if is_xml_whitespace(text.as_ref()) => {}
478 Ok(Event::Comment(_) | Event::PI(_)) => {}
479 Ok(Event::End(_)) => {
480 buffer.clear();
481 return matches!(reader.read_event_into(&mut buffer), Ok(Event::Eof));
482 }
483 _ => return false,
484 }
485 }
486}
487
488fn is_xml_whitespace(bytes: &[u8]) -> bool {
489 bytes
490 .iter()
491 .all(|byte| matches!(byte, b' ' | b'\t' | b'\n' | b'\r'))
492}
493
494const fn affine_identity() -> [f64; 6] {
495 [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
496}
497
498const fn affine_translation(x: f64, y: f64) -> [f64; 6] {
499 [1.0, 0.0, 0.0, 1.0, x, y]
500}
501
502const fn affine_scale(x: f64, y: f64) -> [f64; 6] {
503 [x, 0.0, 0.0, y, 0.0, 0.0]
504}
505
506fn affine_rotation_about(degrees: f64, centre_x: f64, centre_y: f64) -> [f64; 6] {
507 let (sin, cos) = degrees.to_radians().sin_cos();
508 [
509 cos,
510 sin,
511 -sin,
512 cos,
513 centre_x - cos * centre_x + sin * centre_y,
514 centre_y - sin * centre_x - cos * centre_y,
515 ]
516}
517
518const fn affine_scale_about(x: f64, y: f64, centre_x: f64, centre_y: f64) -> [f64; 6] {
519 [x, 0.0, 0.0, y, centre_x * (1.0 - x), centre_y * (1.0 - y)]
520}
521
522const fn affine_then(first: [f64; 6], next: [f64; 6]) -> [f64; 6] {
523 [
524 next[0] * first[0] + next[2] * first[1],
525 next[1] * first[0] + next[3] * first[1],
526 next[0] * first[2] + next[2] * first[3],
527 next[1] * first[2] + next[3] * first[3],
528 next[0] * first[4] + next[2] * first[5] + next[4],
529 next[1] * first[4] + next[3] * first[5] + next[5],
530 ]
531}
532
533#[cfg(test)]
534mod tests {
535 use super::{CT_Transform2D, TransformError};
536
537 const EPSILON: f64 = 1.0e-10;
538
539 #[test]
540 fn nested_group_transform_composes_to_the_hand_computed_matrix() {
541 let transform = CT_Transform2D::from_xml(
542 br#"<a:xfrm rot="5400000" flipH="1" flipV="1"><a:off x="100" y="200"/><a:ext cx="400" cy="200"/><a:chOff x="10" y="20"/><a:chExt cx="200" cy="100"/></a:xfrm>"#,
543 )
544 .unwrap();
545
546 let actual = transform.matrix().unwrap();
547 let expected = [0.0, -2.0, 2.0, 0.0, 160.0, 520.0];
548 for (actual, expected) in actual.into_iter().zip(expected) {
549 assert!((actual - expected).abs() < EPSILON);
550 }
551 }
552
553 #[test]
554 fn transform_reads_any_prefix_and_writes_fixed_a_prefix_in_schema_order() {
555 let transform = CT_Transform2D::from_xml(
556 br#"<p:xfrm rot="-2700000" flipH="true" flipV="1"><p:chExt cx="70" cy="80"/><p:off x="-10" y="20"/><p:chOff x="50" y="60"/><p:ext cx="30" cy="40"/></p:xfrm>"#,
557 )
558 .unwrap();
559
560 assert_eq!(
561 transform.to_xml().unwrap(),
562 br#"<a:xfrm rot="-2700000" flipH="1" flipV="1"><a:off x="-10" y="20"/><a:ext cx="30" cy="40"/><a:chOff x="50" y="60"/><a:chExt cx="70" cy="80"/></a:xfrm>"#
563 );
564 }
565
566 #[test]
567 fn transform_writer_uses_the_requested_root_name() {
568 let transform = CT_Transform2D::default();
569 let mut writer = quick_xml::Writer::new(Vec::new());
570 transform
571 .write_xml_with_root(&mut writer, "p:xfrm")
572 .unwrap();
573 assert_eq!(writer.into_inner(), br#"<p:xfrm/>"#);
574 }
575
576 #[test]
577 fn unknown_transform_children_round_trip_at_their_original_boundaries() {
578 let transform = CT_Transform2D::from_xml(
579 br#"<z:xfrm><x:before x:id="1"/><z:off x="1" y="2"/><x:middle>one & two</x:middle><z:ext cx="3" cy="4"/><x:after><!--kept--></x:after></z:xfrm>"#,
580 )
581 .unwrap();
582
583 assert_eq!(
584 transform.to_xml().unwrap(),
585 br#"<a:xfrm><x:before x:id="1"/><a:off x="1" y="2"/><x:middle>one & two</x:middle><a:ext cx="3" cy="4"/><x:after><!--kept--></x:after></a:xfrm>"#
586 );
587 }
588
589 #[test]
590 fn zero_child_extent_returns_a_transform_error_instead_of_non_finite_coefficients() {
591 let transform = CT_Transform2D::from_xml(
592 br#"<a:xfrm><a:off x="1" y="2"/><a:ext cx="3" cy="4"/><a:chExt cx="0" cy="5"/></a:xfrm>"#,
593 )
594 .unwrap();
595
596 assert!(matches!(
597 transform.matrix(),
598 Err(TransformError::ZeroChildExtent { axis: "x" })
599 ));
600 }
601}