Auto merge of #19388 - BorisChiou:stylo/dommatrix/parser, r=emilio,heycam

stylo: Implement Servo_ParseTransformIntoMatrix

This is an inter-dependent patch of Bug 1408310.

DOMMatrix needs to convert a specified transform list into a matrix, so we rewrite
to_transform_3d_matrix by generics for both specified and computed transform lists.

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix [Bug 1408310](https://bugzilla.mozilla.org/show_bug.cgi?id=1408310).
- [X] These changes do not require tests because we can count on the wpt tests for DOMMatrix on Gecko side.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/19388)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-11-27 20:13:34 -06:00 committed by GitHub
commit 823da9e34a
16 changed files with 450 additions and 238 deletions

View file

@ -46,13 +46,13 @@ use style::computed_values::{transform_style, white_space, word_break};
use style::computed_values::content::ContentItem; use style::computed_values::content::ContentItem;
use style::logical_geometry::{Direction, LogicalMargin, LogicalRect, LogicalSize, WritingMode}; use style::logical_geometry::{Direction, LogicalMargin, LogicalRect, LogicalSize, WritingMode};
use style::properties::ComputedValues; use style::properties::ComputedValues;
use style::properties::longhands::transform::computed_value::T as TransformList;
use style::selector_parser::RestyleDamage; use style::selector_parser::RestyleDamage;
use style::servo::restyle_damage::ServoRestyleDamage; use style::servo::restyle_damage::ServoRestyleDamage;
use style::str::char_is_whitespace; use style::str::char_is_whitespace;
use style::values::{self, Either, Auto}; use style::values::{self, Either, Auto};
use style::values::computed::{Length, LengthOrPercentage, LengthOrPercentageOrAuto}; use style::values::computed::{Length, LengthOrPercentage, LengthOrPercentageOrAuto};
use style::values::generics::box_::VerticalAlign; use style::values::generics::box_::VerticalAlign;
use style::values::generics::transform;
use text; use text;
use text::TextRunScanner; use text::TextRunScanner;
use webrender_api; use webrender_api;
@ -2895,7 +2895,7 @@ impl Fragment {
/// Returns the 4D matrix representing this fragment's transform. /// Returns the 4D matrix representing this fragment's transform.
pub fn transform_matrix(&self, stacking_relative_border_box: &Rect<Au>) -> Option<Transform3D<f32>> { pub fn transform_matrix(&self, stacking_relative_border_box: &Rect<Au>) -> Option<Transform3D<f32>> {
let list = &self.style.get_box().transform; 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 = &self.style.get_box().transform_origin;
let transform_origin_x = let transform_origin_x =
@ -2939,7 +2939,7 @@ impl Fragment {
-perspective_origin.y, -perspective_origin.y,
0.0); 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)) Some(pre_transform.pre_mul(&perspective_matrix).pre_mul(&post_transform))
} }

View file

@ -1577,6 +1577,8 @@ extern "C" {
pub fn Servo_ComputeColor ( set : RawServoStyleSetBorrowedOrNull , current_color : nscolor , value : * const nsAString , result_color : * mut nscolor , ) -> bool ; pub fn Servo_ComputeColor ( set : RawServoStyleSetBorrowedOrNull , current_color : nscolor , value : * const nsAString , result_color : * mut nscolor , ) -> bool ;
} extern "C" { } extern "C" {
pub fn Servo_ParseIntersectionObserverRootMargin ( value : * const nsAString , result : * mut nsCSSRect , ) -> bool ; 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" { } extern "C" {
pub fn Gecko_CreateCSSErrorReporter ( sheet : * mut ServoStyleSheet , loader : * mut Loader , uri : * mut nsIURI , ) -> * mut ErrorReporter ; pub fn Gecko_CreateCSSErrorReporter ( sheet : * mut ServoStyleSheet , loader : * mut Loader , uri : * mut nsIURI , ) -> * mut ErrorReporter ;
} extern "C" { } extern "C" {
@ -1587,4 +1589,4 @@ extern "C" {
pub fn Gecko_ContentList_AppendAll ( aContentList : * mut nsSimpleContentList , aElements : * mut * const RawGeckoElement , aLength : usize , ) ; pub fn Gecko_ContentList_AppendAll ( aContentList : * mut nsSimpleContentList , aElements : * mut * const RawGeckoElement , aLength : usize , ) ;
} extern "C" { } extern "C" {
pub fn Gecko_GetElementsWithId ( aDocument : * const nsIDocument , aId : * mut nsAtom , ) -> * const nsTArray < * mut Element > ; pub fn Gecko_GetElementsWithId ( aDocument : * const nsIDocument , aId : * mut nsAtom , ) -> * const nsTArray < * mut Element > ;
} }

