1use crate::geom::{LogicalPosition, LogicalSize};
6
7#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
10#[repr(C)]
11pub struct ResolvedOffsets {
12 pub top: f32,
13 pub left: f32,
14 pub right: f32,
15 pub bottom: f32,
16}
17
18impl ResolvedOffsets {
19 #[must_use] pub const fn zero() -> Self {
20 Self {
21 top: 0.0,
22 left: 0.0,
23 right: 0.0,
24 bottom: 0.0,
25 }
26 }
27 #[must_use]
28 pub fn total_vertical(&self) -> f32 {
29 self.top + self.bottom
30 }
31 #[must_use]
32 pub fn total_horizontal(&self) -> f32 {
33 self.left + self.right
34 }
35}
36
37type GlyphIndex = u32;
39
40#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd)]
42pub struct GlyphInstance {
43 pub index: GlyphIndex,
44 pub point: LogicalPosition,
45 pub size: LogicalSize,
46}
47
48impl GlyphInstance {
49 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
50 self.point.scale_for_dpi(scale_factor);
51 self.size.scale_for_dpi(scale_factor);
52 }
53}
54
55#[cfg(test)]
56mod autotest_generated {
57 use super::*;
58
59 fn offsets(top: f32, left: f32, right: f32, bottom: f32) -> ResolvedOffsets {
60 ResolvedOffsets {
61 top,
62 left,
63 right,
64 bottom,
65 }
66 }
67
68 fn glyph(index: u32, x: f32, y: f32, w: f32, h: f32) -> GlyphInstance {
69 GlyphInstance {
70 index,
71 point: LogicalPosition::new(x, y),
72 size: LogicalSize::new(w, h),
73 }
74 }
75
76 #[test]
79 fn zero_is_all_zeroes_and_usable_in_const_context() {
80 const Z: ResolvedOffsets = ResolvedOffsets::zero();
81 assert_eq!(Z.top, 0.0);
82 assert_eq!(Z.left, 0.0);
83 assert_eq!(Z.right, 0.0);
84 assert_eq!(Z.bottom, 0.0);
85 assert!(Z.top.is_sign_positive());
87 assert!(Z.left.is_sign_positive());
88 assert!(Z.right.is_sign_positive());
89 assert!(Z.bottom.is_sign_positive());
90 }
91
92 #[test]
93 fn zero_matches_default_and_is_neutral_for_totals() {
94 assert_eq!(ResolvedOffsets::zero(), ResolvedOffsets::default());
95 assert_eq!(ResolvedOffsets::zero().total_vertical(), 0.0);
96 assert_eq!(ResolvedOffsets::zero().total_horizontal(), 0.0);
97 }
98
99 #[test]
102 fn totals_sum_only_their_own_axis() {
103 let o = offsets(1.0, 20.0, 300.0, 4000.0);
104 assert_eq!(o.total_vertical(), 4001.0); assert_eq!(o.total_horizontal(), 320.0); let o2 = offsets(1.0, -20.0, -300.0, 4000.0);
108 assert_eq!(o2.total_vertical(), o.total_vertical());
109 assert_eq!(o2.total_horizontal(), -320.0);
110 }
111
112 #[test]
113 fn totals_handle_negative_and_cancelling_offsets() {
114 let o = offsets(-5.0, -2.5, 2.5, 5.0);
115 assert_eq!(o.total_vertical(), 0.0);
116 assert_eq!(o.total_horizontal(), 0.0);
117 }
118
119 #[test]
120 fn totals_saturate_to_infinity_instead_of_wrapping() {
121 let o = offsets(f32::MAX, f32::MAX, f32::MAX, f32::MAX);
122 assert!(o.total_vertical().is_infinite() && o.total_vertical().is_sign_positive());
123 assert!(o.total_horizontal().is_infinite() && o.total_horizontal().is_sign_positive());
124
125 let o = offsets(f32::MIN, f32::MIN, f32::MIN, f32::MIN);
126 assert!(o.total_vertical().is_infinite() && o.total_vertical().is_sign_negative());
127 assert!(o.total_horizontal().is_infinite() && o.total_horizontal().is_sign_negative());
128 }
129
130 #[test]
131 fn totals_of_opposing_infinities_are_nan_not_a_panic() {
132 let o = offsets(f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY);
133 assert!(o.total_vertical().is_nan());
134 assert!(o.total_horizontal().is_nan());
135 }
136
137 #[test]
138 fn totals_propagate_nan() {
139 let o = offsets(f32::NAN, 1.0, 2.0, 10.0);
140 assert!(o.total_vertical().is_nan());
141 assert_eq!(o.total_horizontal(), 3.0); let o = offsets(1.0, f32::NAN, 2.0, 10.0);
144 assert_eq!(o.total_vertical(), 11.0);
145 assert!(o.total_horizontal().is_nan());
146 }
147
148 #[test]
149 fn totals_on_subnormals_do_not_flush_to_a_wrong_value() {
150 let o = offsets(f32::MIN_POSITIVE, f32::MIN_POSITIVE, 0.0, 0.0);
151 assert_eq!(o.total_vertical(), f32::MIN_POSITIVE);
152 assert_eq!(o.total_horizontal(), f32::MIN_POSITIVE);
153 }
154
155 #[test]
156 fn totals_are_pure_getters() {
157 let o = offsets(3.0, 7.0, 11.0, 13.0);
158 let before = o;
159 let _ = o.total_vertical();
160 let _ = o.total_horizontal();
161 assert_eq!(o, before);
162 let (v1, v2) = (o.total_vertical(), o.total_vertical());
164 let (h1, h2) = (o.total_horizontal(), o.total_horizontal());
165 assert_eq!(v1, v2);
166 assert_eq!(h1, h2);
167 }
168
169 #[test]
172 fn scale_for_dpi_by_one_is_identity_and_never_touches_the_glyph_index() {
173 let mut g = glyph(u32::MAX, 1.5, -2.5, 3.5, 4.5);
174 g.scale_for_dpi(1.0);
175 assert_eq!(g.index, u32::MAX);
176 assert_eq!(g.point.x, 1.5);
177 assert_eq!(g.point.y, -2.5);
178 assert_eq!(g.size.width, 3.5);
179 assert_eq!(g.size.height, 4.5);
180 }
181
182 #[test]
183 fn scale_for_dpi_by_zero_collapses_to_zero_and_preserves_sign() {
184 let mut g = glyph(7, 10.0, -10.0, 20.0, -20.0);
185 g.scale_for_dpi(0.0);
186 assert_eq!(g.index, 7);
187 assert_eq!(g.point.x, 0.0);
188 assert_eq!(g.point.y, 0.0);
189 assert!(g.point.x.is_sign_positive());
190 assert!(g.point.y.is_sign_negative()); assert_eq!(g.size.width, 0.0);
192 assert_eq!(g.size.height, 0.0);
193 }
194
195 #[test]
196 fn scale_for_dpi_negative_factor_mirrors_deterministically() {
197 let mut g = glyph(1, 2.0, -4.0, 8.0, -16.0);
198 g.scale_for_dpi(-2.0);
199 assert_eq!(g.point.x, -4.0);
200 assert_eq!(g.point.y, 8.0);
201 assert_eq!(g.size.width, -16.0);
202 assert_eq!(g.size.height, 32.0);
203 }
204
205 #[test]
206 fn scale_for_dpi_round_trips_for_exact_binary_factors() {
207 let original = glyph(42, 12.0, -6.5, 100.0, 0.25);
208 let mut g = original;
209 g.scale_for_dpi(4.0);
210 g.scale_for_dpi(0.25);
211 assert_eq!(g.index, original.index);
212 assert_eq!(g.point.x, original.point.x);
213 assert_eq!(g.point.y, original.point.y);
214 assert_eq!(g.size.width, original.size.width);
215 assert_eq!(g.size.height, original.size.height);
216 }
217
218 #[test]
219 fn scale_for_dpi_overflows_to_infinity_rather_than_wrapping() {
220 let mut g = glyph(0, f32::MAX, -f32::MAX, f32::MAX, f32::MAX);
221 g.scale_for_dpi(2.0);
222 assert!(g.point.x.is_infinite() && g.point.x.is_sign_positive());
223 assert!(g.point.y.is_infinite() && g.point.y.is_sign_negative());
224 assert!(g.size.width.is_infinite());
225 assert!(g.size.height.is_infinite());
226 }
227
228 #[test]
229 fn scale_for_dpi_underflows_to_zero_rather_than_panicking() {
230 let mut g = glyph(0, f32::MIN_POSITIVE, f32::MIN_POSITIVE, f32::MIN_POSITIVE, 1.0);
231 g.scale_for_dpi(f32::MIN_POSITIVE);
232 assert_eq!(g.point.x, 0.0);
233 assert_eq!(g.point.y, 0.0);
234 assert_eq!(g.size.width, 0.0);
235 assert_eq!(g.size.height, f32::MIN_POSITIVE);
236 }
237
238 #[test]
239 fn scale_for_dpi_with_nan_factor_poisons_all_coordinates_without_panicking() {
240 let mut g = glyph(3, 1.0, 2.0, 3.0, 4.0);
241 g.scale_for_dpi(f32::NAN);
242 assert_eq!(g.index, 3);
243 assert!(g.point.x.is_nan());
244 assert!(g.point.y.is_nan());
245 assert!(g.size.width.is_nan());
246 assert!(g.size.height.is_nan());
247 }
248
249 #[test]
250 fn scale_for_dpi_with_infinite_factor_is_defined_at_zero_and_nonzero_coords() {
251 let mut g = glyph(0, 1.0, -1.0, 2.0, -2.0);
253 g.scale_for_dpi(f32::INFINITY);
254 assert!(g.point.x.is_infinite() && g.point.x.is_sign_positive());
255 assert!(g.point.y.is_infinite() && g.point.y.is_sign_negative());
256 assert!(g.size.width.is_infinite() && g.size.width.is_sign_positive());
257 assert!(g.size.height.is_infinite() && g.size.height.is_sign_negative());
258
259 let mut g = glyph(0, 0.0, 0.0, 0.0, 0.0);
261 g.scale_for_dpi(f32::INFINITY);
262 assert!(g.point.x.is_nan());
263 assert!(g.size.width.is_nan());
264
265 let mut g = glyph(0, 1.0, 1.0, 1.0, 1.0);
267 g.scale_for_dpi(f32::NEG_INFINITY);
268 assert!(g.point.x.is_infinite() && g.point.x.is_sign_negative());
269 assert!(g.size.height.is_infinite() && g.size.height.is_sign_negative());
270 }
271
272 #[test]
273 fn scale_for_dpi_on_default_glyph_is_a_no_op_for_finite_factors() {
274 let mut g = GlyphInstance::default();
275 g.scale_for_dpi(1_000_000.0);
276 assert_eq!(g.index, 0);
277 assert_eq!(g.point.x, 0.0);
278 assert_eq!(g.point.y, 0.0);
279 assert_eq!(g.size.width, 0.0);
280 assert_eq!(g.size.height, 0.0);
281 }
282
283 #[test]
284 fn glyph_equality_stays_reflexive_after_a_nan_scale() {
285 let mut g = glyph(9, 1.0, 2.0, 3.0, 4.0);
288 g.scale_for_dpi(f32::NAN);
289 let same = g;
290 assert_eq!(g, same);
291 assert_ne!(g, glyph(9, 0.0, 0.0, 0.0, 0.0));
293 }
294}