style/values/generics/
transform.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Generic types for CSS values that are related to transformations.
6
7use crate::values::computed::length::Length as ComputedLength;
8use crate::values::computed::length::LengthPercentage as ComputedLengthPercentage;
9use crate::values::specified::angle::Angle as SpecifiedAngle;
10use crate::values::specified::length::Length as SpecifiedLength;
11use crate::values::specified::length::LengthPercentage as SpecifiedLengthPercentage;
12use crate::values::{computed, CSSFloat};
13use crate::{Zero, ZeroNoPercent};
14use euclid::default::{Rect, Transform3D};
15use std::fmt::{self, Write};
16use std::ops::Neg;
17use style_traits::{CssWriter, ToCss};
18
19/// A generic 2D transformation matrix.
20#[allow(missing_docs)]
21#[derive(
22    Clone,
23    Copy,
24    Debug,
25    Deserialize,
26    MallocSizeOf,
27    PartialEq,
28    Serialize,
29    SpecifiedValueInfo,
30    ToAnimatedValue,
31    ToComputedValue,
32    ToCss,
33    ToResolvedValue,
34    ToShmem,
35)]
36#[css(comma, function = "matrix")]
37#[repr(C)]
38pub struct GenericMatrix<T> {
39    pub a: T,
40    pub b: T,
41    pub c: T,
42    pub d: T,
43    pub e: T,
44    pub f: T,
45}
46
47pub use self::GenericMatrix as Matrix;
48
49#[allow(missing_docs)]
50#[cfg_attr(rustfmt, rustfmt_skip)]
51#[derive(
52    Clone,
53    Copy,
54    Debug,
55    Deserialize,
56    MallocSizeOf,
57    PartialEq,
58    Serialize,
59    SpecifiedValueInfo,
60    ToAnimatedValue,
61    ToComputedValue,
62    ToCss,
63    ToResolvedValue,
64    ToShmem,
65)]
66#[css(comma, function = "matrix3d")]
67#[repr(C)]
68pub struct GenericMatrix3D<T> {
69    pub m11: T, pub m12: T, pub m13: T, pub m14: T,
70    pub m21: T, pub m22: T, pub m23: T, pub m24: T,
71    pub m31: T, pub m32: T, pub m33: T, pub m34: T,
72    pub m41: T, pub m42: T, pub m43: T, pub m44: T,
73}
74
75pub use self::GenericMatrix3D as Matrix3D;
76
77#[cfg_attr(rustfmt, rustfmt_skip)]
78impl<T: Into<f64>> From<Matrix<T>> for Transform3D<f64> {
79    #[inline]
80    fn from(m: Matrix<T>) -> Self {
81        Transform3D::new(
82            m.a.into(), m.b.into(), 0.0, 0.0,
83            m.c.into(), m.d.into(), 0.0, 0.0,
84            0.0,        0.0,        1.0, 0.0,
85            m.e.into(), m.f.into(), 0.0, 1.0,
86        )
87    }
88}
89
90#[cfg_attr(rustfmt, rustfmt_skip)]
91impl<T: Into<f64>> From<Matrix3D<T>> for Transform3D<f64> {
92    #[inline]
93    fn from(m: Matrix3D<T>) -> Self {
94        Transform3D::new(
95            m.m11.into(), m.m12.into(), m.m13.into(), m.m14.into(),
96            m.m21.into(), m.m22.into(), m.m23.into(), m.m24.into(),
97            m.m31.into(), m.m32.into(), m.m33.into(), m.m34.into(),
98            m.m41.into(), m.m42.into(), m.m43.into(), m.m44.into(),
99        )
100    }
101}
102
103/// A generic transform origin.
104#[derive(
105    Animate,
106    Clone,
107    ComputeSquaredDistance,
108    Copy,
109    Debug,
110    MallocSizeOf,
111    PartialEq,
112    SpecifiedValueInfo,
113    ToAnimatedValue,
114    ToAnimatedZero,
115    ToComputedValue,
116    ToCss,
117    ToResolvedValue,
118    ToShmem,
119    ToTyped,
120)]
121#[repr(C)]
122pub struct GenericTransformOrigin<H, V, Depth> {
123    /// The horizontal origin.
124    pub horizontal: H,
125    /// The vertical origin.
126    pub vertical: V,
127    /// The depth.
128    pub depth: Depth,
129}
130
131pub use self::GenericTransformOrigin as TransformOrigin;
132
133impl<H, V, D> TransformOrigin<H, V, D> {
134    /// Returns a new transform origin.
135    pub fn new(horizontal: H, vertical: V, depth: D) -> Self {
136        Self {
137            horizontal,
138            vertical,
139            depth,
140        }
141    }
142}
143
144fn is_same<N: PartialEq>(x: &N, y: &N) -> bool {
145    x == y
146}
147
148/// A value for the `perspective()` transform function, which is either a
149/// non-negative `<length>` or `none`.
150#[derive(
151    Clone,
152    Debug,
153    Deserialize,
154    MallocSizeOf,
155    PartialEq,
156    Serialize,
157    SpecifiedValueInfo,
158    ToAnimatedValue,
159    ToComputedValue,
160    ToCss,
161    ToResolvedValue,
162    ToShmem,
163)]
164#[repr(C, u8)]
165pub enum GenericPerspectiveFunction<L> {
166    /// `none`
167    None,
168    /// A `<length>`.
169    Length(L),
170}
171
172impl<L> GenericPerspectiveFunction<L> {
173    /// Returns `f32::INFINITY` or the result of a function on the length value.
174    pub fn infinity_or(&self, f: impl FnOnce(&L) -> f32) -> f32 {
175        match *self {
176            Self::None => f32::INFINITY,
177            Self::Length(ref l) => f(l),
178        }
179    }
180}
181
182pub use self::GenericPerspectiveFunction as PerspectiveFunction;
183
184#[derive(
185    Clone,
186    Debug,
187    Deserialize,
188    MallocSizeOf,
189    PartialEq,
190    Serialize,
191    SpecifiedValueInfo,
192    ToAnimatedValue,
193    ToComputedValue,
194    ToCss,
195    ToResolvedValue,
196    ToShmem,
197)]
198#[repr(C, u8)]
199/// A single operation in the list of a `transform` value
200pub enum GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>
201where
202    Angle: Zero,
203    LengthPercentage: Zero + ZeroNoPercent,
204    Number: PartialEq,
205{
206    /// Represents a 2D 2x3 matrix.
207    Matrix(GenericMatrix<Number>),
208    /// Represents a 3D 4x4 matrix.
209    Matrix3D(GenericMatrix3D<Number>),
210    /// A 2D skew.
211    ///
212    /// If the second angle is not provided it is assumed zero.
213    ///
214    /// Syntax can be skew(angle) or skew(angle, angle)
215    #[css(comma, function)]
216    Skew(Angle, #[css(skip_if = "Zero::is_zero")] Angle),
217    /// skewX(angle)
218    #[css(function = "skewX")]
219    SkewX(Angle),
220    /// skewY(angle)
221    #[css(function = "skewY")]
222    SkewY(Angle),
223    /// translate(x, y) or translate(x)
224    #[css(comma, function)]
225    Translate(
226        LengthPercentage,
227        #[css(skip_if = "ZeroNoPercent::is_zero_no_percent")] LengthPercentage,
228    ),
229    /// translateX(x)
230    #[css(function = "translateX")]
231    TranslateX(LengthPercentage),
232    /// translateY(y)
233    #[css(function = "translateY")]
234    TranslateY(LengthPercentage),
235    /// translateZ(z)
236    #[css(function = "translateZ")]
237    TranslateZ(Length),
238    /// translate3d(x, y, z)
239    #[css(comma, function = "translate3d")]
240    Translate3D(LengthPercentage, LengthPercentage, Length),
241    /// A 2D scaling factor.
242    ///
243    /// Syntax can be scale(factor) or scale(factor, factor)
244    #[css(comma, function)]
245    Scale(Number, #[css(contextual_skip_if = "is_same")] Number),
246    /// scaleX(factor)
247    #[css(function = "scaleX")]
248    ScaleX(Number),
249    /// scaleY(factor)
250    #[css(function = "scaleY")]
251    ScaleY(Number),
252    /// scaleZ(factor)
253    #[css(function = "scaleZ")]
254    ScaleZ(Number),
255    /// scale3D(factorX, factorY, factorZ)
256    #[css(comma, function = "scale3d")]
257    Scale3D(Number, Number, Number),
258    /// Describes a 2D Rotation.
259    ///
260    /// In a 3D scene `rotate(angle)` is equivalent to `rotateZ(angle)`.
261    #[css(function)]
262    Rotate(Angle),
263    /// Rotation in 3D space around the x-axis.
264    #[css(function = "rotateX")]
265    RotateX(Angle),
266    /// Rotation in 3D space around the y-axis.
267    #[css(function = "rotateY")]
268    RotateY(Angle),
269    /// Rotation in 3D space around the z-axis.
270    #[css(function = "rotateZ")]
271    RotateZ(Angle),
272    /// Rotation in 3D space.
273    ///
274    /// Generalization of rotateX, rotateY and rotateZ.
275    #[css(comma, function = "rotate3d")]
276    Rotate3D(Number, Number, Number, Angle),
277    /// Specifies a perspective projection matrix.
278    ///
279    /// Part of CSS Transform Module Level 2 and defined at
280    /// [ยง 13.1. 3D Transform Function](https://drafts.csswg.org/css-transforms-2/#funcdef-perspective).
281    ///
282    /// The value must be greater than or equal to zero.
283    #[css(function)]
284    Perspective(GenericPerspectiveFunction<Length>),
285    /// A intermediate type for interpolation of mismatched transform lists.
286    #[allow(missing_docs)]
287    #[css(comma, function = "interpolatematrix")]
288    InterpolateMatrix {
289        from_list: GenericTransform<
290            GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
291        >,
292        to_list: GenericTransform<
293            GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
294        >,
295        progress: computed::Percentage,
296    },
297    /// A intermediate type for accumulation of mismatched transform lists.
298    #[allow(missing_docs)]
299    #[css(comma, function = "accumulatematrix")]
300    AccumulateMatrix {
301        from_list: GenericTransform<
302            GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
303        >,
304        to_list: GenericTransform<
305            GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>,
306        >,
307        count: Integer,
308    },
309}
310
311pub use self::GenericTransformOperation as TransformOperation;
312
313#[derive(
314    Clone,
315    Debug,
316    Deserialize,
317    MallocSizeOf,
318    PartialEq,
319    Serialize,
320    SpecifiedValueInfo,
321    ToAnimatedValue,
322    ToComputedValue,
323    ToCss,
324    ToResolvedValue,
325    ToShmem,
326    ToTyped,
327)]
328#[repr(C)]
329/// A value of the `transform` property
330pub struct GenericTransform<T>(#[css(if_empty = "none", iterable)] pub crate::OwnedSlice<T>);
331
332pub use self::GenericTransform as Transform;
333
334impl<Angle, Number, Length, Integer, LengthPercentage>
335    TransformOperation<Angle, Number, Length, Integer, LengthPercentage>
336where
337    Angle: Zero,
338    LengthPercentage: Zero + ZeroNoPercent,
339    Number: PartialEq,
340{
341    /// Check if it is any rotate function.
342    pub fn is_rotate(&self) -> bool {
343        use self::TransformOperation::*;
344        matches!(
345            *self,
346            Rotate(..) | Rotate3D(..) | RotateX(..) | RotateY(..) | RotateZ(..)
347        )
348    }
349
350    /// Check if it is any translate function
351    pub fn is_translate(&self) -> bool {
352        use self::TransformOperation::*;
353        match *self {
354            Translate(..) | Translate3D(..) | TranslateX(..) | TranslateY(..) | TranslateZ(..) => {
355                true
356            },
357            _ => false,
358        }
359    }
360
361    /// Check if it is any scale function
362    pub fn is_scale(&self) -> bool {
363        use self::TransformOperation::*;
364        match *self {
365            Scale(..) | Scale3D(..) | ScaleX(..) | ScaleY(..) | ScaleZ(..) => true,
366            _ => false,
367        }
368    }
369}
370
371/// Convert a length type into the absolute lengths.
372pub trait ToAbsoluteLength {
373    /// Returns the absolute length as pixel value.
374    fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()>;
375}
376
377impl ToAbsoluteLength for SpecifiedLength {
378    // This returns Err(()) if there is any relative length or percentage. We use this when
379    // parsing a transform list of DOMMatrix because we want to return a DOM Exception
380    // if there is relative length.
381    #[inline]
382    fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
383        match *self {
384            SpecifiedLength::NoCalc(len) => len.to_computed_pixel_length_without_context(),
385            SpecifiedLength::Calc(ref calc) => calc.to_computed_pixel_length_without_context(),
386        }
387    }
388}
389
390impl ToAbsoluteLength for SpecifiedLengthPercentage {
391    // This returns Err(()) if there is any relative length or percentage. We use this when
392    // parsing a transform list of DOMMatrix because we want to return a DOM Exception
393    // if there is relative length.
394    #[inline]
395    fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
396        use self::SpecifiedLengthPercentage::*;
397        match *self {
398            Length(len) => len.to_computed_pixel_length_without_context(),
399            Calc(ref calc) => calc.to_computed_pixel_length_without_context(),
400            Percentage(..) => Err(()),
401        }
402    }
403}
404
405impl ToAbsoluteLength for ComputedLength {
406    #[inline]
407    fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
408        Ok(self.px())
409    }
410}
411
412impl ToAbsoluteLength for ComputedLengthPercentage {
413    #[inline]
414    fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> {
415        Ok(self
416            .maybe_percentage_relative_to(containing_len)
417            .ok_or(())?
418            .px())
419    }
420}
421
422/// Support the conversion to a 3d matrix.
423pub trait ToMatrix {
424    /// Check if it is a 3d transform function.
425    fn is_3d(&self) -> bool;
426
427    /// Return the equivalent 3d matrix.
428    fn to_3d_matrix(
429        &self,
430        reference_box: Option<&Rect<ComputedLength>>,
431    ) -> Result<Transform3D<f64>, ()>;
432}
433
434/// A little helper to deal with both specified and computed angles.
435pub trait ToRadians {
436    /// Return the radians value as a 64-bit floating point value.
437    fn radians64(&self) -> f64;
438}
439
440impl ToRadians for computed::angle::Angle {
441    #[inline]
442    fn radians64(&self) -> f64 {
443        computed::angle::Angle::radians64(self)
444    }
445}
446
447impl ToRadians for SpecifiedAngle {
448    #[inline]
449    fn radians64(&self) -> f64 {
450        computed::angle::Angle::from_degrees(self.degrees()).radians64()
451    }
452}
453
454impl<Angle, Number, Length, Integer, LoP> ToMatrix
455    for TransformOperation<Angle, Number, Length, Integer, LoP>
456where
457    Angle: Zero + ToRadians + Copy,
458    Number: PartialEq + Copy + Into<f32> + Into<f64>,
459    Length: ToAbsoluteLength,
460    LoP: Zero + ToAbsoluteLength + ZeroNoPercent,
461{
462    #[inline]
463    fn is_3d(&self) -> bool {
464        use self::TransformOperation::*;
465        match *self {
466            Translate3D(..) | TranslateZ(..) | Rotate3D(..) | RotateX(..) | RotateY(..)
467            | RotateZ(..) | Scale3D(..) | ScaleZ(..) | Perspective(..) | Matrix3D(..) => true,
468            _ => false,
469        }
470    }
471
472    /// If |reference_box| is None, we will drop the percent part from translate because
473    /// we cannot resolve it without the layout info, for computed TransformOperation.
474    /// However, for specified TransformOperation, we will return Err(()) if there is any relative
475    /// lengths because the only caller, DOMMatrix, doesn't accept relative lengths.
476    #[inline]
477    fn to_3d_matrix(
478        &self,
479        reference_box: Option<&Rect<ComputedLength>>,
480    ) -> Result<Transform3D<f64>, ()> {
481        use self::TransformOperation::*;
482
483        let reference_width = reference_box.map(|v| v.size.width);
484        let reference_height = reference_box.map(|v| v.size.height);
485        let matrix = match *self {
486            Rotate3D(ax, ay, az, theta) => {
487                let theta = theta.radians64();
488                let (ax, ay, az, theta) =
489                    get_normalized_vector_and_angle(ax.into(), ay.into(), az.into(), theta);
490                Transform3D::rotation(
491                    ax as f64,
492                    ay as f64,
493                    az as f64,
494                    euclid::Angle::radians(theta),
495                )
496            },
497            RotateX(theta) => {
498                let theta = euclid::Angle::radians(theta.radians64());
499                Transform3D::rotation(1., 0., 0., theta)
500            },
501            RotateY(theta) => {
502                let theta = euclid::Angle::radians(theta.radians64());
503                Transform3D::rotation(0., 1., 0., theta)
504            },
505            RotateZ(theta) | Rotate(theta) => {
506                let theta = euclid::Angle::radians(theta.radians64());
507                Transform3D::rotation(0., 0., 1., theta)
508            },
509            Perspective(ref p) => {
510                let px = match p {
511                    PerspectiveFunction::None => f32::INFINITY,
512                    PerspectiveFunction::Length(ref p) => p.to_pixel_length(None)?,
513                };
514                create_perspective_matrix(px).cast()
515            },
516            Scale3D(sx, sy, sz) => Transform3D::scale(sx.into(), sy.into(), sz.into()),
517            Scale(sx, sy) => Transform3D::scale(sx.into(), sy.into(), 1.),
518            ScaleX(s) => Transform3D::scale(s.into(), 1., 1.),
519            ScaleY(s) => Transform3D::scale(1., s.into(), 1.),
520            ScaleZ(s) => Transform3D::scale(1., 1., s.into()),
521            Translate3D(ref tx, ref ty, ref tz) => {
522                let tx = tx.to_pixel_length(reference_width)? as f64;
523                let ty = ty.to_pixel_length(reference_height)? as f64;
524                Transform3D::translation(tx, ty, tz.to_pixel_length(None)? as f64)
525            },
526            Translate(ref tx, ref ty) => {
527                let tx = tx.to_pixel_length(reference_width)? as f64;
528                let ty = ty.to_pixel_length(reference_height)? as f64;
529                Transform3D::translation(tx, ty, 0.)
530            },
531            TranslateX(ref t) => {
532                let t = t.to_pixel_length(reference_width)? as f64;
533                Transform3D::translation(t, 0., 0.)
534            },
535            TranslateY(ref t) => {
536                let t = t.to_pixel_length(reference_height)? as f64;
537                Transform3D::translation(0., t, 0.)
538            },
539            TranslateZ(ref z) => Transform3D::translation(0., 0., z.to_pixel_length(None)? as f64),
540            Skew(theta_x, theta_y) => Transform3D::skew(
541                euclid::Angle::radians(theta_x.radians64()),
542                euclid::Angle::radians(theta_y.radians64()),
543            ),
544            SkewX(theta) => Transform3D::skew(
545                euclid::Angle::radians(theta.radians64()),
546                euclid::Angle::radians(0.),
547            ),
548            SkewY(theta) => Transform3D::skew(
549                euclid::Angle::radians(0.),
550                euclid::Angle::radians(theta.radians64()),
551            ),
552            Matrix3D(m) => m.into(),
553            Matrix(m) => m.into(),
554            InterpolateMatrix { .. } | AccumulateMatrix { .. } => {
555                // TODO: Convert InterpolateMatrix/AccumulateMatrix into a valid Transform3D by
556                // the reference box and do interpolation on these two Transform3D matrices.
557                // Both Gecko and Servo don't support this for computing distance, and Servo
558                // doesn't support animations on InterpolateMatrix/AccumulateMatrix, so
559                // return an identity matrix.
560                // Note: DOMMatrix doesn't go into this arm.
561                Transform3D::identity()
562            },
563        };
564        Ok(matrix)
565    }
566}
567
568impl<T> Transform<T> {
569    /// `none`
570    pub fn none() -> Self {
571        Transform(Default::default())
572    }
573}
574
575impl<T: ToMatrix> Transform<T> {
576    /// Return the equivalent 3d matrix of this transform list.
577    ///
578    /// We return a pair: the first one is the transform matrix, and the second one
579    /// indicates if there is any 3d transform function in this transform list.
580    #[cfg_attr(rustfmt, rustfmt_skip)]
581    pub fn to_transform_3d_matrix(
582        &self,
583        reference_box: Option<&Rect<ComputedLength>>
584    ) -> Result<(Transform3D<CSSFloat>, bool), ()> {
585        Self::components_to_transform_3d_matrix(&self.0, reference_box)
586    }
587
588    /// Converts a series of components to a 3d matrix.
589    #[cfg_attr(rustfmt, rustfmt_skip)]
590    pub fn components_to_transform_3d_matrix(
591        ops: &[T],
592        reference_box: Option<&Rect<ComputedLength>>,
593    ) -> Result<(Transform3D<CSSFloat>, bool), ()> {
594        let cast_3d_transform = |m: Transform3D<f64>| -> Transform3D<CSSFloat> {
595            use std::{f32, f64};
596            let cast = |v: f64| v.min(f32::MAX as f64).max(f32::MIN as f64) as f32;
597            Transform3D::new(
598                cast(m.m11), cast(m.m12), cast(m.m13), cast(m.m14),
599                cast(m.m21), cast(m.m22), cast(m.m23), cast(m.m24),
600                cast(m.m31), cast(m.m32), cast(m.m33), cast(m.m34),
601                cast(m.m41), cast(m.m42), cast(m.m43), cast(m.m44),
602            )
603        };
604
605        let (m, is_3d) = Self::components_to_transform_3d_matrix_f64(ops, reference_box)?;
606        Ok((cast_3d_transform(m), is_3d))
607    }
608
609    /// Same as Transform::to_transform_3d_matrix but a f64 version.
610    pub fn to_transform_3d_matrix_f64(
611        &self,
612        reference_box: Option<&Rect<ComputedLength>>,
613    ) -> Result<(Transform3D<f64>, bool), ()> {
614        Self::components_to_transform_3d_matrix_f64(&self.0, reference_box)
615    }
616
617    /// Same as Transform::components_to_transform_3d_matrix but a f64 version.
618    fn components_to_transform_3d_matrix_f64(
619        ops: &[T],
620        reference_box: Option<&Rect<ComputedLength>>,
621    ) -> Result<(Transform3D<f64>, bool), ()> {
622        // We intentionally use Transform3D<f64> during computation to avoid
623        // error propagation because using f32 to compute triangle functions
624        // (e.g. in rotation()) is not accurate enough. In Gecko, we also use
625        // "double" to compute the triangle functions. Therefore, let's use
626        // Transform3D<f64> during matrix computation and cast it into f32 in
627        // the end.
628        let mut transform = Transform3D::<f64>::identity();
629        let mut contain_3d = false;
630
631        for operation in ops {
632            let matrix = operation.to_3d_matrix(reference_box)?;
633            contain_3d = contain_3d || operation.is_3d();
634            transform = matrix.then(&transform);
635        }
636
637        Ok((transform, contain_3d))
638    }
639}
640
641/// Return the transform matrix from a perspective length.
642#[inline]
643pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> {
644    if d.is_finite() {
645        Transform3D::perspective(d.max(1.))
646    } else {
647        Transform3D::identity()
648    }
649}
650
651/// Return the normalized direction vector and its angle for Rotate3D.
652pub fn get_normalized_vector_and_angle<T: Zero>(
653    x: CSSFloat,
654    y: CSSFloat,
655    z: CSSFloat,
656    angle: T,
657) -> (CSSFloat, CSSFloat, CSSFloat, T) {
658    use crate::values::computed::transform::DirectionVector;
659    use euclid::approxeq::ApproxEq;
660    let vector = DirectionVector::new(x, y, z);
661    if vector.square_length().approx_eq(&f32::zero()) {
662        // https://www.w3.org/TR/css-transforms-1/#funcdef-rotate3d
663        // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the
664        // rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)).
665        (0., 0., 1., T::zero())
666    } else {
667        let vector = vector.robust_normalize();
668        (vector.x, vector.y, vector.z, angle)
669    }
670}
671
672#[derive(
673    Clone,
674    Copy,
675    Debug,
676    Deserialize,
677    MallocSizeOf,
678    PartialEq,
679    Serialize,
680    SpecifiedValueInfo,
681    ToAnimatedValue,
682    ToAnimatedZero,
683    ToComputedValue,
684    ToResolvedValue,
685    ToShmem,
686    ToTyped,
687)]
688#[repr(C, u8)]
689/// A value of the `Rotate` property
690///
691/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
692pub enum GenericRotate<Number, Angle> {
693    /// 'none'
694    None,
695    /// '<angle>'
696    Rotate(Angle),
697    /// '<number>{3} <angle>'
698    Rotate3D(Number, Number, Number, Angle),
699}
700
701pub use self::GenericRotate as Rotate;
702
703/// A trait to check if the current 3D vector is parallel to the DirectionVector.
704/// This is especially for serialization on Rotate.
705pub trait IsParallelTo {
706    /// Returns true if this is parallel to the vector.
707    fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool;
708}
709
710impl<Number, Angle> ToCss for Rotate<Number, Angle>
711where
712    Number: Copy + PartialOrd + ToCss + Zero,
713    Angle: Copy + Neg<Output = Angle> + ToCss + Zero,
714    (Number, Number, Number): IsParallelTo,
715{
716    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
717    where
718        W: fmt::Write,
719    {
720        use crate::values::computed::transform::DirectionVector;
721        match *self {
722            Rotate::None => dest.write_str("none"),
723            Rotate::Rotate(ref angle) => angle.to_css(dest),
724            Rotate::Rotate3D(x, y, z, angle) => {
725                // If the axis is parallel with the x or y axes, it must serialize as the
726                // appropriate keyword. If a rotation about the z axis (that is, in 2D) is
727                // specified, the property must serialize as just an <angle>.
728                //
729                // Note that if the axis is parallel to x/y/z but pointing in the opposite
730                // direction, we need to negate the angle to maintain the correct meaning.
731                //
732                // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization
733                let v = (x, y, z);
734                let (axis, angle) = if x.is_zero() && y.is_zero() && z.is_zero() {
735                    // The zero length vector is parallel to every other vector, so
736                    // is_parallel_to() returns true for it. However, it is definitely different
737                    // from x axis, y axis, or z axis, and it's meaningless to perform a rotation
738                    // using that direction vector. So we *have* to serialize it using that same
739                    // vector - we can't simplify to some theoretically parallel axis-aligned
740                    // vector.
741                    (None, angle)
742                } else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) {
743                    (
744                        Some("x "),
745                        if v.0 < Number::zero() { -angle } else { angle },
746                    )
747                } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) {
748                    (
749                        Some("y "),
750                        if v.1 < Number::zero() { -angle } else { angle },
751                    )
752                } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) {
753                    // When we're parallel to the z-axis, we can just serialize the angle.
754                    let angle = if v.2 < Number::zero() { -angle } else { angle };
755                    return angle.to_css(dest);
756                } else {
757                    (None, angle)
758                };
759                match axis {
760                    Some(a) => dest.write_str(a)?,
761                    None => {
762                        x.to_css(dest)?;
763                        dest.write_char(' ')?;
764                        y.to_css(dest)?;
765                        dest.write_char(' ')?;
766                        z.to_css(dest)?;
767                        dest.write_char(' ')?;
768                    },
769                }
770                angle.to_css(dest)
771            },
772        }
773    }
774}
775
776#[derive(
777    Clone,
778    Copy,
779    Debug,
780    Deserialize,
781    MallocSizeOf,
782    PartialEq,
783    Serialize,
784    SpecifiedValueInfo,
785    ToAnimatedValue,
786    ToAnimatedZero,
787    ToComputedValue,
788    ToResolvedValue,
789    ToShmem,
790    ToTyped,
791)]
792#[repr(C, u8)]
793/// A value of the `Scale` property
794///
795/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
796pub enum GenericScale<Number> {
797    /// 'none'
798    None,
799    /// '<number>{1,3}'
800    Scale(Number, Number, Number),
801}
802
803pub use self::GenericScale as Scale;
804
805impl<Number> ToCss for Scale<Number>
806where
807    Number: ToCss + PartialEq + Copy,
808    f32: From<Number>,
809{
810    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
811    where
812        W: fmt::Write,
813        f32: From<Number>,
814    {
815        match *self {
816            Scale::None => dest.write_str("none"),
817            Scale::Scale(ref x, ref y, ref z) => {
818                x.to_css(dest)?;
819
820                let is_3d = f32::from(*z) != 1.0;
821                if is_3d || x != y {
822                    dest.write_char(' ')?;
823                    y.to_css(dest)?;
824                }
825
826                if is_3d {
827                    dest.write_char(' ')?;
828                    z.to_css(dest)?;
829                }
830                Ok(())
831            },
832        }
833    }
834}
835
836#[inline]
837fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero + ZeroNoPercent, Length: Zero>(
838    _: &LengthPercentage,
839    y: &LengthPercentage,
840    z: &Length,
841) -> bool {
842    y.is_zero_no_percent() && z.is_zero()
843}
844
845#[derive(
846    Clone,
847    Debug,
848    Deserialize,
849    MallocSizeOf,
850    PartialEq,
851    Serialize,
852    SpecifiedValueInfo,
853    ToAnimatedValue,
854    ToAnimatedZero,
855    ToComputedValue,
856    ToCss,
857    ToResolvedValue,
858    ToShmem,
859    ToTyped,
860)]
861#[repr(C, u8)]
862/// A value of the `translate` property
863///
864/// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization:
865///
866/// If a 2d translation is specified, the property must serialize with only one
867/// or two values (per usual, if the second value is 0px, the default, it must
868/// be omitted when serializing; however if 0% is the second value, it is included).
869///
870/// If a 3d translation is specified and the value can be expressed as 2d, we treat as 2d and
871/// serialize accoringly. Otherwise, we serialize all three values.
872/// https://github.com/w3c/csswg-drafts/issues/3305
873///
874/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
875pub enum GenericTranslate<LengthPercentage, Length>
876where
877    LengthPercentage: Zero + ZeroNoPercent,
878    Length: Zero,
879{
880    /// 'none'
881    None,
882    /// <length-percentage> [ <length-percentage> <length>? ]?
883    Translate(
884        LengthPercentage,
885        #[css(contextual_skip_if = "y_axis_and_z_axis_are_zero")] LengthPercentage,
886        #[css(skip_if = "Zero::is_zero")] Length,
887    ),
888}
889
890pub use self::GenericTranslate as Translate;
891
892#[allow(missing_docs)]
893#[derive(
894    Clone,
895    Copy,
896    Debug,
897    MallocSizeOf,
898    Parse,
899    PartialEq,
900    SpecifiedValueInfo,
901    ToComputedValue,
902    ToCss,
903    ToResolvedValue,
904    ToShmem,
905    ToTyped,
906)]
907#[repr(u8)]
908pub enum TransformStyle {
909    Flat,
910    #[css(keyword = "preserve-3d")]
911    Preserve3d,
912}