1use crate::style::properties::filter::interpolate_field;
2use crate::style::{ToCss, unexpected_token};
3use std::{
4 fmt,
5 ops::{Mul, MulAssign},
6};
7
8use cssparser::{Parser, Token, match_ignore_ascii_case};
9use taffy::{Point, Size};
10use tiny_skia::Transform as TinyTransform;
11
12use crate::style::{
13 Angle, Animatable, Color, CssSyntaxKind, CssToken, FromCss, Length, ListInterpolationStrategy,
14 MakeComputed, ParseResult, PercentageNumber, SizingContext, lerp,
15};
16
17const DEFAULT_SCALE: f32 = 1.0;
18
19#[derive(Debug, Clone, Copy, PartialEq)]
21#[non_exhaustive]
22pub enum Transform {
23 Translate(Length, Length),
25 Scale(f32, f32),
27 Rotate(Angle),
29 Skew(Angle, Angle),
31 Matrix(Affine),
33}
34
35impl MakeComputed for Transform {
36 fn make_computed(&mut self, sizing: &SizingContext) {
37 if let Transform::Translate(x, y) = self {
38 x.make_computed(sizing);
39 y.make_computed(sizing);
40 }
41 }
42}
43
44impl Animatable for Transform {
45 fn list_interpolation_strategy() -> ListInterpolationStrategy {
46 ListInterpolationStrategy::PadToLongestWithNeutral
47 }
48
49 fn neutral_value_like(other: &Self) -> Option<Self> {
50 Some(match *other {
51 Transform::Translate(_, _) => Transform::Translate(Length::zero(), Length::zero()),
52 Transform::Scale(_, _) => Transform::Scale(1.0, 1.0),
53 Transform::Rotate(_) => Transform::Rotate(Angle::zero()),
54 Transform::Skew(_, _) => Transform::Skew(Angle::zero(), Angle::zero()),
55 Transform::Matrix(_) => Transform::Matrix(Affine::IDENTITY),
56 })
57 }
58
59 fn interpolate(
60 &mut self,
61 from: &Self,
62 to: &Self,
63 progress: f32,
64 sizing: &SizingContext,
65 current_color: Color,
66 ) {
67 *self = match (*from, *to) {
68 (Transform::Translate(from_x, from_y), Transform::Translate(to_x, to_y)) => {
69 interpolate_field!(
70 Transform::Translate,
71 from_x,
72 to_x,
73 from_y,
74 to_y,
75 progress,
76 sizing,
77 current_color
78 )
79 }
80 (Transform::Scale(from_x, from_y), Transform::Scale(to_x, to_y)) => {
81 Transform::Scale(lerp(from_x, to_x, progress), lerp(from_y, to_y, progress))
82 }
83 (Transform::Rotate(from_angle), Transform::Rotate(to_angle)) => {
84 interpolate_field!(
85 Transform::Rotate,
86 from_angle,
87 to_angle,
88 progress,
89 sizing,
90 current_color
91 )
92 }
93 (Transform::Skew(from_x, from_y), Transform::Skew(to_x, to_y)) => {
94 interpolate_field!(
95 Transform::Skew,
96 from_x,
97 to_x,
98 from_y,
99 to_y,
100 progress,
101 sizing,
102 current_color
103 )
104 }
105 (Transform::Matrix(from_affine), Transform::Matrix(to_affine)) => Transform::Matrix(Affine {
106 a: lerp(from_affine.a, to_affine.a, progress),
107 b: lerp(from_affine.b, to_affine.b, progress),
108 c: lerp(from_affine.c, to_affine.c, progress),
109 d: lerp(from_affine.d, to_affine.d, progress),
110 x: lerp(from_affine.x, to_affine.x, progress),
111 y: lerp(from_affine.y, to_affine.y, progress),
112 }),
113 _ => {
114 if progress >= 0.5 {
115 *to
116 } else {
117 *from
118 }
119 }
120 };
121 }
122}
123
124#[derive(Debug, Clone, Copy, Default, PartialEq)]
128pub struct Affine {
129 pub a: f32,
131 pub b: f32,
133 pub c: f32,
135 pub d: f32,
137 pub x: f32,
139 pub y: f32,
141}
142
143impl From<Affine> for TinyTransform {
144 fn from(transform: Affine) -> Self {
145 TinyTransform::from_row(
146 transform.a,
147 transform.b,
148 transform.c,
149 transform.d,
150 transform.x,
151 transform.y,
152 )
153 }
154}
155
156impl Mul<Affine> for Affine {
157 type Output = Affine;
158
159 fn mul(self, rhs: Affine) -> Self::Output {
160 if self.is_identity() {
161 return rhs;
162 }
163
164 if rhs.is_identity() {
165 return self;
166 }
167
168 Affine {
169 a: self.a * rhs.a + self.c * rhs.b,
170 b: self.b * rhs.a + self.d * rhs.b,
171 c: self.a * rhs.c + self.c * rhs.d,
172 d: self.b * rhs.c + self.d * rhs.d,
173 x: self.a * rhs.x + self.c * rhs.y + self.x,
174 y: self.b * rhs.x + self.d * rhs.y + self.y,
175 }
176 }
177}
178
179impl MulAssign<Affine> for Affine {
180 fn mul_assign(&mut self, rhs: Affine) {
181 *self = *self * rhs;
182 }
183}
184
185impl Affine {
186 pub fn to_cols_array(&self) -> [f32; 6] {
188 [self.a, self.b, self.c, self.d, self.x, self.y]
189 }
190
191 pub const IDENTITY: Self = Self {
193 a: 1.0,
194 b: 0.0,
195 c: 0.0,
196 d: 1.0,
197 x: 0.0,
198 y: 0.0,
199 };
200
201 pub fn is_identity(self) -> bool {
203 (self.a - 1.0).abs() < 1e-6
204 && self.b.abs() < 1e-6
205 && self.c.abs() < 1e-6
206 && (self.d - 1.0).abs() < 1e-6
207 && self.x.abs() < 1e-6
208 && self.y.abs() < 1e-6
209 }
210
211 pub fn decompose_translation(self) -> Point<f32> {
213 Point {
214 x: self.x,
215 y: self.y,
216 }
217 }
218
219 pub fn uniform_scale(self) -> f32 {
221 let sx = (self.a * self.a + self.b * self.b).sqrt();
222 let sy = (self.c * self.c + self.d * self.d).sqrt();
223 (sx * sy).sqrt()
224 }
225
226 pub fn only_translation(self) -> bool {
228 (self.a - 1.0).abs() < 1e-8
229 && self.b.abs() < 1e-8
230 && self.c.abs() < 1e-8
231 && (self.d - 1.0).abs() < 1e-8
232 }
233
234 pub fn rotation(angle: Angle) -> Self {
236 let (sin, cos) = angle.to_radians().sin_cos();
237
238 Self {
239 a: cos,
240 b: sin,
241 c: -sin,
242 d: cos,
243 x: 0.0,
244 y: 0.0,
245 }
246 }
247
248 pub const fn translation(x: f32, y: f32) -> Self {
250 Self {
251 x,
252 y,
253 ..Self::IDENTITY
254 }
255 }
256
257 pub const fn scale(x: f32, y: f32) -> Self {
259 Self {
260 a: x,
261 b: 0.0,
262 c: 0.0,
263 d: y,
264 x: 0.0,
265 y: 0.0,
266 }
267 }
268
269 #[inline(always)]
271 pub fn transform_point(self, point: Point<f32>) -> Point<f32> {
272 if self.only_translation() {
274 return Point {
275 x: point.x + self.x,
276 y: point.y + self.y,
277 };
278 }
279
280 Point {
281 x: self.a * point.x + self.c * point.y + self.x,
282 y: self.b * point.x + self.d * point.y + self.y,
283 }
284 }
285
286 pub fn skew(x: Angle, y: Angle) -> Self {
288 let tanx = x.to_radians().tan();
289 let tany = y.to_radians().tan();
290
291 Self {
292 a: 1.0,
293 b: tany,
294 c: tanx,
295 d: 1.0,
296 x: 0.0,
297 y: 0.0,
298 }
299 }
300
301 #[inline(always)]
303 pub fn determinant(self) -> f32 {
304 self.a * self.d - self.b * self.c
305 }
306
307 #[inline(always)]
309 pub fn is_invertible(self) -> bool {
310 self.determinant().abs() > f32::EPSILON
311 }
312
313 pub fn invert(self) -> Option<Self> {
315 let det = self.determinant();
316 if det.abs() < f32::EPSILON {
317 return None;
318 }
319
320 let inv_det = 1.0 / det;
321
322 Some(Self {
323 a: self.d * inv_det,
324 b: self.b * -inv_det,
325 c: self.c * -inv_det,
326 d: self.a * inv_det,
327 x: (self.d * self.x - self.c * self.y) * -inv_det,
328 y: (self.b * self.x - self.a * self.y) * inv_det,
329 })
330 }
331
332 pub fn from_transforms<'a, I: Iterator<Item = &'a Transform>>(
338 transforms: I,
339 sizing: &SizingContext,
340 border_box: Size<f32>,
341 ) -> Affine {
342 let mut instance = Affine::IDENTITY;
343
344 for transform in transforms {
345 instance *= match *transform {
346 Transform::Translate(x_length, y_length) => Affine::translation(
347 x_length.to_px(sizing, border_box.width),
348 y_length.to_px(sizing, border_box.height),
349 ),
350 Transform::Scale(x_scale, y_scale) => Affine::scale(x_scale, y_scale),
351 Transform::Rotate(angle) => Affine::rotation(angle),
352 Transform::Skew(x_angle, y_angle) => Affine::skew(x_angle, y_angle),
353 Transform::Matrix(affine) => affine,
354 };
355 }
356
357 instance
358 }
359}
360
361impl<'i> FromCss<'i> for Affine {
362 fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
363 let a = input.expect_number()?;
364 input.expect_comma()?;
365 let b = input.expect_number()?;
366 input.expect_comma()?;
367 let c = input.expect_number()?;
368 input.expect_comma()?;
369 let d = input.expect_number()?;
370 input.expect_comma()?;
371 let x = input.expect_number()?;
372 input.expect_comma()?;
373 let y = input.expect_number()?;
374
375 Ok(Affine { a, b, c, d, x, y })
376 }
377
378 const VALID_TOKENS: &'static [CssToken] = &[CssToken::Syntax(CssSyntaxKind::Number)];
379}
380
381#[derive(Debug, Clone, PartialEq)]
383pub struct Transforms(pub Box<[Transform]>);
384
385impl<'i> FromCss<'i> for Transforms {
386 fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
387 let mut transforms = Vec::new();
388
389 while !input.is_exhausted() {
390 let transform = Transform::from_css(input)?;
391 transforms.push(transform);
392 }
393
394 Ok(Transforms(transforms.into_boxed_slice()))
395 }
396
397 const VALID_TOKENS: &'static [CssToken] = Transform::VALID_TOKENS;
398}
399
400impl MakeComputed for Transforms {
401 fn make_computed(&mut self, sizing: &SizingContext) {
402 for transform in self.0.iter_mut() {
403 transform.make_computed(sizing);
404 }
405 }
406}
407
408impl Animatable for Transforms {
409 fn missing_value() -> Option<Self> {
410 <Box<[Transform]>>::missing_value().map(Self)
411 }
412
413 fn interpolate(
414 &mut self,
415 from: &Self,
416 to: &Self,
417 progress: f32,
418 sizing: &SizingContext,
419 current_color: Color,
420 ) {
421 self
422 .0
423 .interpolate(&from.0, &to.0, progress, sizing, current_color);
424 }
425}
426
427impl std::ops::Deref for Transforms {
428 type Target = Box<[Transform]>;
429 fn deref(&self) -> &Self::Target {
430 &self.0
431 }
432}
433
434impl std::ops::DerefMut for Transforms {
435 fn deref_mut(&mut self) -> &mut Self::Target {
436 &mut self.0
437 }
438}
439
440impl From<Box<[Transform]>> for Transforms {
441 fn from(box_slice: Box<[Transform]>) -> Self {
442 Self(box_slice)
443 }
444}
445
446impl From<Vec<Transform>> for Transforms {
447 fn from(vec: Vec<Transform>) -> Self {
448 Self(vec.into_boxed_slice())
449 }
450}
451
452impl<const N: usize> From<[Transform; N]> for Transforms {
453 fn from(arr: [Transform; N]) -> Self {
454 Self(Box::from(arr))
455 }
456}
457
458impl ToCss for Transform {
459 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
460 match self {
461 Self::Translate(x, y) => {
462 dest.write_str("translate(")?;
463 x.to_css(dest)?;
464 dest.write_str(", ")?;
465 y.to_css(dest)?;
466 dest.write_char(')')
467 }
468 Self::Scale(x, y) => write!(dest, "scale({x}, {y})"),
469 Self::Rotate(a) => {
470 dest.write_str("rotate(")?;
471 a.to_css(dest)?;
472 dest.write_char(')')
473 }
474 Self::Skew(x, y) => {
475 dest.write_str("skew(")?;
476 x.to_css(dest)?;
477 dest.write_str(", ")?;
478 y.to_css(dest)?;
479 dest.write_char(')')
480 }
481 Self::Matrix(Affine { a, b, c, d, x, y }) => {
482 write!(dest, "matrix({a}, {b}, {c}, {d}, {x}, {y})")
483 }
484 }
485 }
486}
487
488impl ToCss for Affine {
489 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
490 let Self { a, b, c, d, x, y } = self;
491 write!(dest, "matrix({a}, {b}, {c}, {d}, {x}, {y})")
492 }
493}
494
495impl ToCss for Transforms {
496 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
497 let mut first = true;
498 for transform in self.iter() {
499 if !first {
500 dest.write_char(' ')?;
501 }
502 transform.to_css(dest)?;
503 first = false;
504 }
505 Ok(())
506 }
507}
508
509impl<'i> FromCss<'i> for Transform {
510 fn from_css(parser: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
511 let location = parser.current_source_location();
512 let token = parser.next()?;
513
514 let Token::Function(function) = token else {
515 return Err(
516 location
517 .new_basic_unexpected_token_error(token.clone())
518 .into(),
519 );
520 };
521
522 match_ignore_ascii_case! {function,
523 "translate" => parser.parse_nested_block(|input| {
524 let x = Length::from_css(input)?;
525 input.expect_comma()?;
526 let y = Length::from_css(input)?;
527
528 Ok(Transform::Translate(x, y))
529 }),
530 "translatex" => parser.parse_nested_block(|input| Ok(Transform::Translate(
531 Length::from_css(input)?,
532 Length::zero(),
533 ))),
534 "translatey" => parser.parse_nested_block(|input| Ok(Transform::Translate(
535 Length::zero(),
536 Length::from_css(input)?,
537 ))),
538 "scale" => parser.parse_nested_block(|input| {
539 let PercentageNumber(x) = PercentageNumber::from_css(input)?;
540 if input.try_parse(Parser::expect_comma).is_ok() {
541 let PercentageNumber(y) = PercentageNumber::from_css(input)?;
542 Ok(Transform::Scale(x, y))
543 } else {
544 Ok(Transform::Scale(x, x))
545 }
546 }),
547 "scalex" => parser.parse_nested_block(|input| Ok(Transform::Scale(
548 PercentageNumber::from_css(input)?.0,
549 DEFAULT_SCALE,
550 ))),
551 "scaley" => parser.parse_nested_block(|input| Ok(Transform::Scale(
552 DEFAULT_SCALE,
553 PercentageNumber::from_css(input)?.0,
554 ))),
555 "skew" => parser.parse_nested_block(|input| {
556 let x = Angle::from_css(input)?;
557 input.expect_comma()?;
558 let y = Angle::from_css(input)?;
559
560 Ok(Transform::Skew(x, y))
561 }),
562 "skewx" => parser.parse_nested_block(|input| Ok(Transform::Skew(
563 Angle::from_css(input)?,
564 Angle::default(),
565 ))),
566 "skewy" => parser.parse_nested_block(|input| Ok(Transform::Skew(
567 Angle::default(),
568 Angle::from_css(input)?,
569 ))),
570 "rotate" => parser.parse_nested_block(|input| Ok(Transform::Rotate(
571 Angle::from_css(input)?,
572 ))),
573 "matrix" => parser.parse_nested_block(|input| Ok(Transform::Matrix(
574 Affine::from_css(input)?,
575 ))),
576 _ => Err(unexpected_token!(location, token)),
577 }
578 }
579
580 const VALID_TOKENS: &'static [CssToken] = &[CssToken::Syntax(CssSyntaxKind::TransformFunction)];
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 #[test]
588 fn test_transform_from_str() {
589 assert_eq!(
590 Transform::from_str("translate(10, 20px)"),
591 Ok(Transform::Translate(Length::Px(10.0), Length::Px(20.0)))
592 );
593 }
594
595 #[test]
596 fn test_transform_scale_from_str() {
597 assert_eq!(
598 Transform::from_str("scale(10)"),
599 Ok(Transform::Scale(10.0, 10.0))
600 );
601 }
602
603 #[test]
604 fn test_transform_invert() {
605 let transform = Affine::rotation(Angle::new(45.0));
606
607 assert!(transform.invert().is_some_and(|inverse| {
608 let random_point = Point {
609 x: 1234.0,
610 y: -5678.0,
611 };
612
613 let processed_point = inverse.transform_point(transform.transform_point(random_point));
614
615 (random_point.x - processed_point.x).abs() < 1.0
616 && (random_point.y - processed_point.y).abs() < 1.0
617 }));
618 }
619}