Skip to main content

svgrtypes/
path.rs

1use std::hash::Hash;
2
3use crate::{tokens_helper::TokenizableNumber, Error, Stream};
4
5/// Representation of the path segment.
6///
7/// If you want to change the segment type (for example MoveTo to LineTo)
8/// you should create a new segment.
9/// But you still can change points or make segment relative or absolute.
10#[allow(missing_docs)]
11#[derive(Clone, Copy, PartialEq, Debug)]
12pub enum PathSegment {
13    MoveTo {
14        abs: bool,
15        x: f64,
16        y: f64,
17    },
18    LineTo {
19        abs: bool,
20        x: f64,
21        y: f64,
22    },
23    HorizontalLineTo {
24        abs: bool,
25        x: f64,
26    },
27    VerticalLineTo {
28        abs: bool,
29        y: f64,
30    },
31    CurveTo {
32        abs: bool,
33        x1: f64,
34        y1: f64,
35        x2: f64,
36        y2: f64,
37        x: f64,
38        y: f64,
39    },
40    SmoothCurveTo {
41        abs: bool,
42        x2: f64,
43        y2: f64,
44        x: f64,
45        y: f64,
46    },
47    Quadratic {
48        abs: bool,
49        x1: f64,
50        y1: f64,
51        x: f64,
52        y: f64,
53    },
54    SmoothQuadratic {
55        abs: bool,
56        x: f64,
57        y: f64,
58    },
59    EllipticalArc {
60        abs: bool,
61        rx: f64,
62        ry: f64,
63        x_axis_rotation: f64,
64        large_arc: bool,
65        sweep: bool,
66        x: f64,
67        y: f64,
68    },
69    ClosePath {
70        abs: bool,
71    },
72}
73
74impl Hash for PathSegment {
75    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
76        let discriminant = std::mem::discriminant(self);
77        discriminant.hash(state);
78
79        match self {
80            PathSegment::MoveTo { abs, x, y } => {
81                abs.hash(state);
82                x.to_bits().hash(state);
83                y.to_bits().hash(state);
84            }
85            PathSegment::LineTo { abs, x, y } => {
86                abs.hash(state);
87                x.to_bits().hash(state);
88                y.to_bits().hash(state);
89            }
90            PathSegment::HorizontalLineTo { abs, x } => {
91                abs.hash(state);
92                x.to_bits().hash(state);
93            }
94            PathSegment::VerticalLineTo { abs, y } => {
95                abs.hash(state);
96                y.to_bits().hash(state);
97            }
98            PathSegment::CurveTo {
99                abs,
100                x1,
101                y1,
102                x2,
103                y2,
104                x,
105                y,
106            } => {
107                abs.hash(state);
108                x1.to_bits().hash(state);
109                y1.to_bits().hash(state);
110                x2.to_bits().hash(state);
111                y2.to_bits().hash(state);
112                x.to_bits().hash(state);
113                y.to_bits().hash(state);
114            }
115            PathSegment::SmoothCurveTo { abs, x2, y2, x, y } => {
116                abs.hash(state);
117                x2.to_bits().hash(state);
118                y2.to_bits().hash(state);
119                x.to_bits().hash(state);
120                y.to_bits().hash(state);
121            }
122            PathSegment::Quadratic { abs, x1, y1, x, y } => {
123                abs.hash(state);
124                x1.to_bits().hash(state);
125                y1.to_bits().hash(state);
126                x.to_bits().hash(state);
127                y.to_bits().hash(state);
128            }
129            PathSegment::SmoothQuadratic { abs, x, y } => {
130                abs.hash(state);
131                x.to_bits().hash(state);
132                y.to_bits().hash(state);
133            }
134            PathSegment::EllipticalArc {
135                abs,
136                rx,
137                ry,
138                x_axis_rotation,
139                large_arc,
140                sweep,
141                x,
142                y,
143            } => {
144                abs.hash(state);
145                rx.to_bits().hash(state);
146                ry.to_bits().hash(state);
147                x_axis_rotation.to_bits().hash(state);
148                large_arc.hash(state);
149                sweep.hash(state);
150                x.to_bits().hash(state);
151                y.to_bits().hash(state);
152            }
153            PathSegment::ClosePath { abs } => {
154                abs.hash(state);
155            }
156        }
157    }
158}
159
160impl quote::ToTokens for PathSegment {
161    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
162        use quote::quote;
163        match self {
164            PathSegment::MoveTo { abs, x, y } => {
165                let x = TokenizableNumber(*x);
166                let y = TokenizableNumber(*y);
167                quote! { svgrtypes::PathSegment::MoveTo { abs: #abs, x: #x, y: #y } }
168            }
169            PathSegment::LineTo { abs, x, y } => {
170                let x = TokenizableNumber(*x);
171                let y = TokenizableNumber(*y);
172                quote! { svgrtypes::PathSegment::LineTo { abs: #abs, x: #x, y: #y } }
173            }
174            PathSegment::HorizontalLineTo { abs, x } => {
175                let x = TokenizableNumber(*x);
176                quote! { svgrtypes::PathSegment::HorizontalLineTo { abs: #abs, x: #x } }
177            }
178            PathSegment::VerticalLineTo { abs, y } => {
179                let y = TokenizableNumber(*y);
180                quote! { svgrtypes::PathSegment::VerticalLineTo { abs: #abs, y: #y } }
181            }
182            PathSegment::CurveTo { abs, x1, y1, x2, y2, x, y } => {
183                let x1 = TokenizableNumber(*x1);
184                let y1 = TokenizableNumber(*y1);
185                let x2 = TokenizableNumber(*x2);
186                let y2 = TokenizableNumber(*y2);
187                let x = TokenizableNumber(*x);
188                let y = TokenizableNumber(*y);
189                quote! { svgrtypes::PathSegment::CurveTo { abs: #abs, x1: #x1, y1: #y1, x2: #x2, y2: #y2, x: #x, y: #y } }
190            }
191            PathSegment::SmoothCurveTo { abs, x2, y2, x, y } => {
192                let x2 = TokenizableNumber(*x2);
193                let y2 = TokenizableNumber(*y2);
194                let x = TokenizableNumber(*x);
195                let y = TokenizableNumber(*y);
196                quote! { svgrtypes::PathSegment::SmoothCurveTo { abs: #abs, x2: #x2, y2: #y2, x: #x, y: #y } }
197            }
198            PathSegment::Quadratic { abs, x1, y1, x, y } => {
199                let x1 = TokenizableNumber(*x1);
200                let y1 = TokenizableNumber(*y1);
201                let x = TokenizableNumber(*x);
202                let y = TokenizableNumber(*y);
203                quote! { svgrtypes::PathSegment::Quadratic { abs: #abs, x1: #x1, y1: #y1, x: #x, y: #y } }
204            }
205            PathSegment::SmoothQuadratic { abs, x, y } => {
206                let x = TokenizableNumber(*x);
207                let y = TokenizableNumber(*y);
208                quote! { svgrtypes::PathSegment::SmoothQuadratic { abs: #abs, x: #x, y: #y } }
209            }
210            PathSegment::EllipticalArc { abs, rx, ry, x_axis_rotation, large_arc, sweep, x, y } => {
211                let rx = TokenizableNumber(*rx);
212                let ry = TokenizableNumber(*ry);
213                let x_axis_rotation = TokenizableNumber(*x_axis_rotation);
214                let x = TokenizableNumber(*x);
215                let y = TokenizableNumber(*y);
216                quote! { svgrtypes::PathSegment::EllipticalArc { abs: #abs, rx: #rx, ry: #ry, x_axis_rotation: #x_axis_rotation, large_arc: #large_arc, sweep: #sweep, x: #x, y: #y } }
217            }
218            PathSegment::ClosePath { abs } => {
219                quote! { svgrtypes::PathSegment::ClosePath { abs: #abs } }
220            }
221        }
222        .to_tokens(tokens)
223    }
224}
225
226/// A pull-based [path data] parser.
227///
228/// # Errors
229///
230/// - Most of the `Error` types can occur.
231///
232/// # Notes
233///
234/// The library does not support implicit commands, so they will be converted to an explicit one.
235/// It mostly affects an implicit MoveTo, which will be converted, according to the spec,
236/// into explicit LineTo.
237///
238/// Example: `M 10 20 30 40 50 60` -> `M 10 20 L 30 40 L 50 60`
239///
240/// # Examples
241///
242/// ```
243/// use svgrtypes::{PathParser, PathSegment};
244///
245/// let mut segments = Vec::new();
246/// for segment in PathParser::from("M10-20l30.1.5.1-20z") {
247///     segments.push(segment.unwrap());
248/// }
249///
250/// assert_eq!(segments, &[
251///     PathSegment::MoveTo { abs: true, x: 10.0, y: -20.0 },
252///     PathSegment::LineTo { abs: false, x: 30.1, y: 0.5 },
253///     PathSegment::LineTo { abs: false, x: 0.1, y: -20.0 },
254///     PathSegment::ClosePath { abs: false },
255/// ]);
256/// ```
257///
258/// [path data]: https://www.w3.org/TR/SVG2/paths.html#PathData
259#[derive(Clone, Copy, PartialEq, Eq, Debug)]
260pub struct PathParser<'a> {
261    stream: Stream<'a>,
262    prev_cmd: Option<u8>,
263}
264
265impl<'a> From<&'a str> for PathParser<'a> {
266    #[inline]
267    fn from(v: &'a str) -> Self {
268        PathParser {
269            stream: Stream::from(v),
270            prev_cmd: None,
271        }
272    }
273}
274
275impl<'a> Iterator for PathParser<'a> {
276    type Item = Result<PathSegment, Error>;
277
278    #[inline]
279    fn next(&mut self) -> Option<Self::Item> {
280        let s = &mut self.stream;
281
282        s.skip_spaces();
283
284        if s.at_end() {
285            return None;
286        }
287
288        let res = next_impl(s, &mut self.prev_cmd);
289        if res.is_err() {
290            s.jump_to_end();
291        }
292
293        Some(res)
294    }
295}
296
297fn next_impl(s: &mut Stream, prev_cmd: &mut Option<u8>) -> Result<PathSegment, Error> {
298    let start = s.pos();
299
300    let has_prev_cmd = prev_cmd.is_some();
301    let first_char = s.curr_byte_unchecked();
302
303    if !has_prev_cmd && !is_cmd(first_char) {
304        return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
305    }
306
307    if !has_prev_cmd && !matches!(first_char, b'M' | b'm') {
308        // The first segment must be a MoveTo.
309        return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
310    }
311
312    // TODO: simplify
313    let is_implicit_move_to;
314    let cmd: u8;
315    if is_cmd(first_char) {
316        is_implicit_move_to = false;
317        cmd = first_char;
318        s.advance(1);
319    } else if is_number_start(first_char) && has_prev_cmd {
320        // unwrap is safe, because we checked 'has_prev_cmd'
321        let p_cmd = prev_cmd.unwrap();
322
323        if p_cmd == b'Z' || p_cmd == b'z' {
324            // ClosePath cannot be followed by a number.
325            return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
326        }
327
328        if p_cmd == b'M' || p_cmd == b'm' {
329            // 'If a moveto is followed by multiple pairs of coordinates,
330            // the subsequent pairs are treated as implicit lineto commands.'
331            // So we parse them as LineTo.
332            is_implicit_move_to = true;
333            cmd = if is_absolute(p_cmd) { b'L' } else { b'l' };
334        } else {
335            is_implicit_move_to = false;
336            cmd = p_cmd;
337        }
338    } else {
339        return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
340    }
341
342    let cmdl = to_relative(cmd);
343    let absolute = is_absolute(cmd);
344    let token = match cmdl {
345        b'm' => PathSegment::MoveTo {
346            abs: absolute,
347            x: s.parse_list_number()?,
348            y: s.parse_list_number()?,
349        },
350        b'l' => PathSegment::LineTo {
351            abs: absolute,
352            x: s.parse_list_number()?,
353            y: s.parse_list_number()?,
354        },
355        b'h' => PathSegment::HorizontalLineTo {
356            abs: absolute,
357            x: s.parse_list_number()?,
358        },
359        b'v' => PathSegment::VerticalLineTo {
360            abs: absolute,
361            y: s.parse_list_number()?,
362        },
363        b'c' => PathSegment::CurveTo {
364            abs: absolute,
365            x1: s.parse_list_number()?,
366            y1: s.parse_list_number()?,
367            x2: s.parse_list_number()?,
368            y2: s.parse_list_number()?,
369            x: s.parse_list_number()?,
370            y: s.parse_list_number()?,
371        },
372        b's' => PathSegment::SmoothCurveTo {
373            abs: absolute,
374            x2: s.parse_list_number()?,
375            y2: s.parse_list_number()?,
376            x: s.parse_list_number()?,
377            y: s.parse_list_number()?,
378        },
379        b'q' => PathSegment::Quadratic {
380            abs: absolute,
381            x1: s.parse_list_number()?,
382            y1: s.parse_list_number()?,
383            x: s.parse_list_number()?,
384            y: s.parse_list_number()?,
385        },
386        b't' => PathSegment::SmoothQuadratic {
387            abs: absolute,
388            x: s.parse_list_number()?,
389            y: s.parse_list_number()?,
390        },
391        b'a' => {
392            // TODO: radius cannot be negative
393            PathSegment::EllipticalArc {
394                abs: absolute,
395                rx: s.parse_list_number()?,
396                ry: s.parse_list_number()?,
397                x_axis_rotation: s.parse_list_number()?,
398                large_arc: parse_flag(s)?,
399                sweep: parse_flag(s)?,
400                x: s.parse_list_number()?,
401                y: s.parse_list_number()?,
402            }
403        }
404        b'z' => PathSegment::ClosePath { abs: absolute },
405        _ => unreachable!(),
406    };
407
408    *prev_cmd = Some(if is_implicit_move_to {
409        if absolute {
410            b'M'
411        } else {
412            b'm'
413        }
414    } else {
415        cmd
416    });
417
418    Ok(token)
419}
420
421/// Returns `true` if the selected char is the command.
422#[rustfmt::skip]
423#[inline]
424fn is_cmd(c: u8) -> bool {
425    matches!(c,
426          b'M' | b'm'
427        | b'Z' | b'z'
428        | b'L' | b'l'
429        | b'H' | b'h'
430        | b'V' | b'v'
431        | b'C' | b'c'
432        | b'S' | b's'
433        | b'Q' | b'q'
434        | b'T' | b't'
435        | b'A' | b'a')
436}
437
438/// Returns `true` if the selected char is the absolute command.
439#[inline]
440fn is_absolute(c: u8) -> bool {
441    debug_assert!(is_cmd(c));
442    matches!(
443        c,
444        b'M' | b'Z' | b'L' | b'H' | b'V' | b'C' | b'S' | b'Q' | b'T' | b'A'
445    )
446}
447
448/// Converts the selected command char into the relative command char.
449#[inline]
450fn to_relative(c: u8) -> u8 {
451    debug_assert!(is_cmd(c));
452    match c {
453        b'M' => b'm',
454        b'Z' => b'z',
455        b'L' => b'l',
456        b'H' => b'h',
457        b'V' => b'v',
458        b'C' => b'c',
459        b'S' => b's',
460        b'Q' => b'q',
461        b'T' => b't',
462        b'A' => b'a',
463        _ => c,
464    }
465}
466
467#[inline]
468fn is_number_start(c: u8) -> bool {
469    matches!(c, b'0'..=b'9' | b'.' | b'-' | b'+')
470}
471
472// By the SVG spec 'large-arc' and 'sweep' must contain only one char
473// and can be written without any separators, e.g.: 10 20 30 01 10 20.
474fn parse_flag(s: &mut Stream) -> Result<bool, Error> {
475    s.skip_spaces();
476
477    let c = s.curr_byte()?;
478    match c {
479        b'0' | b'1' => {
480            s.advance(1);
481            if s.is_curr_byte_eq(b',') {
482                s.advance(1);
483            }
484            s.skip_spaces();
485
486            Ok(c == b'1')
487        }
488        _ => Err(Error::UnexpectedData(s.calc_char_pos_at(s.pos()))),
489    }
490}
491
492#[rustfmt::skip]
493#[cfg(test)]
494mod tests {
495    use super::*;
496
497    macro_rules! test {
498        ($name:ident, $text:expr, $( $seg:expr ),*) => (
499            #[test]
500            fn $name() {
501                let mut s = PathParser::from($text);
502                $(
503                    assert_eq!(s.next().unwrap().unwrap(), $seg);
504                )*
505
506                if let Some(res) = s.next() {
507                    assert!(res.is_err());
508                }
509            }
510        )
511    }
512
513    test!(null, "", );
514    test!(not_a_path, "q", );
515    test!(not_a_move_to, "L 20 30", );
516    test!(stop_on_err_1, "M 10 20 L 30 40 L 50",
517        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
518        PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 }
519    );
520
521    test!(move_to_1, "M 10 20", PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 });
522    test!(move_to_2, "m 10 20", PathSegment::MoveTo { abs: false, x: 10.0, y: 20.0 });
523    test!(move_to_3, "M 10 20 30 40 50 60",
524        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
525        PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
526        PathSegment::LineTo { abs: true, x: 50.0, y: 60.0 }
527    );
528    test!(move_to_4, "M 10 20 30 40 50 60 M 70 80 90 100 110 120",
529        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
530        PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
531        PathSegment::LineTo { abs: true, x: 50.0, y: 60.0 },
532        PathSegment::MoveTo { abs: true, x: 70.0, y: 80.0 },
533        PathSegment::LineTo { abs: true, x: 90.0, y: 100.0 },
534        PathSegment::LineTo { abs: true, x: 110.0, y: 120.0 }
535    );
536
537    test!(arc_to_1, "M 10 20 A 5 5 30 1 1 20 20",
538        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
539        PathSegment::EllipticalArc {
540            abs: true,
541            rx: 5.0, ry: 5.0,
542            x_axis_rotation: 30.0,
543            large_arc: true, sweep: true,
544            x: 20.0, y: 20.0
545        }
546    );
547
548    test!(arc_to_2, "M 10 20 a 5 5 30 0 0 20 20",
549        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
550        PathSegment::EllipticalArc {
551            abs: false,
552            rx: 5.0, ry: 5.0,
553            x_axis_rotation: 30.0,
554            large_arc: false, sweep: false,
555            x: 20.0, y: 20.0
556        }
557    );
558
559    test!(arc_to_10, "M10-20A5.5.3-4 010-.1",
560        PathSegment::MoveTo { abs: true, x: 10.0, y: -20.0 },
561        PathSegment::EllipticalArc {
562            abs: true,
563            rx: 5.5, ry: 0.3,
564            x_axis_rotation: -4.0,
565            large_arc: false, sweep: true,
566            x: 0.0, y: -0.1
567        }
568    );
569
570    test!(separator_1, "M 10 20 L 5 15 C 10 20 30 40 50 60",
571        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
572        PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
573        PathSegment::CurveTo {
574            abs: true,
575            x1: 10.0, y1: 20.0,
576            x2: 30.0, y2: 40.0,
577            x:  50.0, y:  60.0,
578        }
579    );
580
581    test!(separator_2, "M 10, 20 L 5, 15 C 10, 20 30, 40 50, 60",
582        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
583        PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
584        PathSegment::CurveTo {
585            abs: true,
586            x1: 10.0, y1: 20.0,
587            x2: 30.0, y2: 40.0,
588            x:  50.0, y:  60.0,
589        }
590    );
591
592    test!(separator_3, "M 10,20 L 5,15 C 10,20 30,40 50,60",
593        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
594        PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
595        PathSegment::CurveTo {
596            abs: true,
597            x1: 10.0, y1: 20.0,
598            x2: 30.0, y2: 40.0,
599            x:  50.0, y:  60.0,
600        }
601    );
602
603    test!(separator_4, "M10, 20 L5, 15 C10, 20 30 40 50 60",
604        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
605        PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
606        PathSegment::CurveTo {
607            abs: true,
608            x1: 10.0, y1: 20.0,
609            x2: 30.0, y2: 40.0,
610            x:  50.0, y:  60.0,
611        }
612    );
613
614    test!(separator_5, "M10 20V30H40V50H60Z",
615        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
616        PathSegment::VerticalLineTo { abs: true, y: 30.0 },
617        PathSegment::HorizontalLineTo { abs: true, x: 40.0 },
618        PathSegment::VerticalLineTo { abs: true, y: 50.0 },
619        PathSegment::HorizontalLineTo { abs: true, x: 60.0 },
620        PathSegment::ClosePath { abs: true }
621    );
622
623    test!(all_segments_1, "M 10 20 L 30 40 H 50 V 60 C 70 80 90 100 110 120 S 130 140 150 160
624        Q 170 180 190 200 T 210 220 A 50 50 30 1 1 230 240 Z",
625        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
626        PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
627        PathSegment::HorizontalLineTo { abs: true, x: 50.0 },
628        PathSegment::VerticalLineTo { abs: true, y: 60.0 },
629        PathSegment::CurveTo {
630            abs: true,
631            x1:  70.0, y1:  80.0,
632            x2:  90.0, y2: 100.0,
633            x:  110.0, y:  120.0,
634        },
635        PathSegment::SmoothCurveTo {
636            abs: true,
637            x2: 130.0, y2: 140.0,
638            x:  150.0, y:  160.0,
639        },
640        PathSegment::Quadratic {
641            abs: true,
642            x1: 170.0, y1: 180.0,
643            x:  190.0, y:  200.0,
644        },
645        PathSegment::SmoothQuadratic { abs: true, x: 210.0, y: 220.0 },
646        PathSegment::EllipticalArc {
647            abs: true,
648            rx: 50.0, ry: 50.0,
649            x_axis_rotation: 30.0,
650            large_arc: true, sweep: true,
651            x: 230.0, y: 240.0
652        },
653        PathSegment::ClosePath { abs: true }
654    );
655
656    test!(all_segments_2, "m 10 20 l 30 40 h 50 v 60 c 70 80 90 100 110 120 s 130 140 150 160
657        q 170 180 190 200 t 210 220 a 50 50 30 1 1 230 240 z",
658        PathSegment::MoveTo { abs: false, x: 10.0, y: 20.0 },
659        PathSegment::LineTo { abs: false, x: 30.0, y: 40.0 },
660        PathSegment::HorizontalLineTo { abs: false, x: 50.0 },
661        PathSegment::VerticalLineTo { abs: false, y: 60.0 },
662        PathSegment::CurveTo {
663            abs: false,
664            x1:  70.0, y1:  80.0,
665            x2:  90.0, y2: 100.0,
666            x:  110.0, y:  120.0,
667        },
668        PathSegment::SmoothCurveTo {
669            abs: false,
670            x2: 130.0, y2: 140.0,
671            x:  150.0, y:  160.0,
672        },
673        PathSegment::Quadratic {
674            abs: false,
675            x1: 170.0, y1: 180.0,
676            x:  190.0, y:  200.0,
677        },
678        PathSegment::SmoothQuadratic { abs: false, x: 210.0, y: 220.0 },
679        PathSegment::EllipticalArc {
680            abs: false,
681            rx: 50.0, ry: 50.0,
682            x_axis_rotation: 30.0,
683            large_arc: true, sweep: true,
684            x: 230.0, y: 240.0
685        },
686        PathSegment::ClosePath { abs: false }
687    );
688
689    test!(close_path_1, "M10 20 L 30 40 ZM 100 200 L 300 400",
690        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
691        PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
692        PathSegment::ClosePath { abs: true },
693        PathSegment::MoveTo { abs: true, x: 100.0, y: 200.0 },
694        PathSegment::LineTo { abs: true, x: 300.0, y: 400.0 }
695    );
696
697    test!(close_path_2, "M10 20 L 30 40 zM 100 200 L 300 400",
698        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
699        PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
700        PathSegment::ClosePath { abs: false },
701        PathSegment::MoveTo { abs: true, x: 100.0, y: 200.0 },
702        PathSegment::LineTo { abs: true, x: 300.0, y: 400.0 }
703    );
704
705    test!(close_path_3, "M10 20 L 30 40 Z Z Z",
706        PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
707        PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
708        PathSegment::ClosePath { abs: true },
709        PathSegment::ClosePath { abs: true },
710        PathSegment::ClosePath { abs: true }
711    );
712
713    // first token should be EndOfStream
714    test!(invalid_1, "M\t.", );
715
716    // ClosePath can't be followed by a number
717    test!(invalid_2, "M 0 0 Z 2",
718        PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 },
719        PathSegment::ClosePath { abs: true }
720    );
721
722    // ClosePath can be followed by any command
723    test!(invalid_3, "M 0 0 Z H 10",
724        PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 },
725        PathSegment::ClosePath { abs: true },
726        PathSegment::HorizontalLineTo { abs: true, x: 10.0 }
727    );
728}
729
730/// Representation of a simple path segment.
731#[allow(missing_docs)]
732#[derive(Clone, Copy, PartialEq, Debug)]
733pub enum SimplePathSegment {
734    MoveTo {
735        x: f64,
736        y: f64,
737    },
738    LineTo {
739        x: f64,
740        y: f64,
741    },
742    CurveTo {
743        x1: f64,
744        y1: f64,
745        x2: f64,
746        y2: f64,
747        x: f64,
748        y: f64,
749    },
750    Quadratic {
751        x1: f64,
752        y1: f64,
753        x: f64,
754        y: f64,
755    },
756    ClosePath,
757}
758
759/// A simplifying Path Data parser.
760///
761/// A more high-level Path Data parser on top of [`PathParser`] that provides:
762///
763/// - Relative to absolute segment coordinates conversion
764/// - ArcTo to CurveTos conversion
765/// - SmoothCurveTo and SmoothQuadratic conversion
766/// - HorizontalLineTo and VerticalLineTo to LineTo conversion
767///
768/// In the end, only absolute MoveTo, LineTo, CurveTo, Quadratic and ClosePath segments
769/// will be produced.
770#[derive(Clone, Debug)]
771pub struct SimplifyingPathParser<'a> {
772    parser: PathParser<'a>,
773
774    // Previous MoveTo coordinates.
775    prev_mx: f64,
776    prev_my: f64,
777
778    // Previous SmoothQuadratic coordinates.
779    prev_tx: f64,
780    prev_ty: f64,
781
782    // Previous coordinates.
783    prev_x: f64,
784    prev_y: f64,
785
786    prev_seg: PathSegment,
787    prev_simple_seg: Option<SimplePathSegment>,
788
789    buffer: Vec<SimplePathSegment>,
790}
791
792impl<'a> From<&'a str> for SimplifyingPathParser<'a> {
793    #[inline]
794    fn from(v: &'a str) -> Self {
795        SimplifyingPathParser {
796            parser: PathParser::from(v),
797            prev_mx: 0.0,
798            prev_my: 0.0,
799            prev_tx: 0.0,
800            prev_ty: 0.0,
801            prev_x: 0.0,
802            prev_y: 0.0,
803            prev_seg: PathSegment::MoveTo {
804                abs: true,
805                x: 0.0,
806                y: 0.0,
807            },
808            prev_simple_seg: None,
809            buffer: Vec::new(),
810        }
811    }
812}
813
814impl<'a> Iterator for SimplifyingPathParser<'a> {
815    type Item = Result<SimplePathSegment, Error>;
816
817    #[inline]
818    fn next(&mut self) -> Option<Self::Item> {
819        if !self.buffer.is_empty() {
820            return Some(Ok(self.buffer.remove(0)));
821        }
822
823        let segment = match self.parser.next()? {
824            Ok(v) => v,
825            Err(e) => return Some(Err(e)),
826        };
827
828        match segment {
829            PathSegment::MoveTo { abs, mut x, mut y } => {
830                if !abs {
831                    // When we get 'm'(relative) segment, which is not first segment - then it's
832                    // relative to a previous 'M'(absolute) segment, not to the first segment.
833                    if let Some(SimplePathSegment::ClosePath) = self.prev_simple_seg {
834                        x += self.prev_mx;
835                        y += self.prev_my;
836                    } else {
837                        x += self.prev_x;
838                        y += self.prev_y;
839                    }
840                }
841
842                self.buffer.push(SimplePathSegment::MoveTo { x, y });
843                self.prev_seg = segment;
844            }
845            PathSegment::LineTo { abs, mut x, mut y } => {
846                if !abs {
847                    x += self.prev_x;
848                    y += self.prev_y;
849                }
850
851                self.buffer.push(SimplePathSegment::LineTo { x, y });
852                self.prev_seg = segment;
853            }
854            PathSegment::HorizontalLineTo { abs, mut x } => {
855                if !abs {
856                    x += self.prev_x;
857                }
858
859                self.buffer
860                    .push(SimplePathSegment::LineTo { x, y: self.prev_y });
861                self.prev_seg = segment;
862            }
863            PathSegment::VerticalLineTo { abs, mut y } => {
864                if !abs {
865                    y += self.prev_y;
866                }
867
868                self.buffer
869                    .push(SimplePathSegment::LineTo { x: self.prev_x, y });
870                self.prev_seg = segment;
871            }
872            PathSegment::CurveTo {
873                abs,
874                mut x1,
875                mut y1,
876                mut x2,
877                mut y2,
878                mut x,
879                mut y,
880            } => {
881                if !abs {
882                    x1 += self.prev_x;
883                    y1 += self.prev_y;
884                    x2 += self.prev_x;
885                    y2 += self.prev_y;
886                    x += self.prev_x;
887                    y += self.prev_y;
888                }
889
890                self.buffer.push(SimplePathSegment::CurveTo {
891                    x1,
892                    y1,
893                    x2,
894                    y2,
895                    x,
896                    y,
897                });
898
899                // Remember as absolute.
900                self.prev_seg = PathSegment::CurveTo {
901                    abs: true,
902                    x1,
903                    y1,
904                    x2,
905                    y2,
906                    x,
907                    y,
908                };
909            }
910            PathSegment::SmoothCurveTo {
911                abs,
912                mut x2,
913                mut y2,
914                mut x,
915                mut y,
916            } => {
917                // 'The first control point is assumed to be the reflection of the second control
918                // point on the previous command relative to the current point.
919                // (If there is no previous command or if the previous command
920                // was not an C, c, S or s, assume the first control point is
921                // coincident with the current point.)'
922                let (x1, y1) = match self.prev_seg {
923                    PathSegment::CurveTo { x2, y2, x, y, .. }
924                    | PathSegment::SmoothCurveTo { x2, y2, x, y, .. } => {
925                        (x * 2.0 - x2, y * 2.0 - y2)
926                    }
927                    _ => (self.prev_x, self.prev_y),
928                };
929
930                if !abs {
931                    x2 += self.prev_x;
932                    y2 += self.prev_y;
933                    x += self.prev_x;
934                    y += self.prev_y;
935                }
936
937                self.buffer.push(SimplePathSegment::CurveTo {
938                    x1,
939                    y1,
940                    x2,
941                    y2,
942                    x,
943                    y,
944                });
945
946                // Remember as absolute.
947                self.prev_seg = PathSegment::SmoothCurveTo {
948                    abs: true,
949                    x2,
950                    y2,
951                    x,
952                    y,
953                };
954            }
955            PathSegment::Quadratic {
956                abs,
957                mut x1,
958                mut y1,
959                mut x,
960                mut y,
961            } => {
962                if !abs {
963                    x1 += self.prev_x;
964                    y1 += self.prev_y;
965                    x += self.prev_x;
966                    y += self.prev_y;
967                }
968
969                self.buffer
970                    .push(SimplePathSegment::Quadratic { x1, y1, x, y });
971
972                // Remember as absolute.
973                self.prev_seg = PathSegment::Quadratic {
974                    abs: true,
975                    x1,
976                    y1,
977                    x,
978                    y,
979                };
980            }
981            PathSegment::SmoothQuadratic { abs, mut x, mut y } => {
982                // 'The control point is assumed to be the reflection of
983                // the control point on the previous command relative to
984                // the current point. (If there is no previous command or
985                // if the previous command was not a Q, q, T or t, assume
986                // the control point is coincident with the current point.)'
987                let (x1, y1) = match self.prev_seg {
988                    PathSegment::Quadratic { x1, y1, x, y, .. } => (x * 2.0 - x1, y * 2.0 - y1),
989                    PathSegment::SmoothQuadratic { x, y, .. } => {
990                        (x * 2.0 - self.prev_tx, y * 2.0 - self.prev_ty)
991                    }
992                    _ => (self.prev_x, self.prev_y),
993                };
994
995                self.prev_tx = x1;
996                self.prev_ty = y1;
997
998                if !abs {
999                    x += self.prev_x;
1000                    y += self.prev_y;
1001                }
1002
1003                self.buffer
1004                    .push(SimplePathSegment::Quadratic { x1, y1, x, y });
1005
1006                // Remember as absolute.
1007                self.prev_seg = PathSegment::SmoothQuadratic { abs: true, x, y };
1008            }
1009            PathSegment::EllipticalArc {
1010                abs,
1011                rx,
1012                ry,
1013                x_axis_rotation,
1014                large_arc,
1015                sweep,
1016                mut x,
1017                mut y,
1018            } => {
1019                if !abs {
1020                    x += self.prev_x;
1021                    y += self.prev_y;
1022                }
1023
1024                let svg_arc = kurbo::SvgArc {
1025                    from: kurbo::Point::new(self.prev_x, self.prev_y),
1026                    to: kurbo::Point::new(x, y),
1027                    radii: kurbo::Vec2::new(rx, ry),
1028                    x_rotation: x_axis_rotation.to_radians(),
1029                    large_arc,
1030                    sweep,
1031                };
1032
1033                match kurbo::Arc::from_svg_arc(&svg_arc) {
1034                    Some(arc) => {
1035                        arc.to_cubic_beziers(0.1, |p1, p2, p| {
1036                            self.buffer.push(SimplePathSegment::CurveTo {
1037                                x1: p1.x,
1038                                y1: p1.y,
1039                                x2: p2.x,
1040                                y2: p2.y,
1041                                x: p.x,
1042                                y: p.y,
1043                            });
1044                        });
1045                    }
1046                    None => {
1047                        self.buffer.push(SimplePathSegment::LineTo { x, y });
1048                    }
1049                }
1050
1051                self.prev_seg = segment;
1052            }
1053            PathSegment::ClosePath { .. } => {
1054                if let Some(SimplePathSegment::ClosePath) = self.prev_simple_seg {
1055                    // Do not add sequential ClosePath segments.
1056                    // Otherwise it will break marker rendering.
1057                } else {
1058                    self.buffer.push(SimplePathSegment::ClosePath);
1059                }
1060
1061                self.prev_seg = segment;
1062            }
1063        }
1064
1065        // Remember last position.
1066        if let Some(new_segment) = self.buffer.last() {
1067            self.prev_simple_seg = Some(*new_segment);
1068
1069            match *new_segment {
1070                SimplePathSegment::MoveTo { x, y } => {
1071                    self.prev_x = x;
1072                    self.prev_y = y;
1073                    self.prev_mx = self.prev_x;
1074                    self.prev_my = self.prev_y;
1075                }
1076                SimplePathSegment::LineTo { x, y } => {
1077                    self.prev_x = x;
1078                    self.prev_y = y;
1079                }
1080                SimplePathSegment::CurveTo { x, y, .. } => {
1081                    self.prev_x = x;
1082                    self.prev_y = y;
1083                }
1084                SimplePathSegment::Quadratic { x, y, .. } => {
1085                    self.prev_x = x;
1086                    self.prev_y = y;
1087                }
1088                SimplePathSegment::ClosePath => {
1089                    // ClosePath moves us to the last MoveTo coordinate,
1090                    // not previous.
1091                    self.prev_x = self.prev_mx;
1092                    self.prev_y = self.prev_my;
1093                }
1094            }
1095        }
1096
1097        if self.buffer.is_empty() {
1098            return self.next();
1099        }
1100
1101        Some(Ok(self.buffer.remove(0)))
1102    }
1103}
1104
1105#[rustfmt::skip]
1106#[cfg(test)]
1107mod simple_tests {
1108    use super::*;
1109
1110    macro_rules! test {
1111        ($name:ident, $text:expr, $( $seg:expr ),*) => (
1112            #[test]
1113            fn $name() {
1114                let mut s = SimplifyingPathParser::from($text);
1115                $(
1116                    assert_eq!(s.next().unwrap().unwrap(), $seg);
1117                )*
1118
1119                if let Some(res) = s.next() {
1120                    assert!(res.is_err());
1121                }
1122            }
1123        )
1124    }
1125
1126    test!(ignore_duplicated_close_paths, "M 10 20 L 30 40 Z Z Z Z",
1127        SimplePathSegment::MoveTo { x: 10.0, y: 20.0 },
1128        SimplePathSegment::LineTo { x: 30.0, y: 40.0 },
1129        SimplePathSegment::ClosePath
1130    );
1131
1132    test!(relative_move_to, "m 30 40 110 120 -20 -130",
1133        SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1134        SimplePathSegment::LineTo { x: 140.0, y: 160.0 },
1135        SimplePathSegment::LineTo { x: 120.0, y: 30.0 }
1136    );
1137
1138    test!(smooth_curve_to_after_move_to, "M 30 40 S 171 45 180 155",
1139        SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1140        SimplePathSegment::CurveTo { x1: 30.0, y1: 40.0, x2: 171.0, y2: 45.0, x: 180.0, y: 155.0 }
1141    );
1142
1143    test!(smooth_curve_to_after_curve_to, "M 30 40 C 16 137 171 45 100 90 S 171 45 180 155",
1144        SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1145        SimplePathSegment::CurveTo { x1: 16.0, y1: 137.0, x2: 171.0, y2: 45.0, x: 100.0, y: 90.0 },
1146        SimplePathSegment::CurveTo { x1: 29.0, y1: 135.0, x2: 171.0, y2: 45.0, x: 180.0, y: 155.0 }
1147    );
1148
1149    test!(smooth_quadratic_after_move_to, "M 30 40 T 180 155",
1150        SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1151        SimplePathSegment::Quadratic { x1: 30.0, y1: 40.0, x: 180.0, y: 155.0 }
1152    );
1153
1154    test!(smooth_quadratic_after_quadratic, "M 30 40 Q 171 45 100 90 T 160 180",
1155        SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1156        SimplePathSegment::Quadratic { x1: 171.0, y1: 45.0, x: 100.0, y: 90.0 },
1157        SimplePathSegment::Quadratic { x1: 29.0, y1: 135.0, x: 160.0, y: 180.0 }
1158    );
1159
1160    test!(relative_smooth_quadratic_after_quadratic, "M 30 40 Q 171 45 100 90 t 60 80",
1161        SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1162        SimplePathSegment::Quadratic { x1: 171.0, y1: 45.0, x: 100.0, y: 90.0 },
1163        SimplePathSegment::Quadratic { x1: 29.0, y1: 135.0, x: 160.0, y: 170.0 }
1164    );
1165
1166    test!(relative_smooth_quadratic_after_relative_quadratic, "M 30 40 q 171 45 50 40 t 60 80",
1167        SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1168        SimplePathSegment::Quadratic { x1: 201.0, y1: 85.0, x: 80.0, y: 80.0 },
1169        SimplePathSegment::Quadratic { x1: -41.0, y1: 75.0, x: 140.0, y: 160.0 }
1170    );
1171
1172    test!(smooth_quadratic_after_smooth_quadratic, "M 30 30 T 40 140 T 170 30",
1173        SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1174        SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1175        SimplePathSegment::Quadratic { x1: 50.0, y1: 250.0, x: 170.0, y: 30.0 }
1176    );
1177
1178    test!(smooth_quadratic_after_relative_smooth_quadratic, "M 30 30 T 40 140 t 100 -30",
1179        SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1180        SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1181        SimplePathSegment::Quadratic { x1: 50.0, y1: 250.0, x: 140.0, y: 110.0 }
1182    );
1183
1184    test!(smooth_quadratic_after_relative_quadratic, "M 30 30 T 40 140 q 30 100 120 -30",
1185        SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1186        SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1187        SimplePathSegment::Quadratic { x1: 70.0, y1: 240.0, x: 160.0, y: 110.0 }
1188    );
1189
1190    test!(smooth_quadratic_after_relative_smooth_curve_to, "M 30 30 T 40 170 s 90 -20 90 -90",
1191        SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1192        SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 170.0 },
1193        SimplePathSegment::CurveTo { x1: 40.0, y1: 170.0, x2: 130.0, y2: 150.0, x: 130.0, y: 80.0 }
1194    );
1195
1196    test!(quadratic_after_smooth_quadratic, "M 30 30 T 40 140 Q 80 180 170 30",
1197        SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1198        SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1199        SimplePathSegment::Quadratic { x1: 80.0, y1: 180.0, x: 170.0, y: 30.0 }
1200    );
1201
1202    test!(arc_to, "M 30 40 A 40 30 20 1 1 150 100",
1203        SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1204        SimplePathSegment::CurveTo {
1205            x1: 44.74826984236894, y1: 15.992274712892893,
1206            x2: 83.56702078968499, y2: 9.961625634418603,
1207            x: 116.70410629329004, y: 26.53016838622112
1208        },
1209        SimplePathSegment::CurveTo {
1210            x1: 149.8411917968951, y1: 43.09871113802364,
1211            x2: 164.74827129549442, y2: 75.99227543945563,
1212            x: 150.0, y: 100.0
1213        }
1214    );
1215}