svgtypes/
path.rs

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