servo: Merge #18234 - stylo: Bug 1390039 - Implement compute_distance for mismatched transform lists (from BorisChiou:stylo/transform/distance_mismatch); r=birtles,nox

Implement ComputeSquaredDistance for mismatched transform lists.
In order to do this, we have to convert a transform list into a 3d matrix,
so I move the code from layout module into style module for reusing it.

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix [Bug 1390039](https://bugzilla.mozilla.org/show_bug.cgi?id=1390039).
- [X] These changes do not require tests because this is a Gecko feature and I add many tests in Gecko already.

Source-Repo: https://github.com/servo/servo
Source-Revision: 3fa5d83ab798a9f1f88a73bf8618e6d7ccbb4b64

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : dd0b5e85f3be270d00657881351cd86286d436ff
This commit is contained in:
Boris Chiou 2017-08-30 06:03:32 -05:00
Родитель 22b2d26b42
Коммит 20b3ff88fa
5 изменённых файлов: 191 добавлений и 151 удалений

Просмотреть файл

@ -10,7 +10,7 @@ use ServoArc;
use app_units::Au;
use canvas_traits::canvas::CanvasMsg;
use context::{LayoutContext, with_thread_local_font_context};
use euclid::{Transform3D, Point2D, Vector2D, Radians, Rect, Size2D};
use euclid::{Transform3D, Point2D, Vector2D, Rect, Size2D};
use floats::ClearType;
use flow::{self, ImmutableFlowUtils};
use flow_ref::FlowRef;
@ -25,7 +25,7 @@ use ipc_channel::ipc::IpcSender;
#[cfg(debug_assertions)]
use layout_debug;
use model::{self, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, SizeConstraint};
use model::{style_length, ToGfxMatrix};
use model::style_length;
use msg::constellation_msg::{BrowsingContextId, PipelineId};
use net_traits::image::base::{Image, ImageMetadata};
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
@ -41,11 +41,12 @@ use std::cmp::{Ordering, max, min};
use std::collections::LinkedList;
use std::sync::{Arc, Mutex};
use style::computed_values::{border_collapse, box_sizing, clear, color, display, mix_blend_mode};
use style::computed_values::{overflow_wrap, overflow_x, position, text_decoration_line, transform};
use style::computed_values::{overflow_wrap, overflow_x, position, text_decoration_line};
use style::computed_values::{transform_style, vertical_align, 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::RECONSTRUCT_FLOW;
use style::str::char_is_whitespace;
@ -2863,12 +2864,12 @@ 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 operations = match self.style.get_box().transform.0 {
let list = &self.style.get_box().transform;
let transform = match list.to_transform_3d_matrix(Some(stacking_relative_border_box)) {
Some(transform) => transform,
None => return None,
Some(ref operations) => operations,
};
let mut transform = Transform3D::identity();
let transform_origin = &self.style.get_box().transform_origin;
let transform_origin_x =
transform_origin.horizontal
@ -2887,55 +2888,6 @@ impl Fragment {
-transform_origin_y,
-transform_origin_z);
for operation in operations {
let matrix = match *operation {
transform::ComputedOperation::Rotate(ax, ay, az, theta) => {
// 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 in this case.
let len = (ax * ax + ay * ay + az * az).sqrt();
if len > 0. {
let theta = 2.0f32 * f32::consts::PI - theta.radians();
Transform3D::create_rotation(ax / len, ay / len, az / len,
Radians::new(theta))
} else {
Transform3D::identity()
}
}
transform::ComputedOperation::Perspective(d) => {
create_perspective_matrix(d)
}
transform::ComputedOperation::Scale(sx, sy, sz) => {
Transform3D::create_scale(sx, sy, sz)
}
transform::ComputedOperation::Translate(tx, ty, tz) => {
let tx = tx.to_used_value(stacking_relative_border_box.size.width).to_f32_px();
let ty = ty.to_used_value(stacking_relative_border_box.size.height).to_f32_px();
let tz = tz.to_f32_px();
Transform3D::create_translation(tx, ty, tz)
}
transform::ComputedOperation::Matrix(m) => {
m.to_gfx_matrix()
}
transform::ComputedOperation::MatrixWithPercents(_) => {
// `-moz-transform` is not implemented in Servo yet.
unreachable!()
}
transform::ComputedOperation::Skew(theta_x, theta_y) => {
Transform3D::create_skew(Radians::new(theta_x.radians()),
Radians::new(theta_y.radians()))
}
transform::ComputedOperation::InterpolateMatrix { .. } |
transform::ComputedOperation::AccumulateMatrix { .. } => {
// TODO: Convert InterpolateMatrix/AccmulateMatrix into a valid Transform3D by
// the reference box.
Transform3D::identity()
}
};
transform = transform.pre_mul(&matrix);
}
Some(pre_transform.pre_mul(&transform).pre_mul(&post_transform))
}
@ -2960,7 +2912,7 @@ impl Fragment {
-perspective_origin.y,
0.0);
let perspective_matrix = create_perspective_matrix(length);
let perspective_matrix = TransformList::create_perspective_matrix(length);
Some(pre_transform.pre_mul(&perspective_matrix).pre_mul(&post_transform))
}
@ -3204,20 +3156,3 @@ impl Serialize for DebugId {
serializer.serialize_u16(self.0)
}
}
// 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.
#[inline]
fn create_perspective_matrix(d: Au) -> Transform3D<f32> {
let d = d.to_f32_px();
if d <= 0.0 {
Transform3D::identity()
} else {
Transform3D::create_perspective(d)
}
}

