Skip to main content

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