diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index b8484a291ab..75c95e1a956 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -8,6 +8,7 @@ use super::animated::ToAnimatedValue; use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent; use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth}; use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize}; +use super::generics::transform::IsParallelTo; use super::generics::{GreaterThanOrEqualToOne, NonNegative}; use super::specified; use super::{CSSFloat, CSSInteger}; @@ -22,6 +23,7 @@ use crate::Atom; #[cfg(feature = "servo")] use crate::Prefix; use euclid::Size2D; +use self::transform::DirectionVector; use std::cell::RefCell; use std::cmp; use std::f32; @@ -459,6 +461,16 @@ trivial_to_computed_value!(Box); /// A `` value. pub type Number = CSSFloat; +impl IsParallelTo for (Number, Number, Number) { + fn is_parallel_to(&self, vector: &DirectionVector) -> bool { + use euclid::approxeq::ApproxEq; + // If a and b is parallel, the angle between them is 0deg, so + // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0. + let self_vector = DirectionVector::new(self.0, self.1, self.2); + self_vector.cross(*vector).square_length().approx_eq(&0.0f32) + } +} + /// A wrapper of Number, but the value >= 0. pub type NonNegativeNumber = NonNegative; diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 12cc97fb10f..ef54c511f63 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -538,7 +538,6 @@ pub fn get_normalized_vector_and_angle( SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, - ToCss, )] /// A value of the `Rotate` property /// @@ -552,6 +551,53 @@ pub enum Rotate { Rotate3D(Number, Number, Number, Angle), } +/// A trait to check if the current 3D vector is parallel to the DirectionVector. +/// This is especially for serialization on Rotate. +pub trait IsParallelTo { + /// Returns true if this is parallel to the vector. + fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool; +} + +impl ToCss for Rotate +where + Number: Copy + ToCss, + Angle: ToCss, + (Number, Number, Number): IsParallelTo, +{ + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + use crate::values::computed::transform::DirectionVector; + match *self { + Rotate::None => dest.write_str("none"), + Rotate::Rotate(ref angle) => angle.to_css(dest), + Rotate::Rotate3D(x, y, z, ref angle) => { + // If a 3d rotation is specified, the property must serialize with an axis + // specified. If the axis is parallel with the x, y, or z axises, it must + // serialize as the appropriate keyword. + // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization + let v = (x, y, z); + if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { + dest.write_char('x')?; + } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) { + dest.write_char('y')?; + } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) { + dest.write_char('z')?; + } else { + x.to_css(dest)?; + dest.write_char(' ')?; + y.to_css(dest)?; + dest.write_char(' ')?; + z.to_css(dest)?; + } + dest.write_char(' ')?; + angle.to_css(dest) + }, + } + } +} + #[derive( Clone, ComputeSquaredDistance, diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index bcca8ed8f49..22bdc233e5d 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -6,9 +6,11 @@ //! //! TODO(emilio): Enhance docs. +use super::computed::transform::DirectionVector; use super::computed::{Context, ToComputedValue}; use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth}; use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize}; +use super::generics::transform::IsParallelTo; use super::generics::{GreaterThanOrEqualToOne, NonNegative}; use super::{Auto, CSSFloat, CSSInteger, Either}; use crate::context::QuirksMode; @@ -292,6 +294,16 @@ impl ToCss for Number { } } +impl IsParallelTo for (Number, Number, Number) { + fn is_parallel_to(&self, vector: &DirectionVector) -> bool { + use euclid::approxeq::ApproxEq; + // If a and b is parallel, the angle between them is 0deg, so + // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0. + let self_vector = DirectionVector::new(self.0.get(), self.1.get(), self.2.get()); + self_vector.cross(*vector).square_length().approx_eq(&0.0f32) + } +} + impl SpecifiedValueInfo for Number {} impl From for f32 { diff --git a/components/style/values/specified/transform.rs b/components/style/values/specified/transform.rs index 38ac07edf63..5f455c733de 100644 --- a/components/style/values/specified/transform.rs +++ b/components/style/values/specified/transform.rs @@ -358,17 +358,24 @@ impl Parse for Rotate { } // Parse or [ x | y | z | {3} ] && . - // TODO: Bug 1504327: Parse [x|y|z] keywords. // // The rotate axis and angle could be in any order, so we parse angle twice to cover // two cases. i.e. `{3} ` or ` {3}` let angle = input.try(|i| specified::Angle::parse(context, i)).ok(); - let axis = input.try(|i| -> Result<_, ParseError> { - Ok(( - Number::parse(context, i)?, - Number::parse(context, i)?, - Number::parse(context, i)?, - )) + let axis = input.try(|i| { + Ok(try_match_ident_ignore_ascii_case! { i, + "x" => (Number::new(1.), Number::new(0.), Number::new(0.)), + "y" => (Number::new(0.), Number::new(1.), Number::new(0.)), + "z" => (Number::new(0.), Number::new(0.), Number::new(1.)), + }) + }).or_else(|_: ParseError| -> Result<_, ParseError> { + input.try(|i| { + Ok(( + Number::parse(context, i)?, + Number::parse(context, i)?, + Number::parse(context, i)?, + )) + }) }).ok(); let angle = match angle { Some(a) => a,