Skip to content

Commit

Permalink
style: Make SVGPathData and clip-path: path() animatable.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
BorisChiou authored and emilio committed Sep 9, 2018
1 parent 9c1c58a commit 14911b9
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 4 deletions.
14 changes: 14 additions & 0 deletions components/style/values/animated/mod.rs
Expand Up @@ -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<Self, ()> {
if *self == *other {
Ok(*other)
} else {
Err(())
}
}
}

impl<T> Animate for Option<T>
where
T: Animate,
Expand Down
7 changes: 7 additions & 0 deletions components/style/values/distance.rs
Expand Up @@ -81,6 +81,13 @@ impl ComputeSquaredDistance for Au {
}
}

impl ComputeSquaredDistance for bool {
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
(*self as i32).compute_squared_distance(&(*other as i32))
}
}

impl<T> ComputeSquaredDistance for Option<T>
where
T: ComputeSquaredDistance,
Expand Down
12 changes: 10 additions & 2 deletions components/style/values/generics/basic_shape.rs
Expand Up @@ -54,7 +54,6 @@ pub enum ShapeSource<BasicShape, ReferenceBox, ImageOrUrl> {
Shape(BasicShape, Option<ReferenceBox>),
#[animation(error)]
Box(ReferenceBox),
#[animation(error)]
#[css(function)]
Path(Path),
#[animation(error)]
Expand Down Expand Up @@ -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,
Expand All @@ -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(()),
}
}
Expand Down
146 changes: 144 additions & 2 deletions components/style/values/specified/svg_path.rs
Expand Up @@ -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.
Expand All @@ -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::<Vec<_>>();
SVGPathData(result.into_boxed_slice())
}
}

impl ToCss for SVGPathData {
Expand Down Expand Up @@ -82,14 +96,42 @@ impl Parse for SVGPathData {
}
}

impl Animate for SVGPathData {
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
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::<Result<Vec<_>, _>>()?;
Ok(SVGPathData::new(result.into_boxed_slice()))
}
}

impl ComputeSquaredDistance for SVGPathData {
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
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.
/// Note: the index of the control points, e.g. control1, control2, are mapping to the control
/// 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 {
Expand Down Expand Up @@ -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<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
Expand Down Expand Up @@ -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);

Expand All @@ -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> {
Expand Down

0 comments on commit 14911b9

Please sign in to comment.