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, matches_local_name};
7use quick_xml::events::{BytesEnd, BytesStart, Event};
8use quick_xml::{Reader, Writer, XmlVersion};
9
10use crate::fill::{Fill, FillError};
11use crate::order::OrderedRawChildren;
12
13const MAX_LINE_WIDTH_EMU: u32 = 20_116_800;
14
15#[derive(Debug)]
17pub enum LineError {
18 Xml(OxmlError),
19 Fill(FillError),
20 UnexpectedElement(String),
21 MissingAttribute {
22 element: String,
23 attribute: String,
24 },
25 InvalidAttribute {
26 element: String,
27 attribute: String,
28 value: String,
29 },
30 LineWidthOutOfRange(u32),
31}
32
33impl fmt::Display for LineError {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::Xml(error) => error.fmt(formatter),
37 Self::Fill(error) => error.fmt(formatter),
38 Self::UnexpectedElement(element) => {
39 write!(formatter, "unexpected DrawingML line element: {element}")
40 }
41 Self::MissingAttribute { element, attribute } => {
42 write!(formatter, "DrawingML {element} requires @{attribute}")
43 }
44 Self::InvalidAttribute {
45 element,
46 attribute,
47 value,
48 } => write!(
49 formatter,
50 "DrawingML {element} has invalid @{attribute}: {value}"
51 ),
52 Self::LineWidthOutOfRange(value) => write!(
53 formatter,
54 "DrawingML line width is outside 0 to {MAX_LINE_WIDTH_EMU}: {value}"
55 ),
56 }
57 }
58}
59
60impl std::error::Error for LineError {
61 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
62 match self {
63 Self::Xml(error) => Some(error),
64 Self::Fill(error) => Some(error),
65 _ => None,
66 }
67 }
68}
69
70impl From<OxmlError> for LineError {
71 fn from(error: OxmlError) -> Self {
72 Self::Xml(error)
73 }
74}
75
76impl From<FillError> for LineError {
77 fn from(error: FillError) -> Self {
78 Self::Fill(error)
79 }
80}
81
82pub type Result<T> = std::result::Result<T, LineError>;
83
84#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub enum LineCap {
87 Round,
88 Square,
89 Flat,
90}
91
92impl LineCap {
93 fn parse(value: &str) -> Option<Self> {
94 match value {
95 "rnd" => Some(Self::Round),
96 "sq" => Some(Self::Square),
97 "flat" => Some(Self::Flat),
98 _ => None,
99 }
100 }
101
102 const fn as_str(self) -> &'static str {
103 match self {
104 Self::Round => "rnd",
105 Self::Square => "sq",
106 Self::Flat => "flat",
107 }
108 }
109}
110
111#[allow(non_camel_case_types)]
113#[derive(Clone, Copy, Debug, Eq, PartialEq)]
114pub enum ST_PresetLineDashVal {
115 Solid,
116 Dot,
117 SystemDot,
118 Dash,
119 SystemDash,
120 LargeDash,
121 DashDot,
122 SystemDashDot,
123 LargeDashDot,
124 LargeDashDotDot,
125 SystemDashDotDot,
126}
127
128impl ST_PresetLineDashVal {
129 pub const ALL: [Self; 11] = [
130 Self::Solid,
131 Self::Dot,
132 Self::SystemDot,
133 Self::Dash,
134 Self::SystemDash,
135 Self::LargeDash,
136 Self::DashDot,
137 Self::SystemDashDot,
138 Self::LargeDashDot,
139 Self::LargeDashDotDot,
140 Self::SystemDashDotDot,
141 ];
142
143 fn parse(value: &str) -> Option<Self> {
144 match value {
145 "solid" => Some(Self::Solid),
146 "dot" => Some(Self::Dot),
147 "sysDot" => Some(Self::SystemDot),
148 "dash" => Some(Self::Dash),
149 "sysDash" => Some(Self::SystemDash),
150 "lgDash" => Some(Self::LargeDash),
151 "dashDot" => Some(Self::DashDot),
152 "sysDashDot" => Some(Self::SystemDashDot),
153 "lgDashDot" => Some(Self::LargeDashDot),
154 "lgDashDotDot" => Some(Self::LargeDashDotDot),
155 "sysDashDotDot" => Some(Self::SystemDashDotDot),
156 _ => None,
157 }
158 }
159
160 pub const fn as_str(self) -> &'static str {
161 match self {
162 Self::Solid => "solid",
163 Self::Dot => "dot",
164 Self::SystemDot => "sysDot",
165 Self::Dash => "dash",
166 Self::SystemDash => "sysDash",
167 Self::LargeDash => "lgDash",
168 Self::DashDot => "dashDot",
169 Self::SystemDashDot => "sysDashDot",
170 Self::LargeDashDot => "lgDashDot",
171 Self::LargeDashDotDot => "lgDashDotDot",
172 Self::SystemDashDotDot => "sysDashDotDot",
173 }
174 }
175
176 pub const fn dash_array(self) -> &'static [u16] {
178 match self {
179 Self::Solid => &[],
180 Self::Dot => &[1, 3],
181 Self::SystemDot => &[1, 1],
182 Self::Dash => &[4, 3],
183 Self::SystemDash => &[3, 1],
184 Self::LargeDash => &[8, 3],
185 Self::DashDot => &[4, 3, 1, 3],
186 Self::SystemDashDot => &[3, 1, 1, 1],
187 Self::LargeDashDot => &[8, 3, 1, 3],
188 Self::LargeDashDotDot => &[8, 3, 1, 3, 1, 3],
189 Self::SystemDashDotDot => &[3, 1, 1, 1, 1, 1],
190 }
191 }
192}
193
194#[derive(Clone, Debug, Eq, PartialEq)]
196pub struct DashStop {
197 pub dash: i32,
198 pub space: i32,
199 raw_children: OrderedRawChildren,
200}
201
202impl DashStop {
203 pub fn new(dash: i32, space: i32) -> Result<Self> {
204 validate_positive_percentage("ds", "d", dash)?;
205 validate_positive_percentage("ds", "sp", space)?;
206 Ok(Self {
207 dash,
208 space,
209 raw_children: OrderedRawChildren::default(),
210 })
211 }
212}
213
214#[derive(Clone, Debug, Eq, PartialEq)]
216pub struct PresetDash {
217 pub value: ST_PresetLineDashVal,
218 raw_children: OrderedRawChildren,
219}
220
221impl PresetDash {
222 pub fn new(value: ST_PresetLineDashVal) -> Self {
223 Self {
224 value,
225 raw_children: OrderedRawChildren::default(),
226 }
227 }
228}
229
230#[derive(Clone, Debug, Default, Eq, PartialEq)]
232pub struct CustomDash {
233 pub stops: Vec<DashStop>,
234 raw_children: OrderedRawChildren,
235}
236
237#[derive(Clone, Debug, Eq, PartialEq)]
239pub enum LineDash {
240 Preset(PresetDash),
241 Custom(CustomDash),
242}
243
244#[derive(Clone, Debug, Eq, PartialEq)]
246pub enum LineJoin {
247 Round {
248 raw_children: OrderedRawChildren,
249 },
250 Bevel {
251 raw_children: OrderedRawChildren,
252 },
253 Miter {
254 limit: Option<i32>,
255 raw_children: OrderedRawChildren,
256 },
257}
258
259impl LineJoin {
260 pub fn round() -> Self {
261 Self::Round {
262 raw_children: OrderedRawChildren::default(),
263 }
264 }
265
266 pub fn bevel() -> Self {
267 Self::Bevel {
268 raw_children: OrderedRawChildren::default(),
269 }
270 }
271
272 pub fn miter(limit: Option<i32>) -> Result<Self> {
273 if let Some(limit) = limit {
274 validate_positive_percentage("miter", "lim", limit)?;
275 }
276 Ok(Self::Miter {
277 limit,
278 raw_children: OrderedRawChildren::default(),
279 })
280 }
281}
282
283#[derive(Clone, Copy, Debug, Eq, PartialEq)]
285pub enum LineEndType {
286 None,
287 Triangle,
288 Stealth,
289 Diamond,
290 Oval,
291 Arrow,
292}
293
294impl LineEndType {
295 fn parse(value: &str) -> Option<Self> {
296 match value {
297 "none" => Some(Self::None),
298 "triangle" => Some(Self::Triangle),
299 "stealth" => Some(Self::Stealth),
300 "diamond" => Some(Self::Diamond),
301 "oval" => Some(Self::Oval),
302 "arrow" => Some(Self::Arrow),
303 _ => None,
304 }
305 }
306
307 const fn as_str(self) -> &'static str {
308 match self {
309 Self::None => "none",
310 Self::Triangle => "triangle",
311 Self::Stealth => "stealth",
312 Self::Diamond => "diamond",
313 Self::Oval => "oval",
314 Self::Arrow => "arrow",
315 }
316 }
317}
318
319#[derive(Clone, Copy, Debug, Eq, PartialEq)]
321pub enum LineEndSize {
322 Small,
323 Medium,
324 Large,
325}
326
327impl LineEndSize {
328 fn parse(value: &str) -> Option<Self> {
329 match value {
330 "sm" => Some(Self::Small),
331 "med" => Some(Self::Medium),
332 "lg" => Some(Self::Large),
333 _ => None,
334 }
335 }
336
337 const fn as_str(self) -> &'static str {
338 match self {
339 Self::Small => "sm",
340 Self::Medium => "med",
341 Self::Large => "lg",
342 }
343 }
344}
345
346#[derive(Clone, Debug, Default, Eq, PartialEq)]
348pub struct LineEnd {
349 pub kind: Option<LineEndType>,
350 pub width: Option<LineEndSize>,
351 pub length: Option<LineEndSize>,
352 raw_children: OrderedRawChildren,
353}
354
355#[allow(non_camel_case_types)]
357#[derive(Clone, Debug, Default, Eq, PartialEq)]
358pub struct CT_LineProperties {
359 pub width: Option<u32>,
360 pub cap: Option<LineCap>,
361 pub fill: Option<Fill>,
362 pub dash: Option<LineDash>,
363 pub join: Option<LineJoin>,
364 pub head_end: Option<LineEnd>,
365 pub tail_end: Option<LineEnd>,
366 raw_attributes: Vec<(String, String)>,
367 raw_children: OrderedRawChildren,
368}
369
370impl CT_LineProperties {
371 pub fn from_xml(xml: &[u8]) -> Result<Self> {
373 let mut reader = Reader::from_reader(xml);
374 let mut buffer = Vec::new();
375 loop {
376 match reader
377 .read_event_into(&mut buffer)
378 .map_err(OxmlError::from)?
379 {
380 Event::Start(element) if matches_local_name(element.name().as_ref(), b"ln") => {
381 return Self::from_element(&mut reader, &element);
382 }
383 Event::Empty(element) if matches_local_name(element.name().as_ref(), b"ln") => {
384 return Self::from_start(&element);
385 }
386 Event::Start(element) | Event::Empty(element) => {
387 return Err(unexpected(&element));
388 }
389 Event::Eof => {
390 return Err(LineError::Xml(OxmlError::MissingElement(
391 "DrawingML line properties".to_owned(),
392 )));
393 }
394 _ => {}
395 }
396 buffer.clear();
397 }
398 }
399
400 fn from_start(start: &BytesStart<'_>) -> Result<Self> {
401 let width = optional_exact_parse(start, b"w")?;
402 if let Some(width) = width {
403 validate_line_width(width)?;
404 }
405 Ok(Self {
406 width,
407 cap: optional_exact_enum(start, b"cap", LineCap::parse)?,
408 raw_attributes: capture_raw_attributes(start, &[b"w", b"cap"])?,
409 ..Self::default()
410 })
411 }
412
413 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
414 let mut line = Self::from_start(start)?;
415 let mut boundary = 0;
416 let mut buffer = Vec::new();
417 loop {
418 match reader
419 .read_event_into(&mut buffer)
420 .map_err(OxmlError::from)?
421 {
422 Event::Start(element) if is_fill(element.name().as_ref()) => {
423 line.fill = Some(Fill::from_element(reader, &element)?);
424 boundary = 1;
425 }
426 Event::Empty(element) if is_fill(element.name().as_ref()) => {
427 line.fill = Some(Fill::from_empty_element(&element)?);
428 boundary = 1;
429 }
430 Event::Start(element)
431 if matches_local_name(element.name().as_ref(), b"prstDash") =>
432 {
433 line.dash = Some(LineDash::Preset(parse_preset_dash(reader, &element)?));
434 boundary = 2;
435 }
436 Event::Empty(element)
437 if matches_local_name(element.name().as_ref(), b"prstDash") =>
438 {
439 line.dash = Some(LineDash::Preset(parse_empty_preset_dash(&element)?));
440 boundary = 2;
441 }
442 Event::Start(element)
443 if matches_local_name(element.name().as_ref(), b"custDash") =>
444 {
445 line.dash = Some(LineDash::Custom(parse_custom_dash(reader)?));
446 boundary = 2;
447 }
448 Event::Empty(element)
449 if matches_local_name(element.name().as_ref(), b"custDash") =>
450 {
451 line.dash = Some(LineDash::Custom(CustomDash::default()));
452 boundary = 2;
453 }
454 Event::Start(element) if is_join(element.name().as_ref()) => {
455 line.join = Some(parse_join(reader, &element)?);
456 boundary = 3;
457 }
458 Event::Empty(element) if is_join(element.name().as_ref()) => {
459 line.join = Some(parse_empty_join(&element)?);
460 boundary = 3;
461 }
462 Event::Start(element)
463 if matches_local_name(element.name().as_ref(), b"headEnd") =>
464 {
465 line.head_end = Some(parse_line_end(reader, &element)?);
466 boundary = 4;
467 }
468 Event::Empty(element)
469 if matches_local_name(element.name().as_ref(), b"headEnd") =>
470 {
471 line.head_end = Some(parse_empty_line_end(&element)?);
472 boundary = 4;
473 }
474 Event::Start(element)
475 if matches_local_name(element.name().as_ref(), b"tailEnd") =>
476 {
477 line.tail_end = Some(parse_line_end(reader, &element)?);
478 boundary = 5;
479 }
480 Event::Empty(element)
481 if matches_local_name(element.name().as_ref(), b"tailEnd") =>
482 {
483 line.tail_end = Some(parse_empty_line_end(&element)?);
484 boundary = 5;
485 }
486 Event::Start(element) => line.raw_children.push(
487 raw_boundary(element.name().as_ref(), boundary),
488 capture_element(reader, &element)?,
489 ),
490 Event::Empty(element) => line.raw_children.push(
491 raw_boundary(element.name().as_ref(), boundary),
492 capture_empty_element(&element)?,
493 ),
494 Event::End(element) if matches_local_name(element.name().as_ref(), b"ln") => break,
495 Event::Eof => return Err(missing_end("ln")),
496 _ => {}
497 }
498 buffer.clear();
499 }
500 Ok(line)
501 }
502
503 pub fn to_xml(&self) -> Result<Vec<u8>> {
505 let mut writer = Writer::new(Vec::new());
506 self.write_xml(&mut writer)?;
507 Ok(writer.into_inner())
508 }
509
510 pub fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
512 if let Some(width) = self.width {
513 validate_line_width(width)?;
514 }
515 if matches!(self.fill.as_ref(), Some(Fill::Blip(_))) {
516 return Err(LineError::UnexpectedElement("a:blipFill".to_owned()));
517 }
518 let mut start = BytesStart::new("a:ln");
519 let width = self.width.map(|value| value.to_string());
520 if let Some(width) = width.as_deref() {
521 start.push_attribute(("w", width));
522 }
523 if let Some(cap) = self.cap {
524 start.push_attribute(("cap", cap.as_str()));
525 }
526 push_raw_attributes(&mut start, &self.raw_attributes);
527 if self.fill.is_none()
528 && self.dash.is_none()
529 && self.join.is_none()
530 && self.head_end.is_none()
531 && self.tail_end.is_none()
532 && self.raw_children.is_empty()
533 {
534 return write_empty(writer, start);
535 }
536
537 write_start(writer, start)?;
538 emit_raw(writer, self.raw_children.at(0))?;
539 if let Some(fill) = &self.fill {
540 fill.write_xml(writer)?;
541 }
542 emit_raw(writer, self.raw_children.at(1))?;
543 if let Some(dash) = &self.dash {
544 write_dash(writer, dash)?;
545 }
546 emit_raw(writer, self.raw_children.at(2))?;
547 if let Some(join) = &self.join {
548 write_join(writer, join)?;
549 }
550 emit_raw(writer, self.raw_children.at(3))?;
551 if let Some(end) = &self.head_end {
552 write_line_end(writer, "a:headEnd", end)?;
553 }
554 emit_raw(writer, self.raw_children.at(4))?;
555 if let Some(end) = &self.tail_end {
556 write_line_end(writer, "a:tailEnd", end)?;
557 }
558 emit_raw(writer, self.raw_children.at(5))?;
559 write_end(writer, "a:ln")
560 }
561
562 pub fn raw_children(&self) -> &OrderedRawChildren {
563 &self.raw_children
564 }
565}
566
567fn parse_empty_preset_dash(start: &BytesStart<'_>) -> Result<PresetDash> {
568 let value = get_attr(start, b"val").unwrap_or_else(|| "solid".to_owned());
569 let value = ST_PresetLineDashVal::parse(&value).ok_or_else(|| invalid(start, b"val", value))?;
570 Ok(PresetDash::new(value))
571}
572
573fn parse_preset_dash(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<PresetDash> {
574 let mut dash = parse_empty_preset_dash(start)?;
575 dash.raw_children = capture_all_children(reader, b"prstDash")?;
576 Ok(dash)
577}
578
579fn parse_custom_dash(reader: &mut Reader<&[u8]>) -> Result<CustomDash> {
580 let mut dash = CustomDash::default();
581 let mut buffer = Vec::new();
582 loop {
583 match reader
584 .read_event_into(&mut buffer)
585 .map_err(OxmlError::from)?
586 {
587 Event::Start(element) if matches_local_name(element.name().as_ref(), b"ds") => {
588 let mut stop = parse_empty_dash_stop(&element)?;
589 stop.raw_children = capture_all_children(reader, b"ds")?;
590 dash.stops.push(stop);
591 }
592 Event::Empty(element) if matches_local_name(element.name().as_ref(), b"ds") => {
593 dash.stops.push(parse_empty_dash_stop(&element)?);
594 }
595 Event::Start(element) => dash
596 .raw_children
597 .push(dash.stops.len(), capture_element(reader, &element)?),
598 Event::Empty(element) => dash
599 .raw_children
600 .push(dash.stops.len(), capture_empty_element(&element)?),
601 Event::End(element) if matches_local_name(element.name().as_ref(), b"custDash") => {
602 break;
603 }
604 Event::Eof => return Err(missing_end("custDash")),
605 _ => {}
606 }
607 buffer.clear();
608 }
609 Ok(dash)
610}
611
612fn parse_empty_dash_stop(start: &BytesStart<'_>) -> Result<DashStop> {
613 DashStop::new(required_parse(start, b"d")?, required_parse(start, b"sp")?)
614}
615
616fn write_dash<W: Write>(writer: &mut Writer<W>, dash: &LineDash) -> Result<()> {
617 match dash {
618 LineDash::Preset(dash) => {
619 let mut start = BytesStart::new("a:prstDash");
620 start.push_attribute(("val", dash.value.as_str()));
621 if dash.raw_children.is_empty() {
622 return write_empty(writer, start);
623 }
624 write_start(writer, start)?;
625 emit_raw(writer, dash.raw_children.at(0))?;
626 write_end(writer, "a:prstDash")
627 }
628 LineDash::Custom(dash) => {
629 if dash.stops.is_empty() && dash.raw_children.is_empty() {
630 return write_empty(writer, BytesStart::new("a:custDash"));
631 }
632 write_start(writer, BytesStart::new("a:custDash"))?;
633 for boundary in 0..=dash.stops.len() {
634 emit_raw(writer, dash.raw_children.at(boundary))?;
635 if let Some(stop) = dash.stops.get(boundary) {
636 write_dash_stop(writer, stop)?;
637 }
638 }
639 write_end(writer, "a:custDash")
640 }
641 }
642}
643
644fn write_dash_stop<W: Write>(writer: &mut Writer<W>, stop: &DashStop) -> Result<()> {
645 validate_positive_percentage("ds", "d", stop.dash)?;
646 validate_positive_percentage("ds", "sp", stop.space)?;
647 let dash = stop.dash.to_string();
648 let space = stop.space.to_string();
649 let mut start = BytesStart::new("a:ds");
650 start.push_attribute(("d", dash.as_str()));
651 start.push_attribute(("sp", space.as_str()));
652 if stop.raw_children.is_empty() {
653 return write_empty(writer, start);
654 }
655 write_start(writer, start)?;
656 emit_raw(writer, stop.raw_children.at(0))?;
657 write_end(writer, "a:ds")
658}
659
660fn parse_empty_join(start: &BytesStart<'_>) -> Result<LineJoin> {
661 match local_name(start.name().as_ref()) {
662 b"round" => Ok(LineJoin::round()),
663 b"bevel" => Ok(LineJoin::bevel()),
664 b"miter" => LineJoin::miter(optional_parse(start, b"lim")?),
665 _ => Err(unexpected(start)),
666 }
667}
668
669fn parse_join(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<LineJoin> {
670 let mut join = parse_empty_join(start)?;
671 let raw = capture_all_children(reader, local_name(start.name().as_ref()))?;
672 match &mut join {
673 LineJoin::Round { raw_children }
674 | LineJoin::Bevel { raw_children }
675 | LineJoin::Miter { raw_children, .. } => *raw_children = raw,
676 }
677 Ok(join)
678}
679
680fn write_join<W: Write>(writer: &mut Writer<W>, join: &LineJoin) -> Result<()> {
681 let (name, limit, raw_children) = match join {
682 LineJoin::Round { raw_children } => ("a:round", None, raw_children),
683 LineJoin::Bevel { raw_children } => ("a:bevel", None, raw_children),
684 LineJoin::Miter {
685 limit,
686 raw_children,
687 } => ("a:miter", *limit, raw_children),
688 };
689 if let Some(limit) = limit {
690 validate_positive_percentage("miter", "lim", limit)?;
691 }
692 let limit_text = limit.map(|value| value.to_string());
693 let mut start = BytesStart::new(name);
694 if let Some(limit) = limit_text.as_deref() {
695 start.push_attribute(("lim", limit));
696 }
697 if raw_children.is_empty() {
698 return write_empty(writer, start);
699 }
700 write_start(writer, start)?;
701 emit_raw(writer, raw_children.at(0))?;
702 write_end(writer, name)
703}
704
705fn parse_empty_line_end(start: &BytesStart<'_>) -> Result<LineEnd> {
706 Ok(LineEnd {
707 kind: optional_enum(start, b"type", LineEndType::parse)?,
708 width: optional_enum(start, b"w", LineEndSize::parse)?,
709 length: optional_enum(start, b"len", LineEndSize::parse)?,
710 raw_children: OrderedRawChildren::default(),
711 })
712}
713
714fn parse_line_end(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<LineEnd> {
715 let mut end = parse_empty_line_end(start)?;
716 end.raw_children = capture_all_children(reader, local_name(start.name().as_ref()))?;
717 Ok(end)
718}
719
720fn write_line_end<W: Write>(writer: &mut Writer<W>, name: &str, end: &LineEnd) -> Result<()> {
721 let mut start = BytesStart::new(name);
722 if let Some(kind) = end.kind {
723 start.push_attribute(("type", kind.as_str()));
724 }
725 if let Some(width) = end.width {
726 start.push_attribute(("w", width.as_str()));
727 }
728 if let Some(length) = end.length {
729 start.push_attribute(("len", length.as_str()));
730 }
731 if end.raw_children.is_empty() {
732 return write_empty(writer, start);
733 }
734 write_start(writer, start)?;
735 emit_raw(writer, end.raw_children.at(0))?;
736 write_end(writer, name)
737}
738
739fn capture_all_children(reader: &mut Reader<&[u8]>, end_name: &[u8]) -> Result<OrderedRawChildren> {
740 let mut raw_children = OrderedRawChildren::default();
741 let mut buffer = Vec::new();
742 loop {
743 match reader
744 .read_event_into(&mut buffer)
745 .map_err(OxmlError::from)?
746 {
747 Event::Start(element) => raw_children.push(0, capture_element(reader, &element)?),
748 Event::Empty(element) => raw_children.push(0, capture_empty_element(&element)?),
749 Event::End(element) if matches_local_name(element.name().as_ref(), end_name) => break,
750 Event::Eof => return Err(missing_end(&String::from_utf8_lossy(end_name))),
751 _ => {}
752 }
753 buffer.clear();
754 }
755 Ok(raw_children)
756}
757
758fn raw_boundary(name: &[u8], boundary: usize) -> usize {
761 if matches_local_name(name, b"extLst") {
762 5
763 } else {
764 boundary
765 }
766}
767
768fn is_fill(name: &[u8]) -> bool {
769 matches!(
770 local_name(name),
771 b"noFill" | b"solidFill" | b"gradFill" | b"pattFill"
772 )
773}
774
775fn is_join(name: &[u8]) -> bool {
776 matches!(local_name(name), b"round" | b"bevel" | b"miter")
777}
778
779fn validate_line_width(value: u32) -> Result<()> {
780 if value <= MAX_LINE_WIDTH_EMU {
781 Ok(())
782 } else {
783 Err(LineError::LineWidthOutOfRange(value))
784 }
785}
786
787fn validate_positive_percentage(element: &str, attribute: &str, value: i32) -> Result<()> {
788 if value > 0 {
789 Ok(())
790 } else {
791 Err(LineError::InvalidAttribute {
792 element: element.to_owned(),
793 attribute: attribute.to_owned(),
794 value: value.to_string(),
795 })
796 }
797}
798
799fn required_parse<T: std::str::FromStr>(start: &BytesStart<'_>, name: &[u8]) -> Result<T> {
800 let value = get_attr(start, name).ok_or_else(|| LineError::MissingAttribute {
801 element: String::from_utf8_lossy(local_name(start.name().as_ref())).into_owned(),
802 attribute: String::from_utf8_lossy(name).into_owned(),
803 })?;
804 value.parse().map_err(|_| invalid(start, name, value))
805}
806
807fn optional_parse<T: std::str::FromStr>(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<T>> {
808 get_attr(start, name)
809 .map(|value| value.parse().map_err(|_| invalid(start, name, value)))
810 .transpose()
811}
812
813fn optional_enum<T>(
814 start: &BytesStart<'_>,
815 name: &[u8],
816 parse: impl FnOnce(&str) -> Option<T>,
817) -> Result<Option<T>> {
818 get_attr(start, name)
819 .map(|value| parse(&value).ok_or_else(|| invalid(start, name, value)))
820 .transpose()
821}
822
823fn exact_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<String>> {
824 for attribute in start.attributes() {
825 let attribute = attribute.map_err(OxmlError::from)?;
826 if attribute.key.as_ref() == name {
827 let value = attribute
828 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
829 .map_err(OxmlError::from)?;
830 return Ok(Some(value.into_owned()));
831 }
832 }
833 Ok(None)
834}
835
836fn optional_exact_parse<T: std::str::FromStr>(
837 start: &BytesStart<'_>,
838 name: &[u8],
839) -> Result<Option<T>> {
840 exact_attr(start, name)?
841 .map(|value| value.parse().map_err(|_| invalid(start, name, value)))
842 .transpose()
843}
844
845fn optional_exact_enum<T>(
846 start: &BytesStart<'_>,
847 name: &[u8],
848 parse: impl FnOnce(&str) -> Option<T>,
849) -> Result<Option<T>> {
850 exact_attr(start, name)?
851 .map(|value| parse(&value).ok_or_else(|| invalid(start, name, value)))
852 .transpose()
853}
854
855fn capture_raw_attributes(
856 start: &BytesStart<'_>,
857 modelled: &[&[u8]],
858) -> Result<Vec<(String, String)>> {
859 let mut raw = Vec::new();
860 for attribute in start.attributes() {
861 let attribute = attribute.map_err(OxmlError::from)?;
862 if modelled.iter().any(|name| attribute.key.as_ref() == *name) {
863 continue;
864 }
865 let name = std::str::from_utf8(attribute.key.as_ref())
866 .map_err(OxmlError::from)?
867 .to_owned();
868 let value = attribute
869 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
870 .map_err(OxmlError::from)?
871 .into_owned();
872 raw.push((name, value));
873 }
874 Ok(raw)
875}
876
877fn push_raw_attributes(start: &mut BytesStart<'_>, attributes: &[(String, String)]) {
878 for (name, value) in attributes {
879 start.push_attribute((name.as_str(), value.as_str()));
880 }
881}
882
883fn invalid(start: &BytesStart<'_>, attribute: &[u8], value: String) -> LineError {
884 LineError::InvalidAttribute {
885 element: String::from_utf8_lossy(local_name(start.name().as_ref())).into_owned(),
886 attribute: String::from_utf8_lossy(attribute).into_owned(),
887 value,
888 }
889}
890
891fn unexpected(start: &BytesStart<'_>) -> LineError {
892 LineError::UnexpectedElement(String::from_utf8_lossy(start.name().as_ref()).into_owned())
893}
894
895fn missing_end(name: &str) -> LineError {
896 LineError::Xml(OxmlError::MissingElement(format!("closing a:{name}")))
897}
898
899fn emit_raw<'a, W: Write>(
900 writer: &mut Writer<W>,
901 children: impl Iterator<Item = &'a [u8]>,
902) -> Result<()> {
903 for child in children {
904 writer.get_mut().write_all(child).map_err(OxmlError::from)?;
905 }
906 Ok(())
907}
908
909fn write_start<W: Write>(writer: &mut Writer<W>, start: BytesStart<'_>) -> Result<()> {
910 writer
911 .write_event(Event::Start(start))
912 .map_err(OxmlError::from)?;
913 Ok(())
914}
915
916fn write_empty<W: Write>(writer: &mut Writer<W>, start: BytesStart<'_>) -> Result<()> {
917 writer
918 .write_event(Event::Empty(start))
919 .map_err(OxmlError::from)?;
920 Ok(())
921}
922
923fn write_end<W: Write>(writer: &mut Writer<W>, name: &str) -> Result<()> {
924 writer
925 .write_event(Event::End(BytesEnd::new(name)))
926 .map_err(OxmlError::from)?;
927 Ok(())
928}
929
930#[cfg(test)]
931mod tests {
932 use super::{
933 CT_LineProperties, LineCap, LineDash, LineEnd, LineEndSize, LineEndType, LineError,
934 LineJoin, ST_PresetLineDashVal,
935 };
936
937 #[test]
938 fn every_preset_line_dash_value_maps_to_a_dash_array() {
939 let expected: &[(&str, &[u16])] = &[
940 ("solid", &[]),
941 ("dot", &[1, 3]),
942 ("sysDot", &[1, 1]),
943 ("dash", &[4, 3]),
944 ("sysDash", &[3, 1]),
945 ("lgDash", &[8, 3]),
946 ("dashDot", &[4, 3, 1, 3]),
947 ("sysDashDot", &[3, 1, 1, 1]),
948 ("lgDashDot", &[8, 3, 1, 3]),
949 ("lgDashDotDot", &[8, 3, 1, 3, 1, 3]),
950 ("sysDashDotDot", &[3, 1, 1, 1, 1, 1]),
951 ];
952
953 assert_eq!(ST_PresetLineDashVal::ALL.len(), expected.len());
954 for (value, (token, dash_array)) in ST_PresetLineDashVal::ALL.iter().zip(expected) {
955 assert_eq!(value.as_str(), *token);
956 assert_eq!(value.dash_array(), *dash_array);
957 }
958 }
959
960 #[test]
961 fn line_root_attributes_round_trip_without_loss() {
962 let line = CT_LineProperties::from_xml(
963 br#"<q:ln w="12700" cap="rnd" cmpd="dbl" algn="ctr" x:w="999"/>"#,
964 )
965 .unwrap();
966 assert_eq!(line.width, Some(12_700));
967 assert_eq!(
968 line.to_xml().unwrap(),
969 br#"<a:ln w="12700" cap="rnd" cmpd="dbl" algn="ctr" x:w="999"/>"#
970 );
971 }
972
973 #[test]
974 fn line_properties_round_trip_width_fill_dash_cap_join_and_ends() {
975 let xml = br#"<z:ln w="12700" cap="rnd"><z:solidFill><z:schemeClr val="accent1"/></z:solidFill><z:custDash><z:ds d="200000" sp="100000"/><z:ds d="50000" sp="25000"/></z:custDash><z:miter lim="800000"/><z:headEnd type="triangle" w="lg" len="sm"/><z:tailEnd type="oval" w="med" len="lg"/></z:ln>"#;
976 let parsed = CT_LineProperties::from_xml(xml).unwrap();
977
978 assert_eq!(parsed.width, Some(12_700));
979 assert_eq!(parsed.cap, Some(LineCap::Round));
980 assert!(matches!(&parsed.dash, Some(LineDash::Custom(dash)) if dash.stops.len() == 2));
981 assert!(matches!(
982 parsed.join,
983 Some(LineJoin::Miter {
984 limit: Some(800_000),
985 ..
986 })
987 ));
988 assert!(
989 matches!(parsed.head_end, Some(ref end) if end.kind == Some(LineEndType::Triangle) && end.width == Some(LineEndSize::Large) && end.length == Some(LineEndSize::Small))
990 );
991 assert!(
992 matches!(parsed.tail_end, Some(ref end) if end.kind == Some(LineEndType::Oval) && end.width == Some(LineEndSize::Medium) && end.length == Some(LineEndSize::Large))
993 );
994
995 let written = parsed.to_xml().unwrap();
996 assert_eq!(CT_LineProperties::from_xml(&written).unwrap(), parsed);
997 }
998
999 #[test]
1000 fn line_properties_write_schema_order_and_preserve_unknown_children() {
1001 let xml = br#"<z:ln w="25400"><x:before x:id="1"/><z:solidFill><z:srgbClr val="102030"/></z:solidFill><x:afterFill><x:item>one & two</x:item><!--note--></x:afterFill><z:prstDash val="lgDashDot"><x:dashExt x:v="kept"/></z:prstDash><x:afterDash/><z:round><x:joinExt/></z:round><z:headEnd type="arrow"><x:headExt/></z:headEnd><x:betweenEnds/><z:tailEnd type="diamond"/><x:after/></z:ln>"#;
1002 let written = CT_LineProperties::from_xml(xml).unwrap().to_xml().unwrap();
1003
1004 assert_eq!(written, br#"<a:ln w="25400"><x:before x:id="1"/><a:solidFill><a:srgbClr val="102030"/></a:solidFill><x:afterFill><x:item>one & two</x:item><!--note--></x:afterFill><a:prstDash val="lgDashDot"><x:dashExt x:v="kept"/></a:prstDash><x:afterDash/><a:round><x:joinExt/></a:round><a:headEnd type="arrow"><x:headExt/></a:headEnd><x:betweenEnds/><a:tailEnd type="diamond"/><x:after/></a:ln>"#);
1005 }
1006
1007 #[test]
1008 fn line_extension_list_stays_last_when_ends_are_added() {
1009 let xml = br#"<a:ln><a:noFill/><a:bevel/><a:extLst><a:ext uri="{X}"/></a:extLst></a:ln>"#;
1010 let mut line = CT_LineProperties::from_xml(xml).unwrap();
1011 line.head_end = Some(LineEnd::default());
1012 line.tail_end = Some(LineEnd {
1013 kind: Some(LineEndType::Stealth),
1014 ..LineEnd::default()
1015 });
1016
1017 assert_eq!(
1018 line.to_xml().unwrap(),
1019 br#"<a:ln><a:noFill/><a:bevel/><a:headEnd/><a:tailEnd type="stealth"/><a:extLst><a:ext uri="{X}"/></a:extLst></a:ln>"#
1020 );
1021 }
1022
1023 #[test]
1024 fn malformed_line_values_return_errors_without_panicking() {
1025 let cases: &[&[u8]] = &[
1026 br#"<a:ln w="wide"/>"#,
1027 br#"<a:ln w="20116801"/>"#,
1028 br#"<a:ln cap="curved"/>"#,
1029 br#"<a:ln><a:prstDash val="longer"/></a:ln>"#,
1030 br#"<a:ln><a:custDash><a:ds sp="100000"/></a:custDash></a:ln>"#,
1031 br#"<a:ln><a:custDash><a:ds d="0" sp="100000"/></a:custDash></a:ln>"#,
1032 br#"<a:ln><a:miter lim="0"/></a:ln>"#,
1033 br#"<a:ln><a:headEnd type="spear"/></a:ln>"#,
1034 br#"<a:ln><a:tailEnd w="huge"/></a:ln>"#,
1035 ];
1036 for xml in cases {
1037 let result = std::panic::catch_unwind(|| CT_LineProperties::from_xml(xml));
1038 assert!(
1039 result.is_ok(),
1040 "line parser panicked for {}",
1041 String::from_utf8_lossy(xml)
1042 );
1043 assert!(
1044 result.unwrap().is_err(),
1045 "malformed line parsed successfully"
1046 );
1047 }
1048 assert!(matches!(
1049 CT_LineProperties::from_xml(cases[1]),
1050 Err(LineError::LineWidthOutOfRange(20_116_801))
1051 ));
1052 }
1053}