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