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