1use core::ops::Sub;
7use peniko::kurbo::Affine;
8
9const SCALAR_NEARLY_ZERO_F32: f32 = 1.0 / (1 << 12) as f32;
11const SCALAR_NEARLY_ZERO_F64: f64 = 1.0 / (1 << 12) as f64;
12
13pub(crate) trait FloatExt: Sized + Sub<f32, Output = f32> {
15 fn is_nearly_zero(&self) -> bool {
17 self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO_F32)
18 }
19
20 fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool;
22}
23
24impl FloatExt for f32 {
25 #[inline(always)]
26 fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool {
27 debug_assert!(tolerance >= 0.0, "tolerance must be positive");
28
29 self.abs() <= tolerance
30 }
31}
32
33pub(crate) trait AffineExt {
34 fn has_skew(&self) -> bool;
36
37 fn has_vertical_skew(&self) -> bool;
39
40 fn is_positive_uniform_scale_without_skew(&self) -> bool;
42
43 fn is_positive_uniform_scale_without_vertical_skew(&self) -> bool;
45
46 fn has_non_unit_skew_or_scale(&self) -> bool;
50}
51
52impl AffineExt for Affine {
53 #[inline]
54 fn has_skew(&self) -> bool {
55 let [_, b, c, _, _, _] = self.as_coeffs();
56 b.abs() > SCALAR_NEARLY_ZERO_F64 || c.abs() > SCALAR_NEARLY_ZERO_F64
57 }
58
59 #[inline]
60 fn is_positive_uniform_scale_without_skew(&self) -> bool {
61 let [a, _, _, d, _, _] = self.as_coeffs();
62 (a - d).abs() <= SCALAR_NEARLY_ZERO_F64 && a > 0.0 && d > 0.0 && !self.has_skew()
63 }
64
65 #[inline]
66 fn has_non_unit_skew_or_scale(&self) -> bool {
67 let [a, _, _, d, _, _] = self.as_coeffs();
68 self.has_skew()
69 || (1.0 - a.abs()).abs() > SCALAR_NEARLY_ZERO_F64
70 || (1.0 - d.abs()).abs() > SCALAR_NEARLY_ZERO_F64
71 }
72
73 #[inline]
75 fn has_vertical_skew(&self) -> bool {
76 let [_, b, _, _, _, _] = self.as_coeffs();
77 b.abs() > SCALAR_NEARLY_ZERO_F64
78 }
79
80 #[inline]
81 fn is_positive_uniform_scale_without_vertical_skew(&self) -> bool {
82 let [a, _, _, d, _, _] = self.as_coeffs();
83 (a - d).abs() <= SCALAR_NEARLY_ZERO_F64 && a > 0.0 && d > 0.0 && !self.has_vertical_skew()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::AffineExt;
90 use peniko::kurbo::Affine;
91
92 #[test]
93 fn detects_positive_uniform_scale_without_skew() {
94 let transform = Affine::scale(2.0);
95
96 assert!(!transform.has_skew());
97 assert!(!transform.has_vertical_skew());
98 assert!(transform.is_positive_uniform_scale_without_skew());
99 assert!(transform.is_positive_uniform_scale_without_vertical_skew());
100 }
101
102 #[test]
103 fn rejects_positive_uniform_scale_without_skew_when_horizontally_skewed() {
104 let transform = Affine::new([2.0, 0.0, 0.25, 2.0, 0.0, 0.0]);
105
106 assert!(transform.has_skew());
107 assert!(!transform.has_vertical_skew());
108 assert!(!transform.is_positive_uniform_scale_without_skew());
109 assert!(transform.is_positive_uniform_scale_without_vertical_skew());
110 }
111
112 #[test]
113 fn rejects_positive_uniform_scale_without_vertical_skew_when_vertically_skewed() {
114 let transform = Affine::new([2.0, 0.25, 0.0, 2.0, 0.0, 0.0]);
115
116 assert!(transform.has_skew());
117 assert!(transform.has_vertical_skew());
118 assert!(!transform.is_positive_uniform_scale_without_skew());
119 assert!(!transform.is_positive_uniform_scale_without_vertical_skew());
120 }
121
122 #[test]
123 fn rejects_non_uniform_or_non_positive_scale() {
124 let non_uniform = Affine::new([2.0, 0.0, 0.0, 3.0, 0.0, 0.0]);
125 let flipped = Affine::new([-2.0, 0.0, 0.0, -2.0, 0.0, 0.0]);
126
127 assert!(non_uniform.has_non_unit_skew_or_scale());
128 assert!(!non_uniform.is_positive_uniform_scale_without_skew());
129 assert!(!non_uniform.is_positive_uniform_scale_without_vertical_skew());
130 assert!(flipped.has_non_unit_skew_or_scale());
131 assert!(!flipped.is_positive_uniform_scale_without_skew());
132 assert!(!flipped.is_positive_uniform_scale_without_vertical_skew());
133 }
134
135 #[test]
136 fn allows_unit_axis_flips() {
137 let flip_x = Affine::new([-1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
138 let flip_y = Affine::new([1.0, 0.0, 0.0, -1.0, 0.0, 0.0]);
139 let flip_xy = Affine::new([-1.0, 0.0, 0.0, -1.0, 0.0, 0.0]);
140
141 assert!(!flip_x.has_non_unit_skew_or_scale());
142 assert!(!flip_y.has_non_unit_skew_or_scale());
143 assert!(!flip_xy.has_non_unit_skew_or_scale());
144 }
145}