View file

@ -45,7 +45,7 @@ use values::computed::ToComputedValue;
use values::computed::transform::{DirectionVector, Matrix, Matrix3D}; use values::computed::transform::{DirectionVector, Matrix, Matrix3D};
use values::computed::transform::TransformOperation as ComputedTransformOperation; use values::computed::transform::TransformOperation as ComputedTransformOperation;
use values::computed::transform::Transform as ComputedTransform; 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}; use values::distance::{ComputeSquaredDistance, SquaredDistance};
#[cfg(feature = "gecko")] use values::generics::FontSettings as GenericFontSettings; #[cfg(feature = "gecko")] use values::generics::FontSettings as GenericFontSettings;
#[cfg(feature = "gecko")] use values::generics::FontSettingTag as GenericFontSettingTag; #[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(fx, fy, fz, fa),
&TransformOperation::Rotate3D(tx, ty, tz, ta), &TransformOperation::Rotate3D(tx, ty, tz, ta),
) => { ) => {
let (fx, fy, fz, fa) = let (fx, fy, fz, fa) = transform::get_normalized_vector_and_angle(fx, fy, fz, fa);
ComputedTransform::get_normalized_vector_and_angle(fx, fy, fz, fa); let (tx, ty, tz, ta) = transform::get_normalized_vector_and_angle(tx, ty, tz, ta);
let (tx, ty, tz, ta) =
ComputedTransform::get_normalized_vector_and_angle(tx, ty, tz, ta);
if (fx, fy, fz) == (tx, ty, tz) { if (fx, fy, fz) == (tx, ty, tz) {
let ia = fa.animate(&ta, procedure)?; let ia = fa.animate(&ta, procedure)?;
Ok(TransformOperation::Rotate3D(fx, fy, fz, ia)) Ok(TransformOperation::Rotate3D(fx, fy, fz, ia))
@ -2416,9 +2414,9 @@ impl ComputeSquaredDistance for ComputedTransformOperation {
&TransformOperation::Rotate3D(tx, ty, tz, ta), &TransformOperation::Rotate3D(tx, ty, tz, ta),
) => { ) => {
let (fx, fy, fz, angle1) = 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) = 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) { if (fx, fy, fz) == (tx, ty, tz) {
angle1.compute_squared_distance(&angle2) angle1.compute_squared_distance(&angle2)
} else { } else {
@ -2509,8 +2507,8 @@ impl ComputeSquaredDistance for ComputedTransform {
// Roll back to matrix interpolation if there is any Err(()) in the transform lists, such // Roll back to matrix interpolation if there is any Err(()) in the transform lists, such
// as mismatched transform functions. // as mismatched transform functions.
if let Err(_) = squared_dist { if let Err(_) = squared_dist {
let matrix1: Matrix3D = self.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).ok_or(())?.into(); let matrix2: Matrix3D = other.to_transform_3d_matrix(None)?.0.into();
return matrix1.compute_squared_distance(&matrix2); return matrix1.compute_squared_distance(&matrix2);
} }
squared_dist squared_dist

View file

@ -4,9 +4,10 @@
//! Computed angles. //! Computed angles.
use euclid::Radians; use num_traits::Zero;
use std::{f32, f64}; use std::{f32, f64};
use std::f64::consts::PI; use std::f64::consts::PI;
use std::ops::Add;
use values::CSSFloat; use values::CSSFloat;
use values::animated::{Animate, Procedure}; use values::animated::{Animate, Procedure};
use values::distance::{ComputeSquaredDistance, SquaredDistance}; use values::distance::{ComputeSquaredDistance, SquaredDistance};
@ -64,11 +65,6 @@ impl Angle {
radians.min(f64::MAX).max(f64::MIN) 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> /// <https://drafts.csswg.org/css-transitions/#animtype-number>
#[inline] #[inline]
fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { 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 { impl ComputeSquaredDistance for Angle {
#[inline] #[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
@ -84,10 +119,3 @@ impl ComputeSquaredDistance for Angle {
self.radians64().compute_squared_distance(&other.radians64()) self.radians64().compute_squared_distance(&other.radians64())
} }
} }
impl From<Angle> for Radians<CSSFloat> {
#[inline]
fn from(a: Angle) -> Self {
Radians::new(a.radians())
}
}

View file

@ -266,6 +266,24 @@ impl specified::CalcLengthOrPercentage {
pub fn to_computed_value_zoomed(&self, context: &Context, base_size: FontBaseSize) -> 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) 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 { impl ToComputedValue for specified::CalcLengthOrPercentage {

View file

@ -4,14 +4,13 @@
//! Computed types for CSS values that are related to transformations. //! Computed types for CSS values that are related to transformations.
use app_units::Au; use euclid::{Transform3D, Vector3D};
use euclid::{Rect, Transform3D, Vector3D}; use num_traits::Zero;
use std::f32;
use super::{CSSFloat, Either}; use super::{CSSFloat, Either};
use values::animated::ToAnimatedZero; use values::animated::ToAnimatedZero;
use values::computed::{Angle, Integer, Length, LengthOrPercentage, Number, Percentage}; use values::computed::{Angle, Integer, Length, LengthOrPercentage, Number, Percentage};
use values::computed::{LengthOrNumber, LengthOrPercentageOrNumber}; 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::{Transform as GenericTransform, TransformOperation as GenericTransformOperation};
use values::generics::transform::TimingFunction as GenericTimingFunction; use values::generics::transform::TimingFunction as GenericTimingFunction;
use values::generics::transform::TransformOrigin as GenericTransformOrigin; use values::generics::transform::TransformOrigin as GenericTransformOrigin;
@ -146,18 +145,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)] #[cfg_attr(rustfmt, rustfmt_skip)]
impl From<Transform3D<CSSFloat>> for Matrix3D { impl From<Transform3D<CSSFloat>> for Matrix3D {
#[inline] #[inline]
@ -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 { impl TransformOperation {
/// Convert to a Translate3D. /// Convert to a Translate3D.
/// ///
@ -280,7 +255,7 @@ impl ToAnimatedZero for TransformOperation {
GenericTransformOperation::ScaleY(..) => Ok(GenericTransformOperation::ScaleY(1.0)), GenericTransformOperation::ScaleY(..) => Ok(GenericTransformOperation::ScaleY(1.0)),
GenericTransformOperation::ScaleZ(..) => Ok(GenericTransformOperation::ScaleZ(1.0)), GenericTransformOperation::ScaleZ(..) => Ok(GenericTransformOperation::ScaleZ(1.0)),
GenericTransformOperation::Rotate3D(x, y, z, a) => { 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())) Ok(GenericTransformOperation::Rotate3D(x, y, z, Angle::zero()))
}, },
GenericTransformOperation::RotateX(_) => Ok(GenericTransformOperation::RotateX(Angle::zero())), GenericTransformOperation::RotateX(_) => Ok(GenericTransformOperation::RotateX(Angle::zero())),
@ -318,174 +293,3 @@ impl ToAnimatedZero for Transform {
.collect::<Result<Vec<_>, _>>()?)) .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)
}
}
}

View file

@ -4,9 +4,16 @@
//! Generic types for CSS values that are related to transformations. //! 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 std::fmt;
use style_traits::ToCss; use style_traits::ToCss;
use values::{computed, CSSFloat}; 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. /// A generic 2D transformation matrix.
#[allow(missing_docs)] #[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, 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. /// A generic transform origin.
#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)]
#[derive(MallocSizeOf, PartialEq, ToAnimatedZero, ToComputedValue, ToCss)] #[derive(MallocSizeOf, PartialEq, ToAnimatedZero, ToComputedValue, ToCss)]
@ -158,8 +191,7 @@ impl TimingKeyword {
} }
} }
#[derive(Clone, Debug, MallocSizeOf, PartialEq)] #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
#[derive(ToComputedValue)]
/// A single operation in the list of a `transform` value /// A single operation in the list of a `transform` value
pub enum TransformOperation<Angle, Number, Length, Integer, LengthOrNumber, LengthOrPercentage, LoPoNumber> { pub enum TransformOperation<Angle, Number, Length, Integer, LengthOrNumber, LengthOrPercentage, LoPoNumber> {
/// Represents a 2D 2x3 matrix. /// 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)] #[cfg_attr(rustfmt, rustfmt_skip)]
impl<Angle: ToCss + Copy, Number: ToCss + Copy, Length: ToCss, impl<Angle: ToCss + Copy, Number: ToCss + Copy, Length: ToCss,
Integer: ToCss + Copy, LengthOrNumber: ToCss, LengthOrPercentage: ToCss, LoPoNumber: ToCss> Integer: ToCss + Copy, LengthOrNumber: ToCss, LengthOrPercentage: ToCss, LoPoNumber: ToCss>
@ -457,3 +677,79 @@ impl<T> Transform<T> {
Transform(vec![]) 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)
}
}

