Implement Servo_ParseTransformIntoMatrix.

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

Besides, we have to update the test case because we use Transform3D<f64> to
compute the matrix, instead of Transform3D<f32>, so the result will be
the same as that in Gecko. Using 0.3 may cause floating point issue
because (0.3f32 as f64) is not equal to 0.3 (i.e. floating point precision
issue), so using 0.25 instead.
This commit is contained in:
Boris Chiou 2017-11-27 14:26:17 +08:00
parent ac6e04ebfb
commit 3a38e815ec
16 changed files with 450 additions and 238 deletions

View file

@ -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;
@ -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)]
impl From<Transform3D<CSSFloat>> for Matrix3D {
#[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 {
/// 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)
}
}
}