From 14911b96e08c663ad90cff2dd00082ff52417d6a Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Fri, 7 Sep 2018 22:15:50 +0000 Subject: [PATCH 1/3] style: Make SVGPathData and clip-path: path() animatable. Implement Animate trait for SVGPathData. The basic idea is: we normalize |this| and |other| svg paths, and then do interpolation on the normalized svg paths. The normalization is to convert relative coordinates into absolute coordinates, so we could do real number interpolation on each path command directly. In this patch, we also make |clip-path:path()| animatable. Differential Revision: https://phabricator.services.mozilla.com/D4786 --- components/style/values/animated/mod.rs | 14 ++ components/style/values/distance.rs | 7 + .../style/values/generics/basic_shape.rs | 12 +- components/style/values/specified/svg_path.rs | 146 +++++++++++++++++- 4 files changed, 175 insertions(+), 4 deletions(-) diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs index 53d3719b021..f09e1886170 100644 --- a/components/style/values/animated/mod.rs +++ b/components/style/values/animated/mod.rs @@ -141,6 +141,20 @@ impl Animate for f64 { } } +/// This is only used in SVG PATH. We return Err(()) if the flags are mismatched. +// FIXME: Bug 653928: If we want to do interpolation on the flags in Arc, we have to update this +// because `absolute`, `large_arc_flag`, and `sweep_flag` are using this implementation for now. +impl Animate for bool { + #[inline] + fn animate(&self, other: &Self, _procedure: Procedure) -> Result { + if *self == *other { + Ok(*other) + } else { + Err(()) + } + } +} + impl Animate for Option where T: Animate, diff --git a/components/style/values/distance.rs b/components/style/values/distance.rs index fbdd4ea0004..07d4c70078f 100644 --- a/components/style/values/distance.rs +++ b/components/style/values/distance.rs @@ -81,6 +81,13 @@ impl ComputeSquaredDistance for Au { } } +impl ComputeSquaredDistance for bool { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + (*self as i32).compute_squared_distance(&(*other as i32)) + } +} + impl ComputeSquaredDistance for Option where T: ComputeSquaredDistance, diff --git a/components/style/values/generics/basic_shape.rs b/components/style/values/generics/basic_shape.rs index 0eccf011c71..d2cc98b755d 100644 --- a/components/style/values/generics/basic_shape.rs +++ b/components/style/values/generics/basic_shape.rs @@ -54,7 +54,6 @@ pub enum ShapeSource { Shape(BasicShape, Option), #[animation(error)] Box(ReferenceBox), - #[animation(error)] #[css(function)] Path(Path), #[animation(error)] @@ -152,10 +151,12 @@ pub enum FillRule { /// /// https://drafts.csswg.org/css-shapes-2/#funcdef-path #[css(comma)] -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)] +#[derive(Animate, Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, + ToComputedValue, ToCss)] pub struct Path { /// The filling rule for the svg path. #[css(skip_if = "fill_is_default")] + #[animation(constant)] pub fill: FillRule, /// The svg path data. pub path: SVGPathData, @@ -177,6 +178,13 @@ where { this.compute_squared_distance(other) }, + ( + &ShapeSource::Path(ref this), + &ShapeSource::Path(ref other), + ) if this.fill == other.fill => + { + this.path.compute_squared_distance(&other.path) + } _ => Err(()), } } diff --git a/components/style/values/specified/svg_path.rs b/components/style/values/specified/svg_path.rs index 48ae185477d..d4583daaad7 100644 --- a/components/style/values/specified/svg_path.rs +++ b/components/style/values/specified/svg_path.rs @@ -8,10 +8,13 @@ use cssparser::Parser; use parser::{Parse, ParserContext}; use std::fmt::{self, Write}; use std::iter::{Cloned, Peekable}; +use std::ops::AddAssign; use std::slice; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use style_traits::values::SequenceWriter; use values::CSSFloat; +use values::animated::{Animate, Procedure}; +use values::distance::{ComputeSquaredDistance, SquaredDistance}; /// The SVG path data. @@ -34,6 +37,17 @@ impl SVGPathData { debug_assert!(!self.0.is_empty()); &self.0 } + + /// Create a normalized copy of this path by converting each relative command to an absolute + /// command. + fn normalize(&self) -> Self { + let mut state = PathTraversalState { + subpath_start: CoordPair::new(0.0, 0.0), + pos: CoordPair::new(0.0, 0.0), + }; + let result = self.0.iter().map(|seg| seg.normalize(&mut state)).collect::>(); + SVGPathData(result.into_boxed_slice()) + } } impl ToCss for SVGPathData { @@ -82,6 +96,33 @@ impl Parse for SVGPathData { } } +impl Animate for SVGPathData { + fn animate(&self, other: &Self, procedure: Procedure) -> Result { + if self.0.len() != other.0.len() { + return Err(()); + } + + let result = self.normalize().0 + .iter() + .zip(other.normalize().0.iter()) + .map(|(a, b)| a.animate(&b, procedure)) + .collect::, _>>()?; + Ok(SVGPathData::new(result.into_boxed_slice())) + } +} + +impl ComputeSquaredDistance for SVGPathData { + fn compute_squared_distance(&self, other: &Self) -> Result { + if self.0.len() != other.0.len() { + return Err(()); + } + self.normalize().0 + .iter() + .zip(other.normalize().0.iter()) + .map(|(this, other)| this.compute_squared_distance(&other)) + .sum() + } +} /// The SVG path command. /// The fields of these commands are self-explanatory, so we skip the documents. @@ -89,7 +130,8 @@ impl Parse for SVGPathData { /// points of the Bézier curve in the spec. /// /// https://www.w3.org/TR/SVG11/paths.html#PathData -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)] +#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, + SpecifiedValueInfo)] #[allow(missing_docs)] #[repr(C, u8)] pub enum PathCommand { @@ -126,6 +168,98 @@ pub enum PathCommand { ClosePath, } +/// For internal SVGPath normalization. +#[allow(missing_docs)] +struct PathTraversalState { + subpath_start: CoordPair, + pos: CoordPair, +} + +impl PathCommand { + /// Create a normalized copy of this PathCommand. Absolute commands will be copied as-is while + /// for relative commands an equivalent absolute command will be returned. + /// + /// See discussion: https://github.com/w3c/svgwg/issues/321 + fn normalize(&self, state: &mut PathTraversalState) -> Self { + use self::PathCommand::*; + match *self { + Unknown => Unknown, + ClosePath => { + state.pos = state.subpath_start; + ClosePath + }, + MoveTo { mut point, absolute } => { + if !absolute { + point += state.pos; + } + state.pos = point; + state.subpath_start = point; + MoveTo { point, absolute: true } + }, + LineTo { mut point, absolute } => { + if !absolute { + point += state.pos; + } + state.pos = point; + LineTo { point, absolute: true } + }, + HorizontalLineTo { mut x, absolute } => { + if !absolute { + x += state.pos.0; + } + state.pos.0 = x; + HorizontalLineTo { x, absolute: true } + }, + VerticalLineTo { mut y, absolute } => { + if !absolute { + y += state.pos.1; + } + state.pos.1 = y; + VerticalLineTo { y, absolute: true } + }, + CurveTo { mut control1, mut control2, mut point, absolute } => { + if !absolute { + control1 += state.pos; + control2 += state.pos; + point += state.pos; + } + state.pos = point; + CurveTo { control1, control2, point, absolute: true } + }, + SmoothCurveTo { mut control2, mut point, absolute } => { + if !absolute { + control2 += state.pos; + point += state.pos; + } + state.pos = point; + SmoothCurveTo { control2, point, absolute: true } + }, + QuadBezierCurveTo { mut control1, mut point, absolute } => { + if !absolute { + control1 += state.pos; + point += state.pos; + } + state.pos = point; + QuadBezierCurveTo { control1, point, absolute: true } + }, + SmoothQuadBezierCurveTo { mut point, absolute } => { + if !absolute { + point += state.pos; + } + state.pos = point; + SmoothQuadBezierCurveTo { point, absolute: true } + }, + EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, mut point, absolute } => { + if !absolute { + point += state.pos; + } + state.pos = point; + EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute: true } + }, + } + } +} + impl ToCss for PathCommand { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where @@ -204,7 +338,8 @@ impl ToCss for PathCommand { /// The path coord type. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)] +#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, + SpecifiedValueInfo, ToCss)] #[repr(C)] pub struct CoordPair(CSSFloat, CSSFloat); @@ -216,6 +351,13 @@ impl CoordPair { } } +impl AddAssign for CoordPair { + #[inline] + fn add_assign(&mut self, other: Self) { + self.0 += other.0; + self.1 += other.1; + } +} /// SVG Path parser. struct PathParser<'a> { From b0604c9be51b1179ad7008dc4ff2278b9c575af6 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Fri, 7 Sep 2018 22:29:12 +0000 Subject: [PATCH 2/3] style: Make offset-path: path() animatable. Here, we change the animation type of offset-path as ComputedValue, so we could do animation on it. Also enable the wpt for offset-path interpolation. In test_transition_per_property.html, we add some basic tests ifor offset-path. ToAnimatedZero for PathCommand will be dropped later. Because the animations of arcs with mismatched flags are fallen back to discrete animations, the result of getComputedValue is not normalized in this case. This makes some wpt failed even though the progress is 100%. Depends on D4786 Differential Revision: https://phabricator.services.mozilla.com/D4787 --- .../style/properties/longhands/box.mako.rs | 2 +- components/style/values/animated/mod.rs | 11 ++++ components/style/values/specified/motion.rs | 4 +- components/style/values/specified/svg_path.rs | 63 ++++++++++++++++++- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 7dfc15cd14e..ed7839360d9 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -379,7 +379,7 @@ ${helpers.predefined_type( "OffsetPath", "computed::OffsetPath::none()", products="gecko", - animation_value_type="none", + animation_value_type="ComputedValue", gecko_pref="layout.css.motion-path.enabled", flags="CREATES_STACKING_CONTEXT FIXPOS_CB", spec="https://drafts.fxtf.org/motion-1/#offset-path-property", diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs index f09e1886170..1d0ccd6904c 100644 --- a/components/style/values/animated/mod.rs +++ b/components/style/values/animated/mod.rs @@ -406,3 +406,14 @@ where )) } } + +impl ToAnimatedZero for Box<[T]> +where + T: ToAnimatedZero, +{ + #[inline] + fn to_animated_zero(&self) -> Result { + let v = self.iter().map(|v| v.to_animated_zero()).collect::, _>>()?; + Ok(v.into_boxed_slice()) + } +} diff --git a/components/style/values/specified/motion.rs b/components/style/values/specified/motion.rs index 87ff39d9a3a..762c1dcfdd5 100644 --- a/components/style/values/specified/motion.rs +++ b/components/style/values/specified/motion.rs @@ -12,7 +12,8 @@ use values::specified::SVGPathData; /// The offset-path value. /// /// https://drafts.fxtf.org/motion-1/#offset-path-property -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)] +#[derive(Animate, Clone, ComputeSquaredDistance, Debug, MallocSizeOf, PartialEq, + SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, ToCss)] pub enum OffsetPath { // We could merge SVGPathData into ShapeSource, so we could reuse them. However, // we don't want to support other value for offset-path, so use SVGPathData only for now. @@ -20,6 +21,7 @@ pub enum OffsetPath { #[css(function)] Path(SVGPathData), /// None value. + #[animation(error)] None, // Bug 1186329: Implement ray(), , , and . } diff --git a/components/style/values/specified/svg_path.rs b/components/style/values/specified/svg_path.rs index d4583daaad7..d31d182d941 100644 --- a/components/style/values/specified/svg_path.rs +++ b/components/style/values/specified/svg_path.rs @@ -13,14 +13,15 @@ use std::slice; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use style_traits::values::SequenceWriter; use values::CSSFloat; -use values::animated::{Animate, Procedure}; +use values::animated::{Animate, Procedure, ToAnimatedZero}; use values::distance::{ComputeSquaredDistance, SquaredDistance}; /// The SVG path data. /// /// https://www.w3.org/TR/SVG11/paths.html#PathData -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToAnimatedZero, + ToComputedValue)] pub struct SVGPathData(Box<[PathCommand]>); impl SVGPathData { @@ -336,10 +337,66 @@ impl ToCss for PathCommand { } } +impl ToAnimatedZero for PathCommand { + #[inline] + fn to_animated_zero(&self) -> Result { + use self::PathCommand::*; + let absolute = true; + match self { + &ClosePath => Ok(ClosePath), + &Unknown => Ok(Unknown), + &MoveTo { ref point, .. } => Ok(MoveTo { point: point.to_animated_zero()?, absolute }), + &LineTo { ref point, .. } => Ok(LineTo { point: point.to_animated_zero()?, absolute }), + &HorizontalLineTo { x, .. } => { + Ok(HorizontalLineTo { x: x.to_animated_zero()?, absolute }) + }, + &VerticalLineTo { y, .. } => { + Ok(VerticalLineTo { y: y.to_animated_zero()?, absolute }) + }, + &CurveTo { ref control1, ref control2, ref point, .. } => { + Ok(CurveTo { + control1: control1.to_animated_zero()?, + control2: control2.to_animated_zero()?, + point: point.to_animated_zero()?, + absolute, + }) + }, + &SmoothCurveTo { ref control2, ref point, .. } => { + Ok(SmoothCurveTo { + control2: control2.to_animated_zero()?, + point: point.to_animated_zero()?, + absolute, + }) + }, + &QuadBezierCurveTo { ref control1, ref point, .. } => { + Ok(QuadBezierCurveTo { + control1: control1.to_animated_zero()?, + point: point.to_animated_zero()?, + absolute, + }) + }, + &SmoothQuadBezierCurveTo { ref point, .. } => { + Ok(SmoothQuadBezierCurveTo { point: point.to_animated_zero()?, absolute }) + }, + &EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, ref point, .. } => { + Ok(EllipticalArc { + rx: rx.to_animated_zero()?, + ry: ry.to_animated_zero()?, + angle: angle.to_animated_zero()?, + large_arc_flag, + sweep_flag, + point: point.to_animated_zero()?, + absolute, + }) + }, + } + } +} + /// The path coord type. #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, - SpecifiedValueInfo, ToCss)] + SpecifiedValueInfo, ToAnimatedZero, ToCss)] #[repr(C)] pub struct CoordPair(CSSFloat, CSSFloat); From 31fc6cd565479144b9b0c2d3fff09caad8089df7 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Fri, 7 Sep 2018 22:25:59 +0000 Subject: [PATCH 3/3] style: Use the standalone struct and enum for the flags in SVG path. We define the standalone types for using derive macro easily and overriding the behaviors of this traits. This could avoid defining the general behavior of booleans. Depends on D4788 Differential Revision: https://phabricator.services.mozilla.com/D4813 --- components/style/values/animated/mod.rs | 14 -- components/style/values/distance.rs | 7 - components/style/values/specified/svg_path.rs | 212 +++++++++--------- 3 files changed, 102 insertions(+), 131 deletions(-) diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs index 1d0ccd6904c..24cf40f845e 100644 --- a/components/style/values/animated/mod.rs +++ b/components/style/values/animated/mod.rs @@ -141,20 +141,6 @@ impl Animate for f64 { } } -/// This is only used in SVG PATH. We return Err(()) if the flags are mismatched. -// FIXME: Bug 653928: If we want to do interpolation on the flags in Arc, we have to update this -// because `absolute`, `large_arc_flag`, and `sweep_flag` are using this implementation for now. -impl Animate for bool { - #[inline] - fn animate(&self, other: &Self, _procedure: Procedure) -> Result { - if *self == *other { - Ok(*other) - } else { - Err(()) - } - } -} - impl Animate for Option where T: Animate, diff --git a/components/style/values/distance.rs b/components/style/values/distance.rs index 07d4c70078f..fbdd4ea0004 100644 --- a/components/style/values/distance.rs +++ b/components/style/values/distance.rs @@ -81,13 +81,6 @@ impl ComputeSquaredDistance for Au { } } -impl ComputeSquaredDistance for bool { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - (*self as i32).compute_squared_distance(&(*other as i32)) - } -} - impl ComputeSquaredDistance for Option where T: ComputeSquaredDistance, diff --git a/components/style/values/specified/svg_path.rs b/components/style/values/specified/svg_path.rs index d31d182d941..490343aa5ce 100644 --- a/components/style/values/specified/svg_path.rs +++ b/components/style/values/specified/svg_path.rs @@ -13,7 +13,7 @@ use std::slice; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use style_traits::values::SequenceWriter; use values::CSSFloat; -use values::animated::{Animate, Procedure, ToAnimatedZero}; +use values::animated::{Animate, Procedure}; use values::distance::{ComputeSquaredDistance, SquaredDistance}; @@ -132,7 +132,7 @@ impl ComputeSquaredDistance for SVGPathData { /// /// https://www.w3.org/TR/SVG11/paths.html#PathData #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, - SpecifiedValueInfo)] + SpecifiedValueInfo, ToAnimatedZero)] #[allow(missing_docs)] #[repr(C, u8)] pub enum PathCommand { @@ -140,30 +140,32 @@ pub enum PathCommand { /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN Unknown, /// The "moveto" command. - MoveTo { point: CoordPair, absolute: bool }, + MoveTo { point: CoordPair, absolute: IsAbsolute }, /// The "lineto" command. - LineTo { point: CoordPair, absolute: bool }, + LineTo { point: CoordPair, absolute: IsAbsolute }, /// The horizontal "lineto" command. - HorizontalLineTo { x: CSSFloat, absolute: bool }, + HorizontalLineTo { x: CSSFloat, absolute: IsAbsolute }, /// The vertical "lineto" command. - VerticalLineTo { y: CSSFloat, absolute: bool }, + VerticalLineTo { y: CSSFloat, absolute: IsAbsolute }, /// The cubic Bézier curve command. - CurveTo { control1: CoordPair, control2: CoordPair, point: CoordPair, absolute: bool }, + CurveTo { control1: CoordPair, control2: CoordPair, point: CoordPair, absolute: IsAbsolute }, /// The smooth curve command. - SmoothCurveTo { control2: CoordPair, point: CoordPair, absolute: bool }, + SmoothCurveTo { control2: CoordPair, point: CoordPair, absolute: IsAbsolute }, /// The quadratic Bézier curve command. - QuadBezierCurveTo { control1: CoordPair, point: CoordPair, absolute: bool }, + QuadBezierCurveTo { control1: CoordPair, point: CoordPair, absolute: IsAbsolute }, /// The smooth quadratic Bézier curve command. - SmoothQuadBezierCurveTo { point: CoordPair, absolute: bool }, + SmoothQuadBezierCurveTo { point: CoordPair, absolute: IsAbsolute }, /// The elliptical arc curve command. EllipticalArc { rx: CSSFloat, ry: CSSFloat, angle: CSSFloat, - large_arc_flag: bool, - sweep_flag: bool, + #[animation(constant)] + large_arc_flag: ArcFlag, + #[animation(constant)] + sweep_flag: ArcFlag, point: CoordPair, - absolute: bool + absolute: IsAbsolute }, /// The "closepath" command. ClosePath, @@ -190,72 +192,74 @@ impl PathCommand { ClosePath }, MoveTo { mut point, absolute } => { - if !absolute { + if !absolute.is_yes() { point += state.pos; } state.pos = point; state.subpath_start = point; - MoveTo { point, absolute: true } + MoveTo { point, absolute: IsAbsolute::Yes } }, LineTo { mut point, absolute } => { - if !absolute { + if !absolute.is_yes() { point += state.pos; } state.pos = point; - LineTo { point, absolute: true } + LineTo { point, absolute: IsAbsolute::Yes } }, HorizontalLineTo { mut x, absolute } => { - if !absolute { + if !absolute.is_yes() { x += state.pos.0; } state.pos.0 = x; - HorizontalLineTo { x, absolute: true } + HorizontalLineTo { x, absolute: IsAbsolute::Yes } }, VerticalLineTo { mut y, absolute } => { - if !absolute { + if !absolute.is_yes() { y += state.pos.1; } state.pos.1 = y; - VerticalLineTo { y, absolute: true } + VerticalLineTo { y, absolute: IsAbsolute::Yes } }, CurveTo { mut control1, mut control2, mut point, absolute } => { - if !absolute { + if !absolute.is_yes() { control1 += state.pos; control2 += state.pos; point += state.pos; } state.pos = point; - CurveTo { control1, control2, point, absolute: true } + CurveTo { control1, control2, point, absolute: IsAbsolute::Yes } }, SmoothCurveTo { mut control2, mut point, absolute } => { - if !absolute { + if !absolute.is_yes() { control2 += state.pos; point += state.pos; } state.pos = point; - SmoothCurveTo { control2, point, absolute: true } + SmoothCurveTo { control2, point, absolute: IsAbsolute::Yes } }, QuadBezierCurveTo { mut control1, mut point, absolute } => { - if !absolute { + if !absolute.is_yes() { control1 += state.pos; point += state.pos; } state.pos = point; - QuadBezierCurveTo { control1, point, absolute: true } + QuadBezierCurveTo { control1, point, absolute: IsAbsolute::Yes } }, SmoothQuadBezierCurveTo { mut point, absolute } => { - if !absolute { + if !absolute.is_yes() { point += state.pos; } state.pos = point; - SmoothQuadBezierCurveTo { point, absolute: true } + SmoothQuadBezierCurveTo { point, absolute: IsAbsolute::Yes } }, EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, mut point, absolute } => { - if !absolute { + if !absolute.is_yes() { point += state.pos; } state.pos = point; - EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute: true } + EllipticalArc { + rx, ry, angle, large_arc_flag, sweep_flag, point, absolute: IsAbsolute::Yes + } }, } } @@ -271,17 +275,17 @@ impl ToCss for PathCommand { Unknown => dest.write_char('X'), ClosePath => dest.write_char('Z'), MoveTo { point, absolute } => { - dest.write_char(if absolute { 'M' } else { 'm' })?; + dest.write_char(if absolute.is_yes() { 'M' } else { 'm' })?; dest.write_char(' ')?; point.to_css(dest) } LineTo { point, absolute } => { - dest.write_char(if absolute { 'L' } else { 'l' })?; + dest.write_char(if absolute.is_yes() { 'L' } else { 'l' })?; dest.write_char(' ')?; point.to_css(dest) } CurveTo { control1, control2, point, absolute } => { - dest.write_char(if absolute { 'C' } else { 'c' })?; + dest.write_char(if absolute.is_yes() { 'C' } else { 'c' })?; dest.write_char(' ')?; control1.to_css(dest)?; dest.write_char(' ')?; @@ -290,14 +294,14 @@ impl ToCss for PathCommand { point.to_css(dest) }, QuadBezierCurveTo { control1, point, absolute } => { - dest.write_char(if absolute { 'Q' } else { 'q' })?; + dest.write_char(if absolute.is_yes() { 'Q' } else { 'q' })?; dest.write_char(' ')?; control1.to_css(dest)?; dest.write_char(' ')?; point.to_css(dest) }, EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute } => { - dest.write_char(if absolute { 'A' } else { 'a' })?; + dest.write_char(if absolute.is_yes() { 'A' } else { 'a' })?; dest.write_char(' ')?; rx.to_css(dest)?; dest.write_char(' ')?; @@ -305,31 +309,31 @@ impl ToCss for PathCommand { dest.write_char(' ')?; angle.to_css(dest)?; dest.write_char(' ')?; - (large_arc_flag as i32).to_css(dest)?; + large_arc_flag.to_css(dest)?; dest.write_char(' ')?; - (sweep_flag as i32).to_css(dest)?; + sweep_flag.to_css(dest)?; dest.write_char(' ')?; point.to_css(dest) }, HorizontalLineTo { x, absolute } => { - dest.write_char(if absolute { 'H' } else { 'h' })?; + dest.write_char(if absolute.is_yes() { 'H' } else { 'h' })?; dest.write_char(' ')?; x.to_css(dest) }, VerticalLineTo { y, absolute } => { - dest.write_char(if absolute { 'V' } else { 'v' })?; + dest.write_char(if absolute.is_yes() { 'V' } else { 'v' })?; dest.write_char(' ')?; y.to_css(dest) }, SmoothCurveTo { control2, point, absolute } => { - dest.write_char(if absolute { 'S' } else { 's' })?; + dest.write_char(if absolute.is_yes() { 'S' } else { 's' })?; dest.write_char(' ')?; control2.to_css(dest)?; dest.write_char(' ')?; point.to_css(dest) }, SmoothQuadBezierCurveTo { point, absolute } => { - dest.write_char(if absolute { 'T' } else { 't' })?; + dest.write_char(if absolute.is_yes() { 'T' } else { 't' })?; dest.write_char(' ')?; point.to_css(dest) }, @@ -337,62 +341,23 @@ impl ToCss for PathCommand { } } -impl ToAnimatedZero for PathCommand { - #[inline] - fn to_animated_zero(&self) -> Result { - use self::PathCommand::*; - let absolute = true; - match self { - &ClosePath => Ok(ClosePath), - &Unknown => Ok(Unknown), - &MoveTo { ref point, .. } => Ok(MoveTo { point: point.to_animated_zero()?, absolute }), - &LineTo { ref point, .. } => Ok(LineTo { point: point.to_animated_zero()?, absolute }), - &HorizontalLineTo { x, .. } => { - Ok(HorizontalLineTo { x: x.to_animated_zero()?, absolute }) - }, - &VerticalLineTo { y, .. } => { - Ok(VerticalLineTo { y: y.to_animated_zero()?, absolute }) - }, - &CurveTo { ref control1, ref control2, ref point, .. } => { - Ok(CurveTo { - control1: control1.to_animated_zero()?, - control2: control2.to_animated_zero()?, - point: point.to_animated_zero()?, - absolute, - }) - }, - &SmoothCurveTo { ref control2, ref point, .. } => { - Ok(SmoothCurveTo { - control2: control2.to_animated_zero()?, - point: point.to_animated_zero()?, - absolute, - }) - }, - &QuadBezierCurveTo { ref control1, ref point, .. } => { - Ok(QuadBezierCurveTo { - control1: control1.to_animated_zero()?, - point: point.to_animated_zero()?, - absolute, - }) - }, - &SmoothQuadBezierCurveTo { ref point, .. } => { - Ok(SmoothQuadBezierCurveTo { point: point.to_animated_zero()?, absolute }) - }, - &EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, ref point, .. } => { - Ok(EllipticalArc { - rx: rx.to_animated_zero()?, - ry: ry.to_animated_zero()?, - angle: angle.to_animated_zero()?, - large_arc_flag, - sweep_flag, - point: point.to_animated_zero()?, - absolute, - }) - }, - } - } +/// The path command absolute type. +#[allow(missing_docs)] +#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, + SpecifiedValueInfo, ToAnimatedZero)] +#[repr(u8)] +pub enum IsAbsolute { + Yes, + No, } +impl IsAbsolute { + /// Return true if this is IsAbsolute::Yes. + #[inline] + pub fn is_yes(&self) -> bool { + *self == IsAbsolute::Yes + } +} /// The path coord type. #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, @@ -416,6 +381,29 @@ impl AddAssign for CoordPair { } } +/// The EllipticalArc flag type. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)] +#[repr(C)] +pub struct ArcFlag(bool); + +impl ToCss for ArcFlag { + #[inline] + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write + { + (self.0 as i32).to_css(dest) + } +} + +impl ComputeSquaredDistance for ArcFlag { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + (self.0 as i32).compute_squared_distance(&(other.0 as i32)) + } +} + + /// SVG Path parser. struct PathParser<'a> { chars: Peekable>>, @@ -475,7 +463,11 @@ impl<'a> PathParser<'a> { match self.chars.next() { Some(command) => { - let abs = command.is_ascii_uppercase(); + let abs = if command.is_ascii_uppercase() { + IsAbsolute::Yes + } else { + IsAbsolute::No + }; macro_rules! parse_command { ( $($($p:pat)|+ => $parse_func:ident,)* ) => { match command { @@ -498,7 +490,7 @@ impl<'a> PathParser<'a> { b'S' | b's' => parse_smooth_curveto, b'Q' | b'q' => parse_quadratic_bezier_curveto, b'T' | b't' => parse_smooth_quadratic_bezier_curveto, - b'A' | b'a' => parse_elliprical_arc, + b'A' | b'a' => parse_elliptical_arc, ); }, _ => break, // no more commands. @@ -516,7 +508,7 @@ impl<'a> PathParser<'a> { skip_wsp(&mut self.chars); let point = parse_coord(&mut self.chars)?; - let absolute = command == b'M'; + let absolute = if command == b'M' { IsAbsolute::Yes } else { IsAbsolute::No }; self.path.push(PathCommand::MoveTo { point, absolute } ); // End of string or the next character is a possible new command. @@ -532,58 +524,58 @@ impl<'a> PathParser<'a> { } /// Parse "closepath" command. - fn parse_closepath(&mut self, _absolute: bool) -> Result<(), ()> { + fn parse_closepath(&mut self, _absolute: IsAbsolute) -> Result<(), ()> { self.path.push(PathCommand::ClosePath); Ok(()) } /// Parse "lineto" command. - fn parse_lineto(&mut self, absolute: bool) -> Result<(), ()> { + fn parse_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { parse_arguments!(self, absolute, LineTo, [ point => parse_coord ]) } /// Parse horizontal "lineto" command. - fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> { + fn parse_h_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ]) } /// Parse vertical "lineto" command. - fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> { + fn parse_v_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ]) } /// Parse cubic Bézier curve command. - fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> { + fn parse_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { parse_arguments!(self, absolute, CurveTo, [ control1 => parse_coord, control2 => parse_coord, point => parse_coord ]) } /// Parse smooth "curveto" command. - fn parse_smooth_curveto(&mut self, absolute: bool) -> Result<(), ()> { + fn parse_smooth_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { parse_arguments!(self, absolute, SmoothCurveTo, [ control2 => parse_coord, point => parse_coord ]) } /// Parse quadratic Bézier curve command. - fn parse_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> { + fn parse_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { parse_arguments!(self, absolute, QuadBezierCurveTo, [ control1 => parse_coord, point => parse_coord ]) } /// Parse smooth quadratic Bézier curveto command. - fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> { + fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ]) } /// Parse elliptical arc curve command. - fn parse_elliprical_arc(&mut self, absolute: bool) -> Result<(), ()> { + fn parse_elliptical_arc(&mut self, absolute: IsAbsolute) -> Result<(), ()> { // Parse a flag whose value is '0' or '1'; otherwise, return Err(()). - let parse_flag = |iter: &mut Peekable>>| -> Result { + let parse_flag = |iter: &mut Peekable>>| { match iter.next() { - Some(c) if c == b'0' || c == b'1' => Ok(c == b'1'), + Some(c) if c == b'0' || c == b'1' => Ok(ArcFlag(c == b'1')), _ => Err(()), } };