summaryrefslogtreecommitdiffstats
path: root/servo/components/style/values/generics/transform.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /servo/components/style/values/generics/transform.rs
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components/style/values/generics/transform.rs')
-rw-r--r--servo/components/style/values/generics/transform.rs879
1 files changed, 879 insertions, 0 deletions
diff --git a/servo/components/style/values/generics/transform.rs b/servo/components/style/values/generics/transform.rs
new file mode 100644
index 0000000000..3a65c460a7
--- /dev/null
+++ b/servo/components/style/values/generics/transform.rs
@@ -0,0 +1,879 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Generic types for CSS values that are related to transformations.
+
+use crate::values::computed::length::Length as ComputedLength;
+use crate::values::computed::length::LengthPercentage as ComputedLengthPercentage;
+use crate::values::specified::angle::Angle as SpecifiedAngle;
+use crate::values::specified::length::Length as SpecifiedLength;
+use crate::values::specified::length::LengthPercentage as SpecifiedLengthPercentage;
+use crate::values::{computed, CSSFloat};
+use crate::{Zero, ZeroNoPercent};
+use euclid;
+use euclid::default::{Rect, Transform3D};
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+
+/// A generic 2D transformation matrix.
+#[allow(missing_docs)]
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(comma, function = "matrix")]
+#[repr(C)]
+pub struct GenericMatrix<T> {
+ pub a: T,
+ pub b: T,
+ pub c: T,
+ pub d: T,
+ pub e: T,
+ pub f: T,
+}
+
+pub use self::GenericMatrix as Matrix;
+
+#[allow(missing_docs)]
+#[cfg_attr(rustfmt, rustfmt_skip)]
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[css(comma, function = "matrix3d")]
+#[repr(C)]
+pub struct GenericMatrix3D<T> {
+ pub m11: T, pub m12: T, pub m13: T, pub m14: T,
+ pub m21: T, pub m22: T, pub m23: T, pub m24: T,
+ pub m31: T, pub m32: T, pub m33: T, pub m34: T,
+ pub m41: T, pub m42: T, pub m43: T, pub m44: T,
+}
+
+pub use self::GenericMatrix3D as Matrix3D;
+
+#[cfg_attr(rustfmt, rustfmt_skip)]
+impl<T: Into<f64>> From<Matrix<T>> for Transform3D<f64> {
+ #[inline]
+ fn from(m: Matrix<T>) -> Self {
+ Transform3D::new(
+ 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::new(
+ 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,
+ MallocSizeOf,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C)]
+pub struct GenericTransformOrigin<H, V, Depth> {
+ /// The horizontal origin.
+ pub horizontal: H,
+ /// The vertical origin.
+ pub vertical: V,
+ /// The depth.
+ pub depth: Depth,
+}
+
+pub use self::GenericTransformOrigin as TransformOrigin;
+
+impl<H, V, D> TransformOrigin<H, V, D> {
+ /// Returns a new transform origin.
+ pub fn new(horizontal: H, vertical: V, depth: D) -> Self {
+ Self {
+ horizontal,
+ vertical,
+ depth,
+ }
+ }
+}
+
+fn is_same<N: PartialEq>(x: &N, y: &N) -> bool {
+ x == y
+}
+
+/// A value for the `perspective()` transform function, which is either a
+/// non-negative `<length>` or `none`.
+#[derive(
+ Clone,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C, u8)]
+pub enum GenericPerspectiveFunction<L> {
+ /// `none`
+ None,
+ /// A `<length>`.
+ Length(L),
+}
+
+impl<L> GenericPerspectiveFunction<L> {
+ /// Returns `f32::INFINITY` or the result of a function on the length value.
+ pub fn infinity_or(&self, f: impl FnOnce(&L) -> f32) -> f32 {
+ match *self {
+ Self::None => f32::INFINITY,
+ Self::Length(ref l) => f(l),
+ }
+ }
+}
+
+pub use self::GenericPerspectiveFunction as PerspectiveFunction;
+
+#[derive(
+ Clone,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C, u8)]
+/// A single operation in the list of a `transform` value
+pub enum GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>
+where
+ Angle: Zero,
+ LengthPercentage: Zero + ZeroNoPercent,
+ Number: PartialEq,
+{
+ /// Represents a 2D 2x3 matrix.
+ Matrix(GenericMatrix<Number>),
+ /// Represents a 3D 4x4 matrix.
+ Matrix3D(GenericMatrix3D<Number>),
+ /// A 2D skew.
+ ///
+ /// If the second angle is not provided it is assumed zero.
+ ///
+ /// Syntax can be skew(angle) or skew(angle, angle)
+ #[css(comma, function)]
+ Skew(Angle, #[css(skip_if = "Zero::is_zero")] Angle),
+ /// skewX(angle)
+ #[css(function = "skewX")]
+ SkewX(Angle),
+ /// skewY(angle)
+ #[css(function = "skewY")]
+ SkewY(Angle),
+ /// translate(x, y) or translate(x)
+ #[css(comma, function)]
+ Translate(
+ LengthPercentage,
+ #[css(skip_if = "ZeroNoPercent::is_zero_no_percent")] LengthPercentage,
+ ),
+ /// translateX(x)
+ #[css(function = "translateX")]
+ TranslateX(LengthPercentage),
+ /// translateY(y)
+ #[css(function = "translateY")]
+ TranslateY(LengthPercentage),
+ /// translateZ(z)
+ #[css(function = "translateZ")]
+ TranslateZ(Length),
+ /// translate3d(x, y, z)
+ #[css(comma, function = "translate3d")]
+ Translate3D(LengthPercentage, LengthPercentage, Length),
+ /// A 2D scaling factor.
+ ///
+ /// Syntax can be scale(factor) or scale(factor, factor)
+ #[css(comma, function)]
+ Scale(Number, #[css(contextual_skip_if = "is_same")] Number),
+ /// scaleX(factor)
+ #[css(function = "scaleX")]
+ ScaleX(Number),
+ /// scaleY(factor)
+ #[css(function = "scaleY")]
+ ScaleY(Number),
+ /// scaleZ(factor)
+ #[css(function = "scaleZ")]
+ ScaleZ(Number),
+ /// scale3D(factorX, factorY, factorZ)
+ #[css(comma, function = "scale3d")]
+ Scale3D(Number, Number, Number),
+ /// Describes a 2D Rotation.
+ ///
+ /// In a 3D scene `rotate(angle)` is equivalent to `rotateZ(angle)`.
+ #[css(function)]
+ Rotate(Angle),
+ /// Rotation in 3D space around the x-axis.
+ #[css(function = "rotateX")]
+ RotateX(Angle),
+ /// Rotation in 3D space around the y-axis.
+ #[css(function = "rotateY")]
+ RotateY(Angle),
+ /// Rotation in 3D space around the z-axis.
+ #[css(function = "rotateZ")]
+ RotateZ(Angle),
+ /// Rotation in 3D space.
+ ///
+ /// Generalization of rotateX, rotateY and rotateZ.
+ #[css(comma, function = "rotate3d")]
+ Rotate3D(Number, Number, Number, Angle),
+ /// Specifies a perspective projection matrix.
+ ///
+ /// Part of CSS Transform Module Level 2 and defined at
+ /// [ยง 13.1. 3D Transform Function](https://drafts.csswg.org/css-transforms-2/#funcdef-perspective).
+ ///
+ /// The value must be greater than or equal to zero.
+ #[css(function)]
+ Perspective(GenericPerspectiveFunction<Length>),
+ /// A intermediate type for interpolation of mismatched transform lists.
+ #[allow(missing_docs)]
+ #[css(comma, function = "interpolatematrix")]
+ InterpolateMatrix {
+ from_list: GenericTransform<
+ GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
+ >,
+ to_list: GenericTransform<
+ GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
+ >,
+ progress: computed::Percentage,
+ },
+ /// A intermediate type for accumulation of mismatched transform lists.
+ #[allow(missing_docs)]
+ #[css(comma, function = "accumulatematrix")]
+ AccumulateMatrix {
+ from_list: GenericTransform<
+ GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
+ >,
+ to_list: GenericTransform<
+ GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
+ >,
+ count: Integer,
+ },
+}
+
+pub use self::GenericTransformOperation as TransformOperation;
+
+#[derive(
+ Clone,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C)]
+/// A value of the `transform` property
+pub struct GenericTransform<T>(#[css(if_empty = "none", iterable)] pub crate::OwnedSlice<T>);
+
+pub use self::GenericTransform as Transform;
+
+impl<Angle, Number, Length, Integer, LengthPercentage>
+ TransformOperation<Angle, Number, Length, Integer, LengthPercentage>
+where
+ Angle: Zero,
+ LengthPercentage: Zero + ZeroNoPercent,
+ Number: PartialEq,
+{
+ /// Check if it is any rotate function.
+ pub fn is_rotate(&self) -> bool {
+ use self::TransformOperation::*;
+ matches!(
+ *self,
+ Rotate(..) | Rotate3D(..) | RotateX(..) | RotateY(..) | RotateZ(..)
+ )
+ }
+
+ /// Check if it is any translate function
+ pub fn is_translate(&self) -> bool {
+ use self::TransformOperation::*;
+ match *self {
+ Translate(..) | Translate3D(..) | TranslateX(..) | TranslateY(..) | TranslateZ(..) => {
+ true
+ },
+ _ => false,
+ }
+ }
+
+ /// Check if it is any scale function
+ pub fn is_scale(&self) -> bool {
+ use self::TransformOperation::*;
+ match *self {
+ Scale(..) | Scale3D(..) | ScaleX(..) | ScaleY(..) | ScaleZ(..) => true,
+ _ => false,
+ }
+ }
+}
+
+/// 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<ComputedLength>) -> 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<ComputedLength>) -> 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 SpecifiedLengthPercentage {
+ // 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<ComputedLength>) -> Result<CSSFloat, ()> {
+ use self::SpecifiedLengthPercentage::*;
+ match *self {
+ Length(len) => len.to_computed_pixel_length_without_context(),
+ Calc(ref calc) => calc.to_computed_pixel_length_without_context(),
+ Percentage(..) => Err(()),
+ }
+ }
+}
+
+impl ToAbsoluteLength for ComputedLength {
+ #[inline]
+ fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
+ Ok(self.px())
+ }
+}
+
+impl ToAbsoluteLength for ComputedLengthPercentage {
+ #[inline]
+ fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
+ Ok(self
+ .maybe_percentage_relative_to(containing_len)
+ .ok_or(())?
+ .px())
+ }
+}
+
+/// 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<ComputedLength>>,
+ ) -> Result<Transform3D<f64>, ()>;
+}
+
+/// A little helper to deal with both specified and computed angles.
+pub trait ToRadians {
+ /// Return the radians value as a 64-bit floating point value.
+ fn radians64(&self) -> f64;
+}
+
+impl ToRadians for computed::angle::Angle {
+ #[inline]
+ fn radians64(&self) -> f64 {
+ computed::angle::Angle::radians64(self)
+ }
+}
+
+impl ToRadians for SpecifiedAngle {
+ #[inline]
+ fn radians64(&self) -> f64 {
+ computed::angle::Angle::from_degrees(self.degrees()).radians64()
+ }
+}
+
+impl<Angle, Number, Length, Integer, LoP> ToMatrix
+ for TransformOperation<Angle, Number, Length, Integer, LoP>
+where
+ Angle: Zero + ToRadians + Copy,
+ Number: PartialEq + Copy + Into<f32> + Into<f64>,
+ Length: ToAbsoluteLength,
+ LoP: Zero + ToAbsoluteLength + ZeroNoPercent,
+{
+ #[inline]
+ fn is_3d(&self) -> bool {
+ use self::TransformOperation::*;
+ match *self {
+ Translate3D(..) | TranslateZ(..) | Rotate3D(..) | RotateX(..) | RotateY(..) |
+ RotateZ(..) | Scale3D(..) | ScaleZ(..) | Perspective(..) | Matrix3D(..) => 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<ComputedLength>>,
+ ) -> Result<Transform3D<f64>, ()> {
+ use self::TransformOperation::*;
+
+ 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 = theta.radians64();
+ let (ax, ay, az, theta) =
+ get_normalized_vector_and_angle(ax.into(), ay.into(), az.into(), theta);
+ Transform3D::rotation(
+ ax as f64,
+ ay as f64,
+ az as f64,
+ euclid::Angle::radians(theta),
+ )
+ },
+ RotateX(theta) => {
+ let theta = euclid::Angle::radians(theta.radians64());
+ Transform3D::rotation(1., 0., 0., theta)
+ },
+ RotateY(theta) => {
+ let theta = euclid::Angle::radians(theta.radians64());
+ Transform3D::rotation(0., 1., 0., theta)
+ },
+ RotateZ(theta) | Rotate(theta) => {
+ let theta = euclid::Angle::radians(theta.radians64());
+ Transform3D::rotation(0., 0., 1., theta)
+ },
+ Perspective(ref p) => {
+ let px = match p {
+ PerspectiveFunction::None => f32::INFINITY,
+ PerspectiveFunction::Length(ref p) => p.to_pixel_length(None)?,
+ };
+ create_perspective_matrix(px).cast()
+ },
+ Scale3D(sx, sy, sz) => Transform3D::scale(sx.into(), sy.into(), sz.into()),
+ Scale(sx, sy) => Transform3D::scale(sx.into(), sy.into(), 1.),
+ ScaleX(s) => Transform3D::scale(s.into(), 1., 1.),
+ ScaleY(s) => Transform3D::scale(1., s.into(), 1.),
+ ScaleZ(s) => Transform3D::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::translation(tx, ty, tz.to_pixel_length(None)? as f64)
+ },
+ Translate(ref tx, ref ty) => {
+ let tx = tx.to_pixel_length(reference_width)? as f64;
+ let ty = ty.to_pixel_length(reference_height)? as f64;
+ Transform3D::translation(tx, ty, 0.)
+ },
+ TranslateX(ref t) => {
+ let t = t.to_pixel_length(reference_width)? as f64;
+ Transform3D::translation(t, 0., 0.)
+ },
+ TranslateY(ref t) => {
+ let t = t.to_pixel_length(reference_height)? as f64;
+ Transform3D::translation(0., t, 0.)
+ },
+ TranslateZ(ref z) => Transform3D::translation(0., 0., z.to_pixel_length(None)? as f64),
+ Skew(theta_x, theta_y) => Transform3D::skew(
+ euclid::Angle::radians(theta_x.radians64()),
+ euclid::Angle::radians(theta_y.radians64()),
+ ),
+ SkewX(theta) => Transform3D::skew(
+ euclid::Angle::radians(theta.radians64()),
+ euclid::Angle::radians(0.),
+ ),
+ SkewY(theta) => Transform3D::skew(
+ euclid::Angle::radians(0.),
+ euclid::Angle::radians(theta.radians64()),
+ ),
+ Matrix3D(m) => m.into(),
+ Matrix(m) => m.into(),
+ 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)
+ }
+}
+
+impl<T> Transform<T> {
+ /// `none`
+ pub fn none() -> Self {
+ Transform(Default::default())
+ }
+}
+
+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<ComputedLength>>
+ ) -> Result<(Transform3D<CSSFloat>, bool), ()> {
+ Self::components_to_transform_3d_matrix(&self.0, reference_box)
+ }
+
+ /// Converts a series of components to a 3d matrix.
+ #[cfg_attr(rustfmt, rustfmt_skip)]
+ pub fn components_to_transform_3d_matrix(
+ ops: &[T],
+ reference_box: Option<&Rect<ComputedLength>>,
+ ) -> 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::new(
+ 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),
+ )
+ };
+
+ let (m, is_3d) = Self::components_to_transform_3d_matrix_f64(ops, reference_box)?;
+ Ok((cast_3d_transform(m), is_3d))
+ }
+
+ /// Same as Transform::to_transform_3d_matrix but a f64 version.
+ fn components_to_transform_3d_matrix_f64(
+ ops: &[T],
+ reference_box: Option<&Rect<ComputedLength>>,
+ ) -> Result<(Transform3D<f64>, bool), ()> {
+ // We intentionally use Transform3D<f64> during computation to avoid
+ // error propagation because using f32 to compute triangle functions
+ // (e.g. in 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 ops {
+ let matrix = operation.to_3d_matrix(reference_box)?;
+ contain_3d = contain_3d || operation.is_3d();
+ transform = matrix.then(&transform);
+ }
+
+ Ok((transform, contain_3d))
+ }
+}
+
+/// Return the transform matrix from a perspective length.
+#[inline]
+pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> {
+ if d.is_finite() {
+ Transform3D::perspective(d.max(1.))
+ } else {
+ Transform3D::identity()
+ }
+}
+
+/// 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 crate::values::computed::transform::DirectionVector;
+ use euclid::approxeq::ApproxEq;
+ 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.robust_normalize();
+ (vector.x, vector.y, vector.z, angle)
+ }
+}
+
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C, u8)]
+/// A value of the `Rotate` property
+///
+/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
+pub enum GenericRotate<Number, Angle> {
+ /// 'none'
+ None,
+ /// '<angle>'
+ Rotate(Angle),
+ /// '<number>{3} <angle>'
+ Rotate3D(Number, Number, Number, Angle),
+}
+
+pub use self::GenericRotate as Rotate;
+
+/// A trait to check if the current 3D vector is parallel to the DirectionVector.
+/// This is especially for serialization on Rotate.
+pub trait IsParallelTo {
+ /// Returns true if this is parallel to the vector.
+ fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool;
+}
+
+impl<Number, Angle> ToCss for Rotate<Number, Angle>
+where
+ Number: Copy + ToCss + Zero,
+ Angle: ToCss,
+ (Number, Number, Number): IsParallelTo,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ use crate::values::computed::transform::DirectionVector;
+ match *self {
+ Rotate::None => dest.write_str("none"),
+ Rotate::Rotate(ref angle) => angle.to_css(dest),
+ Rotate::Rotate3D(x, y, z, ref angle) => {
+ // If the axis is parallel with the x or y axes, it must serialize as the
+ // appropriate keyword. If a rotation about the z axis (that is, in 2D) is
+ // specified, the property must serialize as just an <angle>
+ //
+ // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization
+ let v = (x, y, z);
+ let axis = if x.is_zero() && y.is_zero() && z.is_zero() {
+ // The zero length vector is parallel to every other vector, so
+ // is_parallel_to() returns true for it. However, it is definitely different
+ // from x axis, y axis, or z axis, and it's meaningless to perform a rotation
+ // using that direction vector. So we *have* to serialize it using that same
+ // vector - we can't simplify to some theoretically parallel axis-aligned
+ // vector.
+ None
+ } else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) {
+ Some("x ")
+ } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) {
+ Some("y ")
+ } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) {
+ // When we're parallel to the z-axis, we can just serialize the angle.
+ return angle.to_css(dest);
+ } else {
+ None
+ };
+ match axis {
+ Some(a) => dest.write_str(a)?,
+ None => {
+ x.to_css(dest)?;
+ dest.write_char(' ')?;
+ y.to_css(dest)?;
+ dest.write_char(' ')?;
+ z.to_css(dest)?;
+ dest.write_char(' ')?;
+ },
+ }
+ angle.to_css(dest)
+ },
+ }
+ }
+}
+
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C, u8)]
+/// A value of the `Scale` property
+///
+/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
+pub enum GenericScale<Number> {
+ /// 'none'
+ None,
+ /// '<number>{1,3}'
+ Scale(Number, Number, Number),
+}
+
+pub use self::GenericScale as Scale;
+
+impl<Number> ToCss for Scale<Number>
+where
+ Number: ToCss + PartialEq + Copy,
+ f32: From<Number>,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ f32: From<Number>,
+ {
+ match *self {
+ Scale::None => dest.write_str("none"),
+ Scale::Scale(ref x, ref y, ref z) => {
+ x.to_css(dest)?;
+
+ let is_3d = f32::from(*z) != 1.0;
+ if is_3d || x != y {
+ dest.write_char(' ')?;
+ y.to_css(dest)?;
+ }
+
+ if is_3d {
+ dest.write_char(' ')?;
+ z.to_css(dest)?;
+ }
+ Ok(())
+ },
+ }
+ }
+}
+
+#[inline]
+fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero + ZeroNoPercent, Length: Zero>(
+ _: &LengthPercentage,
+ y: &LengthPercentage,
+ z: &Length,
+) -> bool {
+ y.is_zero_no_percent() && z.is_zero()
+}
+
+#[derive(
+ Clone,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ SpecifiedValueInfo,
+ ToAnimatedZero,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(C, u8)]
+/// A value of the `translate` property
+///
+/// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization:
+///
+/// If a 2d translation is specified, the property must serialize with only one
+/// or two values (per usual, if the second value is 0px, the default, it must
+/// be omitted when serializing; however if 0% is the second value, it is included).
+///
+/// If a 3d translation is specified and the value can be expressed as 2d, we treat as 2d and
+/// serialize accoringly. Otherwise, we serialize all three values.
+/// https://github.com/w3c/csswg-drafts/issues/3305
+///
+/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
+pub enum GenericTranslate<LengthPercentage, Length>
+where
+ LengthPercentage: Zero + ZeroNoPercent,
+ Length: Zero,
+{
+ /// 'none'
+ None,
+ /// <length-percentage> [ <length-percentage> <length>? ]?
+ Translate(
+ LengthPercentage,
+ #[css(contextual_skip_if = "y_axis_and_z_axis_are_zero")] LengthPercentage,
+ #[css(skip_if = "Zero::is_zero")] Length,
+ ),
+}
+
+pub use self::GenericTranslate as Translate;
+
+#[allow(missing_docs)]
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ Parse,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum TransformStyle {
+ Flat,
+ #[css(keyword = "preserve-3d")]
+ Preserve3d,
+}