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