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