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