Skip to content

Commit

Permalink
Implement to_transform_3d_matrix for computing distance of mismatched…
Browse files Browse the repository at this point in the history
… transform lists.

We could use this method to convert a TransformList into a Matrix, and
use this matrix for computing distance for Stylo and rendering the transform
for Servo.

This is an equivalent of nsStyleTransformMatrix::ReadTransforms in Gecko.
  • Loading branch information
BorisChiou committed Aug 25, 2017
1 parent 772a846 commit 5c2d850
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 146 deletions.
81 changes: 8 additions & 73 deletions components/layout/fragment.rs
Expand Up @@ -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;
Expand All @@ -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};
Expand All @@ -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;
Expand Down Expand Up @@ -2867,12 +2868,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
Expand All @@ -2891,55 +2892,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))
}

Expand All @@ -2964,7 +2916,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))
}
Expand Down Expand Up @@ -3208,20 +3160,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)
}
}
17 changes: 1 addition & 16 deletions components/layout/model.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down
79 changes: 23 additions & 56 deletions components/style/properties/helpers/animated_properties.mako.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -53,6 +52,7 @@ use values::computed::{NonNegativeNumber, Number, NumberOrPercentage, Percentage
use values::computed::{PositiveIntegerOrAuto, ToComputedValue};
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;
Expand Down Expand Up @@ -1107,7 +1107,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, _) = DirectionVector::get_normalized_vector_and_angle(x, y, z, a);
Ok(TransformOperation::Rotate(x, y, z, Angle::zero()))
},
TransformOperation::Perspective(..) |
Expand Down Expand Up @@ -1184,8 +1184,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) =
DirectionVector::get_normalized_vector_and_angle(fx, fy, fz, fa);
let (tx, ty, tz, ta) =
DirectionVector::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))
Expand Down Expand Up @@ -1598,11 +1600,6 @@ 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]
Expand Down Expand Up @@ -1642,47 +1639,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, ()> {
Expand Down Expand Up @@ -2347,8 +2303,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) =
DirectionVector::get_normalized_vector_and_angle(fx, fy, fz, fa);
let (tx, ty, tz, angle2) =
DirectionVector::get_normalized_vector_and_angle(tx, ty, tz, ta);
if (fx, fy, fz) == (tx, ty, tz) {
angle1.compute_squared_distance(&angle2)
} else {
Expand Down Expand Up @@ -2395,10 +2353,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)
Expand All @@ -2407,7 +2365,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
}
}

Expand Down
8 changes: 8 additions & 0 deletions components/style/values/computed/angle.rs
Expand Up @@ -4,6 +4,7 @@

//! Computed angles.

use euclid::Radians;
use std::{f32, f64, fmt};
use std::f64::consts::PI;
use style_traits::ToCss;
Expand Down Expand Up @@ -112,3 +113,10 @@ impl ToCss for Angle {
}
}
}

impl From<Angle> for Radians<CSSFloat> {
#[inline]
fn from(a: Angle) -> Self {
Radians::new(a.radians())
}
}

0 comments on commit 5c2d850

Please sign in to comment.