aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout/fragment.rs6
-rw-r--r--components/style/gecko/generated/bindings.rs4
-rw-r--r--components/style/properties/helpers/animated_properties.mako.rs16
-rw-r--r--components/style/values/computed/angle.rs54
-rw-r--r--components/style/values/computed/length.rs18
-rw-r--r--components/style/values/computed/transform.rs204
-rw-r--r--components/style/values/generics/transform.rs300
-rw-r--r--components/style/values/specified/angle.rs7
-rw-r--r--components/style/values/specified/calc.rs4
-rw-r--r--components/style/values/specified/length.rs9
-rw-r--r--components/style/values/specified/mod.rs14
-rw-r--r--ports/geckolib/glue.rs36
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json4
-rw-r--r--tests/wpt/mozilla/meta/css/transform_skew_a.html.ini4
-rw-r--r--tests/wpt/mozilla/tests/css/transform_skew_a.html4
-rw-r--r--tests/wpt/mozilla/tests/css/transform_skew_ref.html4
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>