diff options
-rw-r--r-- | components/layout/fragment.rs | 6 | ||||
-rw-r--r-- | components/style/gecko/generated/bindings.rs | 4 | ||||
-rw-r--r-- | components/style/properties/helpers/animated_properties.mako.rs | 16 | ||||
-rw-r--r-- | components/style/values/computed/angle.rs | 54 | ||||
-rw-r--r-- | components/style/values/computed/length.rs | 18 | ||||
-rw-r--r-- | components/style/values/computed/transform.rs | 204 | ||||
-rw-r--r-- | components/style/values/generics/transform.rs | 300 | ||||
-rw-r--r-- | components/style/values/specified/angle.rs | 7 | ||||
-rw-r--r-- | components/style/values/specified/calc.rs | 4 | ||||
-rw-r--r-- | components/style/values/specified/length.rs | 9 | ||||
-rw-r--r-- | components/style/values/specified/mod.rs | 14 | ||||
-rw-r--r-- | ports/geckolib/glue.rs | 36 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/MANIFEST.json | 4 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/css/transform_skew_a.html.ini | 4 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/css/transform_skew_a.html | 4 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/css/transform_skew_ref.html | 4 |
16 files changed, 450 insertions, 238 deletions
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 020c4b65ab3..5366502ac51 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -46,13 +46,13 @@ use style::computed_values::{transform_style, white_space, word_break}; use style::computed_values::content::ContentItem; use style::logical_geometry::{Direction, LogicalMargin, LogicalRect, LogicalSize, WritingMode}; use style::properties::ComputedValues; -use style::properties::longhands::transform::computed_value::T as TransformList; use style::selector_parser::RestyleDamage; use style::servo::restyle_damage::ServoRestyleDamage; use style::str::char_is_whitespace; use style::values::{self, Either, Auto}; use style::values::computed::{Length, LengthOrPercentage, LengthOrPercentageOrAuto}; use style::values::generics::box_::VerticalAlign; +use style::values::generics::transform; use text; use text::TextRunScanner; use webrender_api; @@ -2895,7 +2895,7 @@ impl Fragment { /// Returns the 4D matrix representing this fragment's transform. pub fn transform_matrix(&self, stacking_relative_border_box: &Rect<Au>) -> Option<Transform3D<f32>> { let list = &self.style.get_box().transform; - let transform = list.to_transform_3d_matrix(Some(stacking_relative_border_box))?; + let transform = list.to_transform_3d_matrix(Some(stacking_relative_border_box)).ok()?.0; let transform_origin = &self.style.get_box().transform_origin; let transform_origin_x = @@ -2939,7 +2939,7 @@ impl Fragment { -perspective_origin.y, 0.0); - let perspective_matrix = TransformList::create_perspective_matrix(length.px()); + let perspective_matrix = transform::create_perspective_matrix(length.px()); Some(pre_transform.pre_mul(&perspective_matrix).pre_mul(&post_transform)) } diff --git a/components/style/gecko/generated/bindings.rs b/components/style/gecko/generated/bindings.rs index 1e09fa42baf..536dd4e74a9 100644 --- a/components/style/gecko/generated/bindings.rs +++ b/components/style/gecko/generated/bindings.rs @@ -1578,6 +1578,8 @@ extern "C" { } extern "C" { pub fn Servo_ParseIntersectionObserverRootMargin ( value : * const nsAString , result : * mut nsCSSRect , ) -> bool ; } extern "C" { + pub fn Servo_ParseTransformIntoMatrix ( value : * const nsAString , contains_3d_transform : * mut bool , result : * mut RawGeckoGfxMatrix4x4 , ) -> bool ; +} extern "C" { pub fn Gecko_CreateCSSErrorReporter ( sheet : * mut ServoStyleSheet , loader : * mut Loader , uri : * mut nsIURI , ) -> * mut ErrorReporter ; } extern "C" { pub fn Gecko_DestroyCSSErrorReporter ( reporter : * mut ErrorReporter , ) ; @@ -1587,4 +1589,4 @@ extern "C" { pub fn Gecko_ContentList_AppendAll ( aContentList : * mut nsSimpleContentList , aElements : * mut * const RawGeckoElement , aLength : usize , ) ; } extern "C" { pub fn Gecko_GetElementsWithId ( aDocument : * const nsIDocument , aId : * mut nsAtom , ) -> * const nsTArray < * mut Element > ; -}
\ No newline at end of file +} diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index b054e3f9863..fa45b34519b 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -45,7 +45,7 @@ use values::computed::ToComputedValue; use values::computed::transform::{DirectionVector, Matrix, Matrix3D}; use values::computed::transform::TransformOperation as ComputedTransformOperation; use values::computed::transform::Transform as ComputedTransform; -use values::generics::transform::{Transform, TransformOperation}; +use values::generics::transform::{self, Transform, TransformOperation}; use values::distance::{ComputeSquaredDistance, SquaredDistance}; #[cfg(feature = "gecko")] use values::generics::FontSettings as GenericFontSettings; #[cfg(feature = "gecko")] use values::generics::FontSettingTag as GenericFontSettingTag; @@ -1147,10 +1147,8 @@ impl Animate for ComputedTransformOperation { &TransformOperation::Rotate3D(fx, fy, fz, fa), &TransformOperation::Rotate3D(tx, ty, tz, ta), ) => { - let (fx, fy, fz, fa) = - ComputedTransform::get_normalized_vector_and_angle(fx, fy, fz, fa); - let (tx, ty, tz, ta) = - ComputedTransform::get_normalized_vector_and_angle(tx, ty, tz, ta); + let (fx, fy, fz, fa) = transform::get_normalized_vector_and_angle(fx, fy, fz, fa); + let (tx, ty, tz, ta) = transform::get_normalized_vector_and_angle(tx, ty, tz, ta); if (fx, fy, fz) == (tx, ty, tz) { let ia = fa.animate(&ta, procedure)?; Ok(TransformOperation::Rotate3D(fx, fy, fz, ia)) @@ -2416,9 +2414,9 @@ impl ComputeSquaredDistance for ComputedTransformOperation { &TransformOperation::Rotate3D(tx, ty, tz, ta), ) => { let (fx, fy, fz, angle1) = - ComputedTransform::get_normalized_vector_and_angle(fx, fy, fz, fa); + transform::get_normalized_vector_and_angle(fx, fy, fz, fa); let (tx, ty, tz, angle2) = - ComputedTransform::get_normalized_vector_and_angle(tx, ty, tz, ta); + transform::get_normalized_vector_and_angle(tx, ty, tz, ta); if (fx, fy, fz) == (tx, ty, tz) { angle1.compute_squared_distance(&angle2) } else { @@ -2509,8 +2507,8 @@ impl ComputeSquaredDistance for ComputedTransform { // Roll back to matrix interpolation if there is any Err(()) in the transform lists, such // as mismatched transform functions. if let Err(_) = squared_dist { - let matrix1: Matrix3D = self.to_transform_3d_matrix(None).ok_or(())?.into(); - let matrix2: Matrix3D = other.to_transform_3d_matrix(None).ok_or(())?.into(); + let matrix1: Matrix3D = self.to_transform_3d_matrix(None)?.0.into(); + let matrix2: Matrix3D = other.to_transform_3d_matrix(None)?.0.into(); return matrix1.compute_squared_distance(&matrix2); } squared_dist diff --git a/components/style/values/computed/angle.rs b/components/style/values/computed/angle.rs index 7cc10e97c4d..fdeda7749bd 100644 --- a/components/style/values/computed/angle.rs +++ b/components/style/values/computed/angle.rs @@ -4,9 +4,10 @@ //! Computed angles. -use euclid::Radians; +use num_traits::Zero; use std::{f32, f64}; use std::f64::consts::PI; +use std::ops::Add; use values::CSSFloat; use values::animated::{Animate, Procedure}; use values::distance::{ComputeSquaredDistance, SquaredDistance}; @@ -64,11 +65,6 @@ impl Angle { radians.min(f64::MAX).max(f64::MIN) } - /// Returns an angle that represents a rotation of zero radians. - pub fn zero() -> Self { - Self::from_radians(0.0) - } - /// <https://drafts.csswg.org/css-transitions/#animtype-number> #[inline] fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { @@ -76,6 +72,45 @@ impl Angle { } } +impl AsRef<Angle> for Angle { + #[inline] + fn as_ref(&self) -> &Self { + self + } +} + +impl Add for Angle { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self { + match (self, rhs) { + (Angle::Deg(x), Angle::Deg(y)) => Angle::Deg(x + y), + (Angle::Grad(x), Angle::Grad(y)) => Angle::Grad(x + y), + (Angle::Turn(x), Angle::Turn(y)) => Angle::Turn(x + y), + (Angle::Rad(x), Angle::Rad(y)) => Angle::Rad(x + y), + _ => Angle::from_radians(self.radians() + rhs.radians()), + } + } +} + +impl Zero for Angle { + #[inline] + fn zero() -> Self { + Angle::from_radians(0.0) + } + + #[inline] + fn is_zero(&self) -> bool { + match *self { + Angle::Deg(val) | + Angle::Grad(val) | + Angle::Turn(val) | + Angle::Rad(val) => val == 0. + } + } +} + impl ComputeSquaredDistance for Angle { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { @@ -84,10 +119,3 @@ impl ComputeSquaredDistance for Angle { self.radians64().compute_squared_distance(&other.radians64()) } } - -impl From<Angle> for Radians<CSSFloat> { - #[inline] - fn from(a: Angle) -> Self { - Radians::new(a.radians()) - } -} diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index e5548629903..7b3a92bbe51 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -266,6 +266,24 @@ impl specified::CalcLengthOrPercentage { pub fn to_computed_value_zoomed(&self, context: &Context, base_size: FontBaseSize) -> CalcLengthOrPercentage { self.to_computed_value_with_zoom(context, |abs| context.maybe_zoom_text(abs.into()).0, base_size) } + + /// Compute the value into pixel length as CSSFloat without context, + /// so it returns Err(()) if there is any non-absolute unit. + pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { + if self.vw.is_some() || self.vh.is_some() || self.vmin.is_some() || self.vmax.is_some() || + self.em.is_some() || self.ex.is_some() || self.ch.is_some() || self.rem.is_some() || + self.percentage.is_some() { + return Err(()); + } + + match self.absolute { + Some(abs) => Ok(abs.to_px()), + None => { + debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self); + Err(()) + } + } + } } impl ToComputedValue for specified::CalcLengthOrPercentage { diff --git a/components/style/values/computed/transform.rs b/components/style/values/computed/transform.rs index 440758a06b1..4ad043850c8 100644 --- a/components/style/values/computed/transform.rs +++ b/components/style/values/computed/transform.rs @@ -4,14 +4,13 @@ //! Computed types for CSS values that are related to transformations. -use app_units::Au; -use euclid::{Rect, Transform3D, Vector3D}; -use std::f32; +use euclid::{Transform3D, Vector3D}; +use num_traits::Zero; use super::{CSSFloat, Either}; use values::animated::ToAnimatedZero; use values::computed::{Angle, Integer, Length, LengthOrPercentage, Number, Percentage}; use values::computed::{LengthOrNumber, LengthOrPercentageOrNumber}; -use values::generics::transform::{Matrix as GenericMatrix, Matrix3D as GenericMatrix3D}; +use values::generics::transform::{self, Matrix as GenericMatrix, Matrix3D as GenericMatrix3D}; use values::generics::transform::{Transform as GenericTransform, TransformOperation as GenericTransformOperation}; use values::generics::transform::TimingFunction as GenericTimingFunction; use values::generics::transform::TransformOrigin as GenericTransformOrigin; @@ -147,18 +146,6 @@ impl PrefixedMatrix { } #[cfg_attr(rustfmt, rustfmt_skip)] -impl From<Matrix3D> for Transform3D<CSSFloat> { - #[inline] - fn from(m: Matrix3D) -> Self { - Transform3D::row_major( - m.m11, m.m12, m.m13, m.m14, - m.m21, m.m22, m.m23, m.m24, - m.m31, m.m32, m.m33, m.m34, - m.m41, m.m42, m.m43, m.m44) - } -} - -#[cfg_attr(rustfmt, rustfmt_skip)] impl From<Transform3D<CSSFloat>> for Matrix3D { #[inline] fn from(m: Transform3D<CSSFloat>) -> Self { @@ -171,18 +158,6 @@ impl From<Transform3D<CSSFloat>> for Matrix3D { } } -#[cfg_attr(rustfmt, rustfmt_skip)] -impl From<Matrix> for Transform3D<CSSFloat> { - #[inline] - fn from(m: Matrix) -> Self { - Transform3D::row_major( - m.a, m.b, 0.0, 0.0, - m.c, m.d, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - m.e, m.f, 0.0, 1.0) - } -} - impl TransformOperation { /// Convert to a Translate3D. /// @@ -280,7 +255,7 @@ impl ToAnimatedZero for TransformOperation { GenericTransformOperation::ScaleY(..) => Ok(GenericTransformOperation::ScaleY(1.0)), GenericTransformOperation::ScaleZ(..) => Ok(GenericTransformOperation::ScaleZ(1.0)), GenericTransformOperation::Rotate3D(x, y, z, a) => { - let (x, y, z, _) = Transform::get_normalized_vector_and_angle(x, y, z, a); + let (x, y, z, _) = transform::get_normalized_vector_and_angle(x, y, z, a); Ok(GenericTransformOperation::Rotate3D(x, y, z, Angle::zero())) }, GenericTransformOperation::RotateX(_) => Ok(GenericTransformOperation::RotateX(Angle::zero())), @@ -318,174 +293,3 @@ impl ToAnimatedZero for Transform { .collect::<Result<Vec<_>, _>>()?)) } } - -impl Transform { - /// Return the equivalent 3d matrix of this transform list. - /// If |reference_box| is None, we will drop the percent part from translate because - /// we can resolve it without the layout info. - pub fn to_transform_3d_matrix(&self, reference_box: Option<&Rect<Au>>) -> Option<Transform3D<CSSFloat>> { - let mut transform = Transform3D::identity(); - let list = &self.0; - if list.len() == 0 { - return None; - } - - let extract_pixel_length = |lop: &LengthOrPercentage| match *lop { - LengthOrPercentage::Length(px) => px.px(), - LengthOrPercentage::Percentage(_) => 0., - LengthOrPercentage::Calc(calc) => calc.length().px(), - }; - - for operation in list { - let matrix = match *operation { - GenericTransformOperation::Rotate3D(ax, ay, az, theta) => { - let theta = Angle::from_radians(2.0f32 * f32::consts::PI - theta.radians()); - let (ax, ay, az, theta) = Self::get_normalized_vector_and_angle(ax, ay, az, theta); - Transform3D::create_rotation(ax, ay, az, theta.into()) - }, - GenericTransformOperation::RotateX(theta) => { - let theta = Angle::from_radians(2.0f32 * f32::consts::PI - theta.radians()); - Transform3D::create_rotation(1., 0., 0., theta.into()) - }, - GenericTransformOperation::RotateY(theta) => { - let theta = Angle::from_radians(2.0f32 * f32::consts::PI - theta.radians()); - Transform3D::create_rotation(0., 1., 0., theta.into()) - }, - GenericTransformOperation::RotateZ(theta) | - GenericTransformOperation::Rotate(theta) => { - let theta = Angle::from_radians(2.0f32 * f32::consts::PI - theta.radians()); - Transform3D::create_rotation(0., 0., 1., theta.into()) - }, - GenericTransformOperation::Perspective(d) => Self::create_perspective_matrix(d.px()), - GenericTransformOperation::Scale3D(sx, sy, sz) => Transform3D::create_scale(sx, sy, sz), - GenericTransformOperation::Scale(sx, sy) => Transform3D::create_scale(sx, sy.unwrap_or(sx), 1.), - GenericTransformOperation::ScaleX(s) => Transform3D::create_scale(s, 1., 1.), - GenericTransformOperation::ScaleY(s) => Transform3D::create_scale(1., s, 1.), - GenericTransformOperation::ScaleZ(s) => Transform3D::create_scale(1., 1., s), - GenericTransformOperation::Translate3D(tx, ty, tz) => { - let (tx, ty) = match reference_box { - Some(relative_border_box) => { - ( - tx.to_pixel_length(relative_border_box.size.width).px(), - ty.to_pixel_length(relative_border_box.size.height).px(), - ) - }, - None => { - // If we don't have reference box, we cannot resolve the used value, - // so only retrieve the length part. This will be used for computing - // distance without any layout info. - (extract_pixel_length(&tx), extract_pixel_length(&ty)) - }, - }; - let tz = tz.px(); - Transform3D::create_translation(tx, ty, tz) - }, - GenericTransformOperation::Translate(tx, Some(ty)) => { - let (tx, ty) = match reference_box { - Some(relative_border_box) => { - ( - tx.to_pixel_length(relative_border_box.size.width).px(), - ty.to_pixel_length(relative_border_box.size.height).px(), - ) - }, - None => { - // If we don't have reference box, we cannot resolve the used value, - // so only retrieve the length part. This will be used for computing - // distance without any layout info. - (extract_pixel_length(&tx), extract_pixel_length(&ty)) - }, - }; - Transform3D::create_translation(tx, ty, 0.) - }, - GenericTransformOperation::TranslateX(t) | - GenericTransformOperation::Translate(t, None) => { - let t = match reference_box { - Some(relative_border_box) => t.to_pixel_length(relative_border_box.size.width).px(), - None => { - // If we don't have reference box, we cannot resolve the used value, - // so only retrieve the length part. This will be used for computing - // distance without any layout info. - extract_pixel_length(&t) - }, - }; - Transform3D::create_translation(t, 0., 0.) - }, - GenericTransformOperation::TranslateY(t) => { - let t = match reference_box { - Some(relative_border_box) => t.to_pixel_length(relative_border_box.size.height).px(), - None => { - // If we don't have reference box, we cannot resolve the used value, - // so only retrieve the length part. This will be used for computing - // distance without any layout info. - extract_pixel_length(&t) - }, - }; - Transform3D::create_translation(0., t, 0.) - }, - GenericTransformOperation::TranslateZ(z) => Transform3D::create_translation(0., 0., z.px()), - GenericTransformOperation::Skew(theta_x, theta_y) => { - Transform3D::create_skew(theta_x.into(), theta_y.unwrap_or(Angle::zero()).into()) - }, - GenericTransformOperation::SkewX(theta) => Transform3D::create_skew(theta.into(), Angle::zero().into()), - GenericTransformOperation::SkewY(theta) => Transform3D::create_skew(Angle::zero().into(), theta.into()), - GenericTransformOperation::Matrix3D(m) => m.into(), - GenericTransformOperation::Matrix(m) => m.into(), - GenericTransformOperation::PrefixedMatrix3D(_) | - GenericTransformOperation::PrefixedMatrix(_) => { - // `-moz-transform` is not implemented in Servo yet. - unreachable!() - }, - GenericTransformOperation::InterpolateMatrix { - .. - } | - GenericTransformOperation::AccumulateMatrix { - .. - } => { - // TODO: Convert InterpolateMatrix/AccmulateMatrix into a valid Transform3D by - // the reference box and do interpolation on these two Transform3D matrices. - // Both Gecko and Servo don't support this for computing distance, and Servo - // doesn't support animations on InterpolateMatrix/AccumulateMatrix, so - // return None. - return None; - }, - }; - - transform = transform.pre_mul(&matrix); - } - - Some(transform) - } - - /// Return the transform matrix from a perspective length. - #[inline] - pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<f32> { - // TODO(gw): The transforms spec says that perspective length must - // be positive. However, there is some confusion between the spec - // and browser implementations as to handling the case of 0 for the - // perspective value. Until the spec bug is resolved, at least ensure - // that a provided perspective value of <= 0.0 doesn't cause panics - // and behaves as it does in other browsers. - // See https://lists.w3.org/Archives/Public/www-style/2016Jan/0020.html for more details. - if d <= 0.0 { - Transform3D::identity() - } else { - Transform3D::create_perspective(d) - } - } - - /// Return the normalized direction vector and its angle for Rotate3D. - pub fn get_normalized_vector_and_angle(x: f32, y: f32, z: f32, angle: Angle) -> (f32, f32, f32, Angle) { - use euclid::approxeq::ApproxEq; - use euclid::num::Zero; - let vector = DirectionVector::new(x, y, z); - if vector.square_length().approx_eq(&f32::zero()) { - // https://www.w3.org/TR/css-transforms-1/#funcdef-rotate3d - // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the - // rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)). - (0., 0., 1., Angle::zero()) - } else { - let vector = vector.normalize(); - (vector.x, vector.y, vector.z, angle) - } - } -} diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index c4c41070065..79f6476e611 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -4,9 +4,16 @@ //! Generic types for CSS values that are related to transformations. +use app_units::Au; +use euclid::{Radians, Rect, Transform3D}; +use num_traits::Zero; use std::fmt; use style_traits::ToCss; use values::{computed, CSSFloat}; +use values::computed::length::Length as ComputedLength; +use values::computed::length::LengthOrPercentage as ComputedLengthOrPercentage; +use values::specified::length::Length as SpecifiedLength; +use values::specified::length::LengthOrPercentage as SpecifiedLengthOrPercentage; /// A generic 2D transformation matrix. #[allow(missing_docs)] @@ -31,6 +38,32 @@ pub struct Matrix3D<T, U = T, V = T> { pub m41: U, pub m42: U, pub m43: V, pub m44: T, } +#[cfg_attr(rustfmt, rustfmt_skip)] +impl<T: Into<f64>> From<Matrix<T>> for Transform3D<f64> { + #[inline] + fn from(m: Matrix<T>) -> Self { + Transform3D::row_major( + m.a.into(), m.b.into(), 0.0, 0.0, + m.c.into(), m.d.into(), 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + m.e.into(), m.f.into(), 0.0, 1.0, + ) + } +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +impl<T: Into<f64>> From<Matrix3D<T>> for Transform3D<f64> { + #[inline] + fn from(m: Matrix3D<T>) -> Self { + Transform3D::row_major( + m.m11.into(), m.m12.into(), m.m13.into(), m.m14.into(), + m.m21.into(), m.m22.into(), m.m23.into(), m.m24.into(), + m.m31.into(), m.m32.into(), m.m33.into(), m.m34.into(), + m.m41.into(), m.m42.into(), m.m43.into(), m.m44.into(), + ) + } +} + /// A generic transform origin. #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] #[derive(MallocSizeOf, PartialEq, ToAnimatedZero, ToComputedValue, ToCss)] @@ -158,8 +191,7 @@ impl TimingKeyword { } } -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -#[derive(ToComputedValue)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)] /// A single operation in the list of a `transform` value pub enum TransformOperation<Angle, Number, Length, Integer, LengthOrNumber, LengthOrPercentage, LoPoNumber> { /// Represents a 2D 2x3 matrix. @@ -319,6 +351,194 @@ impl<Angle, Number, Length, Integer, LengthOrNumber, LengthOrPercentage, LoPoNum } } +/// Convert a length type into the absolute lengths. +pub trait ToAbsoluteLength { + /// Returns the absolute length as pixel value. + fn to_pixel_length(&self, containing_len: Option<Au>) -> Result<CSSFloat, ()>; +} + +impl ToAbsoluteLength for SpecifiedLength { + // This returns Err(()) if there is any relative length or percentage. We use this when + // parsing a transform list of DOMMatrix because we want to return a DOM Exception + // if there is relative length. + #[inline] + fn to_pixel_length(&self, _containing_len: Option<Au>) -> Result<CSSFloat, ()> { + match *self { + SpecifiedLength::NoCalc(len) => len.to_computed_pixel_length_without_context(), + SpecifiedLength::Calc(ref calc) => calc.to_computed_pixel_length_without_context(), + } + } +} + +impl ToAbsoluteLength for SpecifiedLengthOrPercentage { + // This returns Err(()) if there is any relative length or percentage. We use this when + // parsing a transform list of DOMMatrix because we want to return a DOM Exception + // if there is relative length. + #[inline] + fn to_pixel_length(&self, _containing_len: Option<Au>) -> Result<CSSFloat, ()> { + use self::SpecifiedLengthOrPercentage::*; + match *self { + Length(len) => len.to_computed_pixel_length_without_context(), + Calc(ref calc) => calc.to_computed_pixel_length_without_context(), + _ => Err(()), + } + } +} + +impl ToAbsoluteLength for ComputedLength { + #[inline] + fn to_pixel_length(&self, _containing_len: Option<Au>) -> Result<CSSFloat, ()> { + Ok(self.px()) + } +} + +impl ToAbsoluteLength for ComputedLengthOrPercentage { + #[inline] + fn to_pixel_length(&self, containing_len: Option<Au>) -> Result<CSSFloat, ()> { + let extract_pixel_length = |lop: &ComputedLengthOrPercentage| match *lop { + ComputedLengthOrPercentage::Length(px) => px.px(), + ComputedLengthOrPercentage::Percentage(_) => 0., + ComputedLengthOrPercentage::Calc(calc) => calc.length().px(), + }; + + match containing_len { + Some(relative_len) => Ok(self.to_pixel_length(relative_len).px()), + // If we don't have reference box, we cannot resolve the used value, + // so only retrieve the length part. This will be used for computing + // distance without any layout info. + None => Ok(extract_pixel_length(self)) + } + } +} + +/// Support the conversion to a 3d matrix. +pub trait ToMatrix { + /// Check if it is a 3d transform function. + fn is_3d(&self) -> bool; + + /// Return the equivalent 3d matrix. + fn to_3d_matrix(&self, reference_box: Option<&Rect<Au>>) -> Result<Transform3D<f64>, ()>; +} + +impl<Angle, Number, Length, Integer, LoN, LoP, LoPoNumber> ToMatrix + for TransformOperation<Angle, Number, Length, Integer, LoN, LoP, LoPoNumber> +where + Angle: Copy + AsRef<computed::angle::Angle>, + Number: Copy + Into<f32> + Into<f64>, + Length: ToAbsoluteLength, + LoP: ToAbsoluteLength, +{ + #[inline] + fn is_3d(&self) -> bool { + use self::TransformOperation::*; + match *self { + Translate3D(..) | TranslateZ(..) | + Rotate3D(..) | RotateX(..) | RotateY(..) | RotateZ(..) | + Scale3D(..) | ScaleZ(..) | + Perspective(..) | Matrix3D(..) | PrefixedMatrix3D(..) => true, + _ => false, + } + } + + /// If |reference_box| is None, we will drop the percent part from translate because + /// we cannot resolve it without the layout info, for computed TransformOperation. + /// However, for specified TransformOperation, we will return Err(()) if there is any relative + /// lengths because the only caller, DOMMatrix, doesn't accept relative lengths. + #[inline] + fn to_3d_matrix(&self, reference_box: Option<&Rect<Au>>) -> Result<Transform3D<f64>, ()> { + use self::TransformOperation::*; + use std::f64; + + const TWO_PI: f64 = 2.0f64 * f64::consts::PI; + let reference_width = reference_box.map(|v| v.size.width); + let reference_height = reference_box.map(|v| v.size.height); + let matrix = match *self { + Rotate3D(ax, ay, az, theta) => { + let theta = TWO_PI - theta.as_ref().radians64(); + let (ax, ay, az, theta) = + get_normalized_vector_and_angle(ax.into(), ay.into(), az.into(), theta); + Transform3D::create_rotation(ax as f64, ay as f64, az as f64, Radians::new(theta)) + }, + RotateX(theta) => { + let theta = Radians::new(TWO_PI - theta.as_ref().radians64()); + Transform3D::create_rotation(1., 0., 0., theta) + }, + RotateY(theta) => { + let theta = Radians::new(TWO_PI - theta.as_ref().radians64()); + Transform3D::create_rotation(0., 1., 0., theta) + }, + RotateZ(theta) | Rotate(theta) => { + let theta = Radians::new(TWO_PI - theta.as_ref().radians64()); + Transform3D::create_rotation(0., 0., 1., theta) + }, + Perspective(ref d) => { + let m = create_perspective_matrix(d.to_pixel_length(None)?); + m.cast().expect("Casting from f32 to f64 should be successful") + }, + Scale3D(sx, sy, sz) => Transform3D::create_scale(sx.into(), sy.into(), sz.into()), + Scale(sx, sy) => Transform3D::create_scale(sx.into(), sy.unwrap_or(sx).into(), 1.), + ScaleX(s) => Transform3D::create_scale(s.into(), 1., 1.), + ScaleY(s) => Transform3D::create_scale(1., s.into(), 1.), + ScaleZ(s) => Transform3D::create_scale(1., 1., s.into()), + Translate3D(ref tx, ref ty, ref tz) => { + let tx = tx.to_pixel_length(reference_width)? as f64; + let ty = ty.to_pixel_length(reference_height)? as f64; + Transform3D::create_translation(tx, ty, tz.to_pixel_length(None)? as f64) + }, + Translate(ref tx, Some(ref ty)) => { + let tx = tx.to_pixel_length(reference_width)? as f64; + let ty = ty.to_pixel_length(reference_height)? as f64; + Transform3D::create_translation(tx, ty, 0.) + }, + TranslateX(ref t) | Translate(ref t, None) => { + let t = t.to_pixel_length(reference_width)? as f64; + Transform3D::create_translation(t, 0., 0.) + }, + TranslateY(ref t) => { + let t = t.to_pixel_length(reference_height)? as f64; + Transform3D::create_translation(0., t, 0.) + }, + TranslateZ(ref z) => { + Transform3D::create_translation(0., 0., z.to_pixel_length(None)? as f64) + }, + Skew(theta_x, theta_y) => { + Transform3D::create_skew( + Radians::new(theta_x.as_ref().radians64()), + Radians::new(theta_y.map_or(0., |a| a.as_ref().radians64())), + ) + }, + SkewX(theta) => { + Transform3D::create_skew( + Radians::new(theta.as_ref().radians64()), + Radians::new(0.), + ) + }, + SkewY(theta) => { + Transform3D::create_skew( + Radians::new(0.), + Radians::new(theta.as_ref().radians64()), + ) + }, + Matrix3D(m) => m.into(), + Matrix(m) => m.into(), + PrefixedMatrix3D(_) | PrefixedMatrix(_) => { + unreachable!("-moz-transform` is not implemented in Servo yet, and DOMMatrix \ + doesn't support this") + }, + InterpolateMatrix { .. } | AccumulateMatrix { .. } => { + // TODO: Convert InterpolateMatrix/AccumulateMatrix into a valid Transform3D by + // the reference box and do interpolation on these two Transform3D matrices. + // Both Gecko and Servo don't support this for computing distance, and Servo + // doesn't support animations on InterpolateMatrix/AccumulateMatrix, so + // return an identity matrix. + // Note: DOMMatrix doesn't go into this arm. + Transform3D::identity() + }, + }; + Ok(matrix) + } +} + #[cfg_attr(rustfmt, rustfmt_skip)] impl<Angle: ToCss + Copy, Number: ToCss + Copy, Length: ToCss, Integer: ToCss + Copy, LengthOrNumber: ToCss, LengthOrPercentage: ToCss, LoPoNumber: ToCss> @@ -457,3 +677,79 @@ impl<T> Transform<T> { Transform(vec![]) } } + +impl<T: ToMatrix> Transform<T> { + /// Return the equivalent 3d matrix of this transform list. + /// We return a pair: the first one is the transform matrix, and the second one + /// indicates if there is any 3d transform function in this transform list. + #[cfg_attr(rustfmt, rustfmt_skip)] + pub fn to_transform_3d_matrix( + &self, + reference_box: Option<&Rect<Au>> + ) -> Result<(Transform3D<CSSFloat>, bool), ()> { + let cast_3d_transform = |m: Transform3D<f64>| -> Transform3D<CSSFloat> { + use std::{f32, f64}; + let cast = |v: f64| { v.min(f32::MAX as f64).max(f32::MIN as f64) as f32 }; + Transform3D::row_major( + cast(m.m11), cast(m.m12), cast(m.m13), cast(m.m14), + cast(m.m21), cast(m.m22), cast(m.m23), cast(m.m24), + cast(m.m31), cast(m.m32), cast(m.m33), cast(m.m34), + cast(m.m41), cast(m.m42), cast(m.m43), cast(m.m44), + ) + }; + + // We intentionally use Transform3D<f64> during computation to avoid error propagation + // because using f32 to compute triangle functions (e.g. in create_rotation()) is not + // accurate enough. In Gecko, we also use "double" to compute the triangle functions. + // Therefore, let's use Transform3D<f64> during matrix computation and cast it into f32 + // in the end. + let mut transform = Transform3D::<f64>::identity(); + let mut contain_3d = false; + + for operation in &self.0 { + let matrix = operation.to_3d_matrix(reference_box)?; + contain_3d |= operation.is_3d(); + transform = transform.pre_mul(&matrix); + } + + Ok((cast_3d_transform(transform), contain_3d)) + } +} + +/// Return the transform matrix from a perspective length. +#[inline] +pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> { + // TODO(gw): The transforms spec says that perspective length must + // be positive. However, there is some confusion between the spec + // and browser implementations as to handling the case of 0 for the + // perspective value. Until the spec bug is resolved, at least ensure + // that a provided perspective value of <= 0.0 doesn't cause panics + // and behaves as it does in other browsers. + // See https://lists.w3.org/Archives/Public/www-style/2016Jan/0020.html for more details. + if d <= 0.0 { + Transform3D::identity() + } else { + Transform3D::create_perspective(d) + } +} + +/// Return the normalized direction vector and its angle for Rotate3D. +pub fn get_normalized_vector_and_angle<T: Zero>( + x: CSSFloat, + y: CSSFloat, + z: CSSFloat, + angle: T, +) -> (CSSFloat, CSSFloat, CSSFloat, T) { + use euclid::approxeq::ApproxEq; + use values::computed::transform::DirectionVector; + let vector = DirectionVector::new(x, y, z); + if vector.square_length().approx_eq(&f32::zero()) { + // https://www.w3.org/TR/css-transforms-1/#funcdef-rotate3d + // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the + // rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)). + (0., 0., 1., T::zero()) + } else { + let vector = vector.normalize(); + (vector.x, vector.y, vector.z, angle) + } +} diff --git a/components/style/values/specified/angle.rs b/components/style/values/specified/angle.rs index 863a436ab4f..1a89e8235ac 100644 --- a/components/style/values/specified/angle.rs +++ b/components/style/values/specified/angle.rs @@ -95,6 +95,13 @@ impl Angle { } } +impl AsRef<ComputedAngle> for Angle { + #[inline] + fn as_ref(&self) -> &ComputedAngle { + &self.value + } +} + impl Parse for Angle { /// Parses an angle according to CSS-VALUES § 6.1. fn parse<'i, 't>( diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index 00857b733ca..c530bb6e2f3 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -63,6 +63,10 @@ pub enum CalcUnit { } /// A struct to hold a simplified `<length>` or `<percentage>` expression. +/// +/// In some cases, e.g. DOMMatrix, we support calc(), but reject all the relative lengths, and +/// to_computed_pixel_length_without_context() handles this case. Therefore, if you want to add a +/// new field, please make sure this function work properly. #[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)] #[allow(missing_docs)] pub struct CalcLengthOrPercentage { diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 6a74ab33ad1..92307dd31d5 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -463,6 +463,15 @@ impl NoCalcLength { } } + /// Get a px value without context. + #[inline] + pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { + match *self { + NoCalcLength::Absolute(len) => Ok(len.to_px()), + _ => Err(()), + } + } + /// Get an absolute length from a px value. #[inline] pub fn from_px(px_value: CSSFloat) -> NoCalcLength { diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 1b6b19e8a7b..da4ae4be7b4 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -270,6 +270,20 @@ impl ToCss for Number { } } +impl From<Number> for f32 { + #[inline] + fn from(n: Number) -> Self { + n.get() + } +} + +impl From<Number> for f64 { + #[inline] + fn from(n: Number) -> Self { + n.get() as f64 + } +} + /// A Number which is >= 0.0. pub type NonNegativeNumber = NonNegative<Number>; diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index c0b60adaad5..bcf1df2faac 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -4639,6 +4639,42 @@ pub extern "C" fn Servo_ParseIntersectionObserverRootMargin( } #[no_mangle] +pub extern "C" fn Servo_ParseTransformIntoMatrix( + value: *const nsAString, + contain_3d: *mut bool, + result: *mut RawGeckoGfxMatrix4x4 +) -> bool { + use style::properties::longhands::transform; + + let string = unsafe { (*value).to_string() }; + let mut input = ParserInput::new(&string); + let mut parser = Parser::new(&mut input); + let context = ParserContext::new( + Origin::Author, + unsafe { dummy_url_data() }, + Some(CssRuleType::Style), + ParsingMode::DEFAULT, + QuirksMode::NoQuirks + ); + + let transform = match parser.parse_entirely(|t| transform::parse(&context, t)) { + Ok(t) => t, + Err(..) => return false, + }; + + let (m, is_3d) = match transform.to_transform_3d_matrix(None) { + Ok(result) => result, + Err(..) => return false, + }; + + let result = unsafe { result.as_mut() }.expect("not a valid matrix"); + let contain_3d = unsafe { contain_3d.as_mut() }.expect("not a valid bool"); + *result = m.to_row_major_array(); + *contain_3d = is_3d; + true +} + +#[no_mangle] pub unsafe extern "C" fn Servo_SourceSizeList_Parse( value: *const nsACString, ) -> *mut RawServoSourceSizeList { diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 26c3042306c..a52e0f9feb4 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -65189,11 +65189,11 @@ "support" ], "css/transform_skew_a.html": [ - "8ae3384ece6fc2ebd537736e63723e12b974fff3", + "521496ff3fb82a34498d313c1d600a6ca271b1ed", "reftest" ], "css/transform_skew_ref.html": [ - "6f5154aef6acf1428ac391f0d2dbb2e369ca930b", + "a941cd3a8c968494f292ddadd28de5b541ad71b2", "support" ], "css/transform_stacking_context_a.html": [ diff --git a/tests/wpt/mozilla/meta/css/transform_skew_a.html.ini b/tests/wpt/mozilla/meta/css/transform_skew_a.html.ini deleted file mode 100644 index 2540aa1a870..00000000000 --- a/tests/wpt/mozilla/meta/css/transform_skew_a.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[transform_skew_a.html] - type: reftest - expected: - if os == "linux": FAIL diff --git a/tests/wpt/mozilla/tests/css/transform_skew_a.html b/tests/wpt/mozilla/tests/css/transform_skew_a.html index 5b2dde5d3d0..ca807526945 100644 --- a/tests/wpt/mozilla/tests/css/transform_skew_a.html +++ b/tests/wpt/mozilla/tests/css/transform_skew_a.html @@ -18,7 +18,7 @@ div>div { } .transformed1 { - transform: skewX(0.3rad); + transform: skewX(0.25rad); } .transformed2 { @@ -26,7 +26,7 @@ div>div { } .transformed3 { - transform: skew(0.3rad, 0.5rad); + transform: skew(0.25rad, 0.5rad); } </style> </head> diff --git a/tests/wpt/mozilla/tests/css/transform_skew_ref.html b/tests/wpt/mozilla/tests/css/transform_skew_ref.html index c0af1babcd2..caf92ca6f50 100644 --- a/tests/wpt/mozilla/tests/css/transform_skew_ref.html +++ b/tests/wpt/mozilla/tests/css/transform_skew_ref.html @@ -17,7 +17,7 @@ div>div { } .transformed1_ref { - transform: matrix(1, 0, 0.30933624961, 1, 0, 0); + transform: matrix(1, 0, 0.25534192122, 1, 0, 0); } .transformed2_ref { @@ -25,7 +25,7 @@ div>div { } .transformed3_ref { - transform: matrix(1, 0.54630248984, 0.30933624961, 1, 0, 0); + transform: matrix(1, 0.54630248984, 0.25534192122, 1, 0, 0); } </style> |