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