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 53d3719b021..24cf40f845e 100644 --- a/components/style/values/animated/mod.rs +++ b/components/style/values/animated/mod.rs @@ -392,3 +392,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/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/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 48ae185477d..490343aa5ce 100644 --- a/components/style/values/specified/svg_path.rs +++ b/components/style/values/specified/svg_path.rs @@ -8,16 +8,20 @@ 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. /// /// 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 { @@ -34,6 +38,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 +97,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 +131,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, ToAnimatedZero)] #[allow(missing_docs)] #[repr(C, u8)] pub enum PathCommand { @@ -97,35 +140,131 @@ 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, } +/// 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.is_yes() { + point += state.pos; + } + state.pos = point; + state.subpath_start = point; + MoveTo { point, absolute: IsAbsolute::Yes } + }, + LineTo { mut point, absolute } => { + if !absolute.is_yes() { + point += state.pos; + } + state.pos = point; + LineTo { point, absolute: IsAbsolute::Yes } + }, + HorizontalLineTo { mut x, absolute } => { + if !absolute.is_yes() { + x += state.pos.0; + } + state.pos.0 = x; + HorizontalLineTo { x, absolute: IsAbsolute::Yes } + }, + VerticalLineTo { mut y, absolute } => { + if !absolute.is_yes() { + y += state.pos.1; + } + state.pos.1 = y; + VerticalLineTo { y, absolute: IsAbsolute::Yes } + }, + CurveTo { mut control1, mut control2, mut point, absolute } => { + if !absolute.is_yes() { + control1 += state.pos; + control2 += state.pos; + point += state.pos; + } + state.pos = point; + CurveTo { control1, control2, point, absolute: IsAbsolute::Yes } + }, + SmoothCurveTo { mut control2, mut point, absolute } => { + if !absolute.is_yes() { + control2 += state.pos; + point += state.pos; + } + state.pos = point; + SmoothCurveTo { control2, point, absolute: IsAbsolute::Yes } + }, + QuadBezierCurveTo { mut control1, mut point, absolute } => { + if !absolute.is_yes() { + control1 += state.pos; + point += state.pos; + } + state.pos = point; + QuadBezierCurveTo { control1, point, absolute: IsAbsolute::Yes } + }, + SmoothQuadBezierCurveTo { mut point, absolute } => { + if !absolute.is_yes() { + point += state.pos; + } + state.pos = point; + SmoothQuadBezierCurveTo { point, absolute: IsAbsolute::Yes } + }, + EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, mut point, absolute } => { + if !absolute.is_yes() { + point += state.pos; + } + state.pos = point; + EllipticalArc { + rx, ry, angle, large_arc_flag, sweep_flag, point, absolute: IsAbsolute::Yes + } + }, + } + } +} + impl ToCss for PathCommand { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where @@ -136,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(' ')?; @@ -155,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(' ')?; @@ -170,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) }, @@ -202,9 +341,27 @@ impl ToCss for PathCommand { } } +/// 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(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)] +#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, + SpecifiedValueInfo, ToAnimatedZero, ToCss)] #[repr(C)] pub struct CoordPair(CSSFloat, CSSFloat); @@ -216,6 +373,36 @@ impl CoordPair { } } +impl AddAssign for CoordPair { + #[inline] + fn add_assign(&mut self, other: Self) { + self.0 += other.0; + self.1 += other.1; + } +} + +/// 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> { @@ -276,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 { @@ -299,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. @@ -317,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. @@ -333,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(()), } };