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