animate/path/
mod.rs

1mod angle;
2mod error;
3mod length;
4mod options;
5mod parser;
6mod segment;
7mod stream;
8mod writer;
9
10pub use self::angle::*;
11pub use self::error::*;
12pub use self::length::*;
13pub use self::options::*;
14pub use self::parser::*;
15pub use self::segment::*;
16pub use self::stream::*;
17
18use float_cmp::ApproxEqUlps;
19use std::fmt;
20use std::io::Write;
21
22/// Representation of the SVG [path data].
23///
24/// [path data]: https://www.w3.org/TR/SVG11/paths.html#PathData
25#[derive(Clone, PartialEq, Default)]
26pub struct Path(pub Vec<PathSegment>);
27
28impl Path {
29    /// Constructs a new path.
30    #[inline]
31    pub fn new() -> Self {
32        Path(Vec::new())
33    }
34
35    /// Constructs a new path with a specified capacity.
36    #[inline]
37    pub fn with_capacity(capacity: usize) -> Self {
38        Path(Vec::with_capacity(capacity))
39    }
40
41    /// Converts path's segments into absolute one in-place.
42    ///
43    /// Original segments can be mixed (relative, absolute).
44    pub fn conv_to_absolute(&mut self) {
45        // position of the previous segment
46        let mut prev_x = 0.0;
47        let mut prev_y = 0.0;
48
49        // Position of the previous MoveTo segment.
50        // When we get 'm'(relative) segment, which is not first segment - then it's
51        // relative to previous 'M'(absolute) segment, not to first segment.
52        let mut prev_mx = 0.0;
53        let mut prev_my = 0.0;
54
55        let mut prev_cmd = PathCommand::MoveTo;
56        for seg in self.iter_mut() {
57            if seg.cmd() == PathCommand::ClosePath {
58                prev_x = prev_mx;
59                prev_y = prev_my;
60
61                seg.set_absolute(true);
62                continue;
63            }
64
65            let offset_x;
66            let offset_y;
67            if seg.is_relative() {
68                if seg.cmd() == PathCommand::MoveTo && prev_cmd == PathCommand::ClosePath {
69                    offset_x = prev_mx;
70                    offset_y = prev_my;
71                } else {
72                    offset_x = prev_x;
73                    offset_y = prev_y;
74                }
75            } else {
76                offset_x = 0.0;
77                offset_y = 0.0;
78            }
79
80            if seg.is_relative() {
81                shift_segment_data(seg, offset_x, offset_y);
82            }
83
84            if seg.cmd() == PathCommand::MoveTo {
85                prev_mx = seg.x().unwrap();
86                prev_my = seg.y().unwrap();
87            }
88
89            seg.set_absolute(true);
90
91            if seg.cmd() == PathCommand::HorizontalLineTo {
92                prev_x = seg.x().unwrap();
93            } else if seg.cmd() == PathCommand::VerticalLineTo {
94                prev_y = seg.y().unwrap();
95            } else {
96                prev_x = seg.x().unwrap();
97                prev_y = seg.y().unwrap();
98            }
99
100            prev_cmd = seg.cmd();
101        }
102    }
103
104    /// Converts path's segments into relative one in-place.
105    ///
106    /// Original segments can be mixed (relative, absolute).
107    pub fn conv_to_relative(&mut self) {
108        // NOTE: this method may look like 'conv_to_absolute', but it's a bit different.
109
110        // position of the previous segment
111        let mut prev_x = 0.0;
112        let mut prev_y = 0.0;
113
114        // Position of the previous MoveTo segment.
115        // When we get 'm'(relative) segment, which is not first segment - then it's
116        // relative to previous 'M'(absolute) segment, not to first segment.
117        let mut prev_mx = 0.0;
118        let mut prev_my = 0.0;
119
120        let mut prev_cmd = PathCommand::MoveTo;
121        for seg in self.iter_mut() {
122            if seg.cmd() == PathCommand::ClosePath {
123                prev_x = prev_mx;
124                prev_y = prev_my;
125
126                seg.set_absolute(false);
127                continue;
128            }
129
130            let offset_x;
131            let offset_y;
132            if seg.is_absolute() {
133                if seg.cmd() == PathCommand::MoveTo && prev_cmd == PathCommand::ClosePath {
134                    offset_x = prev_mx;
135                    offset_y = prev_my;
136                } else {
137                    offset_x = prev_x;
138                    offset_y = prev_y;
139                }
140            } else {
141                offset_x = 0.0;
142                offset_y = 0.0;
143            }
144
145            // unlike in 'to_absolute', we should take prev values before changing segment data
146            if seg.is_absolute() {
147                if seg.cmd() == PathCommand::HorizontalLineTo {
148                    prev_x = seg.x().unwrap();
149                } else if seg.cmd() == PathCommand::VerticalLineTo {
150                    prev_y = seg.y().unwrap();
151                } else {
152                    prev_x = seg.x().unwrap();
153                    prev_y = seg.y().unwrap();
154                }
155            } else {
156                if seg.cmd() == PathCommand::HorizontalLineTo {
157                    prev_x += seg.x().unwrap();
158                } else if seg.cmd() == PathCommand::VerticalLineTo {
159                    prev_y += seg.y().unwrap();
160                } else {
161                    prev_x += seg.x().unwrap();
162                    prev_y += seg.y().unwrap();
163                }
164            }
165
166            if seg.cmd() == PathCommand::MoveTo {
167                if seg.is_absolute() {
168                    prev_mx = seg.x().unwrap();
169                    prev_my = seg.y().unwrap();
170                } else {
171                    prev_mx += seg.x().unwrap();
172                    prev_my += seg.y().unwrap();
173                }
174            }
175
176            if seg.is_absolute() {
177                shift_segment_data(seg, -offset_x, -offset_y);
178            }
179
180            seg.set_absolute(false);
181
182            prev_cmd = seg.cmd();
183        }
184    }
185
186    /// Appends an absolute MoveTo segment.
187    #[inline]
188    pub fn push_move_to(&mut self, x: f64, y: f64) {
189        self.push(PathSegment::MoveTo { abs: true, x, y });
190    }
191
192    /// Appends a relative MoveTo segment.
193    #[inline]
194    pub fn push_rel_move_to(&mut self, x: f64, y: f64) {
195        self.push(PathSegment::MoveTo { abs: false, x, y });
196    }
197
198    /// Appends an absolute ClosePath segment.
199    #[inline]
200    pub fn push_close_path(&mut self) {
201        self.push(PathSegment::ClosePath { abs: true });
202    }
203
204    /// Appends a relative ClosePath segment.
205    #[inline]
206    pub fn push_rel_close_path(&mut self) {
207        self.push(PathSegment::ClosePath { abs: false });
208    }
209
210    /// Appends an absolute LineTo segment.
211    #[inline]
212    pub fn push_line_to(&mut self, x: f64, y: f64) {
213        self.push(PathSegment::LineTo { abs: true, x, y });
214    }
215
216    /// Appends a relative LineTo segment.
217    #[inline]
218    pub fn push_rel_line_to(&mut self, x: f64, y: f64) {
219        self.push(PathSegment::LineTo { abs: false, x, y });
220    }
221
222    /// Appends an absolute HorizontalLineTo segment.
223    #[inline]
224    pub fn push_hline_to(&mut self, x: f64) {
225        self.push(PathSegment::HorizontalLineTo { abs: true, x });
226    }
227
228    /// Appends a relative HorizontalLineTo segment.
229    #[inline]
230    pub fn push_rel_hline_to(&mut self, x: f64) {
231        self.push(PathSegment::HorizontalLineTo { abs: false, x });
232    }
233
234    /// Appends an absolute VerticalLineTo segment.
235    #[inline]
236    pub fn push_vline_to(&mut self, y: f64) {
237        self.push(PathSegment::VerticalLineTo { abs: true, y });
238    }
239
240    /// Appends a relative VerticalLineTo segment.
241    #[inline]
242    pub fn push_rel_vline_to(&mut self, y: f64) {
243        self.push(PathSegment::VerticalLineTo { abs: false, y });
244    }
245
246    /// Appends an absolute CurveTo segment.
247    #[inline]
248    pub fn push_curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64) {
249        self.push(PathSegment::CurveTo {
250            abs: true,
251            x1,
252            y1,
253            x2,
254            y2,
255            x,
256            y,
257        });
258    }
259
260    /// Appends a relative CurveTo segment.
261    #[inline]
262    pub fn push_rel_curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64) {
263        self.push(PathSegment::CurveTo {
264            abs: false,
265            x1,
266            y1,
267            x2,
268            y2,
269            x,
270            y,
271        });
272    }
273
274    /// Appends an absolute SmoothCurveTo segment.
275    #[inline]
276    pub fn push_smooth_curve_to(&mut self, x2: f64, y2: f64, x: f64, y: f64) {
277        self.push(PathSegment::SmoothCurveTo {
278            abs: true,
279            x2,
280            y2,
281            x,
282            y,
283        });
284    }
285
286    /// Appends a relative SmoothCurveTo segment.
287    #[inline]
288    pub fn push_rel_smooth_curve_to(&mut self, x2: f64, y2: f64, x: f64, y: f64) {
289        self.push(PathSegment::SmoothCurveTo {
290            abs: false,
291            x2,
292            y2,
293            x,
294            y,
295        });
296    }
297
298    /// Appends an absolute QuadTo segment.
299    #[inline]
300    pub fn push_quad_to(&mut self, x1: f64, y1: f64, x: f64, y: f64) {
301        self.push(PathSegment::Quadratic {
302            abs: true,
303            x1,
304            y1,
305            x,
306            y,
307        });
308    }
309
310    /// Appends a relative QuadTo segment.
311    #[inline]
312    pub fn push_rel_quad_to(&mut self, x1: f64, y1: f64, x: f64, y: f64) {
313        self.push(PathSegment::Quadratic {
314            abs: false,
315            x1,
316            y1,
317            x,
318            y,
319        });
320    }
321
322    /// Appends an absolute SmoothQuadTo segment.
323    #[inline]
324    pub fn push_smooth_quad_to(&mut self, x: f64, y: f64) {
325        self.push(PathSegment::SmoothQuadratic { abs: true, x, y });
326    }
327
328    /// Appends a relative SmoothQuadTo segment.
329    #[inline]
330    pub fn push_rel_smooth_quad_to(&mut self, x: f64, y: f64) {
331        self.push(PathSegment::SmoothQuadratic { abs: false, x, y });
332    }
333
334    /// Appends an absolute ArcTo segment.
335    #[inline]
336    pub fn push_arc_to(
337        &mut self,
338        rx: f64,
339        ry: f64,
340        x_axis_rotation: f64,
341        large_arc: bool,
342        sweep: bool,
343        x: f64,
344        y: f64,
345    ) {
346        self.push(PathSegment::EllipticalArc {
347            abs: true,
348            rx,
349            ry,
350            x_axis_rotation,
351            large_arc,
352            sweep,
353            x,
354            y,
355        });
356    }
357
358    /// Appends a relative ArcTo segment.
359    #[inline]
360    pub fn push_rel_arc_to(
361        &mut self,
362        rx: f64,
363        ry: f64,
364        x_axis_rotation: f64,
365        large_arc: bool,
366        sweep: bool,
367        x: f64,
368        y: f64,
369    ) {
370        self.push(PathSegment::EllipticalArc {
371            abs: false,
372            rx,
373            ry,
374            x_axis_rotation,
375            large_arc,
376            sweep,
377            x,
378            y,
379        });
380    }
381}
382
383fn shift_segment_data(d: &mut PathSegment, offset_x: f64, offset_y: f64) {
384    match *d {
385        PathSegment::MoveTo {
386            ref mut x,
387            ref mut y,
388            ..
389        } => {
390            *x += offset_x;
391            *y += offset_y;
392        }
393        PathSegment::LineTo {
394            ref mut x,
395            ref mut y,
396            ..
397        } => {
398            *x += offset_x;
399            *y += offset_y;
400        }
401        PathSegment::HorizontalLineTo { ref mut x, .. } => {
402            *x += offset_x;
403        }
404        PathSegment::VerticalLineTo { ref mut y, .. } => {
405            *y += offset_y;
406        }
407        PathSegment::CurveTo {
408            ref mut x1,
409            ref mut y1,
410            ref mut x2,
411            ref mut y2,
412            ref mut x,
413            ref mut y,
414            ..
415        } => {
416            *x1 += offset_x;
417            *y1 += offset_y;
418            *x2 += offset_x;
419            *y2 += offset_y;
420            *x += offset_x;
421            *y += offset_y;
422        }
423        PathSegment::SmoothCurveTo {
424            ref mut x2,
425            ref mut y2,
426            ref mut x,
427            ref mut y,
428            ..
429        } => {
430            *x2 += offset_x;
431            *y2 += offset_y;
432            *x += offset_x;
433            *y += offset_y;
434        }
435        PathSegment::Quadratic {
436            ref mut x1,
437            ref mut y1,
438            ref mut x,
439            ref mut y,
440            ..
441        } => {
442            *x1 += offset_x;
443            *y1 += offset_y;
444            *x += offset_x;
445            *y += offset_y;
446        }
447        PathSegment::SmoothQuadratic {
448            ref mut x,
449            ref mut y,
450            ..
451        } => {
452            *x += offset_x;
453            *y += offset_y;
454        }
455        PathSegment::EllipticalArc {
456            ref mut x,
457            ref mut y,
458            ..
459        } => {
460            *x += offset_x;
461            *y += offset_y;
462        }
463        PathSegment::ClosePath { .. } => {}
464    }
465}
466
467impl From<Vec<PathSegment>> for Path {
468    #[inline]
469    fn from(v: Vec<PathSegment>) -> Self {
470        Path(v)
471    }
472}
473
474impl ::std::ops::Deref for Path {
475    type Target = Vec<PathSegment>;
476
477    #[inline]
478    fn deref(&self) -> &Self::Target {
479        &self.0
480    }
481}
482
483impl ::std::ops::DerefMut for Path {
484    #[inline]
485    fn deref_mut(&mut self) -> &mut Self::Target {
486        &mut self.0
487    }
488}
489
490/// A trait for fuzzy/approximate equality comparisons of float numbers.
491pub trait FuzzyEq<Rhs: ?Sized = Self> {
492    /// Returns `true` if values are approximately equal.
493    fn fuzzy_eq(&self, other: &Rhs) -> bool;
494
495    /// Returns `true` if values are not approximately equal.
496    #[inline]
497    fn fuzzy_ne(&self, other: &Rhs) -> bool {
498        !self.fuzzy_eq(other)
499    }
500}
501
502impl<T: FuzzyEq> FuzzyEq for Vec<T> {
503    fn fuzzy_eq(&self, other: &Self) -> bool {
504        if self.len() != other.len() {
505            return false;
506        }
507
508        for (a, b) in self.iter().zip(other.iter()) {
509            if a.fuzzy_ne(b) {
510                return false;
511            }
512        }
513
514        true
515    }
516}
517
518/// A trait for writing data to the buffer.
519pub trait WriteBuffer {
520    /// Writes data to the `Vec<u8>` buffer using specified `WriteOptions`.
521    fn write_buf_opt(&self, opt: &WriteOptions, buf: &mut Vec<u8>);
522
523    /// Writes data to the `Vec<u8>` buffer using default `WriteOptions`.
524    fn write_buf(&self, buf: &mut Vec<u8>) {
525        self.write_buf_opt(&WriteOptions::default(), buf);
526    }
527
528    /// Returns an object that implements `fmt::Display` using provided write options.
529    fn with_write_opt<'a>(&'a self, opt: &'a WriteOptions) -> DisplaySvg<'a, Self>
530    where
531        Self: Sized,
532    {
533        DisplaySvg { value: self, opt }
534    }
535}
536
537impl<T: WriteBuffer> WriteBuffer for Vec<T> {
538    fn write_buf_opt(&self, opt: &WriteOptions, buf: &mut Vec<u8>) {
539        for (n, l) in self.iter().enumerate() {
540            l.write_buf_opt(opt, buf);
541            if n < self.len() - 1 {
542                opt.write_separator(buf);
543            }
544        }
545    }
546}
547
548/// A wrapper to use `fmt::Display` with [`WriteOptions`].
549///
550/// Should be used via `WriteBuffer::with_write_opt`.
551///
552/// # Example
553///
554// /// ```
555// /// use svgtypes::{Transform, WriteOptions, WriteBuffer, DisplaySvg};
556// ///
557// /// let ts = Transform::new(1.0, 0.0, 0.0, 1.0, 10.0, 20.0);
558// /// assert_eq!(ts.to_string(), "matrix(1 0 0 1 10 20)");
559// ///
560// /// let opt = WriteOptions {
561// ///     simplify_transform_matrices: true,
562// ///     .. WriteOptions::default()
563// /// };
564// /// assert_eq!(ts.with_write_opt(&opt).to_string(), "translate(10 20)");
565// /// ```
566///
567/// [`WriteOptions`]: struct.WriteOptions.html
568pub struct DisplaySvg<'a, T: 'a + WriteBuffer> {
569    value: &'a T,
570    opt: &'a WriteOptions,
571}
572
573impl<'a, T: WriteBuffer> fmt::Debug for DisplaySvg<'a, T> {
574    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
575        // Use Display.
576        write!(f, "{}", self)
577    }
578}
579
580impl<'a, T: WriteBuffer> fmt::Display for DisplaySvg<'a, T> {
581    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
582        use std::str;
583
584        let mut out = Vec::with_capacity(32);
585        self.value.write_buf_opt(self.opt, &mut out);
586        write!(f, "{}", str::from_utf8(&out).unwrap())
587    }
588}
589
590impl WriteBuffer for f64 {
591    fn write_buf_opt(&self, opt: &WriteOptions, buf: &mut Vec<u8>) {
592        write_num(self, opt.remove_leading_zero, buf);
593    }
594}
595
596impl FuzzyEq for f32 {
597    #[inline]
598    fn fuzzy_eq(&self, other: &f32) -> bool {
599        self.approx_eq_ulps(other, 4)
600    }
601}
602
603impl FuzzyEq for f64 {
604    #[inline]
605    fn fuzzy_eq(&self, other: &f64) -> bool {
606        self.approx_eq_ulps(other, 4)
607    }
608}
609
610impl FuzzyZero for f32 {
611    #[inline]
612    fn is_fuzzy_zero(&self) -> bool {
613        self.fuzzy_eq(&0.0)
614    }
615}
616
617impl FuzzyZero for f64 {
618    #[inline]
619    fn is_fuzzy_zero(&self) -> bool {
620        self.fuzzy_eq(&0.0)
621    }
622}
623
624fn write_num(num: &f64, rm_leading_zero: bool, buf: &mut Vec<u8>) {
625    // If number is an integer, it's faster to write it as i32.
626    if num.fract().is_fuzzy_zero() {
627        write!(buf, "{}", *num as i32).unwrap();
628        return;
629    }
630
631    // Round numbers up to 11 digits to prevent writing
632    // ugly numbers like 29.999999999999996.
633    // It's not 100% correct, but differences are insignificant.
634    let v = (num * 100_000_000_000.0).round() / 100_000_000_000.0;
635
636    let start_pos = buf.len();
637
638    write!(buf, "{}", v).unwrap();
639
640    if rm_leading_zero {
641        let mut has_dot = false;
642        let mut pos = 0;
643        for c in buf.iter().skip(start_pos) {
644            if *c == b'.' {
645                has_dot = true;
646                break;
647            }
648            pos += 1;
649        }
650
651        if has_dot && buf[start_pos + pos - 1] == b'0' {
652            if pos == 2 && num.is_sign_negative() {
653                // -0.1 -> -.1
654                buf.remove(start_pos + 1);
655            } else if pos == 1 && num.is_sign_positive() {
656                // 0.1 -> .1
657                buf.remove(start_pos);
658            }
659        }
660    }
661}
662
663/// A trait for fuzzy/approximate comparisons of float numbers.
664pub trait FuzzyZero: FuzzyEq {
665    /// Returns `true` if the number is approximately zero.
666    fn is_fuzzy_zero(&self) -> bool;
667}
668
669#[cfg(test)]
670mod to_absolute {
671    use super::*;
672    use std::str::FromStr;
673
674    macro_rules! test {
675        ($name:ident, $in_text:expr, $out_text:expr) => {
676            #[test]
677            fn $name() {
678                let mut path = Path::from_str($in_text).unwrap();
679                path.conv_to_absolute();
680                assert_eq!(path.to_string(), $out_text);
681            }
682        };
683    }
684
685    test!(line_to, "m 10 20 l 20 20", "M 10 20 L 30 40");
686
687    test!(close_path, "m 10 20 l 20 20 z", "M 10 20 L 30 40 Z");
688
689    // test to check that parses implicit MoveTo as LineTo
690    test!(implicit_line_to, "m 10 20 20 20", "M 10 20 L 30 40");
691
692    test!(
693        hline_vline,
694        "m 10 20 v 10 h 10 l 10 10",
695        "M 10 20 V 30 H 20 L 30 40"
696    );
697
698    test!(
699        curve,
700        "m 10 20 c 10 10 10 10 10 10",
701        "M 10 20 C 20 30 20 30 20 30"
702    );
703
704    test!(
705        move_to_1,
706        "m 10 20 l 10 10 m 10 10 l 10 10",
707        "M 10 20 L 20 30 M 30 40 L 40 50"
708    );
709
710    test!(
711        move_to_2,
712        "m 10 20 l 10 10 z m 10 10 l 10 10",
713        "M 10 20 L 20 30 Z M 20 30 L 30 40"
714    );
715
716    test!(
717        move_to_3,
718        "m 10 20 l 10 10 Z m 10 10 l 10 10",
719        "M 10 20 L 20 30 Z M 20 30 L 30 40"
720    );
721
722    // MoveTo after ClosePath can be skipped
723    test!(
724        move_to_4,
725        "m 10 20 l 10 10 Z l 10 10",
726        "M 10 20 L 20 30 Z L 20 30"
727    );
728
729    test!(
730        smooth_curve,
731        "m 10 20 s 10 10 10 10",
732        "M 10 20 S 20 30 20 30"
733    );
734
735    test!(quad, "m 10 20 q 10 10 10 10", "M 10 20 Q 20 30 20 30");
736
737    test!(
738        arc_mixed,
739        "M 30 150 a 40 40 0 0 1 65 50 Z m 30 30 A 20 20 0 0 0 125 230 Z \
740           m 40 24 a 20 20 0 0 1 65 50 z",
741        "M 30 150 A 40 40 0 0 1 95 200 Z M 60 180 A 20 20 0 0 0 125 230 Z \
742           M 100 204 A 20 20 0 0 1 165 254 Z"
743    );
744}
745
746#[cfg(test)]
747mod to_relative {
748    use super::*;
749    use std::str::FromStr;
750
751    macro_rules! test {
752        ($name:ident, $in_text:expr, $out_text:expr) => {
753            #[test]
754            fn $name() {
755                let mut path = Path::from_str($in_text).unwrap();
756                path.conv_to_relative();
757                assert_eq!(path.to_string(), $out_text);
758            }
759        };
760    }
761
762    test!(line_to, "M 10 20 L 30 40", "m 10 20 l 20 20");
763
764    test!(close_path, "M 10 20 L 30 40 Z", "m 10 20 l 20 20 z");
765
766    test!(implicit_line_to, "M 10 20 30 40", "m 10 20 l 20 20");
767
768    test!(
769        hline_vline,
770        "M 10 20 V 30 H 20 L 30 40",
771        "m 10 20 v 10 h 10 l 10 10"
772    );
773
774    test!(
775        curve,
776        "M 10 20 C 20 30 20 30 20 30",
777        "m 10 20 c 10 10 10 10 10 10"
778    );
779
780    test!(
781        move_to_1,
782        "M 10 20 L 20 30 M 30 40 L 40 50",
783        "m 10 20 l 10 10 m 10 10 l 10 10"
784    );
785
786    test!(
787        move_to_2,
788        "M 10 20 L 20 30 Z M 20 30 L 30 40",
789        "m 10 20 l 10 10 z m 10 10 l 10 10"
790    );
791
792    test!(
793        move_to_3,
794        "M 10 20 L 20 30 z M 20 30 L 30 40",
795        "m 10 20 l 10 10 z m 10 10 l 10 10"
796    );
797
798    // MoveTo after ClosePath can be skipped
799    test!(
800        move_to_4,
801        "M 10 20 L 20 30 Z L 20 30",
802        "m 10 20 l 10 10 z l 10 10"
803    );
804
805    test!(
806        smooth_curve,
807        "M 10 20 S 20 30 20 30",
808        "m 10 20 s 10 10 10 10"
809    );
810
811    test!(quad, "M 10 20 Q 20 30 20 30", "m 10 20 q 10 10 10 10");
812
813    test!(
814        arc_mixed,
815        "M 30 150 a 40 40 0 0 1 65 50 Z m 30 30 A 20 20 0 0 0 125 230 Z \
816           m 40 24 a 20 20 0 0 1 65 50 z",
817        "m 30 150 a 40 40 0 0 1 65 50 z m 30 30 a 20 20 0 0 0 65 50 z \
818           m 40 24 a 20 20 0 0 1 65 50 z"
819    );
820}