Просмотреть файл

@ -7,11 +7,10 @@
#![deny(unsafe_code)]
use app_units::Au;
use euclid::{Transform3D, SideOffsets2D, Size2D};
use euclid::{SideOffsets2D, Size2D};
use fragment::Fragment;
use std::cmp::{max, min};
use std::fmt;
use style::computed_values::transform::ComputedMatrix;
use style::logical_geometry::{LogicalMargin, WritingMode};
use style::properties::ComputedValues;
use style::values::computed::{BorderCornerRadius, LengthOrPercentageOrAuto};
@ -508,20 +507,6 @@ pub fn specified_margin_from_style(style: &ComputedValues,
MaybeAuto::from_style(margin_style.margin_left, Au(0)).specified_or_zero()))
}
pub trait ToGfxMatrix {
fn to_gfx_matrix(&self) -> Transform3D<f32>;
}
impl ToGfxMatrix for ComputedMatrix {
fn to_gfx_matrix(&self) -> Transform3D<f32> {
Transform3D::row_major(
self.m11 as f32, self.m12 as f32, self.m13 as f32, self.m14 as f32,
self.m21 as f32, self.m22 as f32, self.m23 as f32, self.m24 as f32,
self.m31 as f32, self.m32 as f32, self.m33 as f32, self.m34 as f32,
self.m41 as f32, self.m42 as f32, self.m43 as f32, self.m44 as f32)
}
}
/// A min-size and max-size constraint. The constructor has a optional `border`
/// parameter, and when it is present the constraint will be subtracted. This is
/// used to adjust the constraint for `box-sizing: border-box`, and when you do so

Просмотреть файл

