mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
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:
commit
823da9e34a
16 changed files with 450 additions and 238 deletions
|
@ -46,13 +46,13 @@ use style::computed_values::{transform_style, 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::ServoRestyleDamage;
|
||||
use style::str::char_is_whitespace;
|
||||
use style::values::{self, Either, Auto};
|
||||
use style::values::computed::{Length, LengthOrPercentage, LengthOrPercentageOrAuto};
|
||||
use style::values::generics::box_::VerticalAlign;
|
||||
use style::values::generics::transform;
|
||||
use text;
|
||||
use text::TextRunScanner;
|
||||
use webrender_api;
|
||||
|
@ -2895,7 +2895,7 @@ 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 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_x =
|
||||
|
@ -2939,7 +2939,7 @@ impl Fragment {
|
|||
-perspective_origin.y,
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -1577,6 +1577,8 @@ extern "C" {
|
|||
pub fn Servo_ComputeColor ( set : RawServoStyleSetBorrowedOrNull , current_color : nscolor , value : * const nsAString , result_color : * mut nscolor , ) -> bool ;
|
||||
} extern "C" {
|
||||
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" {
|
||||
pub fn Gecko_CreateCSSErrorReporter ( sheet : * mut ServoStyleSheet , loader : * mut Loader , uri : * mut nsIURI , ) -> * mut ErrorReporter ;
|
||||
} extern "C" {
|
||||
|
@ -1587,4 +1589,4 @@ extern "C" {
|
|||
pub fn Gecko_ContentList_AppendAll ( aContentList : * mut nsSimpleContentList , aElements : * mut * const RawGeckoElement , aLength : usize , ) ;
|
||||
} extern "C" {
|
||||
pub fn Gecko_GetElementsWithId ( aDocument : * const nsIDocument , aId : * mut nsAtom , ) -> * const nsTArray < * mut Element > ;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ use values::computed::ToComputedValue;
|
|||
use values::computed::transform::{DirectionVector, Matrix, Matrix3D};
|
||||
use values::computed::transform::TransformOperation as ComputedTransformOperation;
|
||||
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};
|
||||
#[cfg(feature = "gecko")] use values::generics::FontSettings as GenericFontSettings;
|
||||
#[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(tx, ty, tz, ta),
|
||||
) => {
|
||||
let (fx, fy, fz, fa) =
|
||||
ComputedTransform::get_normalized_vector_and_angle(fx, fy, fz, fa);
|
||||
let (tx, ty, tz, ta) =
|
||||
ComputedTransform::get_normalized_vector_and_angle(tx, ty, tz, ta);
|
||||
let (fx, fy, fz, fa) = transform::get_normalized_vector_and_angle(fx, fy, fz, fa);
|
||||
let (tx, ty, tz, ta) = transform::get_normalized_vector_and_angle(tx, ty, tz, ta);
|
||||
if (fx, fy, fz) == (tx, ty, tz) {
|
||||
let ia = fa.animate(&ta, procedure)?;
|
||||
Ok(TransformOperation::Rotate3D(fx, fy, fz, ia))
|
||||
|
@ -2416,9 +2414,9 @@ impl ComputeSquaredDistance for ComputedTransformOperation {
|
|||
&TransformOperation::Rotate3D(tx, ty, tz, ta),
|
||||
) => {
|
||||
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) =
|
||||
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) {
|
||||
angle1.compute_squared_distance(&angle2)
|
||||
} else {
|
||||
|
@ -2509,8 +2507,8 @@ impl ComputeSquaredDistance for ComputedTransform {
|
|||
// 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: Matrix3D = self.to_transform_3d_matrix(None).ok_or(())?.into();
|
||||
let matrix2: Matrix3D = other.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)?.0.into();
|
||||
return matrix1.compute_squared_distance(&matrix2);
|
||||
}
|
||||
squared_dist
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
|
||||
//! Computed angles.
|
||||
|
||||
use euclid::Radians;
|
||||
use num_traits::Zero;
|
||||
use std::{f32, f64};
|
||||
use std::f64::consts::PI;
|
||||
use std::ops::Add;
|
||||
use values::CSSFloat;
|
||||
use values::animated::{Animate, Procedure};
|
||||
use values::distance::{ComputeSquaredDistance, SquaredDistance};
|
||||
|
@ -64,11 +65,6 @@ impl Angle {
|
|||
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>
|
||||
#[inline]
|
||||
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 {
|
||||
#[inline]
|
||||
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
|
@ -84,10 +119,3 @@ impl ComputeSquaredDistance for Angle {
|
|||
self.radians64().compute_squared_distance(&other.radians64())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Angle> for Radians<CSSFloat> {
|
||||
#[inline]
|
||||
fn from(a: Angle) -> Self {
|
||||
Radians::new(a.radians())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,6 +266,24 @@ impl specified::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)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,16 @@
|
|||
|
||||
//! 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 style_traits::ToCss;
|
||||
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.
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[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.
|
||||
#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)]
|
||||
#[derive(MallocSizeOf, PartialEq, ToAnimatedZero, ToComputedValue, ToCss)]
|
||||
|
@ -158,8 +191,7 @@ impl TimingKeyword {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
|
||||
#[derive(ToComputedValue)]
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
|
||||
/// A single operation in the list of a `transform` value
|
||||
pub enum TransformOperation<Angle, Number, Length, Integer, LengthOrNumber, LengthOrPercentage, LoPoNumber> {
|
||||
/// 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)]
|
||||
impl<Angle: ToCss + Copy, Number: ToCss + Copy, Length: ToCss,
|
||||
Integer: ToCss + Copy, LengthOrNumber: ToCss, LengthOrPercentage: ToCss, LoPoNumber: ToCss>
|
||||
|
@ -457,3 +677,79 @@ impl<T> Transform<T> {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,13 @@ impl Angle {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsRef<ComputedAngle> for Angle {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &ComputedAngle {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Angle {
|
||||
/// Parses an angle according to CSS-VALUES § 6.1.
|
||||
fn parse<'i, 't>(
|
||||
|
|
|
@ -63,6 +63,10 @@ pub enum CalcUnit {
|
|||
}
|
||||
|
||||
/// 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)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct CalcLengthOrPercentage {
|
||||
|
|
|
@ -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.
|
||||
#[inline]
|
||||
pub fn from_px(px_value: CSSFloat) -> NoCalcLength {
|
||||
|
|
|
@ -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.
|
||||
pub type NonNegativeNumber = NonNegative<Number>;
|
||||
|
||||
|
|
|
@ -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]
|
||||
pub unsafe extern "C" fn Servo_SourceSizeList_Parse(
|
||||
value: *const nsACString,
|
||||
|
|
|
@ -65189,11 +65189,11 @@
|
|||
"support"
|
||||
],
|
||||
"css/transform_skew_a.html": [
|
||||
"8ae3384ece6fc2ebd537736e63723e12b974fff3",
|
||||
"521496ff3fb82a34498d313c1d600a6ca271b1ed",
|
||||
"reftest"
|
||||
],
|
||||
"css/transform_skew_ref.html": [
|
||||
"6f5154aef6acf1428ac391f0d2dbb2e369ca930b",
|
||||
"a941cd3a8c968494f292ddadd28de5b541ad71b2",
|
||||
"support"
|
||||
],
|
||||
"css/transform_stacking_context_a.html": [
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[transform_skew_a.html]
|
||||
type: reftest
|
||||
expected:
|
||||
if os == "linux": FAIL
|
|
@ -18,7 +18,7 @@ div>div {
|
|||
}
|
||||
|
||||
.transformed1 {
|
||||
transform: skewX(0.3rad);
|
||||
transform: skewX(0.25rad);
|
||||
}
|
||||
|
||||
.transformed2 {
|
||||
|
@ -26,7 +26,7 @@ div>div {
|
|||
}
|
||||
|
||||
.transformed3 {
|
||||
transform: skew(0.3rad, 0.5rad);
|
||||
transform: skew(0.25rad, 0.5rad);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
|
@ -17,7 +17,7 @@ div>div {
|
|||
}
|
||||
|
||||
.transformed1_ref {
|
||||
transform: matrix(1, 0, 0.30933624961, 1, 0, 0);
|
||||
transform: matrix(1, 0, 0.25534192122, 1, 0, 0);
|
||||
}
|
||||
|
||||
.transformed2_ref {
|
||||
|
@ -25,7 +25,7 @@ div>div {
|
|||
}
|
||||
|
||||
.transformed3_ref {
|
||||
transform: matrix(1, 0.54630248984, 0.30933624961, 1, 0, 0);
|
||||
transform: matrix(1, 0.54630248984, 0.25534192122, 1, 0, 0);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue