1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use euclid::default::Rect;
use kurbo::{Affine, Vec2};
use style::{
properties::generated::style_structs::Box as BoxStyleStruct,
values::{
computed::{CSSPixelLength, Rotate},
generics::transform::{Scale, Translate},
},
};
// 6. Current Transformation Matrix
//
// The transformation matrix is computed from the transform, transform-origin, translate, rotate, scale, and offset properties as follows:
//
// - Start with the identity matrix.
// - Translate by the computed X, Y, and Z values of transform-origin.
// - Translate by the computed X, Y, and Z values of translate.
// - Rotate by the computed <angle> about the specified axis of rotate.
// - Scale by the computed X, Y, and Z values of scale.
// - Translate and rotate by the transform specified by offset.
// - Multiply by each of the transform functions in transform from left to right.
// - Translate by the negated computed X, Y and Z values of transform-origin.
//
// <https://drafts.csswg.org/css-transforms-2/#ctm>
pub fn resolve_2d_transform(
box_styles: &BoxStyleStruct,
reference_box: Rect<CSSPixelLength>,
) -> Option<Affine> {
let translate = match &box_styles.translate {
Translate::None => None,
Translate::Translate(x, y, _z) => Some(Vec2 {
x: x.resolve(reference_box.width()).px() as f64,
y: y.resolve(reference_box.height()).px() as f64,
}),
};
let rotate = match &box_styles.rotate {
Rotate::None => None,
Rotate::Rotate(angle) => Some(angle.radians64()),
// A rotation about the z axis stays in the plane; the sign of the
// axis component decides the direction, its magnitude cancels out.
Rotate::Rotate3D(x, y, z, angle) if *x == 0.0 && *y == 0.0 && *z != 0.0 => {
Some(angle.radians64() * z.signum() as f64)
}
// TODO: support 3D transforms
Rotate::Rotate3D(_, _, _, _) => None,
};
let scale_transform = match &box_styles.scale {
Scale::None => None,
Scale::Scale(x, y, _z) => Some(Vec2 {
x: *x as f64,
y: *y as f64,
}),
};
let transform = if box_styles.transform.0.is_empty() {
None
} else {
box_styles
.transform
.to_transform_3d_matrix(Some(&reference_box))
.ok()
// The `has_3d` flag means a 3d function appeared in the list, not that the
// matrix needs 3d: `translate3d(x, y, 0)` sets it while resolving to a
// plain 2d translation.
//
// TODO: support 3D transforms. Outside a 3d rendering context the spec
// still applies the transform as a painting effect -- its own example
// paints `rotateY(50deg)` narrower -- so dropping one is wrong. `is_2d()`
// is closer than the flag, not the rule.
// See: https://drafts.csswg.org/css-transforms-2/#3d-transform-rendering
.filter(|(t, _has_3d)| t.is_2d())
.map(|(t, _)| {
// See: https://drafts.csswg.org/css-transforms-2/#two-dimensional-subset
// And https://docs.rs/kurbo/latest/kurbo/struct.Affine.html#method.new
Affine::new([t.m11, t.m12, t.m21, t.m22, t.m41, t.m42].map(|v| v as f64))
})
};
// TODO: support the "offset" property
// <https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/offset>
if translate.is_none() && rotate.is_none() && scale_transform.is_none() && transform.is_none() {
return None;
}
// Apply the transform origin by:
// - Translating by the origin offset
// - Applying our transform
// - Translating by the inverse of the origin offset
let transform_origin = &box_styles.transform_origin;
let origin_translation = Affine::translate(Vec2 {
x: transform_origin
.horizontal
.resolve(reference_box.width())
.px() as f64,
y: transform_origin
.vertical
.resolve(reference_box.height())
.px() as f64,
});
let mut resolved = Affine::IDENTITY;
if let Some(translation) = translate {
resolved *= Affine::translate(translation)
}
if let Some(rotation) = rotate {
resolved *= Affine::rotate(rotation)
}
if let Some(scale_transform) = scale_transform {
resolved *= Affine::scale_non_uniform(scale_transform.x, scale_transform.y)
}
if let Some(transform) = transform {
resolved *= transform;
}
resolved = origin_translation * resolved * origin_translation.inverse();
if resolved != Affine::IDENTITY {
Some(resolved)
} else {
None
}
}