@ -8,7 +8,6 @@
use app_units::Au;
use cssparser::Parser;
use euclid::Point3D;
#[cfg(feature = "gecko")] use gecko_bindings::bindings::RawServoAnimationValueMap;
#[cfg(feature = "gecko")] use gecko_bindings::structs::RawGeckoGfxMatrix4x4;
#[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
@ -54,6 +53,7 @@ use values::computed::{PositiveIntegerOrAuto, ToComputedValue};
#[cfg(feature = "gecko")] use values::computed::MozLength;
use values::computed::length::{NonNegativeLengthOrAuto, NonNegativeLengthOrNormal};
use values::computed::length::NonNegativeLengthOrPercentage;
use values::computed::transform::DirectionVector;
use values::distance::{ComputeSquaredDistance, SquaredDistance};
use values::generics::NonNegative;
use values::generics::effects::Filter;
@ -959,7 +959,7 @@ impl ToAnimatedZero for TransformOperation {
Ok(TransformOperation::Scale(1.0, 1.0, 1.0))
},
TransformOperation::Rotate(x, y, z, a) => {
let (x, y, z, _) = get_normalized_vector_and_angle(x, y, z, a);
let (x, y, z, _) = TransformList::get_normalized_vector_and_angle(x, y, z, a);
Ok(TransformOperation::Rotate(x, y, z, Angle::zero()))
},
TransformOperation::Perspective(..) |
@ -1036,8 +1036,10 @@ impl Animate for TransformOperation {
&TransformOperation::Rotate(fx, fy, fz, fa),
&TransformOperation::Rotate(tx, ty, tz, ta),
) => {
let (fx, fy, fz, fa) = get_normalized_vector_and_angle(fx, fy, fz, fa);
let (tx, ty, tz, ta) = get_normalized_vector_and_angle(tx, ty, tz, ta);
let (fx, fy, fz, fa) =
TransformList::get_normalized_vector_and_angle(fx, fy, fz, fa);
let (tx, ty, tz, ta) =
TransformList::get_normalized_vector_and_angle(tx, ty, tz, ta);
if (fx, fy, fz) == (tx, ty, tz) {
let ia = fa.animate(&ta, procedure)?;
Ok(TransformOperation::Rotate(fx, fy, fz, ia))
@ -1450,17 +1452,12 @@ pub struct MatrixDecomposed3D {
pub quaternion: Quaternion,
}
/// A wrapper of Point3D to represent the direction vector (rotate axis) for Rotate3D.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct DirectionVector(Point3D<f64>);
impl Quaternion {
/// Return a quaternion from a unit direction vector and angle (unit: radian).
#[inline]
fn from_direction_and_angle(vector: &DirectionVector, angle: f64) -> Self {
debug_assert!((vector.length() - 1.).abs() < 0.0001f64,
"Only accept an unit direction vector to create a quaternion");
debug_assert!((vector.length() - 1.).abs() < 0.0001,
"Only accept an unit direction vector to create a quaternion");
// Reference:
// https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
//
@ -1470,9 +1467,9 @@ impl Quaternion {
// q = cos(theta/2) + (xi + yj + zk)(sin(theta/2))
// = cos(theta/2) +
// x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k
Quaternion(vector.0.x * (angle / 2.).sin(),
vector.0.y * (angle / 2.).sin(),
vector.0.z * (angle / 2.).sin(),
Quaternion(vector.x as f64 * (angle / 2.).sin(),
vector.y as f64 * (angle / 2.).sin(),
vector.z as f64 * (angle / 2.).sin(),
(angle / 2.).cos())
}
@ -1494,47 +1491,6 @@ impl ComputeSquaredDistance for Quaternion {
}
}
impl DirectionVector {
/// Create a DirectionVector.
#[inline]
fn new(x: f32, y: f32, z: f32) -> Self {
DirectionVector(Point3D::new(x as f64, y as f64, z as f64))
}
/// Return the normalized direction vector.
#[inline]
fn normalize(&mut self) -> bool {
let len = self.length();
if len > 0. {
self.0.x = self.0.x / len;
self.0.y = self.0.y / len;
self.0.z = self.0.z / len;
true
} else {
false
}
}
/// Get the length of this vector.
#[inline]
fn length(&self) -> f64 {
self.0.to_array().iter().fold(0f64, |sum, v| sum + v * v).sqrt()
}
}
/// Return the normalized direction vector and its angle.
// A direction vector that cannot be normalized, such as [0,0,0], will cause the
// rotation to not be applied. i.e. Use an identity matrix or rotate3d(0, 0, 1, 0).
fn get_normalized_vector_and_angle(x: f32, y: f32, z: f32, angle: Angle)
-> (f32, f32, f32, Angle) {
let mut vector = DirectionVector::new(x, y, z);
if vector.normalize() {
(vector.0.x as f32, vector.0.y as f32, vector.0.z as f32, angle)
} else {
(0., 0., 1., Angle::zero())
}
}
/// Decompose a 3D matrix.
/// https://drafts.csswg.org/css-transforms/#decomposing-a-3d-matrix
fn decompose_3d_matrix(mut matrix: ComputedMatrix) -> Result<MatrixDecomposed3D, ()> {
@ -2201,8 +2157,10 @@ impl ComputeSquaredDistance for TransformOperation {
&TransformOperation::Rotate(fx, fy, fz, fa),
&TransformOperation::Rotate(tx, ty, tz, ta),
) => {
let (fx, fy, fz, angle1) = get_normalized_vector_and_angle(fx, fy, fz, fa);
let (tx, ty, tz, angle2) = get_normalized_vector_and_angle(tx, ty, tz, ta);
let (fx, fy, fz, angle1) =
TransformList::get_normalized_vector_and_angle(fx, fy, fz, fa);
let (tx, ty, tz, angle2) =
TransformList::get_normalized_vector_and_angle(tx, ty, tz, ta);
if (fx, fy, fz) == (tx, ty, tz) {
angle1.compute_squared_distance(&angle2)
} else {
@ -2249,10 +2207,10 @@ impl ComputeSquaredDistance for TransformOperation {
impl ComputeSquaredDistance for TransformList {
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
let this = self.0.as_ref().map_or(&[][..], |l| l);
let other = other.0.as_ref().map_or(&[][..], |l| l);
let list1 = self.0.as_ref().map_or(&[][..], |l| l);
let list2 = other.0.as_ref().map_or(&[][..], |l| l);
this.iter().zip_longest(other).map(|it| {
let squared_dist: Result<SquaredDistance, _> = list1.iter().zip_longest(list2).map(|it| {
match it {
EitherOrBoth::Both(this, other) => {
this.compute_squared_distance(other)
@ -2261,7 +2219,16 @@ impl ComputeSquaredDistance for TransformList {
list.to_animated_zero()?.compute_squared_distance(list)
},
}
}).sum()
}).sum();
// 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: ComputedMatrix = self.to_transform_3d_matrix(None).ok_or(())?.into();
let matrix2: ComputedMatrix = other.to_transform_3d_matrix(None).ok_or(())?.into();
return matrix1.compute_squared_distance(&matrix2);
}
squared_dist
}
}

Просмотреть файл

@ -4,6 +4,7 @@
//! Computed angles.
use euclid::Radians;
use std::{f32, f64, fmt};
use std::f64::consts::PI;
use style_traits::ToCss;
@ -99,3 +100,10 @@ impl ToCss for Angle {
}
}
}
impl From<Angle> for Radians<CSSFloat> {
#[inline]
fn from(a: Angle) -> Self {
Radians::new(a.radians())
}
}

Просмотреть файл

@ -4,7 +4,13 @@
//! Computed types for CSS values that are related to transformations.
use values::computed::{Length, LengthOrPercentage, Number, Percentage};
use app_units::Au;
use euclid::{Rect, Transform3D, Vector3D};
use properties::longhands::transform::computed_value::{ComputedOperation, ComputedMatrix};
use properties::longhands::transform::computed_value::T as TransformList;
use std::f32;
use super::CSSFloat;
use values::computed::{Angle, Length, LengthOrPercentage, Number, Percentage};
use values::generics::transform::TimingFunction as GenericTimingFunction;
use values::generics::transform::TransformOrigin as GenericTransformOrigin;
@ -14,6 +20,9 @@ pub type TransformOrigin = GenericTransformOrigin<LengthOrPercentage, LengthOrPe
/// A computed timing function.
pub type TimingFunction = GenericTimingFunction<u32, Number>;
/// A vector to represent the direction vector (rotate axis) for Rotate3D.
pub type DirectionVector = Vector3D<CSSFloat>;
impl TransformOrigin {
/// Returns the initial computed value for `transform-origin`.
#[inline]
@ -25,3 +34,139 @@ impl TransformOrigin {
)
}
}
impl From<ComputedMatrix> for Transform3D<CSSFloat> {
#[inline]
fn from(m: ComputedMatrix) -> 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)
}
}
impl From<Transform3D<CSSFloat>> for ComputedMatrix {
#[inline]
fn from(m: Transform3D<CSSFloat>) -> Self {
ComputedMatrix {
m11: m.m11, m12: m.m12, m13: m.m13, m14: m.m14,
m21: m.m21, m22: m.m22, m23: m.m23, m24: m.m24,
m31: m.m31, m32: m.m32, m33: m.m33, m34: m.m34,
m41: m.m41, m42: m.m42, m43: m.m43, m44: m.m44
}
}
}
impl TransformList {
/// 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 = match self.0.as_ref() {
Some(list) => list,
None => return None,
};
let extract_pixel_length = |lop: &LengthOrPercentage| {
match *lop {
LengthOrPercentage::Length(au) => au.to_f32_px(),
LengthOrPercentage::Percentage(_) => 0.,
LengthOrPercentage::Calc(calc) => calc.length().to_f32_px(),
}
};
for operation in list {
let matrix = match *operation {
ComputedOperation::Rotate(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())
}
ComputedOperation::Perspective(d) => {
Self::create_perspective_matrix(d)
}
ComputedOperation::Scale(sx, sy, sz) => {
Transform3D::create_scale(sx, sy, sz)
}
ComputedOperation::Translate(tx, ty, tz) => {
let (tx, ty) = match reference_box {
Some(relative_border_box) => {
(tx.to_used_value(relative_border_box.size.width).to_f32_px(),
ty.to_used_value(relative_border_box.size.height).to_f32_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.to_f32_px();
Transform3D::create_translation(tx, ty, tz)
}
ComputedOperation::Matrix(m) => {
m.into()
}
ComputedOperation::MatrixWithPercents(_) => {
// `-moz-transform` is not implemented in Servo yet.
unreachable!()
}
ComputedOperation::Skew(theta_x, theta_y) => {
Transform3D::create_skew(theta_x.into(), theta_y.into())
}
ComputedOperation::InterpolateMatrix { .. } |
ComputedOperation::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: Au) -> 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.
let d = d.to_f32_px();
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)
}
}
}