tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

svg_path.rs (39196B)


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