lightningcss/properties/
transform.rs

1//! CSS properties related to 2D and 3D transforms.
2
3use super::{Property, PropertyId};
4use crate::context::PropertyHandlerContext;
5use crate::declaration::DeclarationList;
6use crate::error::{ParserError, PrinterError};
7use crate::macros::enum_property;
8use crate::prefixes::Feature;
9use crate::printer::Printer;
10use crate::stylesheet::PrinterOptions;
11use crate::traits::{Parse, PropertyHandler, ToCss, Zero};
12use crate::values::{
13  angle::Angle,
14  length::{Length, LengthPercentage},
15  percentage::NumberOrPercentage,
16};
17use crate::vendor_prefix::VendorPrefix;
18#[cfg(feature = "visitor")]
19use crate::visitor::Visit;
20use cssparser::*;
21use std::f32::consts::PI;
22
23/// A value for the [transform](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#propdef-transform) property.
24#[derive(Debug, Clone, PartialEq, Default)]
25#[cfg_attr(feature = "visitor", derive(Visit))]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
27#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
28#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
29pub struct TransformList(pub Vec<Transform>);
30
31impl<'i> Parse<'i> for TransformList {
32  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
33    if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
34      return Ok(TransformList(vec![]));
35    }
36
37    input.skip_whitespace();
38    let mut results = vec![Transform::parse(input)?];
39    loop {
40      input.skip_whitespace();
41      if let Ok(item) = input.try_parse(Transform::parse) {
42        results.push(item);
43      } else {
44        return Ok(TransformList(results));
45      }
46    }
47  }
48}
49
50impl ToCss for TransformList {
51  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
52  where
53    W: std::fmt::Write,
54  {
55    if self.0.is_empty() {
56      dest.write_str("none")?;
57      return Ok(());
58    }
59
60    // TODO: Re-enable with a better solution
61    //       See: https://github.com/parcel-bundler/lightningcss/issues/288
62    if dest.minify {
63      let mut base = String::new();
64      self.to_css_base(&mut Printer::new(
65        &mut base,
66        PrinterOptions {
67          minify: true,
68          ..PrinterOptions::default()
69        },
70      ))?;
71
72      dest.write_str(&base)?;
73
74      return Ok(());
75    }
76    // if dest.minify {
77    //   // Combine transforms into a single matrix.
78    //   if let Some(matrix) = self.to_matrix() {
79    //     // Generate based on the original transforms.
80    //     let mut base = String::new();
81    //     self.to_css_base(&mut Printer::new(
82    //       &mut base,
83    //       PrinterOptions {
84    //         minify: true,
85    //         ..PrinterOptions::default()
86    //       },
87    //     ))?;
88    //
89    //     // Decompose the matrix into transform functions if possible.
90    //     // If the resulting length is shorter than the original, use it.
91    //     if let Some(d) = matrix.decompose() {
92    //       let mut decomposed = String::new();
93    //       d.to_css_base(&mut Printer::new(
94    //         &mut decomposed,
95    //         PrinterOptions {
96    //           minify: true,
97    //           ..PrinterOptions::default()
98    //         },
99    //       ))?;
100    //       if decomposed.len() < base.len() {
101    //         base = decomposed;
102    //       }
103    //     }
104    //
105    //     // Also generate a matrix() or matrix3d() representation and compare that.
106    //     let mut mat = String::new();
107    //     if let Some(matrix) = matrix.to_matrix2d() {
108    //       Transform::Matrix(matrix).to_css(&mut Printer::new(
109    //         &mut mat,
110    //         PrinterOptions {
111    //           minify: true,
112    //           ..PrinterOptions::default()
113    //         },
114    //       ))?
115    //     } else {
116    //       Transform::Matrix3d(matrix).to_css(&mut Printer::new(
117    //         &mut mat,
118    //         PrinterOptions {
119    //           minify: true,
120    //           ..PrinterOptions::default()
121    //         },
122    //       ))?
123    //     }
124    //
125    //     if mat.len() < base.len() {
126    //       dest.write_str(&mat)?;
127    //     } else {
128    //       dest.write_str(&base)?;
129    //     }
130    //
131    //     return Ok(());
132    //   }
133    // }
134
135    self.to_css_base(dest)
136  }
137}
138
139impl TransformList {
140  fn to_css_base<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
141  where
142    W: std::fmt::Write,
143  {
144    for item in &self.0 {
145      item.to_css(dest)?;
146    }
147    Ok(())
148  }
149
150  /// Converts the transform list to a 3D matrix if possible.
151  pub fn to_matrix(&self) -> Option<Matrix3d<f32>> {
152    let mut matrix = Matrix3d::identity();
153    for transform in &self.0 {
154      if let Some(m) = transform.to_matrix() {
155        matrix = m.multiply(&matrix);
156      } else {
157        return None;
158      }
159    }
160    Some(matrix)
161  }
162}
163
164/// An individual [transform function](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions).
165#[derive(Debug, Clone, PartialEq)]
166#[cfg_attr(feature = "visitor", derive(Visit))]
167#[cfg_attr(
168  feature = "serde",
169  derive(serde::Serialize, serde::Deserialize),
170  serde(tag = "type", content = "value", rename_all = "camelCase")
171)]
172#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
173#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
174pub enum Transform {
175  /// A 2D translation.
176  Translate(LengthPercentage, LengthPercentage),
177  /// A translation in the X direction.
178  TranslateX(LengthPercentage),
179  /// A translation in the Y direction.
180  TranslateY(LengthPercentage),
181  /// A translation in the Z direction.
182  TranslateZ(Length),
183  /// A 3D translation.
184  Translate3d(LengthPercentage, LengthPercentage, Length),
185  /// A 2D scale.
186  Scale(NumberOrPercentage, NumberOrPercentage),
187  /// A scale in the X direction.
188  ScaleX(NumberOrPercentage),
189  /// A scale in the Y direction.
190  ScaleY(NumberOrPercentage),
191  /// A scale in the Z direction.
192  ScaleZ(NumberOrPercentage),
193  /// A 3D scale.
194  Scale3d(NumberOrPercentage, NumberOrPercentage, NumberOrPercentage),
195  /// A 2D rotation.
196  Rotate(Angle),
197  /// A rotation around the X axis.
198  RotateX(Angle),
199  /// A rotation around the Y axis.
200  RotateY(Angle),
201  /// A rotation around the Z axis.
202  RotateZ(Angle),
203  /// A 3D rotation.
204  Rotate3d(f32, f32, f32, Angle),
205  /// A 2D skew.
206  Skew(Angle, Angle),
207  /// A skew along the X axis.
208  SkewX(Angle),
209  /// A skew along the Y axis.
210  SkewY(Angle),
211  /// A perspective transform.
212  Perspective(Length),
213  /// A 2D matrix transform.
214  Matrix(Matrix<f32>),
215  /// A 3D matrix transform.
216  Matrix3d(Matrix3d<f32>),
217}
218
219/// A 2D matrix.
220#[derive(Debug, Clone, PartialEq)]
221#[cfg_attr(feature = "visitor", derive(Visit))]
222#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
223#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
224#[allow(missing_docs)]
225#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
226pub struct Matrix<T> {
227  pub a: T,
228  pub b: T,
229  pub c: T,
230  pub d: T,
231  pub e: T,
232  pub f: T,
233}
234
235impl Matrix<f32> {
236  /// Converts the matrix to a 3D matrix.
237  pub fn to_matrix3d(&self) -> Matrix3d<f32> {
238    Matrix3d {
239      m11: self.a,
240      m12: self.b,
241      m13: 0.0,
242      m14: 0.0,
243      m21: self.c,
244      m22: self.d,
245      m23: 0.0,
246      m24: 0.0,
247      m31: 0.0,
248      m32: 0.0,
249      m33: 1.0,
250      m34: 0.0,
251      m41: self.e,
252      m42: self.f,
253      m43: 0.0,
254      m44: 1.0,
255    }
256  }
257}
258
259/// A 3D matrix.
260#[derive(Debug, Clone, PartialEq)]
261#[cfg_attr(feature = "visitor", derive(Visit))]
262#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
263#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
264#[allow(missing_docs)]
265pub struct Matrix3d<T> {
266  pub m11: T,
267  pub m12: T,
268  pub m13: T,
269  pub m14: T,
270  pub m21: T,
271  pub m22: T,
272  pub m23: T,
273  pub m24: T,
274  pub m31: T,
275  pub m32: T,
276  pub m33: T,
277  pub m34: T,
278  pub m41: T,
279  pub m42: T,
280  pub m43: T,
281  pub m44: T,
282}
283
284// https://drafts.csswg.org/css-transforms-2/#mathematical-description
285impl Matrix3d<f32> {
286  /// Creates an identity matrix.
287  pub fn identity() -> Matrix3d<f32> {
288    Matrix3d {
289      m11: 1.0,
290      m12: 0.0,
291      m13: 0.0,
292      m14: 0.0,
293      m21: 0.0,
294      m22: 1.0,
295      m23: 0.0,
296      m24: 0.0,
297      m31: 0.0,
298      m32: 0.0,
299      m33: 1.0,
300      m34: 0.0,
301      m41: 0.0,
302      m42: 0.0,
303      m43: 0.0,
304      m44: 1.0,
305    }
306  }
307
308  /// Creates a translation matrix.
309  pub fn translate(x: f32, y: f32, z: f32) -> Matrix3d<f32> {
310    Matrix3d {
311      m11: 1.0,
312      m12: 0.0,
313      m13: 0.0,
314      m14: 0.0,
315      m21: 0.0,
316      m22: 1.0,
317      m23: 0.0,
318      m24: 0.0,
319      m31: 0.0,
320      m32: 0.0,
321      m33: 1.0,
322      m34: 0.0,
323      m41: x,
324      m42: y,
325      m43: z,
326      m44: 1.0,
327    }
328  }
329
330  /// Creates a scale matrix.
331  pub fn scale(x: f32, y: f32, z: f32) -> Matrix3d<f32> {
332    Matrix3d {
333      m11: x,
334      m12: 0.0,
335      m13: 0.0,
336      m14: 0.0,
337      m21: 0.0,
338      m22: y,
339      m23: 0.0,
340      m24: 0.0,
341      m31: 0.0,
342      m32: 0.0,
343      m33: z,
344      m34: 0.0,
345      m41: 0.0,
346      m42: 0.0,
347      m43: 0.0,
348      m44: 1.0,
349    }
350  }
351
352  /// Creates a rotation matrix.
353  pub fn rotate(x: f32, y: f32, z: f32, angle: f32) -> Matrix3d<f32> {
354    // Normalize the vector.
355    let length = (x * x + y * y + z * z).sqrt();
356    if length == 0.0 {
357      // A direction vector that cannot be normalized, such as [0,0,0], will cause the rotation to not be applied.
358      return Matrix3d::identity();
359    }
360
361    let x = x / length;
362    let y = y / length;
363    let z = z / length;
364
365    let half_angle = angle / 2.0;
366    let sin = half_angle.sin();
367    let sc = sin * half_angle.cos();
368    let sq = sin * sin;
369    let m11 = 1.0 - 2.0 * (y * y + z * z) * sq;
370    let m12 = 2.0 * (x * y * sq + z * sc);
371    let m13 = 2.0 * (x * z * sq - y * sc);
372    let m21 = 2.0 * (x * y * sq - z * sc);
373    let m22 = 1.0 - 2.0 * (x * x + z * z) * sq;
374    let m23 = 2.0 * (y * z * sq + x * sc);
375    let m31 = 2.0 * (x * z * sq + y * sc);
376    let m32 = 2.0 * (y * z * sq - x * sc);
377    let m33 = 1.0 - 2.0 * (x * x + y * y) * sq;
378    Matrix3d {
379      m11,
380      m12,
381      m13,
382      m14: 0.0,
383      m21,
384      m22,
385      m23,
386      m24: 0.0,
387      m31,
388      m32,
389      m33,
390      m34: 0.0,
391      m41: 0.0,
392      m42: 0.0,
393      m43: 0.0,
394      m44: 1.0,
395    }
396  }
397
398  /// Creates a skew matrix.
399  pub fn skew(a: f32, b: f32) -> Matrix3d<f32> {
400    Matrix3d {
401      m11: 1.0,
402      m12: b.tan(),
403      m13: 0.0,
404      m14: 0.0,
405      m21: a.tan(),
406      m22: 1.0,
407      m23: 0.0,
408      m24: 0.0,
409      m31: 0.0,
410      m32: 0.0,
411      m33: 1.0,
412      m34: 0.0,
413      m41: 0.0,
414      m42: 0.0,
415      m43: 0.0,
416      m44: 1.0,
417    }
418  }
419
420  /// Creates a perspective matrix.
421  pub fn perspective(d: f32) -> Matrix3d<f32> {
422    Matrix3d {
423      m11: 1.0,
424      m12: 0.0,
425      m13: 0.0,
426      m14: 0.0,
427      m21: 0.0,
428      m22: 1.0,
429      m23: 0.0,
430      m24: 0.0,
431      m31: 0.0,
432      m32: 0.0,
433      m33: 1.0,
434      m34: -1.0 / d,
435      m41: 0.0,
436      m42: 0.0,
437      m43: 0.0,
438      m44: 1.0,
439    }
440  }
441
442  /// Multiplies this matrix by another, returning a new matrix.
443  pub fn multiply(&self, other: &Self) -> Self {
444    Matrix3d {
445      m11: self.m11 * other.m11 + self.m12 * other.m21 + self.m13 * other.m31 + self.m14 * other.m41,
446      m12: self.m11 * other.m12 + self.m12 * other.m22 + self.m13 * other.m32 + self.m14 * other.m42,
447      m13: self.m11 * other.m13 + self.m12 * other.m23 + self.m13 * other.m33 + self.m14 * other.m43,
448      m14: self.m11 * other.m14 + self.m12 * other.m24 + self.m13 * other.m34 + self.m14 * other.m44,
449      m21: self.m21 * other.m11 + self.m22 * other.m21 + self.m23 * other.m31 + self.m24 * other.m41,
450      m22: self.m21 * other.m12 + self.m22 * other.m22 + self.m23 * other.m32 + self.m24 * other.m42,
451      m23: self.m21 * other.m13 + self.m22 * other.m23 + self.m23 * other.m33 + self.m24 * other.m43,
452      m24: self.m21 * other.m14 + self.m22 * other.m24 + self.m23 * other.m34 + self.m24 * other.m44,
453      m31: self.m31 * other.m11 + self.m32 * other.m21 + self.m33 * other.m31 + self.m34 * other.m41,
454      m32: self.m31 * other.m12 + self.m32 * other.m22 + self.m33 * other.m32 + self.m34 * other.m42,
455      m33: self.m31 * other.m13 + self.m32 * other.m23 + self.m33 * other.m33 + self.m34 * other.m43,
456      m34: self.m31 * other.m14 + self.m32 * other.m24 + self.m33 * other.m34 + self.m34 * other.m44,
457      m41: self.m41 * other.m11 + self.m42 * other.m21 + self.m43 * other.m31 + self.m44 * other.m41,
458      m42: self.m41 * other.m12 + self.m42 * other.m22 + self.m43 * other.m32 + self.m44 * other.m42,
459      m43: self.m41 * other.m13 + self.m42 * other.m23 + self.m43 * other.m33 + self.m44 * other.m43,
460      m44: self.m41 * other.m14 + self.m42 * other.m24 + self.m43 * other.m34 + self.m44 * other.m44,
461    }
462  }
463
464  /// Returns whether this matrix could be converted to a 2D matrix.
465  pub fn is_2d(&self) -> bool {
466    self.m31 == 0.0
467      && self.m32 == 0.0
468      && self.m13 == 0.0
469      && self.m23 == 0.0
470      && self.m43 == 0.0
471      && self.m14 == 0.0
472      && self.m24 == 0.0
473      && self.m34 == 0.0
474      && self.m33 == 1.0
475      && self.m44 == 1.0
476  }
477
478  /// Attempts to convert the matrix to 2D.
479  /// Returns `None` if the conversion is not possible.
480  pub fn to_matrix2d(&self) -> Option<Matrix<f32>> {
481    if self.is_2d() {
482      return Some(Matrix {
483        a: self.m11,
484        b: self.m12,
485        c: self.m21,
486        d: self.m22,
487        e: self.m41,
488        f: self.m42,
489      });
490    }
491    None
492  }
493
494  /// Scales the matrix by the given factor.
495  pub fn scale_by_factor(&mut self, scaling_factor: f32) {
496    self.m11 *= scaling_factor;
497    self.m12 *= scaling_factor;
498    self.m13 *= scaling_factor;
499    self.m14 *= scaling_factor;
500    self.m21 *= scaling_factor;
501    self.m22 *= scaling_factor;
502    self.m23 *= scaling_factor;
503    self.m24 *= scaling_factor;
504    self.m31 *= scaling_factor;
505    self.m32 *= scaling_factor;
506    self.m33 *= scaling_factor;
507    self.m34 *= scaling_factor;
508    self.m41 *= scaling_factor;
509    self.m42 *= scaling_factor;
510    self.m43 *= scaling_factor;
511    self.m44 *= scaling_factor;
512  }
513
514  /// Returns the determinant of the matrix.
515  pub fn determinant(&self) -> f32 {
516    self.m14 * self.m23 * self.m32 * self.m41
517      - self.m13 * self.m24 * self.m32 * self.m41
518      - self.m14 * self.m22 * self.m33 * self.m41
519      + self.m12 * self.m24 * self.m33 * self.m41
520      + self.m13 * self.m22 * self.m34 * self.m41
521      - self.m12 * self.m23 * self.m34 * self.m41
522      - self.m14 * self.m23 * self.m31 * self.m42
523      + self.m13 * self.m24 * self.m31 * self.m42
524      + self.m14 * self.m21 * self.m33 * self.m42
525      - self.m11 * self.m24 * self.m33 * self.m42
526      - self.m13 * self.m21 * self.m34 * self.m42
527      + self.m11 * self.m23 * self.m34 * self.m42
528      + self.m14 * self.m22 * self.m31 * self.m43
529      - self.m12 * self.m24 * self.m31 * self.m43
530      - self.m14 * self.m21 * self.m32 * self.m43
531      + self.m11 * self.m24 * self.m32 * self.m43
532      + self.m12 * self.m21 * self.m34 * self.m43
533      - self.m11 * self.m22 * self.m34 * self.m43
534      - self.m13 * self.m22 * self.m31 * self.m44
535      + self.m12 * self.m23 * self.m31 * self.m44
536      + self.m13 * self.m21 * self.m32 * self.m44
537      - self.m11 * self.m23 * self.m32 * self.m44
538      - self.m12 * self.m21 * self.m33 * self.m44
539      + self.m11 * self.m22 * self.m33 * self.m44
540  }
541
542  /// Returns the inverse of the matrix if possible.
543  pub fn inverse(&self) -> Option<Matrix3d<f32>> {
544    let mut det = self.determinant();
545    if det == 0.0 {
546      return None;
547    }
548
549    det = 1.0 / det;
550    Some(Matrix3d {
551      m11: det
552        * (self.m23 * self.m34 * self.m42 - self.m24 * self.m33 * self.m42 + self.m24 * self.m32 * self.m43
553          - self.m22 * self.m34 * self.m43
554          - self.m23 * self.m32 * self.m44
555          + self.m22 * self.m33 * self.m44),
556      m12: det
557        * (self.m14 * self.m33 * self.m42 - self.m13 * self.m34 * self.m42 - self.m14 * self.m32 * self.m43
558          + self.m12 * self.m34 * self.m43
559          + self.m13 * self.m32 * self.m44
560          - self.m12 * self.m33 * self.m44),
561      m13: det
562        * (self.m13 * self.m24 * self.m42 - self.m14 * self.m23 * self.m42 + self.m14 * self.m22 * self.m43
563          - self.m12 * self.m24 * self.m43
564          - self.m13 * self.m22 * self.m44
565          + self.m12 * self.m23 * self.m44),
566      m14: det
567        * (self.m14 * self.m23 * self.m32 - self.m13 * self.m24 * self.m32 - self.m14 * self.m22 * self.m33
568          + self.m12 * self.m24 * self.m33
569          + self.m13 * self.m22 * self.m34
570          - self.m12 * self.m23 * self.m34),
571      m21: det
572        * (self.m24 * self.m33 * self.m41 - self.m23 * self.m34 * self.m41 - self.m24 * self.m31 * self.m43
573          + self.m21 * self.m34 * self.m43
574          + self.m23 * self.m31 * self.m44
575          - self.m21 * self.m33 * self.m44),
576      m22: det
577        * (self.m13 * self.m34 * self.m41 - self.m14 * self.m33 * self.m41 + self.m14 * self.m31 * self.m43
578          - self.m11 * self.m34 * self.m43
579          - self.m13 * self.m31 * self.m44
580          + self.m11 * self.m33 * self.m44),
581      m23: det
582        * (self.m14 * self.m23 * self.m41 - self.m13 * self.m24 * self.m41 - self.m14 * self.m21 * self.m43
583          + self.m11 * self.m24 * self.m43
584          + self.m13 * self.m21 * self.m44
585          - self.m11 * self.m23 * self.m44),
586      m24: det
587        * (self.m13 * self.m24 * self.m31 - self.m14 * self.m23 * self.m31 + self.m14 * self.m21 * self.m33
588          - self.m11 * self.m24 * self.m33
589          - self.m13 * self.m21 * self.m34
590          + self.m11 * self.m23 * self.m34),
591      m31: det
592        * (self.m22 * self.m34 * self.m41 - self.m24 * self.m32 * self.m41 + self.m24 * self.m31 * self.m42
593          - self.m21 * self.m34 * self.m42
594          - self.m22 * self.m31 * self.m44
595          + self.m21 * self.m32 * self.m44),
596      m32: det
597        * (self.m14 * self.m32 * self.m41 - self.m12 * self.m34 * self.m41 - self.m14 * self.m31 * self.m42
598          + self.m11 * self.m34 * self.m42
599          + self.m12 * self.m31 * self.m44
600          - self.m11 * self.m32 * self.m44),
601      m33: det
602        * (self.m12 * self.m24 * self.m41 - self.m14 * self.m22 * self.m41 + self.m14 * self.m21 * self.m42
603          - self.m11 * self.m24 * self.m42
604          - self.m12 * self.m21 * self.m44
605          + self.m11 * self.m22 * self.m44),
606      m34: det
607        * (self.m14 * self.m22 * self.m31 - self.m12 * self.m24 * self.m31 - self.m14 * self.m21 * self.m32
608          + self.m11 * self.m24 * self.m32
609          + self.m12 * self.m21 * self.m34
610          - self.m11 * self.m22 * self.m34),
611      m41: det
612        * (self.m23 * self.m32 * self.m41 - self.m22 * self.m33 * self.m41 - self.m23 * self.m31 * self.m42
613          + self.m21 * self.m33 * self.m42
614          + self.m22 * self.m31 * self.m43
615          - self.m21 * self.m32 * self.m43),
616      m42: det
617        * (self.m12 * self.m33 * self.m41 - self.m13 * self.m32 * self.m41 + self.m13 * self.m31 * self.m42
618          - self.m11 * self.m33 * self.m42
619          - self.m12 * self.m31 * self.m43
620          + self.m11 * self.m32 * self.m43),
621      m43: det
622        * (self.m13 * self.m22 * self.m41 - self.m12 * self.m23 * self.m41 - self.m13 * self.m21 * self.m42
623          + self.m11 * self.m23 * self.m42
624          + self.m12 * self.m21 * self.m43
625          - self.m11 * self.m22 * self.m43),
626      m44: det
627        * (self.m12 * self.m23 * self.m31 - self.m13 * self.m22 * self.m31 + self.m13 * self.m21 * self.m32
628          - self.m11 * self.m23 * self.m32
629          - self.m12 * self.m21 * self.m33
630          + self.m11 * self.m22 * self.m33),
631    })
632  }
633
634  /// Transposes the matrix.
635  pub fn transpose(&self) -> Self {
636    Self {
637      m11: self.m11,
638      m12: self.m21,
639      m13: self.m31,
640      m14: self.m41,
641      m21: self.m12,
642      m22: self.m22,
643      m23: self.m32,
644      m24: self.m42,
645      m31: self.m13,
646      m32: self.m23,
647      m33: self.m33,
648      m34: self.m43,
649      m41: self.m14,
650      m42: self.m24,
651      m43: self.m34,
652      m44: self.m44,
653    }
654  }
655
656  /// Multiplies a vector by the matrix.
657  pub fn multiply_vector(&self, pin: &[f32; 4]) -> [f32; 4] {
658    [
659      pin[0] * self.m11 + pin[1] * self.m21 + pin[2] * self.m31 + pin[3] * self.m41,
660      pin[0] * self.m12 + pin[1] * self.m22 + pin[2] * self.m32 + pin[3] * self.m42,
661      pin[0] * self.m13 + pin[1] * self.m23 + pin[2] * self.m33 + pin[3] * self.m43,
662      pin[0] * self.m14 + pin[1] * self.m24 + pin[2] * self.m34 + pin[3] * self.m44,
663    ]
664  }
665
666  /// Decomposes the matrix into a list of transform functions if possible.
667  pub fn decompose(&self) -> Option<TransformList> {
668    // https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix
669    // Combine 2 point.
670    let combine = |a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32| {
671      [
672        (ascl * a[0]) + (bscl * b[0]),
673        (ascl * a[1]) + (bscl * b[1]),
674        (ascl * a[2]) + (bscl * b[2]),
675      ]
676    };
677
678    // Dot product.
679    let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
680
681    // Cross product.
682    let cross = |row1: [f32; 3], row2: [f32; 3]| {
683      [
684        row1[1] * row2[2] - row1[2] * row2[1],
685        row1[2] * row2[0] - row1[0] * row2[2],
686        row1[0] * row2[1] - row1[1] * row2[0],
687      ]
688    };
689
690    if self.m44 == 0.0 {
691      return None;
692    }
693
694    let scaling_factor = self.m44;
695
696    // Normalize the matrix.
697    let mut matrix = self.clone();
698    matrix.scale_by_factor(1.0 / scaling_factor);
699
700    // perspective_matrix is used to solve for perspective, but it also provides
701    // an easy way to test for singularity of the upper 3x3 component.
702    let mut perspective_matrix = matrix.clone();
703    perspective_matrix.m14 = 0.0;
704    perspective_matrix.m24 = 0.0;
705    perspective_matrix.m34 = 0.0;
706    perspective_matrix.m44 = 1.0;
707
708    if perspective_matrix.determinant() == 0.0 {
709      return None;
710    }
711
712    let mut transforms = vec![];
713
714    // First, isolate perspective.
715    if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 {
716      let right_hand_side: [f32; 4] = [matrix.m14, matrix.m24, matrix.m34, matrix.m44];
717
718      perspective_matrix = perspective_matrix.inverse().unwrap().transpose();
719      let perspective = perspective_matrix.multiply_vector(&right_hand_side);
720      if perspective[0] == 0.0 && perspective[1] == 0.0 && perspective[3] == 0.0 {
721        transforms.push(Transform::Perspective(Length::px(-1.0 / perspective[2])))
722      } else {
723        return None;
724      }
725    }
726
727    // Next take care of translation (easy).
728    // let translate = Translate3D(matrix.m41, matrix.m42, matrix.m43);
729    if matrix.m41 != 0.0 || matrix.m42 != 0.0 || matrix.m43 != 0.0 {
730      transforms.push(Transform::Translate3d(
731        LengthPercentage::px(matrix.m41),
732        LengthPercentage::px(matrix.m42),
733        Length::px(matrix.m43),
734      ));
735    }
736
737    // Now get scale and shear. 'row' is a 3 element array of 3 component vectors
738    let mut row = [
739      [matrix.m11, matrix.m12, matrix.m13],
740      [matrix.m21, matrix.m22, matrix.m23],
741      [matrix.m31, matrix.m32, matrix.m33],
742    ];
743
744    // Compute X scale factor and normalize first row.
745    let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt();
746    let mut scale_x = row0len;
747    row[0] = [row[0][0] / row0len, row[0][1] / row0len, row[0][2] / row0len];
748
749    // Compute XY shear factor and make 2nd row orthogonal to 1st.
750    let mut skew_x = dot(row[0], row[1]);
751    row[1] = combine(row[1], row[0], 1.0, -skew_x);
752
753    // Now, compute Y scale and normalize 2nd row.
754    let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt();
755    let mut scale_y = row1len;
756    row[1] = [row[1][0] / row1len, row[1][1] / row1len, row[1][2] / row1len];
757    skew_x /= scale_y;
758
759    // Compute XZ and YZ shears, orthogonalize 3rd row
760    let mut skew_y = dot(row[0], row[2]);
761    row[2] = combine(row[2], row[0], 1.0, -skew_y);
762    let mut skew_z = dot(row[1], row[2]);
763    row[2] = combine(row[2], row[1], 1.0, -skew_z);
764
765    // Next, get Z scale and normalize 3rd row.
766    let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt();
767    let mut scale_z = row2len;
768    row[2] = [row[2][0] / row2len, row[2][1] / row2len, row[2][2] / row2len];
769    skew_y /= scale_z;
770    skew_z /= scale_z;
771
772    if skew_z != 0.0 {
773      return None; // ???
774    }
775
776    // Round to 5 digits of precision, which is what we print.
777    macro_rules! round {
778      ($var: ident) => {
779        $var = ($var * 100000.0).round() / 100000.0;
780      };
781    }
782
783    round!(skew_x);
784    round!(skew_y);
785    round!(skew_z);
786
787    if skew_x != 0.0 || skew_y != 0.0 || skew_z != 0.0 {
788      transforms.push(Transform::Skew(Angle::Rad(skew_x), Angle::Rad(skew_y)));
789    }
790
791    // At this point, the matrix (in rows) is orthonormal.
792    // Check for a coordinate system flip.  If the determinant
793    // is -1, then negate the matrix and the scaling factors.
794    if dot(row[0], cross(row[1], row[2])) < 0.0 {
795      scale_x = -scale_x;
796      scale_y = -scale_y;
797      scale_z = -scale_z;
798      for i in 0..3 {
799        row[i][0] *= -1.0;
800        row[i][1] *= -1.0;
801        row[i][2] *= -1.0;
802      }
803    }
804
805    round!(scale_x);
806    round!(scale_y);
807    round!(scale_z);
808
809    if scale_x != 1.0 || scale_y != 1.0 || scale_z != 1.0 {
810      transforms.push(Transform::Scale3d(
811        NumberOrPercentage::Number(scale_x),
812        NumberOrPercentage::Number(scale_y),
813        NumberOrPercentage::Number(scale_z),
814      ))
815    }
816
817    // Now, get the rotations out.
818    let mut rotate_x = 0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0)).sqrt();
819    let mut rotate_y = 0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0)).sqrt();
820    let mut rotate_z = 0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0)).sqrt();
821    let rotate_w = 0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0)).sqrt();
822
823    if row[2][1] > row[1][2] {
824      rotate_x = -rotate_x
825    }
826
827    if row[0][2] > row[2][0] {
828      rotate_y = -rotate_y
829    }
830
831    if row[1][0] > row[0][1] {
832      rotate_z = -rotate_z
833    }
834
835    let len = (rotate_x * rotate_x + rotate_y * rotate_y + rotate_z * rotate_z).sqrt();
836    if len != 0.0 {
837      rotate_x /= len;
838      rotate_y /= len;
839      rotate_z /= len;
840    }
841    let a = 2.0 * len.atan2(rotate_w);
842
843    // normalize the vector so one of the values is 1
844    let max = rotate_x.max(rotate_y).max(rotate_z);
845    rotate_x /= max;
846    rotate_y /= max;
847    rotate_z /= max;
848
849    if a != 0.0 {
850      transforms.push(Transform::Rotate3d(rotate_x, rotate_y, rotate_z, Angle::Rad(a)))
851    }
852
853    if transforms.is_empty() {
854      return None;
855    }
856
857    Some(TransformList(transforms))
858  }
859}
860
861impl<'i> Parse<'i> for Transform {
862  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
863    let function = input.expect_function()?.clone();
864    input.parse_nested_block(|input| {
865      let location = input.current_source_location();
866      match_ignore_ascii_case! { &function,
867        "matrix" => {
868          let a = f32::parse(input)?;
869          input.expect_comma()?;
870          let b = f32::parse(input)?;
871          input.expect_comma()?;
872          let c = f32::parse(input)?;
873          input.expect_comma()?;
874          let d = f32::parse(input)?;
875          input.expect_comma()?;
876          let e = f32::parse(input)?;
877          input.expect_comma()?;
878          let f = f32::parse(input)?;
879          Ok(Transform::Matrix(Matrix { a, b, c, d, e, f }))
880        },
881        "matrix3d" => {
882          let m11 = f32::parse(input)?;
883          input.expect_comma()?;
884          let m12 = f32::parse(input)?;
885          input.expect_comma()?;
886          let m13 = f32::parse(input)?;
887          input.expect_comma()?;
888          let m14 = f32::parse(input)?;
889          input.expect_comma()?;
890          let m21 = f32::parse(input)?;
891          input.expect_comma()?;
892          let m22 = f32::parse(input)?;
893          input.expect_comma()?;
894          let m23 = f32::parse(input)?;
895          input.expect_comma()?;
896          let m24 = f32::parse(input)?;
897          input.expect_comma()?;
898          let m31 = f32::parse(input)?;
899          input.expect_comma()?;
900          let m32 = f32::parse(input)?;
901          input.expect_comma()?;
902          let m33 = f32::parse(input)?;
903          input.expect_comma()?;
904          let m34 = f32::parse(input)?;
905          input.expect_comma()?;
906          let m41 = f32::parse(input)?;
907          input.expect_comma()?;
908          let m42 = f32::parse(input)?;
909          input.expect_comma()?;
910          let m43 = f32::parse(input)?;
911          input.expect_comma()?;
912          let m44 = f32::parse(input)?;
913          Ok(Transform::Matrix3d(Matrix3d {
914            m11, m12, m13, m14,
915            m21, m22, m23, m24,
916            m31, m32, m33, m34,
917            m41, m42, m43, m44
918          }))
919        },
920        "translate" => {
921          let x = LengthPercentage::parse(input)?;
922          if input.try_parse(|input| input.expect_comma()).is_ok() {
923            let y = LengthPercentage::parse(input)?;
924            Ok(Transform::Translate(x, y))
925          } else {
926            Ok(Transform::Translate(x, LengthPercentage::zero()))
927          }
928        },
929        "translatex" => {
930          let x = LengthPercentage::parse(input)?;
931          Ok(Transform::TranslateX(x))
932        },
933        "translatey" => {
934          let y = LengthPercentage::parse(input)?;
935          Ok(Transform::TranslateY(y))
936        },
937        "translatez" => {
938          let z = Length::parse(input)?;
939          Ok(Transform::TranslateZ(z))
940        },
941        "translate3d" => {
942          let x = LengthPercentage::parse(input)?;
943          input.expect_comma()?;
944          let y = LengthPercentage::parse(input)?;
945          input.expect_comma()?;
946          let z = Length::parse(input)?;
947          Ok(Transform::Translate3d(x, y, z))
948        },
949        "scale" => {
950          let x = NumberOrPercentage::parse(input)?;
951          if input.try_parse(|input| input.expect_comma()).is_ok() {
952            let y = NumberOrPercentage::parse(input)?;
953            Ok(Transform::Scale(x, y))
954          } else {
955            Ok(Transform::Scale(x.clone(), x))
956          }
957        },
958        "scalex" => {
959          let x = NumberOrPercentage::parse(input)?;
960          Ok(Transform::ScaleX(x))
961        },
962        "scaley" => {
963          let y = NumberOrPercentage::parse(input)?;
964          Ok(Transform::ScaleY(y))
965        },
966        "scalez" => {
967          let z = NumberOrPercentage::parse(input)?;
968          Ok(Transform::ScaleZ(z))
969        },
970        "scale3d" => {
971          let x = NumberOrPercentage::parse(input)?;
972          input.expect_comma()?;
973          let y = NumberOrPercentage::parse(input)?;
974          input.expect_comma()?;
975          let z = NumberOrPercentage::parse(input)?;
976          Ok(Transform::Scale3d(x, y, z))
977        },
978        "rotate" => {
979          let angle = Angle::parse_with_unitless_zero(input)?;
980          Ok(Transform::Rotate(angle))
981        },
982        "rotatex" => {
983          let angle = Angle::parse_with_unitless_zero(input)?;
984          Ok(Transform::RotateX(angle))
985        },
986        "rotatey" => {
987          let angle = Angle::parse_with_unitless_zero(input)?;
988          Ok(Transform::RotateY(angle))
989        },
990        "rotatez" => {
991          let angle = Angle::parse_with_unitless_zero(input)?;
992          Ok(Transform::RotateZ(angle))
993        },
994        "rotate3d" => {
995          let x = f32::parse(input)?;
996          input.expect_comma()?;
997          let y = f32::parse(input)?;
998          input.expect_comma()?;
999          let z = f32::parse(input)?;
1000          input.expect_comma()?;
1001          let angle = Angle::parse_with_unitless_zero(input)?;
1002          Ok(Transform::Rotate3d(x, y, z, angle))
1003        },
1004        "skew" => {
1005          let x = Angle::parse_with_unitless_zero(input)?;
1006          if input.try_parse(|input| input.expect_comma()).is_ok() {
1007            let y = Angle::parse_with_unitless_zero(input)?;
1008            Ok(Transform::Skew(x, y))
1009          } else {
1010            Ok(Transform::Skew(x, Angle::Deg(0.0)))
1011          }
1012        },
1013        "skewx" => {
1014          let angle = Angle::parse_with_unitless_zero(input)?;
1015          Ok(Transform::SkewX(angle))
1016        },
1017        "skewy" => {
1018          let angle = Angle::parse_with_unitless_zero(input)?;
1019          Ok(Transform::SkewY(angle))
1020        },
1021        "perspective" => {
1022          let len = Length::parse(input)?;
1023          Ok(Transform::Perspective(len))
1024        },
1025        _ => Err(location.new_unexpected_token_error(
1026          cssparser::Token::Ident(function.clone())
1027        ))
1028      }
1029    })
1030  }
1031}
1032
1033impl ToCss for Transform {
1034  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1035  where
1036    W: std::fmt::Write,
1037  {
1038    use Transform::*;
1039    match self {
1040      Translate(x, y) => {
1041        if dest.minify && x.is_zero() && !y.is_zero() {
1042          dest.write_str("translateY(")?;
1043          y.to_css(dest)?
1044        } else {
1045          dest.write_str("translate(")?;
1046          x.to_css(dest)?;
1047          if !y.is_zero() {
1048            dest.delim(',', false)?;
1049            y.to_css(dest)?;
1050          }
1051        }
1052        dest.write_char(')')
1053      }
1054      TranslateX(x) => {
1055        dest.write_str(if dest.minify { "translate(" } else { "translateX(" })?;
1056        x.to_css(dest)?;
1057        dest.write_char(')')
1058      }
1059      TranslateY(y) => {
1060        dest.write_str("translateY(")?;
1061        y.to_css(dest)?;
1062        dest.write_char(')')
1063      }
1064      TranslateZ(z) => {
1065        dest.write_str("translateZ(")?;
1066        z.to_css(dest)?;
1067        dest.write_char(')')
1068      }
1069      Translate3d(x, y, z) => {
1070        if dest.minify && !x.is_zero() && y.is_zero() && z.is_zero() {
1071          dest.write_str("translate(")?;
1072          x.to_css(dest)?;
1073        } else if dest.minify && x.is_zero() && !y.is_zero() && z.is_zero() {
1074          dest.write_str("translateY(")?;
1075          y.to_css(dest)?;
1076        } else if dest.minify && x.is_zero() && y.is_zero() && !z.is_zero() {
1077          dest.write_str("translateZ(")?;
1078          z.to_css(dest)?;
1079        } else if dest.minify && z.is_zero() {
1080          dest.write_str("translate(")?;
1081          x.to_css(dest)?;
1082          dest.delim(',', false)?;
1083          y.to_css(dest)?;
1084        } else {
1085          dest.write_str("translate3d(")?;
1086          x.to_css(dest)?;
1087          dest.delim(',', false)?;
1088          y.to_css(dest)?;
1089          dest.delim(',', false)?;
1090          z.to_css(dest)?;
1091        }
1092        dest.write_char(')')
1093      }
1094      Scale(x, y) => {
1095        let x: f32 = x.into();
1096        let y: f32 = y.into();
1097        if dest.minify && x == 1.0 && y != 1.0 {
1098          dest.write_str("scaleY(")?;
1099          y.to_css(dest)?;
1100        } else if dest.minify && x != 1.0 && y == 1.0 {
1101          dest.write_str("scaleX(")?;
1102          x.to_css(dest)?;
1103        } else {
1104          dest.write_str("scale(")?;
1105          x.to_css(dest)?;
1106          if y != x {
1107            dest.delim(',', false)?;
1108            y.to_css(dest)?;
1109          }
1110        }
1111        dest.write_char(')')
1112      }
1113      ScaleX(x) => {
1114        dest.write_str("scaleX(")?;
1115        x.to_css(dest)?;
1116        dest.write_char(')')
1117      }
1118      ScaleY(y) => {
1119        dest.write_str("scaleY(")?;
1120        y.to_css(dest)?;
1121        dest.write_char(')')
1122      }
1123      ScaleZ(z) => {
1124        dest.write_str("scaleZ(")?;
1125        z.to_css(dest)?;
1126        dest.write_char(')')
1127      }
1128      Scale3d(x, y, z) => {
1129        let x: f32 = x.into();
1130        let y: f32 = y.into();
1131        let z: f32 = z.into();
1132        if dest.minify && z == 1.0 && x == y {
1133          // scale3d(x, x, 1) => scale(x)
1134          dest.write_str("scale(")?;
1135          x.to_css(dest)?;
1136        } else if dest.minify && x != 1.0 && y == 1.0 && z == 1.0 {
1137          // scale3d(x, 1, 1) => scaleX(x)
1138          dest.write_str("scaleX(")?;
1139          x.to_css(dest)?;
1140        } else if dest.minify && x == 1.0 && y != 1.0 && z == 1.0 {
1141          // scale3d(1, y, 1) => scaleY(y)
1142          dest.write_str("scaleY(")?;
1143          y.to_css(dest)?;
1144        } else if dest.minify && x == 1.0 && y == 1.0 && z != 1.0 {
1145          // scale3d(1, 1, z) => scaleZ(z)
1146          dest.write_str("scaleZ(")?;
1147          z.to_css(dest)?;
1148        } else if dest.minify && z == 1.0 {
1149          // scale3d(x, y, 1) => scale(x, y)
1150          dest.write_str("scale(")?;
1151          x.to_css(dest)?;
1152          dest.delim(',', false)?;
1153          y.to_css(dest)?;
1154        } else {
1155          dest.write_str("scale3d(")?;
1156          x.to_css(dest)?;
1157          dest.delim(',', false)?;
1158          y.to_css(dest)?;
1159          dest.delim(',', false)?;
1160          z.to_css(dest)?;
1161        }
1162        dest.write_char(')')
1163      }
1164      Rotate(angle) => {
1165        dest.write_str("rotate(")?;
1166        angle.to_css_with_unitless_zero(dest)?;
1167        dest.write_char(')')
1168      }
1169      RotateX(angle) => {
1170        dest.write_str("rotateX(")?;
1171        angle.to_css_with_unitless_zero(dest)?;
1172        dest.write_char(')')
1173      }
1174      RotateY(angle) => {
1175        dest.write_str("rotateY(")?;
1176        angle.to_css_with_unitless_zero(dest)?;
1177        dest.write_char(')')
1178      }
1179      RotateZ(angle) => {
1180        dest.write_str(if dest.minify { "rotate(" } else { "rotateZ(" })?;
1181        angle.to_css_with_unitless_zero(dest)?;
1182        dest.write_char(')')
1183      }
1184      Rotate3d(x, y, z, angle) => {
1185        if dest.minify && *x == 1.0 && *y == 0.0 && *z == 0.0 {
1186          // rotate3d(1, 0, 0, a) => rotateX(a)
1187          dest.write_str("rotateX(")?;
1188          angle.to_css_with_unitless_zero(dest)?;
1189        } else if dest.minify && *x == 0.0 && *y == 1.0 && *z == 0.0 {
1190          // rotate3d(0, 1, 0, a) => rotateY(a)
1191          dest.write_str("rotateY(")?;
1192          angle.to_css_with_unitless_zero(dest)?;
1193        } else if dest.minify && *x == 0.0 && *y == 0.0 && *z == 1.0 {
1194          // rotate3d(0, 0, 1, a) => rotate(a)
1195          dest.write_str("rotate(")?;
1196          angle.to_css_with_unitless_zero(dest)?;
1197        } else {
1198          dest.write_str("rotate3d(")?;
1199          x.to_css(dest)?;
1200          dest.delim(',', false)?;
1201          y.to_css(dest)?;
1202          dest.delim(',', false)?;
1203          z.to_css(dest)?;
1204          dest.delim(',', false)?;
1205          angle.to_css_with_unitless_zero(dest)?;
1206        }
1207        dest.write_char(')')
1208      }
1209      Skew(x, y) => {
1210        if dest.minify && x.is_zero() && !y.is_zero() {
1211          dest.write_str("skewY(")?;
1212          y.to_css_with_unitless_zero(dest)?
1213        } else {
1214          dest.write_str("skew(")?;
1215          x.to_css(dest)?;
1216          if !y.is_zero() {
1217            dest.delim(',', false)?;
1218            y.to_css_with_unitless_zero(dest)?;
1219          }
1220        }
1221        dest.write_char(')')
1222      }
1223      SkewX(angle) => {
1224        dest.write_str(if dest.minify { "skew(" } else { "skewX(" })?;
1225        angle.to_css_with_unitless_zero(dest)?;
1226        dest.write_char(')')
1227      }
1228      SkewY(angle) => {
1229        dest.write_str("skewY(")?;
1230        angle.to_css_with_unitless_zero(dest)?;
1231        dest.write_char(')')
1232      }
1233      Perspective(len) => {
1234        dest.write_str("perspective(")?;
1235        len.to_css(dest)?;
1236        dest.write_char(')')
1237      }
1238      Matrix(super::transform::Matrix { a, b, c, d, e, f }) => {
1239        dest.write_str("matrix(")?;
1240        a.to_css(dest)?;
1241        dest.delim(',', false)?;
1242        b.to_css(dest)?;
1243        dest.delim(',', false)?;
1244        c.to_css(dest)?;
1245        dest.delim(',', false)?;
1246        d.to_css(dest)?;
1247        dest.delim(',', false)?;
1248        e.to_css(dest)?;
1249        dest.delim(',', false)?;
1250        f.to_css(dest)?;
1251        dest.write_char(')')
1252      }
1253      Matrix3d(super::transform::Matrix3d {
1254        m11,
1255        m12,
1256        m13,
1257        m14,
1258        m21,
1259        m22,
1260        m23,
1261        m24,
1262        m31,
1263        m32,
1264        m33,
1265        m34,
1266        m41,
1267        m42,
1268        m43,
1269        m44,
1270      }) => {
1271        dest.write_str("matrix3d(")?;
1272        m11.to_css(dest)?;
1273        dest.delim(',', false)?;
1274        m12.to_css(dest)?;
1275        dest.delim(',', false)?;
1276        m13.to_css(dest)?;
1277        dest.delim(',', false)?;
1278        m14.to_css(dest)?;
1279        dest.delim(',', false)?;
1280        m21.to_css(dest)?;
1281        dest.delim(',', false)?;
1282        m22.to_css(dest)?;
1283        dest.delim(',', false)?;
1284        m23.to_css(dest)?;
1285        dest.delim(',', false)?;
1286        m24.to_css(dest)?;
1287        dest.delim(',', false)?;
1288        m31.to_css(dest)?;
1289        dest.delim(',', false)?;
1290        m32.to_css(dest)?;
1291        dest.delim(',', false)?;
1292        m33.to_css(dest)?;
1293        dest.delim(',', false)?;
1294        m34.to_css(dest)?;
1295        dest.delim(',', false)?;
1296        m41.to_css(dest)?;
1297        dest.delim(',', false)?;
1298        m42.to_css(dest)?;
1299        dest.delim(',', false)?;
1300        m43.to_css(dest)?;
1301        dest.delim(',', false)?;
1302        m44.to_css(dest)?;
1303        dest.write_char(')')
1304      }
1305    }
1306  }
1307}
1308
1309impl Transform {
1310  /// Converts the transform to a 3D matrix.
1311  pub fn to_matrix(&self) -> Option<Matrix3d<f32>> {
1312    macro_rules! to_radians {
1313      ($angle: ident) => {{
1314        // If the angle is negative or more than a full circle, we cannot
1315        // safely convert to a matrix. Transforms are interpolated numerically
1316        // when types match, and this will have different results than
1317        // when interpolating matrices with large angles.
1318        // https://www.w3.org/TR/css-transforms-1/#matrix-interpolation
1319        let rad = $angle.to_radians();
1320        if rad < 0.0 || rad >= 2.0 * PI {
1321          return None;
1322        }
1323
1324        rad
1325      }};
1326    }
1327
1328    match &self {
1329      Transform::Translate(LengthPercentage::Dimension(x), LengthPercentage::Dimension(y)) => {
1330        if let (Some(x), Some(y)) = (x.to_px(), y.to_px()) {
1331          return Some(Matrix3d::translate(x, y, 0.0));
1332        }
1333      }
1334      Transform::TranslateX(LengthPercentage::Dimension(x)) => {
1335        if let Some(x) = x.to_px() {
1336          return Some(Matrix3d::translate(x, 0.0, 0.0));
1337        }
1338      }
1339      Transform::TranslateY(LengthPercentage::Dimension(y)) => {
1340        if let Some(y) = y.to_px() {
1341          return Some(Matrix3d::translate(0.0, y, 0.0));
1342        }
1343      }
1344      Transform::TranslateZ(z) => {
1345        if let Some(z) = z.to_px() {
1346          return Some(Matrix3d::translate(0.0, 0.0, z));
1347        }
1348      }
1349      Transform::Translate3d(LengthPercentage::Dimension(x), LengthPercentage::Dimension(y), z) => {
1350        if let (Some(x), Some(y), Some(z)) = (x.to_px(), y.to_px(), z.to_px()) {
1351          return Some(Matrix3d::translate(x, y, z));
1352        }
1353      }
1354      Transform::Scale(x, y) => return Some(Matrix3d::scale(x.into(), y.into(), 1.0)),
1355      Transform::ScaleX(x) => return Some(Matrix3d::scale(x.into(), 1.0, 1.0)),
1356      Transform::ScaleY(y) => return Some(Matrix3d::scale(1.0, y.into(), 1.0)),
1357      Transform::ScaleZ(z) => return Some(Matrix3d::scale(1.0, 1.0, z.into())),
1358      Transform::Scale3d(x, y, z) => return Some(Matrix3d::scale(x.into(), y.into(), z.into())),
1359      Transform::Rotate(angle) | Transform::RotateZ(angle) => {
1360        return Some(Matrix3d::rotate(0.0, 0.0, 1.0, to_radians!(angle)))
1361      }
1362      Transform::RotateX(angle) => return Some(Matrix3d::rotate(1.0, 0.0, 0.0, to_radians!(angle))),
1363      Transform::RotateY(angle) => return Some(Matrix3d::rotate(0.0, 1.0, 0.0, to_radians!(angle))),
1364      Transform::Rotate3d(x, y, z, angle) => return Some(Matrix3d::rotate(*x, *y, *z, to_radians!(angle))),
1365      Transform::Skew(x, y) => return Some(Matrix3d::skew(to_radians!(x), to_radians!(y))),
1366      Transform::SkewX(x) => return Some(Matrix3d::skew(to_radians!(x), 0.0)),
1367      Transform::SkewY(y) => return Some(Matrix3d::skew(0.0, to_radians!(y))),
1368      Transform::Perspective(len) => {
1369        if let Some(len) = len.to_px() {
1370          return Some(Matrix3d::perspective(len));
1371        }
1372      }
1373      Transform::Matrix(m) => return Some(m.to_matrix3d()),
1374      Transform::Matrix3d(m) => return Some(m.clone()),
1375      _ => {}
1376    }
1377    None
1378  }
1379}
1380
1381enum_property! {
1382  /// A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property.
1383  #[allow(missing_docs)]
1384  pub enum TransformStyle {
1385    Flat,
1386    Preserve3d,
1387  }
1388}
1389
1390enum_property! {
1391  /// A value for the [transform-box](https://drafts.csswg.org/css-transforms-1/#transform-box) property.
1392  pub enum TransformBox {
1393    /// Uses the content box as reference box.
1394    ContentBox,
1395    /// Uses the border box as reference box.
1396    BorderBox,
1397    /// Uses the object bounding box as reference box.
1398    FillBox,
1399    /// Uses the stroke bounding box as reference box.
1400    StrokeBox,
1401    /// Uses the nearest SVG viewport as reference box.
1402    ViewBox,
1403  }
1404}
1405
1406enum_property! {
1407  /// A value for the [backface-visibility](https://drafts.csswg.org/css-transforms-2/#backface-visibility-property) property.
1408  #[allow(missing_docs)]
1409  pub enum BackfaceVisibility {
1410    Visible,
1411    Hidden,
1412  }
1413}
1414
1415/// A value for the [perspective](https://drafts.csswg.org/css-transforms-2/#perspective-property) property.
1416#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
1417#[cfg_attr(feature = "visitor", derive(Visit))]
1418#[cfg_attr(
1419  feature = "serde",
1420  derive(serde::Serialize, serde::Deserialize),
1421  serde(tag = "type", content = "value", rename_all = "kebab-case")
1422)]
1423#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1424#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1425pub enum Perspective {
1426  /// No perspective transform is applied.
1427  None,
1428  /// Distance to the center of projection.
1429  Length(Length),
1430}
1431
1432/// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
1433#[derive(Debug, Clone, PartialEq)]
1434#[cfg_attr(feature = "visitor", derive(Visit))]
1435#[cfg_attr(
1436  feature = "serde",
1437  derive(serde::Serialize, serde::Deserialize),
1438  serde(rename_all = "lowercase")
1439)]
1440#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1441#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1442pub enum Translate {
1443  /// The "none" keyword.
1444  None,
1445
1446  /// The x, y, and z translations.
1447  #[cfg_attr(feature = "serde", serde(untagged))]
1448  XYZ {
1449    /// The x translation.
1450    x: LengthPercentage,
1451    /// The y translation.
1452    y: LengthPercentage,
1453    /// The z translation.
1454    z: Length,
1455  },
1456}
1457
1458impl<'i> Parse<'i> for Translate {
1459  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1460    if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
1461      return Ok(Translate::None);
1462    }
1463
1464    let x = LengthPercentage::parse(input)?;
1465    let y = input.try_parse(LengthPercentage::parse);
1466    let z = if y.is_ok() {
1467      input.try_parse(Length::parse).ok()
1468    } else {
1469      None
1470    };
1471
1472    Ok(Translate::XYZ {
1473      x,
1474      y: y.unwrap_or(LengthPercentage::zero()),
1475      z: z.unwrap_or(Length::zero()),
1476    })
1477  }
1478}
1479
1480impl ToCss for Translate {
1481  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1482  where
1483    W: std::fmt::Write,
1484  {
1485    match self {
1486      Translate::None => {
1487        dest.write_str("none")?;
1488      }
1489      Translate::XYZ { x, y, z } => {
1490        x.to_css(dest)?;
1491        if !y.is_zero() || !z.is_zero() {
1492          dest.write_char(' ')?;
1493          y.to_css(dest)?;
1494          if !z.is_zero() {
1495            dest.write_char(' ')?;
1496            z.to_css(dest)?;
1497          }
1498        }
1499      }
1500    };
1501
1502    Ok(())
1503  }
1504}
1505
1506impl Translate {
1507  /// Converts the translation to a transform function.
1508  pub fn to_transform(&self) -> Transform {
1509    match self {
1510      Translate::None => {
1511        Transform::Translate3d(LengthPercentage::zero(), LengthPercentage::zero(), Length::zero())
1512      }
1513      Translate::XYZ { x, y, z } => Transform::Translate3d(x.clone(), y.clone(), z.clone()),
1514    }
1515  }
1516}
1517
1518/// A value for the [rotate](https://drafts.csswg.org/css-transforms-2/#propdef-rotate) property.
1519#[derive(Debug, Clone, PartialEq)]
1520#[cfg_attr(feature = "visitor", derive(Visit))]
1521#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1522#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1523#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1524pub struct Rotate {
1525  /// Rotation around the x axis.
1526  pub x: f32,
1527  /// Rotation around the y axis.
1528  pub y: f32,
1529  /// Rotation around the z axis.
1530  pub z: f32,
1531  /// The angle of rotation.
1532  pub angle: Angle,
1533}
1534
1535impl<'i> Parse<'i> for Rotate {
1536  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1537    if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
1538      return Ok(Rotate {
1539        x: 0.0,
1540        y: 0.0,
1541        z: 1.0,
1542        angle: Angle::Deg(0.0),
1543      });
1544    }
1545
1546    let angle = input.try_parse(Angle::parse);
1547    let (x, y, z) = input
1548      .try_parse(|input| {
1549        let location = input.current_source_location();
1550        let ident = input.expect_ident()?;
1551        match_ignore_ascii_case! { &*ident,
1552          "x" => Ok((1.0, 0.0, 0.0)),
1553          "y" => Ok((0.0, 1.0, 0.0)),
1554          "z" => Ok((0.0, 0.0, 1.0)),
1555          _ => Err(location.new_unexpected_token_error(
1556            cssparser::Token::Ident(ident.clone())
1557          ))
1558        }
1559      })
1560      .or_else(
1561        |_: ParseError<'i, ParserError<'i>>| -> Result<_, ParseError<'i, ParserError<'i>>> {
1562          input.try_parse(|input| Ok((f32::parse(input)?, f32::parse(input)?, f32::parse(input)?)))
1563        },
1564      )
1565      .unwrap_or((0.0, 0.0, 1.0));
1566    let angle = angle.or_else(|_| Angle::parse(input))?;
1567    Ok(Rotate { x, y, z, angle })
1568  }
1569}
1570
1571impl ToCss for Rotate {
1572  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1573  where
1574    W: std::fmt::Write,
1575  {
1576    if self.x == 0.0 && self.y == 0.0 && self.z == 1.0 && self.angle.is_zero() {
1577      dest.write_str("none")?;
1578      return Ok(());
1579    }
1580
1581    if self.x == 1.0 && self.y == 0.0 && self.z == 0.0 {
1582      dest.write_str("x ")?;
1583    } else if self.x == 0.0 && self.y == 1.0 && self.z == 0.0 {
1584      dest.write_str("y ")?;
1585    } else if !(self.x == 0.0 && self.y == 0.0 && self.z == 1.0) {
1586      self.x.to_css(dest)?;
1587      dest.write_char(' ')?;
1588      self.y.to_css(dest)?;
1589      dest.write_char(' ')?;
1590      self.z.to_css(dest)?;
1591      dest.write_char(' ')?;
1592    }
1593
1594    self.angle.to_css(dest)
1595  }
1596}
1597
1598impl Rotate {
1599  /// Converts the rotation to a transform function.
1600  pub fn to_transform(&self) -> Transform {
1601    Transform::Rotate3d(self.x, self.y, self.z, self.angle.clone())
1602  }
1603}
1604
1605/// A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
1606#[derive(Debug, Clone, PartialEq)]
1607#[cfg_attr(feature = "visitor", derive(Visit))]
1608#[cfg_attr(
1609  feature = "serde",
1610  derive(serde::Serialize, serde::Deserialize),
1611  serde(rename_all = "lowercase")
1612)]
1613#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1614#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1615pub enum Scale {
1616  /// The "none" keyword.
1617  None,
1618
1619  /// Scale on the x, y, and z axis.
1620  #[cfg_attr(feature = "serde", serde(untagged))]
1621  XYZ {
1622    /// Scale on the x axis.
1623    x: NumberOrPercentage,
1624    /// Scale on the y axis.
1625    y: NumberOrPercentage,
1626    /// Scale on the z axis.
1627    z: NumberOrPercentage,
1628  },
1629}
1630
1631impl<'i> Parse<'i> for Scale {
1632  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1633    if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
1634      return Ok(Scale::None);
1635    }
1636
1637    let x = NumberOrPercentage::parse(input)?;
1638    let y = input.try_parse(NumberOrPercentage::parse);
1639    let z = if y.is_ok() {
1640      input.try_parse(NumberOrPercentage::parse).ok()
1641    } else {
1642      None
1643    };
1644
1645    Ok(Scale::XYZ {
1646      x: x.clone(),
1647      y: y.unwrap_or(x),
1648      z: z.unwrap_or(NumberOrPercentage::Number(1.0)),
1649    })
1650  }
1651}
1652
1653impl ToCss for Scale {
1654  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1655  where
1656    W: std::fmt::Write,
1657  {
1658    match self {
1659      Scale::None => {
1660        dest.write_str("none")?;
1661      }
1662      Scale::XYZ { x, y, z } => {
1663        x.to_css(dest)?;
1664        let zv: f32 = z.into();
1665        if y != x || zv != 1.0 {
1666          dest.write_char(' ')?;
1667          y.to_css(dest)?;
1668          if zv != 1.0 {
1669            dest.write_char(' ')?;
1670            z.to_css(dest)?;
1671          }
1672        }
1673      }
1674    }
1675
1676    Ok(())
1677  }
1678}
1679
1680impl Scale {
1681  /// Converts the scale to a transform function.
1682  pub fn to_transform(&self) -> Transform {
1683    match self {
1684      Scale::None => Transform::Scale3d(
1685        NumberOrPercentage::Number(1.0),
1686        NumberOrPercentage::Number(1.0),
1687        NumberOrPercentage::Number(1.0),
1688      ),
1689      Scale::XYZ { x, y, z } => Transform::Scale3d(x.clone(), y.clone(), z.clone()),
1690    }
1691  }
1692}
1693
1694#[derive(Default)]
1695pub(crate) struct TransformHandler {
1696  transform: Option<(TransformList, VendorPrefix)>,
1697  translate: Option<Translate>,
1698  rotate: Option<Rotate>,
1699  scale: Option<Scale>,
1700  has_any: bool,
1701}
1702
1703impl<'i> PropertyHandler<'i> for TransformHandler {
1704  fn handle_property(
1705    &mut self,
1706    property: &Property<'i>,
1707    dest: &mut DeclarationList<'i>,
1708    context: &mut PropertyHandlerContext<'i, '_>,
1709  ) -> bool {
1710    use Property::*;
1711
1712    macro_rules! individual_property {
1713      ($prop: ident, $val: ident) => {
1714        if let Some((transform, _)) = &mut self.transform {
1715          transform.0.push($val.to_transform())
1716        } else {
1717          self.$prop = Some($val.clone());
1718          self.has_any = true;
1719        }
1720      };
1721    }
1722
1723    match property {
1724      Transform(val, vp) => {
1725        // If two vendor prefixes for the same property have different
1726        // values, we need to flush what we have immediately to preserve order.
1727        if let Some((cur, prefixes)) = &self.transform {
1728          if cur != val && !prefixes.contains(*vp) {
1729            self.flush(dest, context);
1730          }
1731        }
1732
1733        // Otherwise, update the value and add the prefix.
1734        if let Some((transform, prefixes)) = &mut self.transform {
1735          *transform = val.clone();
1736          *prefixes |= *vp;
1737        } else {
1738          self.transform = Some((val.clone(), *vp));
1739          self.has_any = true;
1740        }
1741
1742        self.translate = None;
1743        self.rotate = None;
1744        self.scale = None;
1745      }
1746      Translate(val) => individual_property!(translate, val),
1747      Rotate(val) => individual_property!(rotate, val),
1748      Scale(val) => individual_property!(scale, val),
1749      Unparsed(val)
1750        if matches!(
1751          val.property_id,
1752          PropertyId::Transform(_) | PropertyId::Translate | PropertyId::Rotate | PropertyId::Scale
1753        ) =>
1754      {
1755        self.flush(dest, context);
1756        let prop = if matches!(val.property_id, PropertyId::Transform(_)) {
1757          Property::Unparsed(val.get_prefixed(context.targets, Feature::Transform))
1758        } else {
1759          property.clone()
1760        };
1761        dest.push(prop)
1762      }
1763      _ => return false,
1764    }
1765
1766    true
1767  }
1768
1769  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
1770    self.flush(dest, context);
1771  }
1772}
1773
1774impl TransformHandler {
1775  fn flush<'i>(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
1776    if !self.has_any {
1777      return;
1778    }
1779
1780    self.has_any = false;
1781
1782    let transform = std::mem::take(&mut self.transform);
1783    let translate = std::mem::take(&mut self.translate);
1784    let rotate = std::mem::take(&mut self.rotate);
1785    let scale = std::mem::take(&mut self.scale);
1786
1787    if let Some((transform, prefix)) = transform {
1788      let prefix = context.targets.prefixes(prefix, Feature::Transform);
1789      dest.push(Property::Transform(transform, prefix))
1790    }
1791
1792    if let Some(translate) = translate {
1793      dest.push(Property::Translate(translate))
1794    }
1795
1796    if let Some(rotate) = rotate {
1797      dest.push(Property::Rotate(rotate))
1798    }
1799
1800    if let Some(scale) = scale {
1801      dest.push(Property::Scale(scale))
1802    }
1803  }
1804}