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
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()),
// 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 = resolved.then_translate(translation)
}
if let Some(rotation) = rotate {
resolved = resolved.then_rotate(rotation)
}
if let Some(scale_transform) = scale_transform {
resolved = resolved.then_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
}
}