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::xml::{get_attr, local_name};
7use quick_xml::events::{BytesEnd, BytesStart, Event};
8use quick_xml::{Reader, Writer};
9
10use crate::color::{ColorChoice, ColorError};
11use crate::order::OrderedRawChildren;
12
13#[derive(Debug)]
15pub enum StyleReferenceError {
16 Xml(OxmlError),
17 Color(ColorError),
18 UnexpectedElement(String),
19 MissingAttribute {
20 element: String,
21 attribute: String,
22 },
23 InvalidAttribute {
24 element: String,
25 attribute: String,
26 value: String,
27 },
28 NotFillReference,
29}
30
31impl fmt::Display for StyleReferenceError {
32 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 Self::Xml(error) => error.fmt(formatter),
35 Self::Color(error) => error.fmt(formatter),
36 Self::UnexpectedElement(element) => {
37 write!(
38 formatter,
39 "unexpected DrawingML style-reference element: {element}"
40 )
41 }
42 Self::MissingAttribute { element, attribute } => {
43 write!(formatter, "DrawingML {element} requires @{attribute}")
44 }
45 Self::InvalidAttribute {
46 element,
47 attribute,
48 value,
49 } => write!(
50 formatter,
51 "DrawingML {element} has invalid @{attribute}: {value}"
52 ),
53 Self::NotFillReference => write!(formatter, "style reference is not a fillRef"),
54 }
55 }
56}
57
58impl std::error::Error for StyleReferenceError {
59 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
60 match self {
61 Self::Xml(error) => Some(error),
62 Self::Color(error) => Some(error),
63 _ => None,
64 }
65 }
66}
67
68impl From<OxmlError> for StyleReferenceError {
69 fn from(error: OxmlError) -> Self {
70 Self::Xml(error)
71 }
72}
73
74impl From<ColorError> for StyleReferenceError {
75 fn from(error: ColorError) -> Self {
76 Self::Color(error)
77 }
78}
79
80pub type Result<T> = std::result::Result<T, StyleReferenceError>;
81
82#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84pub enum FillStyleSelection {
85 FillStyle(u32),
86 BackgroundFillStyle(u32),
87}
88
89impl FillStyleSelection {
90 pub const fn from_index(index: u32) -> Self {
92 if index > 1000 {
93 Self::BackgroundFillStyle(index - 1000)
94 } else {
95 Self::FillStyle(index)
96 }
97 }
98}
99
100#[derive(Clone, Debug, Eq, PartialEq)]
102pub struct StyleMatrixReference {
103 pub index: u32,
104 pub color: Option<ColorChoice>,
105 raw_children: OrderedRawChildren,
106}
107
108impl StyleMatrixReference {
109 pub fn new(index: u32, color: ColorChoice) -> Self {
110 Self {
111 index,
112 color: Some(color),
113 raw_children: OrderedRawChildren::default(),
114 }
115 }
116
117 pub fn raw_children(&self) -> &OrderedRawChildren {
118 &self.raw_children
119 }
120}
121
122#[derive(Clone, Copy, Debug, Eq, PartialEq)]
124pub enum FontCollectionIndex {
125 Major,
126 Minor,
127 None,
128}
129
130impl FontCollectionIndex {
131 fn parse(value: &str) -> Option<Self> {
132 match value {
133 "major" => Some(Self::Major),
134 "minor" => Some(Self::Minor),
135 "none" => Some(Self::None),
136 _ => None,
137 }
138 }
139
140 const fn as_str(self) -> &'static str {
141 match self {
142 Self::Major => "major",
143 Self::Minor => "minor",
144 Self::None => "none",
145 }
146 }
147}
148
149#[derive(Clone, Debug, Eq, PartialEq)]
151pub struct FontReference {
152 pub index: FontCollectionIndex,
153 pub color: Option<ColorChoice>,
154 raw_children: OrderedRawChildren,
155}
156
157impl FontReference {
158 pub fn new(index: FontCollectionIndex, color: ColorChoice) -> Self {
159 Self {
160 index,
161 color: Some(color),
162 raw_children: OrderedRawChildren::default(),
163 }
164 }
165
166 pub fn raw_children(&self) -> &OrderedRawChildren {
167 &self.raw_children
168 }
169}
170
171#[derive(Clone, Debug, Eq, PartialEq)]
173pub enum StyleReference {
174 Line(StyleMatrixReference),
175 Fill(StyleMatrixReference),
176 Effect(StyleMatrixReference),
177 Font(FontReference),
178}
179
180impl StyleReference {
181 pub fn from_xml(xml: &[u8]) -> Result<Self> {
183 let mut reader = Reader::from_reader(xml);
184 let mut buffer = Vec::new();
185 loop {
186 match reader
187 .read_event_into(&mut buffer)
188 .map_err(OxmlError::from)?
189 {
190 Event::Start(element) => {
191 let kind = ReferenceKind::parse(element.name().as_ref())?;
192 return Self::from_element(&mut reader, &element, kind);
193 }
194 Event::Empty(element) => {
195 let kind = ReferenceKind::parse(element.name().as_ref())?;
196 let index = kind.parse_index(&element)?;
197 return kind.build(index, None, OrderedRawChildren::default());
198 }
199 Event::Eof => {
200 return Err(StyleReferenceError::Xml(OxmlError::MissingElement(
201 "DrawingML style reference".to_owned(),
202 )));
203 }
204 _ => {}
205 }
206 buffer.clear();
207 }
208 }
209
210 fn from_element(
211 reader: &mut Reader<&[u8]>,
212 start: &BytesStart<'_>,
213 kind: ReferenceKind,
214 ) -> Result<Self> {
215 let index = kind.parse_index(start)?;
216 let mut color = None;
217 let mut has_color = false;
218 let mut raw_children = OrderedRawChildren::default();
219 let mut boundary = 0;
220 let mut buffer = Vec::new();
221
222 loop {
223 match reader
224 .read_event_into(&mut buffer)
225 .map_err(OxmlError::from)?
226 {
227 Event::Start(element)
228 if is_modelled_color(element.name().as_ref()) && !has_color =>
229 {
230 color = Some(ColorChoice::from_xml(reader, &element)?);
231 has_color = true;
232 boundary = 1;
233 }
234 Event::Empty(element)
235 if is_modelled_color(element.name().as_ref()) && !has_color =>
236 {
237 color = Some(ColorChoice::from_empty_xml(&element)?);
238 has_color = true;
239 boundary = 1;
240 }
241 Event::Start(element) => {
242 let occupies_color = is_any_color(element.name().as_ref()) && !has_color;
243 raw_children.push(boundary, capture_element(reader, &element)?);
244 if occupies_color {
245 has_color = true;
246 boundary = 1;
247 }
248 }
249 Event::Empty(element) => {
250 let occupies_color = is_any_color(element.name().as_ref()) && !has_color;
251 raw_children.push(boundary, capture_empty_element(&element)?);
252 if occupies_color {
253 has_color = true;
254 boundary = 1;
255 }
256 }
257 Event::End(element)
258 if local_name(element.name().as_ref()) == kind.element_name().as_bytes() =>
259 {
260 break;
261 }
262 Event::Eof => return Err(missing_end(kind.element_name())),
263 _ => {}
264 }
265 buffer.clear();
266 }
267
268 kind.build(index, color, raw_children)
269 }
270
271 pub fn fill_style_selection(&self) -> Result<FillStyleSelection> {
273 match self {
274 Self::Fill(reference) => Ok(FillStyleSelection::from_index(reference.index)),
275 _ => Err(StyleReferenceError::NotFillReference),
276 }
277 }
278
279 pub fn to_xml(&self) -> Result<Vec<u8>> {
281 let mut writer = Writer::new(Vec::new());
282 self.write_xml(&mut writer)?;
283 Ok(writer.into_inner())
284 }
285
286 pub fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
288 match self {
289 Self::Line(reference) => write_matrix_reference(writer, "a:lnRef", reference),
290 Self::Fill(reference) => write_matrix_reference(writer, "a:fillRef", reference),
291 Self::Effect(reference) => write_matrix_reference(writer, "a:effectRef", reference),
292 Self::Font(reference) => write_font_reference(writer, reference),
293 }
294 }
295}
296
297#[derive(Clone, Copy)]
298enum ReferenceKind {
299 Line,
300 Fill,
301 Effect,
302 Font,
303}
304
305impl ReferenceKind {
306 fn parse(name: &[u8]) -> Result<Self> {
307 match local_name(name) {
308 b"lnRef" => Ok(Self::Line),
309 b"fillRef" => Ok(Self::Fill),
310 b"effectRef" => Ok(Self::Effect),
311 b"fontRef" => Ok(Self::Font),
312 _ => Err(StyleReferenceError::UnexpectedElement(
313 String::from_utf8_lossy(name).into_owned(),
314 )),
315 }
316 }
317
318 const fn element_name(self) -> &'static str {
319 match self {
320 Self::Line => "lnRef",
321 Self::Fill => "fillRef",
322 Self::Effect => "effectRef",
323 Self::Font => "fontRef",
324 }
325 }
326
327 fn parse_index(self, start: &BytesStart<'_>) -> Result<ReferenceIndex> {
328 let value =
329 get_attr(start, b"idx").ok_or_else(|| StyleReferenceError::MissingAttribute {
330 element: self.element_name().to_owned(),
331 attribute: "idx".to_owned(),
332 })?;
333 if matches!(self, Self::Font) {
334 return FontCollectionIndex::parse(&value)
335 .map(ReferenceIndex::Font)
336 .ok_or_else(|| invalid_attribute(self.element_name(), "idx", value));
337 }
338 let index = value
339 .parse::<u32>()
340 .map_err(|_| invalid_attribute(self.element_name(), "idx", value.clone()))?;
341 Ok(ReferenceIndex::Matrix(index))
342 }
343
344 fn build(
345 self,
346 index: ReferenceIndex,
347 color: Option<ColorChoice>,
348 raw_children: OrderedRawChildren,
349 ) -> Result<StyleReference> {
350 let reference = match (self, index) {
351 (Self::Line, ReferenceIndex::Matrix(index)) => {
352 StyleReference::Line(StyleMatrixReference {
353 index,
354 color,
355 raw_children,
356 })
357 }
358 (Self::Fill, ReferenceIndex::Matrix(index)) => {
359 StyleReference::Fill(StyleMatrixReference {
360 index,
361 color,
362 raw_children,
363 })
364 }
365 (Self::Effect, ReferenceIndex::Matrix(index)) => {
366 StyleReference::Effect(StyleMatrixReference {
367 index,
368 color,
369 raw_children,
370 })
371 }
372 (Self::Font, ReferenceIndex::Font(index)) => StyleReference::Font(FontReference {
373 index,
374 color,
375 raw_children,
376 }),
377 _ => {
378 return Err(StyleReferenceError::InvalidAttribute {
379 element: self.element_name().to_owned(),
380 attribute: "idx".to_owned(),
381 value: "index kind mismatch".to_owned(),
382 });
383 }
384 };
385 Ok(reference)
386 }
387}
388
389enum ReferenceIndex {
390 Matrix(u32),
391 Font(FontCollectionIndex),
392}
393
394fn write_matrix_reference<W: Write>(
395 writer: &mut Writer<W>,
396 tag: &str,
397 reference: &StyleMatrixReference,
398) -> Result<()> {
399 write_reference(
400 writer,
401 tag,
402 reference.index.to_string(),
403 reference.color.as_ref(),
404 &reference.raw_children,
405 )
406}
407
408fn write_font_reference<W: Write>(writer: &mut Writer<W>, reference: &FontReference) -> Result<()> {
409 write_reference(
410 writer,
411 "a:fontRef",
412 reference.index.as_str().to_owned(),
413 reference.color.as_ref(),
414 &reference.raw_children,
415 )
416}
417
418fn write_reference<W: Write>(
419 writer: &mut Writer<W>,
420 tag: &str,
421 index: String,
422 color: Option<&ColorChoice>,
423 raw_children: &OrderedRawChildren,
424) -> Result<()> {
425 let mut start = BytesStart::new(tag);
426 start.push_attribute(("idx", index.as_str()));
427 if color.is_none() && raw_children.is_empty() {
428 writer
429 .write_event(Event::Empty(start))
430 .map_err(OxmlError::from)?;
431 return Ok(());
432 }
433 writer
434 .write_event(Event::Start(start))
435 .map_err(OxmlError::from)?;
436 emit_raw(writer, raw_children.at(0))?;
437 if let Some(color) = color {
438 color.to_xml(writer)?;
439 }
440 emit_raw(writer, raw_children.at(1))?;
441 writer
442 .write_event(Event::End(BytesEnd::new(tag)))
443 .map_err(OxmlError::from)?;
444 Ok(())
445}
446
447fn is_modelled_color(name: &[u8]) -> bool {
448 matches!(
449 local_name(name),
450 b"srgbClr" | b"schemeClr" | b"sysClr" | b"prstClr"
451 )
452}
453
454fn is_any_color(name: &[u8]) -> bool {
455 matches!(
456 local_name(name),
457 b"scrgbClr" | b"srgbClr" | b"hslClr" | b"sysClr" | b"schemeClr" | b"prstClr"
458 )
459}
460
461fn emit_raw<'a, W: Write>(
462 writer: &mut Writer<W>,
463 children: impl Iterator<Item = &'a [u8]>,
464) -> Result<()> {
465 for child in children {
466 writer.get_mut().write_all(child).map_err(OxmlError::from)?;
467 }
468 Ok(())
469}
470
471fn invalid_attribute(element: &str, attribute: &str, value: String) -> StyleReferenceError {
472 StyleReferenceError::InvalidAttribute {
473 element: element.to_owned(),
474 attribute: attribute.to_owned(),
475 value,
476 }
477}
478
479fn missing_end(element: &str) -> StyleReferenceError {
480 StyleReferenceError::Xml(OxmlError::MissingElement(format!(
481 "closing DrawingML {element}"
482 )))
483}
484
485#[cfg(test)]
486mod tests {
487 use std::panic;
488
489 use super::{FillStyleSelection, StyleReference};
490 use crate::shape_props::CT_ShapeProperties;
491
492 #[test]
493 fn fill_ref_1001_resolves_to_background_fill_style_1() {
494 let reference = StyleReference::from_xml(
495 br#"<q:fillRef idx="1001"><q:schemeClr val="phClr"/></q:fillRef>"#,
496 )
497 .unwrap();
498 assert_eq!(
499 reference.fill_style_selection().unwrap(),
500 FillStyleSelection::BackgroundFillStyle(1)
501 );
502 }
503
504 #[test]
505 fn all_four_style_reference_forms_round_trip() {
506 let cases: &[(&[u8], &[u8])] = &[
507 (
508 br#"<q:lnRef idx="2"><x:before/><q:schemeClr val="accent1"/><x:after/></q:lnRef>"#,
509 br#"<a:lnRef idx="2"><x:before/><a:schemeClr val="accent1"/><x:after/></a:lnRef>"#,
510 ),
511 (
512 br#"<q:fillRef idx="1001"><q:srgbClr val="102030"/></q:fillRef>"#,
513 br#"<a:fillRef idx="1001"><a:srgbClr val="102030"/></a:fillRef>"#,
514 ),
515 (
516 br#"<q:effectRef idx="3"><q:prstClr val="black"/></q:effectRef>"#,
517 br#"<a:effectRef idx="3"><a:prstClr val="black"/></a:effectRef>"#,
518 ),
519 (
520 br#"<q:fontRef idx="minor"><q:sysClr val="windowText" lastClr="000000"/></q:fontRef>"#,
521 br#"<a:fontRef idx="minor"><a:sysClr val="windowText" lastClr="000000"/></a:fontRef>"#,
522 ),
523 ];
524
525 for (xml, expected) in cases {
526 let parsed = StyleReference::from_xml(xml).unwrap();
527 let written = parsed.to_xml().unwrap();
528 assert_eq!(&written, expected);
529 assert_eq!(StyleReference::from_xml(&written).unwrap(), parsed);
530 }
531
532 let raw_color = br#"<q:fillRef idx="4"><q:hslClr hue="0" sat="0" lum="0"><x:kept/></q:hslClr></q:fillRef>"#;
533 assert_eq!(
534 StyleReference::from_xml(raw_color).unwrap().to_xml().unwrap(),
535 br#"<a:fillRef idx="4"><q:hslClr hue="0" sat="0" lum="0"><x:kept/></q:hslClr></a:fillRef>"#
536 );
537 }
538
539 #[test]
540 fn zero_indices_and_colourless_style_references_round_trip() {
541 let cases: &[(&[u8], &[u8])] = &[
542 (br#"<q:lnRef idx="0"/>"#, br#"<a:lnRef idx="0"/>"#),
543 (br#"<q:fillRef idx="0"/>"#, br#"<a:fillRef idx="0"/>"#),
544 (br#"<q:effectRef idx="0"/>"#, br#"<a:effectRef idx="0"/>"#),
545 (
546 br#"<q:fontRef idx="minor"/>"#,
547 br#"<a:fontRef idx="minor"/>"#,
548 ),
549 ];
550
551 for (xml, expected) in cases {
552 let parsed = StyleReference::from_xml(xml).unwrap();
553 let written = parsed.to_xml().unwrap();
554 assert_eq!(&written, expected);
555 assert_eq!(StyleReference::from_xml(&written).unwrap(), parsed);
556 }
557
558 let fill = StyleReference::from_xml(cases[1].0).unwrap();
559 assert_eq!(
560 fill.fill_style_selection().unwrap(),
561 FillStyleSelection::FillStyle(0)
562 );
563 }
564
565 #[test]
566 fn malformed_shape_and_style_references_return_errors_without_panicking() {
567 let style_cases: &[&[u8]] = &[
568 br#"<q:otherRef idx="1"><q:schemeClr val="accent1"/></q:otherRef>"#,
569 br#"<q:lnRef><q:schemeClr val="accent1"/></q:lnRef>"#,
570 br#"<q:fillRef idx="4294967296"><q:schemeClr val="accent1"/></q:fillRef>"#,
571 br#"<q:effectRef idx="wide"><q:schemeClr val="accent1"/></q:effectRef>"#,
572 br#"<q:fontRef idx="body"><q:schemeClr val="accent1"/></q:fontRef>"#,
573 br#"<q:lnRef idx="1"><q:srgbClr val="not-rgb"/></q:lnRef>"#,
574 ];
575 for xml in style_cases {
576 let result = panic::catch_unwind(|| StyleReference::from_xml(xml));
577 assert!(result.is_ok(), "style-reference parser panicked");
578 assert!(result.unwrap().is_err(), "malformed style reference parsed");
579 }
580
581 let shape_cases: &[&[u8]] = &[
582 br#"<q:notSpPr/>"#,
583 br#"<q:spPr><q:xfrm rot="bad"/></q:spPr>"#,
584 br#"<q:spPr><q:custGeom/></q:spPr>"#,
585 br#"<q:spPr><q:ln w="20116801"/></q:spPr>"#,
586 br#"<q:spPr><q:solidFill><q:srgbClr val="not-rgb"/></q:solidFill></q:spPr>"#,
587 ];
588 for xml in shape_cases {
589 let result = panic::catch_unwind(|| CT_ShapeProperties::from_xml(xml));
590 assert!(result.is_ok(), "shape-properties parser panicked");
591 assert!(
592 result.unwrap().is_err(),
593 "malformed shape properties parsed"
594 );
595 }
596 }
597}