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#[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#[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#[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                #[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                #[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(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    #[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    #[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            let cast = |v: f64| v.min(f32::MAX as f64).max(f32::MIN as f64) as f32;
831            Transform3D::new(
832                cast(m.m11), cast(m.m12), cast(m.m13), cast(m.m14),
833                cast(m.m21), cast(m.m22), cast(m.m23), cast(m.m24),
834                cast(m.m31), cast(m.m32), cast(m.m33), cast(m.m34),
835                cast(m.m41), cast(m.m42), cast(m.m43), cast(m.m44),
836            )
837        };
838
839        let (m, is_3d) = Self::components_to_transform_3d_matrix_f64(ops, reference_box)?;
840        Ok((cast_3d_transform(m), is_3d))
841    }
842
843    /// Same as Transform::to_transform_3d_matrix but a f64 version.
844    pub fn to_transform_3d_matrix_f64(
845        &self,
846        reference_box: Option<&Rect<ComputedLength>>,
847    ) -> Result<(Transform3D<f64>, bool), ()> {
848        Self::components_to_transform_3d_matrix_f64(&self.0, reference_box)
849    }
850
851    /// Same as Transform::components_to_transform_3d_matrix but a f64 version.
852    fn components_to_transform_3d_matrix_f64(
853        ops: &[T],
854        reference_box: Option<&Rect<ComputedLength>>,
855    ) -> Result<(Transform3D<f64>, bool), ()> {
856        // We intentionally use Transform3D<f64> during computation to avoid
857        // error propagation because using f32 to compute triangle functions
858        // (e.g. in rotation()) is not accurate enough. In Gecko, we also use
859        // "double" to compute the triangle functions. Therefore, let's use
860        // Transform3D<f64> during matrix computation and cast it into f32 in
861        // the end.
862        let mut transform = Transform3D::<f64>::identity();
863        let mut contain_3d = false;
864
865        for operation in ops {
866            let matrix = operation.to_3d_matrix(reference_box)?;
867            contain_3d = contain_3d || operation.is_3d();
868            transform = matrix.then(&transform);
869        }
870
871        Ok((transform, contain_3d))
872    }
873}
874
875/// Return the transform matrix from a perspective length.
876#[inline]
877pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> {
878    if d.is_finite() {
879        Transform3D::perspective(d.max(1.))
880    } else {
881        Transform3D::identity()
882    }
883}
884
885/// Return the normalized direction vector and its angle for Rotate3D.
886pub fn get_normalized_vector_and_angle<T: Zero>(
887    x: CSSFloat,
888    y: CSSFloat,
889    z: CSSFloat,
890    angle: T,
891) -> (CSSFloat, CSSFloat, CSSFloat, T) {
892    use crate::values::computed::transform::DirectionVector;
893    use euclid::approxeq::ApproxEq;
894    let vector = DirectionVector::new(x, y, z);
895    if vector.square_length().approx_eq(&f32::zero()) {
896        // https://www.w3.org/TR/css-transforms-1/#funcdef-rotate3d
897        // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the
898        // rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)).
899        (0., 0., 1., T::zero())
900    } else {
901        let vector = vector.robust_normalize();
902        (vector.x, vector.y, vector.z, angle)
903    }
904}
905
906#[derive(
907    Clone,
908    Copy,
909    Debug,
910    Deserialize,
911    MallocSizeOf,
912    PartialEq,
913    Serialize,
914    SpecifiedValueInfo,
915    ToAnimatedValue,
916    ToAnimatedZero,
917    ToComputedValue,
918    ToResolvedValue,
919    ToShmem,
920    ToTyped,
921)]
922#[repr(C, u8)]
923#[typed(todo_derive_fields)]
924/// A value of the `Rotate` property
925///
926/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
927pub enum GenericRotate<Number, Angle> {
928    /// 'none'
929    None,
930    /// '<angle>'
931    Rotate(Angle),
932    /// '<number>{3} <angle>'
933    Rotate3D(Number, Number, Number, Angle),
934}
935
936pub use self::GenericRotate as Rotate;
937
938/// A trait to check if the current 3D vector is parallel to the DirectionVector.
939/// This is especially for serialization on Rotate.
940pub trait IsParallelTo {
941    /// Returns true if this is parallel to the vector.
942    fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool;
943}
944
945impl<Number, Angle> ToCss for Rotate<Number, Angle>
946where
947    Number: Clone + PartialOrd + ToCss + Zero,
948    Angle: Clone + Neg<Output = Angle> + ToCss + Zero,
949    (Number, Number, Number): IsParallelTo,
950{
951    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
952    where
953        W: fmt::Write,
954    {
955        use crate::values::computed::transform::DirectionVector;
956        match *self {
957            Rotate::None => dest.write_str("none"),
958            Rotate::Rotate(ref angle) => angle.to_css(dest),
959            Rotate::Rotate3D(ref x, ref y, ref z, ref angle) => {
960                // If the axis is parallel with the x or y axes, it must serialize as the
961                // appropriate keyword. If a rotation about the z axis (that is, in 2D) is
962                // specified, the property must serialize as just an <angle>.
963                //
964                // Note that if the axis is parallel to x/y/z but pointing in the opposite
965                // direction, we need to negate the angle to maintain the correct meaning.
966                //
967                // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization
968                let v = (x.clone(), y.clone(), z.clone());
969                let (axis, angle) = if v.0.is_zero() && v.1.is_zero() && v.2.is_zero() {
970                    // The zero length vector is parallel to every other vector, so
971                    // is_parallel_to() returns true for it. However, it is definitely different
972                    // from x axis, y axis, or z axis, and it's meaningless to perform a rotation
973                    // using that direction vector. So we *have* to serialize it using that same
974                    // vector - we can't simplify to some theoretically parallel axis-aligned
975                    // vector.
976                    (None, angle.clone())
977                } else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) {
978                    (
979                        Some("x "),
980                        if v.0 < Number::zero() {
981                            -angle.clone()
982                        } else {
983                            angle.clone()
984                        },
985                    )
986                } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) {
987                    (
988                        Some("y "),
989                        if v.1 < Number::zero() {
990                            -angle.clone()
991                        } else {
992                            angle.clone()
993                        },
994                    )
995                } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) {
996                    // When we're parallel to the z-axis, we can just serialize the angle.
997                    let angle = if v.2 < Number::zero() {
998                        -angle.clone()
999                    } else {
1000                        angle.clone()
1001                    };
1002                    return angle.to_css(dest);
1003                } else {
1004                    (None, angle.clone())
1005                };
1006                match axis {
1007                    Some(a) => dest.write_str(a)?,
1008                    None => {
1009                        x.to_css(dest)?;
1010                        dest.write_char(' ')?;
1011                        y.to_css(dest)?;
1012                        dest.write_char(' ')?;
1013                        z.to_css(dest)?;
1014                        dest.write_char(' ')?;
1015                    },
1016                }
1017                angle.to_css(dest)
1018            },
1019        }
1020    }
1021}
1022
1023#[derive(
1024    Clone,
1025    Copy,
1026    Debug,
1027    Deserialize,
1028    MallocSizeOf,
1029    PartialEq,
1030    Serialize,
1031    SpecifiedValueInfo,
1032    ToAnimatedValue,
1033    ToAnimatedZero,
1034    ToComputedValue,
1035    ToResolvedValue,
1036    ToShmem,
1037    ToTyped,
1038)]
1039#[repr(C, u8)]
1040/// A value of the `Scale` property
1041///
1042/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
1043pub enum GenericScale<Number> {
1044    /// 'none'
1045    None,
1046    /// '<number>{1,3}'
1047    Scale(Number, Number, Number),
1048}
1049
1050pub use self::GenericScale as Scale;
1051
1052impl<Number> ToCss for Scale<Number>
1053where
1054    Number: ToCss + PartialEq + Clone + ToFloat,
1055{
1056    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1057    where
1058        W: fmt::Write,
1059    {
1060        match *self {
1061            Scale::None => dest.write_str("none"),
1062            Scale::Scale(ref x, ref y, ref z) => {
1063                x.to_css(dest)?;
1064
1065                let serialize_z = z.to_f32() != Ok(1.0);
1066                if serialize_z || x != y {
1067                    dest.write_char(' ')?;
1068                    y.to_css(dest)?;
1069                }
1070
1071                if serialize_z {
1072                    dest.write_char(' ')?;
1073                    z.to_css(dest)?;
1074                }
1075                Ok(())
1076            },
1077        }
1078    }
1079}
1080
1081#[inline]
1082fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero + ZeroNoPercent, Length: Zero>(
1083    _: &LengthPercentage,
1084    y: &LengthPercentage,
1085    z: &Length,
1086) -> bool {
1087    y.is_zero_no_percent() && z.is_zero()
1088}
1089
1090#[derive(
1091    Clone,
1092    Debug,
1093    Deserialize,
1094    MallocSizeOf,
1095    PartialEq,
1096    Serialize,
1097    SpecifiedValueInfo,
1098    ToAnimatedValue,
1099    ToAnimatedZero,
1100    ToComputedValue,
1101    ToCss,
1102    ToResolvedValue,
1103    ToShmem,
1104    ToTyped,
1105)]
1106#[repr(C, u8)]
1107/// A value of the `translate` property
1108///
1109/// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization:
1110///
1111/// If a 2d translation is specified, the property must serialize with only one
1112/// or two values (per usual, if the second value is 0px, the default, it must
1113/// be omitted when serializing; however if 0% is the second value, it is included).
1114///
1115/// If a 3d translation is specified and the value can be expressed as 2d, we treat as 2d and
1116/// serialize accoringly. Otherwise, we serialize all three values.
1117/// https://github.com/w3c/csswg-drafts/issues/3305
1118///
1119/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms>
1120pub enum GenericTranslate<LengthPercentage, Length>
1121where
1122    LengthPercentage: Zero + ZeroNoPercent,
1123    Length: Zero,
1124{
1125    /// 'none'
1126    None,
1127    /// <length-percentage> [ <length-percentage> <length>? ]?
1128    Translate(
1129        LengthPercentage,
1130        #[css(contextual_skip_if = "y_axis_and_z_axis_are_zero")] LengthPercentage,
1131        #[css(skip_if = "Zero::is_zero")] Length,
1132    ),
1133}
1134
1135pub use self::GenericTranslate as Translate;
1136
1137#[allow(missing_docs)]
1138#[derive(
1139    Clone,
1140    Copy,
1141    Debug,
1142    MallocSizeOf,
1143    Parse,
1144    PartialEq,
1145    SpecifiedValueInfo,
1146    ToComputedValue,
1147    ToCss,
1148    ToResolvedValue,
1149    ToShmem,
1150    ToTyped,
1151)]
1152#[repr(u8)]
1153pub enum TransformStyle {
1154    Flat,
1155    #[css(keyword = "preserve-3d")]
1156    Preserve3d,
1157}