Skip to main content

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