diff --git a/servo/components/layout/fragment.rs b/servo/components/layout/fragment.rs index df0bca4cc7d9..44dc9b8731ea 100644 --- a/servo/components/layout/fragment.rs +++ b/servo/components/layout/fragment.rs @@ -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) -> Option> { - 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 { - let d = d.to_f32_px(); - if d <= 0.0 { - Transform3D::identity() - } else { - Transform3D::create_perspective(d) - } -} diff --git a/servo/components/layout/model.rs b/servo/components/layout/model.rs index 9094f82ae1d1..34a6f89671ba 100644 --- a/servo/components/layout/model.rs +++ b/servo/components/layout/model.rs @@ -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; -} - -impl ToGfxMatrix for ComputedMatrix { - fn to_gfx_matrix(&self) -> Transform3D { - 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 diff --git a/servo/components/style/properties/helpers/animated_properties.mako.rs b/servo/components/style/properties/helpers/animated_properties.mako.rs index 79e9a4df6459..25cc6b47e35c 100644 --- a/servo/components/style/properties/helpers/animated_properties.mako.rs +++ b/servo/components/style/properties/helpers/animated_properties.mako.rs @@ -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); - 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 { @@ -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 { - 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 = 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 } } diff --git a/servo/components/style/values/computed/angle.rs b/servo/components/style/values/computed/angle.rs index 9dd159354ff8..b26c8ccc02b4 100644 --- a/servo/components/style/values/computed/angle.rs +++ b/servo/components/style/values/computed/angle.rs @@ -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 for Radians { + #[inline] + fn from(a: Angle) -> Self { + Radians::new(a.radians()) + } +} diff --git a/servo/components/style/values/computed/transform.rs b/servo/components/style/values/computed/transform.rs index ab7e1477d8f2..02de62984445 100644 --- a/servo/components/style/values/computed/transform.rs +++ b/servo/components/style/values/computed/transform.rs @@ -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; +/// A vector to represent the direction vector (rotate axis) for Rotate3D. +pub type DirectionVector = Vector3D; + impl TransformOrigin { /// Returns the initial computed value for `transform-origin`. #[inline] @@ -25,3 +34,139 @@ impl TransformOrigin { ) } } + +impl From for Transform3D { + #[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> for ComputedMatrix { + #[inline] + fn from(m: Transform3D) -> 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>) + -> Option> { + 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 { + // 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) + } + } +}