diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs index 53d3719b0213..f09e18861702 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 fbdd4ea00043..07d4c70078f8 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 0eccf011c716..d2cc98b755d9 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 48ae185477df..d4583daaad7a 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> {