diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /servo/components/style/values/generics/transform.rs | |
parent | Initial commit. (diff) | |
download | firefox-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.rs | 879 |
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, +} |