Skip to main content

svgtypes/
path.rs

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