style/values/specified/
svg_path.rs

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
7use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::values::animated::{lists, Animate, Procedure};
10use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
11use crate::values::generics::basic_shape::GenericShapeCommand;
12use crate::values::generics::basic_shape::{
13    ArcRadii, ArcSize, ArcSweep, AxisEndPoint, AxisPosition, CommandEndPoint, ControlPoint,
14    ControlReference, CoordinatePair, RelativeControlPoint, ShapePosition,
15};
16use crate::values::generics::position::GenericPosition;
17use crate::values::CSSFloat;
18use cssparser::Parser;
19use std::fmt::{self, Write};
20use std::iter::{Cloned, Peekable};
21use std::ops;
22use std::slice;
23use style_traits::values::SequenceWriter;
24use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
25
26/// Whether to allow empty string in the parser.
27#[derive(Clone, Debug, Eq, PartialEq)]
28#[allow(missing_docs)]
29pub enum AllowEmpty {
30    Yes,
31    No,
32}
33
34/// The SVG path data.
35///
36/// https://www.w3.org/TR/SVG11/paths.html#PathData
37#[derive(
38    Clone,
39    Debug,
40    Deserialize,
41    MallocSizeOf,
42    PartialEq,
43    Serialize,
44    SpecifiedValueInfo,
45    ToAnimatedZero,
46    ToComputedValue,
47    ToResolvedValue,
48    ToShmem,
49)]
50#[repr(C)]
51pub struct SVGPathData(
52    // TODO(emilio): Should probably measure this somehow only from the
53    // specified values.
54    #[ignore_malloc_size_of = "Arc"] pub crate::ArcSlice<PathCommand>,
55);
56
57impl SVGPathData {
58    /// Get the array of PathCommand.
59    #[inline]
60    pub fn commands(&self) -> &[PathCommand] {
61        &self.0
62    }
63
64    /// Create a normalized copy of this path by converting each relative
65    /// command to an absolute command.
66    pub fn normalize(&self, reduce: bool) -> Self {
67        let mut state = PathTraversalState {
68            subpath_start: CoordPair::new(0.0, 0.0),
69            pos: CoordPair::new(0.0, 0.0),
70            last_command: PathCommand::Close,
71            last_control: CoordPair::new(0.0, 0.0),
72        };
73        let iter = self.0.iter().map(|seg| seg.normalize(&mut state, reduce));
74        SVGPathData(crate::ArcSlice::from_iter(iter))
75    }
76
77    /// Parse this SVG path string with the argument that indicates whether we should allow the
78    /// empty string.
79    // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make
80    // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.)
81    // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident
82    // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable
83    // str::Char iterator to check each character.
84    //
85    // css-shapes-1 says a path data string that does conform but defines an empty path is
86    // invalid and causes the entire path() to be invalid, so we use allow_empty to decide
87    // whether we should allow it.
88    // https://drafts.csswg.org/css-shapes-1/#typedef-basic-shape
89    pub fn parse<'i, 't>(
90        input: &mut Parser<'i, 't>,
91        allow_empty: AllowEmpty,
92    ) -> Result<Self, ParseError<'i>> {
93        let location = input.current_source_location();
94        let path_string = input.expect_string()?.as_ref();
95        let (path, ok) = Self::parse_bytes(path_string.as_bytes());
96        if !ok || (allow_empty == AllowEmpty::No && path.0.is_empty()) {
97            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
98        }
99        return Ok(path);
100    }
101
102    /// As above, but just parsing the raw byte stream.
103    ///
104    /// Returns the (potentially empty or partial) path, and whether the parsing was ok or we found
105    /// an error. The API is a bit weird because some SVG callers require "parse until first error"
106    /// behavior.
107    pub fn parse_bytes(input: &[u8]) -> (Self, bool) {
108        // Parse the svg path string as multiple sub-paths.
109        let mut ok = true;
110        let mut path_parser = PathParser::new(input);
111
112        while skip_wsp(&mut path_parser.chars) {
113            if path_parser.parse_subpath().is_err() {
114                ok = false;
115                break;
116            }
117        }
118
119        let path = Self(crate::ArcSlice::from_iter(path_parser.path.into_iter()));
120        (path, ok)
121    }
122
123    /// Serializes to the path string, potentially including quotes.
124    pub fn to_css<W>(&self, dest: &mut CssWriter<W>, quote: bool) -> fmt::Result
125    where
126        W: fmt::Write,
127    {
128        if quote {
129            dest.write_char('"')?;
130        }
131        let mut writer = SequenceWriter::new(dest, " ");
132        for command in self.commands() {
133            writer.write_item(|inner| command.to_css_for_svg(inner))?;
134        }
135        if quote {
136            dest.write_char('"')?;
137        }
138        Ok(())
139    }
140}
141
142impl ToCss for SVGPathData {
143    #[inline]
144    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
145    where
146        W: fmt::Write,
147    {
148        self.to_css(dest, /* quote = */ true)
149    }
150}
151
152impl Parse for SVGPathData {
153    fn parse<'i, 't>(
154        _context: &ParserContext,
155        input: &mut Parser<'i, 't>,
156    ) -> Result<Self, ParseError<'i>> {
157        // Note that the EBNF allows the path data string in the d property to be empty, so we
158        // don't reject empty SVG path data.
159        // https://svgwg.org/svg2-draft/single-page.html#paths-PathDataBNF
160        SVGPathData::parse(input, AllowEmpty::Yes)
161    }
162}
163
164impl Animate for SVGPathData {
165    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
166        if self.0.len() != other.0.len() {
167            return Err(());
168        }
169
170        // FIXME(emilio): This allocates three copies of the path, that's not
171        // great! Specially, once we're normalized once, we don't need to
172        // re-normalize again.
173        let left = self.normalize(false);
174        let right = other.normalize(false);
175
176        let items: Vec<_> = lists::by_computed_value::animate(&left.0, &right.0, procedure)?;
177        Ok(SVGPathData(crate::ArcSlice::from_iter(items.into_iter())))
178    }
179}
180
181impl ComputeSquaredDistance for SVGPathData {
182    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
183        if self.0.len() != other.0.len() {
184            return Err(());
185        }
186        let left = self.normalize(false);
187        let right = other.normalize(false);
188        lists::by_computed_value::squared_distance(&left.0, &right.0)
189    }
190}
191
192/// The SVG path command.
193/// The fields of these commands are self-explanatory, so we skip the documents.
194/// Note: the index of the control points, e.g. control1, control2, are mapping to the control
195/// points of the Bézier curve in the spec.
196///
197/// https://www.w3.org/TR/SVG11/paths.html#PathData
198pub type PathCommand = GenericShapeCommand<CSSFloat, ShapePosition<CSSFloat>, CSSFloat>;
199
200/// For internal SVGPath normalization.
201#[allow(missing_docs)]
202struct PathTraversalState {
203    subpath_start: CoordPair,
204    pos: CoordPair,
205    last_command: PathCommand,
206    last_control: CoordPair,
207}
208
209impl PathCommand {
210    /// Create a normalized copy of this PathCommand. Absolute commands will be copied as-is while
211    /// for relative commands an equivalent absolute command will be returned.
212    ///
213    /// See discussion: https://github.com/w3c/svgwg/issues/321
214    /// If reduce is true then the path will be restricted to
215    /// "M", "L", "C", "A" and "Z" commands.
216    fn normalize(&self, state: &mut PathTraversalState, reduce: bool) -> Self {
217        use crate::values::generics::basic_shape::GenericShapeCommand::*;
218        match *self {
219            Close => {
220                state.pos = state.subpath_start;
221                if reduce {
222                    state.last_command = *self;
223                }
224                Close
225            },
226            Move { mut point } => {
227                point = point.to_abs(state.pos);
228                state.pos = point.into();
229                state.subpath_start = point.into();
230                if reduce {
231                    state.last_command = *self;
232                }
233                Move { point }
234            },
235            Line { mut point } => {
236                point = point.to_abs(state.pos);
237                state.pos = point.into();
238                if reduce {
239                    state.last_command = *self;
240                }
241                Line { point }
242            },
243            HLine { mut x } => {
244                x = x.to_abs(state.pos.x);
245                state.pos.x = x.into();
246                if reduce {
247                    state.last_command = *self;
248                    PathCommand::Line {
249                        point: CommandEndPoint::ToPosition(state.pos.into()),
250                    }
251                } else {
252                    HLine { x }
253                }
254            },
255            VLine { mut y } => {
256                y = y.to_abs(state.pos.y);
257                state.pos.y = y.into();
258                if reduce {
259                    state.last_command = *self;
260                    PathCommand::Line {
261                        point: CommandEndPoint::ToPosition(state.pos.into()),
262                    }
263                } else {
264                    VLine { y }
265                }
266            },
267            CubicCurve {
268                mut point,
269                mut control1,
270                mut control2,
271            } => {
272                control1 = control1.to_abs(state.pos, point);
273                control2 = control2.to_abs(state.pos, point);
274                point = point.to_abs(state.pos);
275                state.pos = point.into();
276                if reduce {
277                    state.last_command = *self;
278                    state.last_control = control2.into();
279                }
280                CubicCurve {
281                    point,
282                    control1,
283                    control2,
284                }
285            },
286            QuadCurve {
287                mut point,
288                mut control1,
289            } => {
290                control1 = control1.to_abs(state.pos, point);
291                point = point.to_abs(state.pos);
292                if reduce {
293                    let c1 = state.pos + 2. * (CoordPair::from(control1) - state.pos) / 3.;
294                    let control2 = CoordPair::from(point)
295                        + 2. * (CoordPair::from(control1) - point.into()) / 3.;
296                    state.pos = point.into();
297                    state.last_command = *self;
298                    state.last_control = control1.into();
299                    CubicCurve {
300                        point,
301                        control1: ControlPoint::Absolute(c1.into()),
302                        control2: ControlPoint::Absolute(control2.into()),
303                    }
304                } else {
305                    state.pos = point.into();
306                    QuadCurve { point, control1 }
307                }
308            },
309            SmoothCubic {
310                mut point,
311                mut control2,
312            } => {
313                control2 = control2.to_abs(state.pos, point);
314                point = point.to_abs(state.pos);
315                if reduce {
316                    let control1 = match state.last_command {
317                        PathCommand::CubicCurve {
318                            point: _,
319                            control1: _,
320                            control2: _,
321                        }
322                        | PathCommand::SmoothCubic {
323                            point: _,
324                            control2: _,
325                        } => state.pos + state.pos - state.last_control,
326                        _ => state.pos,
327                    };
328                    state.pos = point.into();
329                    state.last_control = control2.into();
330                    state.last_command = *self;
331                    CubicCurve {
332                        point,
333                        control1: ControlPoint::Absolute(control1.into()),
334                        control2,
335                    }
336                } else {
337                    state.pos = point.into();
338                    SmoothCubic { point, control2 }
339                }
340            },
341            SmoothQuad { mut point } => {
342                point = point.to_abs(state.pos);
343                if reduce {
344                    let control = match state.last_command {
345                        PathCommand::QuadCurve {
346                            point: _,
347                            control1: _,
348                        }
349                        | PathCommand::SmoothQuad { point: _ } => {
350                            state.pos + state.pos - state.last_control
351                        },
352                        _ => state.pos,
353                    };
354                    let control1 = state.pos + 2. * (control - state.pos) / 3.;
355                    let control2 = CoordPair::from(point) + 2. * (control - point.into()) / 3.;
356                    state.pos = point.into();
357                    state.last_command = *self;
358                    state.last_control = control;
359                    CubicCurve {
360                        point,
361                        control1: ControlPoint::Absolute(control1.into()),
362                        control2: ControlPoint::Absolute(control2.into()),
363                    }
364                } else {
365                    state.pos = point.into();
366                    SmoothQuad { point }
367                }
368            },
369            Arc {
370                mut point,
371                radii,
372                arc_sweep,
373                arc_size,
374                rotate,
375            } => {
376                point = point.to_abs(state.pos);
377                state.pos = point.into();
378                if reduce {
379                    state.last_command = *self;
380                    if radii.rx == 0. && radii.ry.as_ref().is_none_or(|v| *v == 0.) {
381                        let end_point = CoordPair::from(point);
382                        CubicCurve {
383                            point: CommandEndPoint::ToPosition(state.pos.into()),
384                            control1: ControlPoint::Absolute(end_point.into()),
385                            control2: ControlPoint::Absolute(end_point.into()),
386                        }
387                    } else {
388                        Arc {
389                            point,
390                            radii,
391                            arc_sweep,
392                            arc_size,
393                            rotate,
394                        }
395                    }
396                } else {
397                    Arc {
398                        point,
399                        radii,
400                        arc_sweep,
401                        arc_size,
402                        rotate,
403                    }
404                }
405            },
406        }
407    }
408
409    /// The serialization of the svg path.
410    fn to_css_for_svg<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
411    where
412        W: fmt::Write,
413    {
414        use crate::values::generics::basic_shape::GenericShapeCommand::*;
415        match *self {
416            Close => dest.write_char('Z'),
417            Move { point } => {
418                dest.write_char(if point.is_abs() { 'M' } else { 'm' })?;
419                dest.write_char(' ')?;
420                CoordPair::from(point).to_css(dest)
421            },
422            Line { point } => {
423                dest.write_char(if point.is_abs() { 'L' } else { 'l' })?;
424                dest.write_char(' ')?;
425                CoordPair::from(point).to_css(dest)
426            },
427            CubicCurve {
428                point,
429                control1,
430                control2,
431            } => {
432                dest.write_char(if point.is_abs() { 'C' } else { 'c' })?;
433                dest.write_char(' ')?;
434                control1.to_css(dest, point.is_abs())?;
435                dest.write_char(' ')?;
436                control2.to_css(dest, point.is_abs())?;
437                dest.write_char(' ')?;
438                CoordPair::from(point).to_css(dest)
439            },
440            QuadCurve { point, control1 } => {
441                dest.write_char(if point.is_abs() { 'Q' } else { 'q' })?;
442                dest.write_char(' ')?;
443                control1.to_css(dest, point.is_abs())?;
444                dest.write_char(' ')?;
445                CoordPair::from(point).to_css(dest)
446            },
447            Arc {
448                point,
449                radii,
450                arc_sweep,
451                arc_size,
452                rotate,
453            } => {
454                dest.write_char(if point.is_abs() { 'A' } else { 'a' })?;
455                dest.write_char(' ')?;
456                radii.to_css(dest)?;
457                dest.write_char(' ')?;
458                rotate.to_css(dest)?;
459                dest.write_char(' ')?;
460                (arc_size as i32).to_css(dest)?;
461                dest.write_char(' ')?;
462                (arc_sweep as i32).to_css(dest)?;
463                dest.write_char(' ')?;
464                CoordPair::from(point).to_css(dest)
465            },
466            HLine { x } => {
467                dest.write_char(if x.is_abs() { 'H' } else { 'h' })?;
468                dest.write_char(' ')?;
469                CSSFloat::from(x).to_css(dest)
470            },
471            VLine { y } => {
472                dest.write_char(if y.is_abs() { 'V' } else { 'v' })?;
473                dest.write_char(' ')?;
474                CSSFloat::from(y).to_css(dest)
475            },
476            SmoothCubic { point, control2 } => {
477                dest.write_char(if point.is_abs() { 'S' } else { 's' })?;
478                dest.write_char(' ')?;
479                control2.to_css(dest, point.is_abs())?;
480                dest.write_char(' ')?;
481                CoordPair::from(point).to_css(dest)
482            },
483            SmoothQuad { point } => {
484                dest.write_char(if point.is_abs() { 'T' } else { 't' })?;
485                dest.write_char(' ')?;
486                CoordPair::from(point).to_css(dest)
487            },
488        }
489    }
490}
491
492/// The path coord type.
493pub type CoordPair = CoordinatePair<CSSFloat>;
494
495impl ops::Add<CoordPair> for CoordPair {
496    type Output = CoordPair;
497
498    fn add(self, rhs: CoordPair) -> CoordPair {
499        Self {
500            x: self.x + rhs.x,
501            y: self.y + rhs.y,
502        }
503    }
504}
505
506impl ops::Sub<CoordPair> for CoordPair {
507    type Output = CoordPair;
508
509    fn sub(self, rhs: CoordPair) -> CoordPair {
510        Self {
511            x: self.x - rhs.x,
512            y: self.y - rhs.y,
513        }
514    }
515}
516
517impl ops::Mul<CSSFloat> for CoordPair {
518    type Output = CoordPair;
519
520    fn mul(self, f: CSSFloat) -> CoordPair {
521        Self {
522            x: self.x * f,
523            y: self.y * f,
524        }
525    }
526}
527
528impl ops::Mul<CoordPair> for CSSFloat {
529    type Output = CoordPair;
530
531    fn mul(self, rhs: CoordPair) -> CoordPair {
532        rhs * self
533    }
534}
535
536impl ops::Div<CSSFloat> for CoordPair {
537    type Output = CoordPair;
538
539    fn div(self, f: CSSFloat) -> CoordPair {
540        Self {
541            x: self.x / f,
542            y: self.y / f,
543        }
544    }
545}
546
547impl CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat> {
548    /// Converts <command-end-point> into absolutely positioned type.
549    pub fn to_abs(self, state_pos: CoordPair) -> Self {
550        // Consume self value.
551        match self {
552            CommandEndPoint::ToPosition(_) => self,
553            CommandEndPoint::ByCoordinate(coord) => {
554                let pos = GenericPosition {
555                    horizontal: coord.x + state_pos.x,
556                    vertical: coord.y + state_pos.y,
557                };
558                CommandEndPoint::ToPosition(pos)
559            },
560        }
561    }
562}
563
564impl AxisEndPoint<CSSFloat> {
565    /// Converts possibly relative end point into absolutely positioned type.
566    pub fn to_abs(self, base: CSSFloat) -> AxisEndPoint<CSSFloat> {
567        // Consume self value.
568        match self {
569            AxisEndPoint::ToPosition(_) => self,
570            AxisEndPoint::ByCoordinate(coord) => {
571                AxisEndPoint::ToPosition(AxisPosition::LengthPercent(coord + base))
572            },
573        }
574    }
575}
576
577impl ControlPoint<ShapePosition<CSSFloat>, CSSFloat> {
578    /// Converts <control-point> into absolutely positioned control point type.
579    pub fn to_abs(
580        self,
581        state_pos: CoordPair,
582        end_point: CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>,
583    ) -> Self {
584        // Consume self value.
585        match self {
586            ControlPoint::Absolute(_) => self,
587            ControlPoint::Relative(point) => {
588                let mut pos = GenericPosition {
589                    horizontal: point.coord.x,
590                    vertical: point.coord.y,
591                };
592
593                match point.reference {
594                    ControlReference::Start => {
595                        pos.horizontal += state_pos.x;
596                        pos.vertical += state_pos.y;
597                    },
598                    ControlReference::End => {
599                        let end = CoordPair::from(end_point);
600                        pos.horizontal += end.x;
601                        pos.vertical += end.y;
602                    },
603                    _ => (),
604                }
605                ControlPoint::Absolute(pos)
606            },
607        }
608    }
609}
610
611impl From<CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>> for CoordPair {
612    #[inline]
613    fn from(p: CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>) -> Self {
614        match p {
615            CommandEndPoint::ToPosition(pos) => CoordPair {
616                x: pos.horizontal,
617                y: pos.vertical,
618            },
619            CommandEndPoint::ByCoordinate(coord) => coord,
620        }
621    }
622}
623
624impl From<ControlPoint<ShapePosition<CSSFloat>, CSSFloat>> for CoordPair {
625    #[inline]
626    fn from(point: ControlPoint<ShapePosition<CSSFloat>, CSSFloat>) -> Self {
627        match point {
628            ControlPoint::Absolute(pos) => CoordPair {
629                x: pos.horizontal,
630                y: pos.vertical,
631            },
632            ControlPoint::Relative(_) => {
633                panic!(
634                    "Attempted to convert a relative ControlPoint to CoordPair, which is lossy. \
635                        Consider converting it to absolute type first using `.to_abs()`."
636                )
637            },
638        }
639    }
640}
641
642impl From<CoordPair> for CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat> {
643    #[inline]
644    fn from(coord: CoordPair) -> Self {
645        CommandEndPoint::ByCoordinate(coord)
646    }
647}
648
649impl From<CoordPair> for ShapePosition<CSSFloat> {
650    #[inline]
651    fn from(coord: CoordPair) -> Self {
652        GenericPosition {
653            horizontal: coord.x,
654            vertical: coord.y,
655        }
656    }
657}
658
659impl From<AxisEndPoint<CSSFloat>> for CSSFloat {
660    #[inline]
661    fn from(p: AxisEndPoint<CSSFloat>) -> Self {
662        match p {
663            AxisEndPoint::ToPosition(AxisPosition::LengthPercent(a)) => a,
664            AxisEndPoint::ToPosition(AxisPosition::Keyword(_)) => {
665                unreachable!("Invalid state: SVG path commands cannot contain a keyword.")
666            },
667            AxisEndPoint::ByCoordinate(a) => a,
668        }
669    }
670}
671
672impl ToCss for ShapePosition<CSSFloat> {
673    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
674    where
675        W: Write,
676    {
677        self.horizontal.to_css(dest)?;
678        dest.write_char(' ')?;
679        self.vertical.to_css(dest)
680    }
681}
682
683/// SVG Path parser.
684struct PathParser<'a> {
685    chars: Peekable<Cloned<slice::Iter<'a, u8>>>,
686    path: Vec<PathCommand>,
687}
688
689macro_rules! parse_arguments {
690    (
691        $parser:ident,
692        $enum:ident,
693        $( $field:ident : $value:expr, )*
694        [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
695    ) => {
696        {
697            loop {
698                let $para = $func(&mut $parser.chars)?;
699                $(
700                    skip_comma_wsp(&mut $parser.chars);
701                    let $other_para = $other_func(&mut $parser.chars)?;
702                )*
703                $parser.path.push(
704                    PathCommand::$enum { $( $field: $value, )* $para $(, $other_para)* }
705                );
706
707                // End of string or the next character is a possible new command.
708                if !skip_wsp(&mut $parser.chars) ||
709                   $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
710                    break;
711                }
712                skip_comma_wsp(&mut $parser.chars);
713            }
714            Ok(())
715        }
716    }
717}
718
719impl<'a> PathParser<'a> {
720    /// Return a PathParser.
721    #[inline]
722    fn new(bytes: &'a [u8]) -> Self {
723        PathParser {
724            chars: bytes.iter().cloned().peekable(),
725            path: Vec::new(),
726        }
727    }
728
729    /// Parse a sub-path.
730    fn parse_subpath(&mut self) -> Result<(), ()> {
731        // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path
732        // (i.e. not a valid moveto-drawto-command-group).
733        self.parse_moveto()?;
734
735        // Handle other commands.
736        loop {
737            skip_wsp(&mut self.chars);
738            if self.chars.peek().map_or(true, |&m| m == b'M' || m == b'm') {
739                break;
740            }
741
742            let command = self.chars.next().unwrap();
743
744            skip_wsp(&mut self.chars);
745            match command {
746                b'Z' | b'z' => self.parse_closepath(),
747                b'L' => self.parse_line_abs(),
748                b'l' => self.parse_line_rel(),
749                b'H' => self.parse_h_line_abs(),
750                b'h' => self.parse_h_line_rel(),
751                b'V' => self.parse_v_line_abs(),
752                b'v' => self.parse_v_line_rel(),
753                b'C' => self.parse_curve_abs(),
754                b'c' => self.parse_curve_rel(),
755                b'S' => self.parse_smooth_curve_abs(),
756                b's' => self.parse_smooth_curve_rel(),
757                b'Q' => self.parse_quadratic_bezier_curve_abs(),
758                b'q' => self.parse_quadratic_bezier_curve_rel(),
759                b'T' => self.parse_smooth_quadratic_bezier_curve_abs(),
760                b't' => self.parse_smooth_quadratic_bezier_curve_rel(),
761                b'A' => self.parse_elliptical_arc_abs(),
762                b'a' => self.parse_elliptical_arc_rel(),
763                _ => return Err(()),
764            }?;
765        }
766        Ok(())
767    }
768
769    /// Parse "moveto" command.
770    fn parse_moveto(&mut self) -> Result<(), ()> {
771        let command = match self.chars.next() {
772            Some(c) if c == b'M' || c == b'm' => c,
773            _ => return Err(()),
774        };
775
776        skip_wsp(&mut self.chars);
777        let point = if command == b'M' {
778            parse_command_end_abs(&mut self.chars)
779        } else {
780            parse_command_end_rel(&mut self.chars)
781        }?;
782        self.path.push(PathCommand::Move { point });
783
784        // End of string or the next character is a possible new command.
785        if !skip_wsp(&mut self.chars) || self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic())
786        {
787            return Ok(());
788        }
789        skip_comma_wsp(&mut self.chars);
790
791        // If a moveto is followed by multiple pairs of coordinates, the subsequent
792        // pairs are treated as implicit lineto commands.
793        if point.is_abs() {
794            self.parse_line_abs()
795        } else {
796            self.parse_line_rel()
797        }
798    }
799
800    /// Parse "closepath" command.
801    fn parse_closepath(&mut self) -> Result<(), ()> {
802        self.path.push(PathCommand::Close);
803        Ok(())
804    }
805
806    /// Parse an absolute "lineto" ("L") command.
807    fn parse_line_abs(&mut self) -> Result<(), ()> {
808        parse_arguments!(self, Line, [ point => parse_command_end_abs ])
809    }
810
811    /// Parse a relative "lineto" ("l") command.
812    fn parse_line_rel(&mut self) -> Result<(), ()> {
813        parse_arguments!(self, Line, [ point => parse_command_end_rel ])
814    }
815
816    /// Parse an absolute horizontal "lineto" ("H") command.
817    fn parse_h_line_abs(&mut self) -> Result<(), ()> {
818        parse_arguments!(self, HLine, [ x => parse_axis_end_abs ])
819    }
820
821    /// Parse a relative horizontal "lineto" ("h") command.
822    fn parse_h_line_rel(&mut self) -> Result<(), ()> {
823        parse_arguments!(self, HLine, [ x => parse_axis_end_rel ])
824    }
825
826    /// Parse an absolute vertical "lineto" ("V") command.
827    fn parse_v_line_abs(&mut self) -> Result<(), ()> {
828        parse_arguments!(self, VLine, [ y => parse_axis_end_abs ])
829    }
830
831    /// Parse a relative vertical "lineto" ("v") command.
832    fn parse_v_line_rel(&mut self) -> Result<(), ()> {
833        parse_arguments!(self, VLine, [ y => parse_axis_end_rel ])
834    }
835
836    /// Parse an absolute cubic Bézier curve ("C") command.
837    fn parse_curve_abs(&mut self) -> Result<(), ()> {
838        parse_arguments!(self, CubicCurve, [
839            control1 => parse_control_point_abs, control2 => parse_control_point_abs, point => parse_command_end_abs
840        ])
841    }
842
843    /// Parse a relative cubic Bézier curve ("c") command.
844    fn parse_curve_rel(&mut self) -> Result<(), ()> {
845        parse_arguments!(self, CubicCurve, [
846            control1 => parse_control_point_rel, control2 => parse_control_point_rel, point => parse_command_end_rel
847        ])
848    }
849
850    /// Parse an absolute smooth "curveto" ("S") command.
851    fn parse_smooth_curve_abs(&mut self) -> Result<(), ()> {
852        parse_arguments!(self, SmoothCubic, [
853            control2 => parse_control_point_abs, point => parse_command_end_abs
854        ])
855    }
856
857    /// Parse a relative smooth "curveto" ("s") command.
858    fn parse_smooth_curve_rel(&mut self) -> Result<(), ()> {
859        parse_arguments!(self, SmoothCubic, [
860            control2 => parse_control_point_rel, point => parse_command_end_rel
861        ])
862    }
863
864    /// Parse an absolute quadratic Bézier curve ("Q") command.
865    fn parse_quadratic_bezier_curve_abs(&mut self) -> Result<(), ()> {
866        parse_arguments!(self, QuadCurve, [
867            control1 => parse_control_point_abs, point => parse_command_end_abs
868        ])
869    }
870
871    /// Parse a relative quadratic Bézier curve ("q") command.
872    fn parse_quadratic_bezier_curve_rel(&mut self) -> Result<(), ()> {
873        parse_arguments!(self, QuadCurve, [
874            control1 => parse_control_point_rel, point => parse_command_end_rel
875        ])
876    }
877
878    /// Parse an absolute smooth quadratic Bézier curveto ("T") command.
879    fn parse_smooth_quadratic_bezier_curve_abs(&mut self) -> Result<(), ()> {
880        parse_arguments!(self, SmoothQuad, [ point => parse_command_end_abs ])
881    }
882
883    /// Parse a relative smooth quadratic Bézier curveto ("t") command.
884    fn parse_smooth_quadratic_bezier_curve_rel(&mut self) -> Result<(), ()> {
885        parse_arguments!(self, SmoothQuad, [ point => parse_command_end_rel ])
886    }
887
888    /// Parse an absolute elliptical arc curve ("A") command.
889    fn parse_elliptical_arc_abs(&mut self) -> Result<(), ()> {
890        let (parse_arc_size, parse_arc_sweep) = Self::arc_flag_parsers();
891        parse_arguments!(self, Arc, [
892            radii => parse_arc_radii,
893            rotate => parse_number,
894            arc_size => parse_arc_size,
895            arc_sweep => parse_arc_sweep,
896            point => parse_command_end_abs
897        ])
898    }
899
900    /// Parse a relative elliptical arc curve ("a") command.
901    fn parse_elliptical_arc_rel(&mut self) -> Result<(), ()> {
902        let (parse_arc_size, parse_arc_sweep) = Self::arc_flag_parsers();
903        parse_arguments!(self, Arc, [
904            radii => parse_arc_radii,
905            rotate => parse_number,
906            arc_size => parse_arc_size,
907            arc_sweep => parse_arc_sweep,
908            point => parse_command_end_rel
909        ])
910    }
911
912    /// Helper that returns parsers for the arc-size and arc-sweep flags.
913    fn arc_flag_parsers() -> (
914        impl Fn(&mut Peekable<Cloned<slice::Iter<'_, u8>>>) -> Result<ArcSize, ()>,
915        impl Fn(&mut Peekable<Cloned<slice::Iter<'_, u8>>>) -> Result<ArcSweep, ()>,
916    ) {
917        // Parse a flag whose value is '0' or '1'; otherwise, return Err(()).
918        let parse_arc_size = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
919            Some(c) if c == b'1' => Ok(ArcSize::Large),
920            Some(c) if c == b'0' => Ok(ArcSize::Small),
921            _ => Err(()),
922        };
923        let parse_arc_sweep = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
924            Some(c) if c == b'1' => Ok(ArcSweep::Cw),
925            Some(c) if c == b'0' => Ok(ArcSweep::Ccw),
926            _ => Err(()),
927        };
928        (parse_arc_size, parse_arc_sweep)
929    }
930}
931
932/// Parse a pair of numbers into CoordPair.
933fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()> {
934    let x = parse_number(iter)?;
935    skip_comma_wsp(iter);
936    let y = parse_number(iter)?;
937    Ok(CoordPair::new(x, y))
938}
939
940/// Parse a pair of numbers that describes the absolutely positioned end point.
941fn parse_command_end_abs(
942    iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
943) -> Result<CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>, ()> {
944    let coord = parse_coord(iter)?;
945    Ok(CommandEndPoint::ToPosition(coord.into()))
946}
947
948/// Parse a pair of numbers that describes the relatively positioned end point.
949fn parse_command_end_rel(
950    iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
951) -> Result<CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>, ()> {
952    let coord = parse_coord(iter)?;
953    Ok(CommandEndPoint::ByCoordinate(coord))
954}
955
956/// Parse a pair of values that describe the absolutely positioned curve control point.
957fn parse_control_point_abs(
958    iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
959) -> Result<ControlPoint<ShapePosition<CSSFloat>, CSSFloat>, ()> {
960    let coord = parse_coord(iter)?;
961    Ok(ControlPoint::Relative(RelativeControlPoint {
962        coord,
963        reference: ControlReference::Origin,
964    }))
965}
966
967/// Parse a pair of values that describe the relatively positioned curve control point.
968fn parse_control_point_rel(
969    iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
970) -> Result<ControlPoint<ShapePosition<CSSFloat>, CSSFloat>, ()> {
971    let coord = parse_coord(iter)?;
972    Ok(ControlPoint::Relative(RelativeControlPoint {
973        coord,
974        reference: ControlReference::Start,
975    }))
976}
977
978/// Parse a number that describes the absolutely positioned axis end point.
979fn parse_axis_end_abs(
980    iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
981) -> Result<AxisEndPoint<f32>, ()> {
982    let value = parse_number(iter)?;
983    Ok(AxisEndPoint::ToPosition(AxisPosition::LengthPercent(value)))
984}
985
986/// Parse a number that describes the relatively positioned axis end point.
987fn parse_axis_end_rel(
988    iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
989) -> Result<AxisEndPoint<f32>, ()> {
990    let value = parse_number(iter)?;
991    Ok(AxisEndPoint::ByCoordinate(value))
992}
993
994/// Parse a pair of numbers that describes the size of the ellipse that the arc is taken from.
995fn parse_arc_radii(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<ArcRadii<CSSFloat>, ()> {
996    let coord = parse_coord(iter)?;
997    Ok(ArcRadii {
998        rx: coord.x,
999        ry: Some(coord.y).into(),
1000    })
1001}
1002
1003/// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed
1004/// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating
1005/// point number. In other words, the logic here is similar with that of
1006/// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the
1007/// input is a Peekable and we only accept an integer of a floating point number.
1008///
1009/// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF
1010fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> {
1011    // 1. Check optional sign.
1012    let sign = if iter
1013        .peek()
1014        .map_or(false, |&sign| sign == b'+' || sign == b'-')
1015    {
1016        if iter.next().unwrap() == b'-' {
1017            -1.
1018        } else {
1019            1.
1020        }
1021    } else {
1022        1.
1023    };
1024
1025    // 2. Check integer part.
1026    let mut integral_part: f64 = 0.;
1027    let got_dot = if !iter.peek().map_or(false, |&n| n == b'.') {
1028        // If the first digit in integer part is neither a dot nor a digit, this is not a number.
1029        if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
1030            return Err(());
1031        }
1032
1033        while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
1034            integral_part = integral_part * 10. + (iter.next().unwrap() - b'0') as f64;
1035        }
1036
1037        iter.peek().map_or(false, |&n| n == b'.')
1038    } else {
1039        true
1040    };
1041
1042    // 3. Check fractional part.
1043    let mut fractional_part: f64 = 0.;
1044    if got_dot {
1045        // Consume '.'.
1046        iter.next();
1047        // If the first digit in fractional part is not a digit, this is not a number.
1048        if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
1049            return Err(());
1050        }
1051
1052        let mut factor = 0.1;
1053        while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
1054            fractional_part += (iter.next().unwrap() - b'0') as f64 * factor;
1055            factor *= 0.1;
1056        }
1057    }
1058
1059    let mut value = sign * (integral_part + fractional_part);
1060
1061    // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to
1062    //    treat the numbers after 'E' or 'e' are in the exponential part.
1063    if iter.peek().map_or(false, |&exp| exp == b'E' || exp == b'e') {
1064        // Consume 'E' or 'e'.
1065        iter.next();
1066        let exp_sign = if iter
1067            .peek()
1068            .map_or(false, |&sign| sign == b'+' || sign == b'-')
1069        {
1070            if iter.next().unwrap() == b'-' {
1071                -1.
1072            } else {
1073                1.
1074            }
1075        } else {
1076            1.
1077        };
1078
1079        let mut exp: f64 = 0.;
1080        while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
1081            exp = exp * 10. + (iter.next().unwrap() - b'0') as f64;
1082        }
1083
1084        value *= f64::powf(10., exp * exp_sign);
1085    }
1086
1087    if value.is_finite() {
1088        Ok(value.min(f32::MAX as f64).max(f32::MIN as f64) as CSSFloat)
1089    } else {
1090        Err(())
1091    }
1092}
1093
1094/// Skip all svg whitespaces, and return true if |iter| hasn't finished.
1095#[inline]
1096fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1097    // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}.
1098    //       However, SVG 2 has one extra whitespace: \u{C}.
1099    //       Therefore, we follow the newest spec for the definition of whitespace,
1100    //       i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}.
1101    while iter.peek().map_or(false, |c| c.is_ascii_whitespace()) {
1102        iter.next();
1103    }
1104    iter.peek().is_some()
1105}
1106
1107/// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished.
1108#[inline]
1109fn skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1110    if !skip_wsp(iter) {
1111        return false;
1112    }
1113
1114    if *iter.peek().unwrap() != b',' {
1115        return true;
1116    }
1117    iter.next();
1118
1119    skip_wsp(iter)
1120}