1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 //! Specified types for SVG Path.
6 
7 use crate::parser::{Parse, ParserContext};
8 use crate::values::animated::{Animate, Procedure, ToAnimatedZero};
9 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
10 use crate::values::CSSFloat;
11 use cssparser::Parser;
12 use std::fmt::{self, Write};
13 use std::iter::{Cloned, Peekable};
14 use std::slice;
15 use style_traits::values::SequenceWriter;
16 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
17 
18 /// The SVG path data.
19 ///
20 /// https://www.w3.org/TR/SVG11/paths.html#PathData
21 #[derive(
22     Clone,
23     Debug,
24     Deserialize,
25     MallocSizeOf,
26     PartialEq,
27     Serialize,
28     SpecifiedValueInfo,
29     ToAnimatedZero,
30     ToComputedValue,
31     ToResolvedValue,
32     ToShmem,
33 )]
34 #[repr(C)]
35 pub struct SVGPathData(
36     // TODO(emilio): Should probably measure this somehow only from the
37     // specified values.
38     #[ignore_malloc_size_of = "Arc"] pub crate::ArcSlice<PathCommand>,
39 );
40 
41 impl SVGPathData {
42     /// Get the array of PathCommand.
43     #[inline]
commands(&self) -> &[PathCommand]44     pub fn commands(&self) -> &[PathCommand] {
45         &self.0
46     }
47 
48     /// Create a normalized copy of this path by converting each relative
49     /// command to an absolute command.
normalize(&self) -> Self50     pub fn normalize(&self) -> Self {
51         let mut state = PathTraversalState {
52             subpath_start: CoordPair::new(0.0, 0.0),
53             pos: CoordPair::new(0.0, 0.0),
54         };
55         let result = self
56             .0
57             .iter()
58             .map(|seg| seg.normalize(&mut state))
59             .collect::<Vec<_>>();
60 
61         SVGPathData(crate::ArcSlice::from_iter(result.into_iter()))
62     }
63 
64     // FIXME: Bug 1714238, we may drop this once we use the same data structure for both SVG and
65     // CSS.
66     /// Decode the svg path raw data from Gecko.
67     #[cfg(feature = "gecko")]
decode_from_f32_array(path: &[f32]) -> Result<Self, ()>68     pub fn decode_from_f32_array(path: &[f32]) -> Result<Self, ()> {
69         use crate::gecko_bindings::structs::dom::SVGPathSeg_Binding::*;
70 
71         let mut result: Vec<PathCommand> = Vec::new();
72         let mut i: usize = 0;
73         while i < path.len() {
74             // See EncodeType() and DecodeType() in SVGPathSegUtils.h.
75             // We are using reinterpret_cast<> to encode and decode between u32 and f32, so here we
76             // use to_bits() to decode the type.
77             let seg_type = path[i].to_bits() as u16;
78             i = i + 1;
79             match seg_type {
80                 PATHSEG_CLOSEPATH => result.push(PathCommand::ClosePath),
81                 PATHSEG_MOVETO_ABS | PATHSEG_MOVETO_REL => {
82                     debug_assert!(i + 1 < path.len());
83                     result.push(PathCommand::MoveTo {
84                         point: CoordPair::new(path[i], path[i + 1]),
85                         absolute: IsAbsolute::new(seg_type == PATHSEG_MOVETO_ABS),
86                     });
87                     i = i + 2;
88                 },
89                 PATHSEG_LINETO_ABS | PATHSEG_LINETO_REL => {
90                     debug_assert!(i + 1 < path.len());
91                     result.push(PathCommand::LineTo {
92                         point: CoordPair::new(path[i], path[i + 1]),
93                         absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_ABS),
94                     });
95                     i = i + 2;
96                 },
97                 PATHSEG_CURVETO_CUBIC_ABS | PATHSEG_CURVETO_CUBIC_REL => {
98                     debug_assert!(i + 5 < path.len());
99                     result.push(PathCommand::CurveTo {
100                         control1: CoordPair::new(path[i], path[i + 1]),
101                         control2: CoordPair::new(path[i + 2], path[i + 3]),
102                         point: CoordPair::new(path[i + 4], path[i + 5]),
103                         absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_ABS),
104                     });
105                     i = i + 6;
106                 },
107                 PATHSEG_CURVETO_QUADRATIC_ABS | PATHSEG_CURVETO_QUADRATIC_REL => {
108                     debug_assert!(i + 3 < path.len());
109                     result.push(PathCommand::QuadBezierCurveTo {
110                         control1: CoordPair::new(path[i], path[i + 1]),
111                         point: CoordPair::new(path[i + 2], path[i + 3]),
112                         absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_ABS),
113                     });
114                     i = i + 4;
115                 },
116                 PATHSEG_ARC_ABS | PATHSEG_ARC_REL => {
117                     debug_assert!(i + 6 < path.len());
118                     result.push(PathCommand::EllipticalArc {
119                         rx: path[i],
120                         ry: path[i + 1],
121                         angle: path[i + 2],
122                         large_arc_flag: ArcFlag(path[i + 3] != 0.0f32),
123                         sweep_flag: ArcFlag(path[i + 4] != 0.0f32),
124                         point: CoordPair::new(path[i + 5], path[i + 6]),
125                         absolute: IsAbsolute::new(seg_type == PATHSEG_ARC_ABS),
126                     });
127                     i = i + 7;
128                 },
129                 PATHSEG_LINETO_HORIZONTAL_ABS | PATHSEG_LINETO_HORIZONTAL_REL => {
130                     debug_assert!(i < path.len());
131                     result.push(PathCommand::HorizontalLineTo {
132                         x: path[i],
133                         absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_HORIZONTAL_ABS),
134                     });
135                     i = i + 1;
136                 },
137                 PATHSEG_LINETO_VERTICAL_ABS | PATHSEG_LINETO_VERTICAL_REL => {
138                     debug_assert!(i < path.len());
139                     result.push(PathCommand::VerticalLineTo {
140                         y: path[i],
141                         absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_VERTICAL_ABS),
142                     });
143                     i = i + 1;
144                 },
145                 PATHSEG_CURVETO_CUBIC_SMOOTH_ABS | PATHSEG_CURVETO_CUBIC_SMOOTH_REL => {
146                     debug_assert!(i + 3 < path.len());
147                     result.push(PathCommand::SmoothCurveTo {
148                         control2: CoordPair::new(path[i], path[i + 1]),
149                         point: CoordPair::new(path[i + 2], path[i + 3]),
150                         absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS),
151                     });
152                     i = i + 4;
153                 },
154                 PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS | PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL => {
155                     debug_assert!(i + 1 < path.len());
156                     result.push(PathCommand::SmoothQuadBezierCurveTo {
157                         point: CoordPair::new(path[i], path[i + 1]),
158                         absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS),
159                     });
160                     i = i + 2;
161                 },
162                 PATHSEG_UNKNOWN | _ => return Err(()),
163             }
164         }
165 
166         Ok(SVGPathData(crate::ArcSlice::from_iter(result.into_iter())))
167     }
168 }
169 
170 impl ToCss for SVGPathData {
171     #[inline]
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,172     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
173     where
174         W: fmt::Write,
175     {
176         dest.write_char('"')?;
177         {
178             let mut writer = SequenceWriter::new(dest, " ");
179             for command in self.commands() {
180                 writer.item(command)?;
181             }
182         }
183         dest.write_char('"')
184     }
185 }
186 
187 impl Parse for SVGPathData {
188     // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make
189     // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.)
190     // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident
191     // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable
192     // str::Char iterator to check each character.
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>193     fn parse<'i, 't>(
194         _context: &ParserContext,
195         input: &mut Parser<'i, 't>,
196     ) -> Result<Self, ParseError<'i>> {
197         let location = input.current_source_location();
198         let path_string = input.expect_string()?.as_ref();
199 
200         // Parse the svg path string as multiple sub-paths.
201         let mut path_parser = PathParser::new(path_string);
202         while skip_wsp(&mut path_parser.chars) {
203             if path_parser.parse_subpath().is_err() {
204                 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
205             }
206         }
207 
208         Ok(SVGPathData(crate::ArcSlice::from_iter(
209             path_parser.path.into_iter(),
210         )))
211     }
212 }
213 
214 impl Animate for SVGPathData {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>215     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
216         if self.0.len() != other.0.len() {
217             return Err(());
218         }
219 
220         // FIXME(emilio): This allocates three copies of the path, that's not
221         // great! Specially, once we're normalized once, we don't need to
222         // re-normalize again.
223         let result = self
224             .normalize()
225             .0
226             .iter()
227             .zip(other.normalize().0.iter())
228             .map(|(a, b)| a.animate(&b, procedure))
229             .collect::<Result<Vec<_>, _>>()?;
230 
231         Ok(SVGPathData(crate::ArcSlice::from_iter(result.into_iter())))
232     }
233 }
234 
235 impl ComputeSquaredDistance for SVGPathData {
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>236     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
237         if self.0.len() != other.0.len() {
238             return Err(());
239         }
240         self.normalize()
241             .0
242             .iter()
243             .zip(other.normalize().0.iter())
244             .map(|(this, other)| this.compute_squared_distance(&other))
245             .sum()
246     }
247 }
248 
249 /// The SVG path command.
250 /// The fields of these commands are self-explanatory, so we skip the documents.
251 /// Note: the index of the control points, e.g. control1, control2, are mapping to the control
252 /// points of the Bézier curve in the spec.
253 ///
254 /// https://www.w3.org/TR/SVG11/paths.html#PathData
255 #[derive(
256     Animate,
257     Clone,
258     ComputeSquaredDistance,
259     Copy,
260     Debug,
261     Deserialize,
262     MallocSizeOf,
263     PartialEq,
264     Serialize,
265     SpecifiedValueInfo,
266     ToAnimatedZero,
267     ToComputedValue,
268     ToResolvedValue,
269     ToShmem,
270 )]
271 #[allow(missing_docs)]
272 #[repr(C, u8)]
273 pub enum PathCommand {
274     /// The unknown type.
275     /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN
276     Unknown,
277     /// The "moveto" command.
278     MoveTo {
279         point: CoordPair,
280         absolute: IsAbsolute,
281     },
282     /// The "lineto" command.
283     LineTo {
284         point: CoordPair,
285         absolute: IsAbsolute,
286     },
287     /// The horizontal "lineto" command.
288     HorizontalLineTo { x: CSSFloat, absolute: IsAbsolute },
289     /// The vertical "lineto" command.
290     VerticalLineTo { y: CSSFloat, absolute: IsAbsolute },
291     /// The cubic Bézier curve command.
292     CurveTo {
293         control1: CoordPair,
294         control2: CoordPair,
295         point: CoordPair,
296         absolute: IsAbsolute,
297     },
298     /// The smooth curve command.
299     SmoothCurveTo {
300         control2: CoordPair,
301         point: CoordPair,
302         absolute: IsAbsolute,
303     },
304     /// The quadratic Bézier curve command.
305     QuadBezierCurveTo {
306         control1: CoordPair,
307         point: CoordPair,
308         absolute: IsAbsolute,
309     },
310     /// The smooth quadratic Bézier curve command.
311     SmoothQuadBezierCurveTo {
312         point: CoordPair,
313         absolute: IsAbsolute,
314     },
315     /// The elliptical arc curve command.
316     EllipticalArc {
317         rx: CSSFloat,
318         ry: CSSFloat,
319         angle: CSSFloat,
320         large_arc_flag: ArcFlag,
321         sweep_flag: ArcFlag,
322         point: CoordPair,
323         absolute: IsAbsolute,
324     },
325     /// The "closepath" command.
326     ClosePath,
327 }
328 
329 /// For internal SVGPath normalization.
330 #[allow(missing_docs)]
331 struct PathTraversalState {
332     subpath_start: CoordPair,
333     pos: CoordPair,
334 }
335 
336 impl PathCommand {
337     /// Create a normalized copy of this PathCommand. Absolute commands will be copied as-is while
338     /// for relative commands an equivalent absolute command will be returned.
339     ///
340     /// See discussion: https://github.com/w3c/svgwg/issues/321
normalize(&self, state: &mut PathTraversalState) -> Self341     fn normalize(&self, state: &mut PathTraversalState) -> Self {
342         use self::PathCommand::*;
343         match *self {
344             Unknown => Unknown,
345             ClosePath => {
346                 state.pos = state.subpath_start;
347                 ClosePath
348             },
349             MoveTo {
350                 mut point,
351                 absolute,
352             } => {
353                 if !absolute.is_yes() {
354                     point += state.pos;
355                 }
356                 state.pos = point;
357                 state.subpath_start = point;
358                 MoveTo {
359                     point,
360                     absolute: IsAbsolute::Yes,
361                 }
362             },
363             LineTo {
364                 mut point,
365                 absolute,
366             } => {
367                 if !absolute.is_yes() {
368                     point += state.pos;
369                 }
370                 state.pos = point;
371                 LineTo {
372                     point,
373                     absolute: IsAbsolute::Yes,
374                 }
375             },
376             HorizontalLineTo { mut x, absolute } => {
377                 if !absolute.is_yes() {
378                     x += state.pos.0;
379                 }
380                 state.pos.0 = x;
381                 HorizontalLineTo {
382                     x,
383                     absolute: IsAbsolute::Yes,
384                 }
385             },
386             VerticalLineTo { mut y, absolute } => {
387                 if !absolute.is_yes() {
388                     y += state.pos.1;
389                 }
390                 state.pos.1 = y;
391                 VerticalLineTo {
392                     y,
393                     absolute: IsAbsolute::Yes,
394                 }
395             },
396             CurveTo {
397                 mut control1,
398                 mut control2,
399                 mut point,
400                 absolute,
401             } => {
402                 if !absolute.is_yes() {
403                     control1 += state.pos;
404                     control2 += state.pos;
405                     point += state.pos;
406                 }
407                 state.pos = point;
408                 CurveTo {
409                     control1,
410                     control2,
411                     point,
412                     absolute: IsAbsolute::Yes,
413                 }
414             },
415             SmoothCurveTo {
416                 mut control2,
417                 mut point,
418                 absolute,
419             } => {
420                 if !absolute.is_yes() {
421                     control2 += state.pos;
422                     point += state.pos;
423                 }
424                 state.pos = point;
425                 SmoothCurveTo {
426                     control2,
427                     point,
428                     absolute: IsAbsolute::Yes,
429                 }
430             },
431             QuadBezierCurveTo {
432                 mut control1,
433                 mut point,
434                 absolute,
435             } => {
436                 if !absolute.is_yes() {
437                     control1 += state.pos;
438                     point += state.pos;
439                 }
440                 state.pos = point;
441                 QuadBezierCurveTo {
442                     control1,
443                     point,
444                     absolute: IsAbsolute::Yes,
445                 }
446             },
447             SmoothQuadBezierCurveTo {
448                 mut point,
449                 absolute,
450             } => {
451                 if !absolute.is_yes() {
452                     point += state.pos;
453                 }
454                 state.pos = point;
455                 SmoothQuadBezierCurveTo {
456                     point,
457                     absolute: IsAbsolute::Yes,
458                 }
459             },
460             EllipticalArc {
461                 rx,
462                 ry,
463                 angle,
464                 large_arc_flag,
465                 sweep_flag,
466                 mut point,
467                 absolute,
468             } => {
469                 if !absolute.is_yes() {
470                     point += state.pos;
471                 }
472                 state.pos = point;
473                 EllipticalArc {
474                     rx,
475                     ry,
476                     angle,
477                     large_arc_flag,
478                     sweep_flag,
479                     point,
480                     absolute: IsAbsolute::Yes,
481                 }
482             },
483         }
484     }
485 }
486 
487 impl ToCss for PathCommand {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,488     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
489     where
490         W: fmt::Write,
491     {
492         use self::PathCommand::*;
493         match *self {
494             Unknown => dest.write_char('X'),
495             ClosePath => dest.write_char('Z'),
496             MoveTo { point, absolute } => {
497                 dest.write_char(if absolute.is_yes() { 'M' } else { 'm' })?;
498                 dest.write_char(' ')?;
499                 point.to_css(dest)
500             },
501             LineTo { point, absolute } => {
502                 dest.write_char(if absolute.is_yes() { 'L' } else { 'l' })?;
503                 dest.write_char(' ')?;
504                 point.to_css(dest)
505             },
506             CurveTo {
507                 control1,
508                 control2,
509                 point,
510                 absolute,
511             } => {
512                 dest.write_char(if absolute.is_yes() { 'C' } else { 'c' })?;
513                 dest.write_char(' ')?;
514                 control1.to_css(dest)?;
515                 dest.write_char(' ')?;
516                 control2.to_css(dest)?;
517                 dest.write_char(' ')?;
518                 point.to_css(dest)
519             },
520             QuadBezierCurveTo {
521                 control1,
522                 point,
523                 absolute,
524             } => {
525                 dest.write_char(if absolute.is_yes() { 'Q' } else { 'q' })?;
526                 dest.write_char(' ')?;
527                 control1.to_css(dest)?;
528                 dest.write_char(' ')?;
529                 point.to_css(dest)
530             },
531             EllipticalArc {
532                 rx,
533                 ry,
534                 angle,
535                 large_arc_flag,
536                 sweep_flag,
537                 point,
538                 absolute,
539             } => {
540                 dest.write_char(if absolute.is_yes() { 'A' } else { 'a' })?;
541                 dest.write_char(' ')?;
542                 rx.to_css(dest)?;
543                 dest.write_char(' ')?;
544                 ry.to_css(dest)?;
545                 dest.write_char(' ')?;
546                 angle.to_css(dest)?;
547                 dest.write_char(' ')?;
548                 large_arc_flag.to_css(dest)?;
549                 dest.write_char(' ')?;
550                 sweep_flag.to_css(dest)?;
551                 dest.write_char(' ')?;
552                 point.to_css(dest)
553             },
554             HorizontalLineTo { x, absolute } => {
555                 dest.write_char(if absolute.is_yes() { 'H' } else { 'h' })?;
556                 dest.write_char(' ')?;
557                 x.to_css(dest)
558             },
559             VerticalLineTo { y, absolute } => {
560                 dest.write_char(if absolute.is_yes() { 'V' } else { 'v' })?;
561                 dest.write_char(' ')?;
562                 y.to_css(dest)
563             },
564             SmoothCurveTo {
565                 control2,
566                 point,
567                 absolute,
568             } => {
569                 dest.write_char(if absolute.is_yes() { 'S' } else { 's' })?;
570                 dest.write_char(' ')?;
571                 control2.to_css(dest)?;
572                 dest.write_char(' ')?;
573                 point.to_css(dest)
574             },
575             SmoothQuadBezierCurveTo { point, absolute } => {
576                 dest.write_char(if absolute.is_yes() { 'T' } else { 't' })?;
577                 dest.write_char(' ')?;
578                 point.to_css(dest)
579             },
580         }
581     }
582 }
583 
584 /// The path command absolute type.
585 #[allow(missing_docs)]
586 #[derive(
587     Animate,
588     Clone,
589     ComputeSquaredDistance,
590     Copy,
591     Debug,
592     Deserialize,
593     MallocSizeOf,
594     PartialEq,
595     Serialize,
596     SpecifiedValueInfo,
597     ToAnimatedZero,
598     ToComputedValue,
599     ToResolvedValue,
600     ToShmem,
601 )]
602 #[repr(u8)]
603 pub enum IsAbsolute {
604     Yes,
605     No,
606 }
607 
608 impl IsAbsolute {
609     /// Return true if this is IsAbsolute::Yes.
610     #[inline]
is_yes(&self) -> bool611     pub fn is_yes(&self) -> bool {
612         *self == IsAbsolute::Yes
613     }
614 
615     /// Return Yes if value is true. Otherwise, return No.
616     #[inline]
new(value: bool) -> Self617     fn new(value: bool) -> Self {
618         if value {
619             IsAbsolute::Yes
620         } else {
621             IsAbsolute::No
622         }
623     }
624 }
625 
626 /// The path coord type.
627 #[derive(
628     AddAssign,
629     Animate,
630     Clone,
631     ComputeSquaredDistance,
632     Copy,
633     Debug,
634     Deserialize,
635     MallocSizeOf,
636     PartialEq,
637     Serialize,
638     SpecifiedValueInfo,
639     ToAnimatedZero,
640     ToComputedValue,
641     ToCss,
642     ToResolvedValue,
643     ToShmem,
644 )]
645 #[repr(C)]
646 pub struct CoordPair(CSSFloat, CSSFloat);
647 
648 impl CoordPair {
649     /// Create a CoordPair.
650     #[inline]
new(x: CSSFloat, y: CSSFloat) -> Self651     pub fn new(x: CSSFloat, y: CSSFloat) -> Self {
652         CoordPair(x, y)
653     }
654 }
655 
656 /// The EllipticalArc flag type.
657 #[derive(
658     Clone,
659     Copy,
660     Debug,
661     Deserialize,
662     MallocSizeOf,
663     PartialEq,
664     Serialize,
665     SpecifiedValueInfo,
666     ToComputedValue,
667     ToResolvedValue,
668     ToShmem,
669 )]
670 #[repr(C)]
671 pub struct ArcFlag(bool);
672 
673 impl ToCss for ArcFlag {
674     #[inline]
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,675     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
676     where
677         W: fmt::Write,
678     {
679         (self.0 as i32).to_css(dest)
680     }
681 }
682 
683 impl Animate for ArcFlag {
684     #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>685     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
686         (self.0 as i32)
687             .animate(&(other.0 as i32), procedure)
688             .map(|v| ArcFlag(v > 0))
689     }
690 }
691 
692 impl ComputeSquaredDistance for ArcFlag {
693     #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>694     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
695         (self.0 as i32).compute_squared_distance(&(other.0 as i32))
696     }
697 }
698 
699 impl ToAnimatedZero for ArcFlag {
700     #[inline]
to_animated_zero(&self) -> Result<Self, ()>701     fn to_animated_zero(&self) -> Result<Self, ()> {
702         // The 2 ArcFlags in EllipticalArc determine which one of the 4 different arcs will be
703         // used. (i.e. From 4 combinations). In other words, if we change the flag, we get a
704         // different arc. Therefore, we return *self.
705         // https://svgwg.org/svg2-draft/paths.html#PathDataEllipticalArcCommands
706         Ok(*self)
707     }
708 }
709 
710 /// SVG Path parser.
711 struct PathParser<'a> {
712     chars: Peekable<Cloned<slice::Iter<'a, u8>>>,
713     path: Vec<PathCommand>,
714 }
715 
716 macro_rules! parse_arguments {
717     (
718         $parser:ident,
719         $abs:ident,
720         $enum:ident,
721         [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
722     ) => {
723         {
724             loop {
725                 let $para = $func(&mut $parser.chars)?;
726                 $(
727                     skip_comma_wsp(&mut $parser.chars);
728                     let $other_para = $other_func(&mut $parser.chars)?;
729                 )*
730                 $parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs });
731 
732                 // End of string or the next character is a possible new command.
733                 if !skip_wsp(&mut $parser.chars) ||
734                    $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
735                     break;
736                 }
737                 skip_comma_wsp(&mut $parser.chars);
738             }
739             Ok(())
740         }
741     }
742 }
743 
744 impl<'a> PathParser<'a> {
745     /// Return a PathParser.
746     #[inline]
new(string: &'a str) -> Self747     fn new(string: &'a str) -> Self {
748         PathParser {
749             chars: string.as_bytes().iter().cloned().peekable(),
750             path: Vec::new(),
751         }
752     }
753 
754     /// Parse a sub-path.
parse_subpath(&mut self) -> Result<(), ()>755     fn parse_subpath(&mut self) -> Result<(), ()> {
756         // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path
757         // (i.e. not a valid moveto-drawto-command-group).
758         self.parse_moveto()?;
759 
760         // Handle other commands.
761         loop {
762             skip_wsp(&mut self.chars);
763             if self.chars.peek().map_or(true, |&m| m == b'M' || m == b'm') {
764                 break;
765             }
766 
767             let command = self.chars.next().unwrap();
768             let abs = if command.is_ascii_uppercase() {
769                 IsAbsolute::Yes
770             } else {
771                 IsAbsolute::No
772             };
773 
774             skip_wsp(&mut self.chars);
775             match command {
776                 b'Z' | b'z' => self.parse_closepath(),
777                 b'L' | b'l' => self.parse_lineto(abs),
778                 b'H' | b'h' => self.parse_h_lineto(abs),
779                 b'V' | b'v' => self.parse_v_lineto(abs),
780                 b'C' | b'c' => self.parse_curveto(abs),
781                 b'S' | b's' => self.parse_smooth_curveto(abs),
782                 b'Q' | b'q' => self.parse_quadratic_bezier_curveto(abs),
783                 b'T' | b't' => self.parse_smooth_quadratic_bezier_curveto(abs),
784                 b'A' | b'a' => self.parse_elliptical_arc(abs),
785                 _ => return Err(()),
786             }?;
787         }
788         Ok(())
789     }
790 
791     /// Parse "moveto" command.
parse_moveto(&mut self) -> Result<(), ()>792     fn parse_moveto(&mut self) -> Result<(), ()> {
793         let command = match self.chars.next() {
794             Some(c) if c == b'M' || c == b'm' => c,
795             _ => return Err(()),
796         };
797 
798         skip_wsp(&mut self.chars);
799         let point = parse_coord(&mut self.chars)?;
800         let absolute = if command == b'M' {
801             IsAbsolute::Yes
802         } else {
803             IsAbsolute::No
804         };
805         self.path.push(PathCommand::MoveTo { point, absolute });
806 
807         // End of string or the next character is a possible new command.
808         if !skip_wsp(&mut self.chars) || self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic())
809         {
810             return Ok(());
811         }
812         skip_comma_wsp(&mut self.chars);
813 
814         // If a moveto is followed by multiple pairs of coordinates, the subsequent
815         // pairs are treated as implicit lineto commands.
816         self.parse_lineto(absolute)
817     }
818 
819     /// Parse "closepath" command.
parse_closepath(&mut self) -> Result<(), ()>820     fn parse_closepath(&mut self) -> Result<(), ()> {
821         self.path.push(PathCommand::ClosePath);
822         Ok(())
823     }
824 
825     /// Parse "lineto" command.
parse_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()>826     fn parse_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
827         parse_arguments!(self, absolute, LineTo, [ point => parse_coord ])
828     }
829 
830     /// Parse horizontal "lineto" command.
parse_h_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()>831     fn parse_h_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
832         parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ])
833     }
834 
835     /// Parse vertical "lineto" command.
parse_v_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()>836     fn parse_v_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
837         parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ])
838     }
839 
840     /// Parse cubic Bézier curve command.
parse_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()>841     fn parse_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
842         parse_arguments!(self, absolute, CurveTo, [
843             control1 => parse_coord, control2 => parse_coord, point => parse_coord
844         ])
845     }
846 
847     /// Parse smooth "curveto" command.
parse_smooth_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()>848     fn parse_smooth_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
849         parse_arguments!(self, absolute, SmoothCurveTo, [
850             control2 => parse_coord, point => parse_coord
851         ])
852     }
853 
854     /// Parse quadratic Bézier curve command.
parse_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()>855     fn parse_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
856         parse_arguments!(self, absolute, QuadBezierCurveTo, [
857             control1 => parse_coord, point => parse_coord
858         ])
859     }
860 
861     /// Parse smooth quadratic Bézier curveto command.
parse_smooth_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()>862     fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
863         parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ])
864     }
865 
866     /// Parse elliptical arc curve command.
parse_elliptical_arc(&mut self, absolute: IsAbsolute) -> Result<(), ()>867     fn parse_elliptical_arc(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
868         // Parse a flag whose value is '0' or '1'; otherwise, return Err(()).
869         let parse_flag = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
870             Some(c) if c == b'0' || c == b'1' => Ok(ArcFlag(c == b'1')),
871             _ => Err(()),
872         };
873         parse_arguments!(self, absolute, EllipticalArc, [
874             rx => parse_number,
875             ry => parse_number,
876             angle => parse_number,
877             large_arc_flag => parse_flag,
878             sweep_flag => parse_flag,
879             point => parse_coord
880         ])
881     }
882 }
883 
884 /// Parse a pair of numbers into CoordPair.
parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()>885 fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()> {
886     let x = parse_number(iter)?;
887     skip_comma_wsp(iter);
888     let y = parse_number(iter)?;
889     Ok(CoordPair::new(x, y))
890 }
891 
892 /// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed
893 /// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating
894 /// point number. In other words, the logic here is similar with that of
895 /// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the
896 /// input is a Peekable and we only accept an integer of a floating point number.
897 ///
898 /// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF
parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()>899 fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> {
900     // 1. Check optional sign.
901     let sign = if iter
902         .peek()
903         .map_or(false, |&sign| sign == b'+' || sign == b'-')
904     {
905         if iter.next().unwrap() == b'-' {
906             -1.
907         } else {
908             1.
909         }
910     } else {
911         1.
912     };
913 
914     // 2. Check integer part.
915     let mut integral_part: f64 = 0.;
916     let got_dot = if !iter.peek().map_or(false, |&n| n == b'.') {
917         // If the first digit in integer part is neither a dot nor a digit, this is not a number.
918         if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
919             return Err(());
920         }
921 
922         while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
923             integral_part = integral_part * 10. + (iter.next().unwrap() - b'0') as f64;
924         }
925 
926         iter.peek().map_or(false, |&n| n == b'.')
927     } else {
928         true
929     };
930 
931     // 3. Check fractional part.
932     let mut fractional_part: f64 = 0.;
933     if got_dot {
934         // Consume '.'.
935         iter.next();
936         // If the first digit in fractional part is not a digit, this is not a number.
937         if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
938             return Err(());
939         }
940 
941         let mut factor = 0.1;
942         while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
943             fractional_part += (iter.next().unwrap() - b'0') as f64 * factor;
944             factor *= 0.1;
945         }
946     }
947 
948     let mut value = sign * (integral_part + fractional_part);
949 
950     // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to
951     //    treat the numbers after 'E' or 'e' are in the exponential part.
952     if iter.peek().map_or(false, |&exp| exp == b'E' || exp == b'e') {
953         // Consume 'E' or 'e'.
954         iter.next();
955         let exp_sign = if iter
956             .peek()
957             .map_or(false, |&sign| sign == b'+' || sign == b'-')
958         {
959             if iter.next().unwrap() == b'-' {
960                 -1.
961             } else {
962                 1.
963             }
964         } else {
965             1.
966         };
967 
968         let mut exp: f64 = 0.;
969         while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
970             exp = exp * 10. + (iter.next().unwrap() - b'0') as f64;
971         }
972 
973         value *= f64::powf(10., exp * exp_sign);
974     }
975 
976     if value.is_finite() {
977         Ok(value
978             .min(::std::f32::MAX as f64)
979             .max(::std::f32::MIN as f64) as CSSFloat)
980     } else {
981         Err(())
982     }
983 }
984 
985 /// Skip all svg whitespaces, and return true if |iter| hasn't finished.
986 #[inline]
skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool987 fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
988     // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}.
989     //       However, SVG 2 has one extra whitespace: \u{C}.
990     //       Therefore, we follow the newest spec for the definition of whitespace,
991     //       i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}.
992     while iter.peek().map_or(false, |c| c.is_ascii_whitespace()) {
993         iter.next();
994     }
995     iter.peek().is_some()
996 }
997 
998 /// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished.
999 #[inline]
skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool1000 fn skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1001     if !skip_wsp(iter) {
1002         return false;
1003     }
1004 
1005     if *iter.peek().unwrap() != b',' {
1006         return true;
1007     }
1008     iter.next();
1009 
1010     skip_wsp(iter)
1011 }
1012