View file

@ -95,6 +95,13 @@ impl Angle {
} }
} }
impl AsRef<ComputedAngle> for Angle {
#[inline]
fn as_ref(&self) -> &ComputedAngle {
&self.value
}
}
impl Parse for Angle { impl Parse for Angle {
/// Parses an angle according to CSS-VALUES § 6.1. /// Parses an angle according to CSS-VALUES § 6.1.
fn parse<'i, 't>( fn parse<'i, 't>(

View file

@ -63,6 +63,10 @@ pub enum CalcUnit {
} }
/// A struct to hold a simplified `<length>` or `<percentage>` expression. /// 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)] #[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub struct CalcLengthOrPercentage { pub struct CalcLengthOrPercentage {

View file

@ -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. /// Get an absolute length from a px value.
#[inline] #[inline]
pub fn from_px(px_value: CSSFloat) -> NoCalcLength { pub fn from_px(px_value: CSSFloat) -> NoCalcLength {

View file

@ -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. /// A Number which is >= 0.0.
pub type NonNegativeNumber = NonNegative<Number>; pub type NonNegativeNumber = NonNegative<Number>;

View file

@ -4650,6 +4650,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] #[no_mangle]
pub unsafe extern "C" fn Servo_SourceSizeList_Parse( pub unsafe extern "C" fn Servo_SourceSizeList_Parse(
value: *const nsACString, value: *const nsACString,

View file

@ -65189,11 +65189,11 @@
"support" "support"
], ],
"css/transform_skew_a.html": [ "css/transform_skew_a.html": [
"8ae3384ece6fc2ebd537736e63723e12b974fff3", "521496ff3fb82a34498d313c1d600a6ca271b1ed",
"reftest" "reftest"
], ],
"css/transform_skew_ref.html": [ "css/transform_skew_ref.html": [
"6f5154aef6acf1428ac391f0d2dbb2e369ca930b", "a941cd3a8c968494f292ddadd28de5b541ad71b2",
"support" "support"
], ],
"css/transform_stacking_context_a.html": [ "css/transform_stacking_context_a.html": [

View file

@ -1,4 +0,0 @@
[transform_skew_a.html]
type: reftest
expected:
if os == "linux": FAIL

View file

@ -18,7 +18,7 @@ div>div {
} }
.transformed1 { .transformed1 {
transform: skewX(0.3rad); transform: skewX(0.25rad);
} }
.transformed2 { .transformed2 {
@ -26,7 +26,7 @@ div>div {
} }
.transformed3 { .transformed3 {
transform: skew(0.3rad, 0.5rad); transform: skew(0.25rad, 0.5rad);
} }
</style> </style>
</head> </head>

View file

@ -17,7 +17,7 @@ div>div {
} }
.transformed1_ref { .transformed1_ref {
transform: matrix(1, 0, 0.30933624961, 1, 0, 0); transform: matrix(1, 0, 0.25534192122, 1, 0, 0);
} }
.transformed2_ref { .transformed2_ref {
@ -25,7 +25,7 @@ div>div {
} }
.transformed3_ref { .transformed3_ref {
transform: matrix(1, 0.54630248984, 0.30933624961, 1, 0, 0); transform: matrix(1, 0.54630248984, 0.25534192122, 1, 0, 0);
} }
</style> </style>