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