1use super::Point;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq)]
21pub struct Size {
22 pub width: f64,
24 pub height: f64,
26}
27
28impl Size {
29 pub const fn new(width: f64, height: f64) -> Self {
31 Self { width, height }
32 }
33
34 pub fn to_rect(self) -> Rect {
36 Rect::from_size(self)
37 }
38}
39
40#[derive(Clone, Copy, Debug, Default, PartialEq)]
45pub enum Pivot {
46 TopLeft,
48 Top,
50 TopRight,
52 Left,
54 #[default]
56 Center,
57 Right,
59 BottomLeft,
61 Bottom,
63 BottomRight,
65 Baseline,
67 Relative(f64, f64),
69 Point(Point),
71}
72
73#[derive(Clone, Copy, Debug, Default, PartialEq)]
92pub struct Rect {
93 pub left: f64,
95 pub top: f64,
97 pub right: f64,
99 pub bottom: f64,
101}
102
103impl Rect {
104 pub const fn new(left: f64, top: f64, right: f64, bottom: f64) -> Self {
106 Self {
107 left,
108 top,
109 right,
110 bottom,
111 }
112 }
113
114 pub fn from_origin_size(origin: Point, size: Size) -> Self {
116 Self::new(
117 origin.x,
118 origin.y,
119 origin.x + size.width,
120 origin.y + size.height,
121 )
122 }
123
124 pub fn from_center_size(center: Point, size: Size) -> Self {
126 Self::new(
127 center.x - size.width * 0.5,
128 center.y - size.height * 0.5,
129 center.x + size.width * 0.5,
130 center.y + size.height * 0.5,
131 )
132 }
133
134 pub fn from_size(size: Size) -> Self {
136 Self::from_origin_size(Point::ORIGIN, size)
137 }
138
139 pub fn width(self) -> f64 {
141 self.right - self.left
142 }
143
144 pub fn height(self) -> f64 {
146 self.bottom - self.top
147 }
148
149 pub fn size(self) -> Size {
151 Size::new(self.width(), self.height())
152 }
153
154 pub fn center(self) -> Point {
156 Point::new(
157 self.left.midpoint(self.right),
158 self.top.midpoint(self.bottom),
159 )
160 }
161
162 pub fn anchor(self, pivot: Pivot) -> Point {
167 fn relative(start: f64, end: f64, fraction: f64) -> f64 {
168 match fraction {
169 0.0 => start,
170 1.0 => end,
171 0.5 => start.midpoint(end),
172 _ if start.is_sign_negative() != end.is_sign_negative() => {
173 let middle = start.midpoint(end);
174 if fraction < 0.5 {
175 start + (middle - start) * (fraction * 2.0)
176 } else {
177 end + (middle - end) * ((1.0 - fraction) * 2.0)
178 }
179 }
180 _ => start + (end - start) * fraction,
181 }
182 }
183 let center = self.center();
184 match pivot {
185 Pivot::TopLeft => Point::new(self.left, self.top),
186 Pivot::Top => Point::new(center.x, self.top),
187 Pivot::TopRight => Point::new(self.right, self.top),
188 Pivot::Left => Point::new(self.left, center.y),
189 Pivot::Center => center,
190 Pivot::Right => Point::new(self.right, center.y),
191 Pivot::BottomLeft | Pivot::Baseline => Point::new(self.left, self.bottom),
192 Pivot::Bottom => Point::new(center.x, self.bottom),
193 Pivot::BottomRight => Point::new(self.right, self.bottom),
194 Pivot::Relative(horizontal, vertical) => Point::new(
195 relative(self.left, self.right, horizontal),
196 relative(self.top, self.bottom, vertical),
197 ),
198 Pivot::Point(point) => point,
199 }
200 }
201
202 pub fn is_finite(self) -> bool {
204 [self.left, self.top, self.right, self.bottom]
205 .iter()
206 .all(|value| value.is_finite())
207 }
208
209 pub fn is_empty(self) -> bool {
212 self.is_finite() && (self.right <= self.left || self.bottom <= self.top)
213 }
214
215 pub fn contains(self, point: Point) -> bool {
217 self.is_finite()
218 && point.x.is_finite()
219 && point.y.is_finite()
220 && point.x >= self.left
221 && point.x < self.right
222 && point.y >= self.top
223 && point.y < self.bottom
224 }
225
226 pub fn union(self, other: Self) -> Self {
229 if !self.is_finite() {
230 return self;
231 }
232 if !other.is_finite() {
233 return other;
234 }
235 if self.is_empty() {
236 return other;
237 }
238 if other.is_empty() {
239 return self;
240 }
241 Self::new(
242 self.left.min(other.left),
243 self.top.min(other.top),
244 self.right.max(other.right),
245 self.bottom.max(other.bottom),
246 )
247 }
248
249 pub fn intersect(self, other: Self) -> Self {
252 if !self.is_finite() {
253 return self;
254 }
255 if !other.is_finite() {
256 return other;
257 }
258 let left = self.left.max(other.left);
259 let top = self.top.max(other.top);
260 Self::new(
261 left,
262 top,
263 self.right.min(other.right).max(left),
264 self.bottom.min(other.bottom).max(top),
265 )
266 }
267
268 pub fn inset(self, amount: f64) -> Self {
271 Self::new(
272 self.left + amount,
273 self.top + amount,
274 self.right - amount,
275 self.bottom - amount,
276 )
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 #[test]
283 fn pivot_anchors_cover_corners_edges_center_and_nontext_baseline() {
284 use super::{Pivot, Rect};
285 use crate::Point;
286 let bounds = Rect::new(10.0, 20.0, 50.0, 80.0);
287 for (pivot, expected) in [
288 (Pivot::TopLeft, (10.0, 20.0)),
289 (Pivot::Top, (30.0, 20.0)),
290 (Pivot::TopRight, (50.0, 20.0)),
291 (Pivot::Left, (10.0, 50.0)),
292 (Pivot::Center, (30.0, 50.0)),
293 (Pivot::Right, (50.0, 50.0)),
294 (Pivot::BottomLeft, (10.0, 80.0)),
295 (Pivot::Bottom, (30.0, 80.0)),
296 (Pivot::BottomRight, (50.0, 80.0)),
297 (Pivot::Baseline, (10.0, 80.0)),
298 ] {
299 assert_eq!(bounds.anchor(pivot), Point::new(expected.0, expected.1));
300 }
301 assert_eq!(Pivot::default(), Pivot::Center);
302 assert_eq!(
303 bounds.anchor(Pivot::Relative(-0.25, 1.5)),
304 Point::new(0.0, 110.0)
305 );
306 assert_eq!(
307 bounds.anchor(Pivot::Point(Point::new(-8.0, 5.0))),
308 Point::new(-8.0, 5.0)
309 );
310 }
311
312 #[test]
313 fn pivot_relative_positions_preserve_extreme_midpoints_and_subnormals() {
314 use super::{Pivot, Rect};
315 use crate::Point;
316 let wide = Rect::new(-f64::MAX, 0.0, f64::MAX, f64::from_bits(1));
317 assert_eq!(
318 wide.anchor(Pivot::Relative(0.5, 1.0)),
319 Point::new(0.0, f64::from_bits(1))
320 );
321 assert_eq!(wide.anchor(Pivot::Relative(0.25, 0.0)).x, -f64::MAX * 0.5);
322 let tiny = f64::from_bits(1);
323 let equal = Rect::new(tiny, tiny, tiny, tiny);
324 assert_eq!(
325 equal.anchor(Pivot::Relative(0.5, 0.5)),
326 Point::new(tiny, tiny)
327 );
328 let inverted = Rect::new(50.0, 80.0, 10.0, 20.0);
329 assert_eq!(inverted.anchor(Pivot::TopLeft), Point::new(50.0, 80.0));
330 assert_eq!(
331 inverted.anchor(Pivot::Relative(0.25, 0.5)),
332 Point::new(40.0, 50.0)
333 );
334 }
335
336 use super::*;
337 use crate::{Interpolate, Rect, Size};
338
339 #[test]
340 fn constructors_use_opposite_corners_and_preserve_double_precision() {
341 let bounds = Rect::new(10.0, 20.0, 40.0, 30.0);
342 assert_eq!(bounds.size(), Size::new(30.0, 10.0));
343 assert_eq!(
344 Rect::from_origin_size(Point::new(10.0, 20.0), bounds.size()),
345 bounds
346 );
347 assert_eq!(
348 Rect::from_center_size(Point::new(25.0, 25.0), bounds.size()),
349 bounds
350 );
351 assert_eq!(bounds.size().to_rect(), Rect::new(0.0, 0.0, 30.0, 10.0));
352 let origin = 1_000_000_000.0;
353 let precise = Rect::new(origin, 0.0, origin + 0.25, 0.5);
354 assert_eq!(precise.width(), 0.25);
355 assert_eq!(precise.center(), Point::new(origin + 0.125, 0.25));
356 assert_eq!(precise.left as f32, precise.right as f32);
357 assert_eq!(Size::new(-1.0, 2.0).to_rect().width(), -1.0);
358 }
359
360 #[test]
361 fn containment_intersection_union_and_inset_have_explicit_empty_edges() {
362 let bounds = Rect::new(10.0, 20.0, 40.0, 30.0);
363 assert!(bounds.contains(Point::new(10.0, 20.0)));
364 assert!(!bounds.contains(Point::new(40.0, 20.0)));
365 assert!(!bounds.contains(Point::new(10.0, 30.0)));
366 assert!(!bounds.contains(Point::new(f64::NAN, 25.0)));
367 let adjacent = Rect::new(40.0, 20.0, 50.0, 30.0);
368 assert_eq!(
369 bounds.intersect(adjacent),
370 Rect::new(40.0, 20.0, 40.0, 30.0)
371 );
372 assert!(bounds.intersect(adjacent).is_empty());
373 assert_eq!(bounds.union(adjacent), Rect::new(10.0, 20.0, 50.0, 30.0));
374 assert_eq!(bounds.union(Rect::default()), bounds);
375 assert_eq!(Rect::default().union(bounds), bounds);
376 assert_eq!(bounds.inset(2.0), Rect::new(12.0, 22.0, 38.0, 28.0));
377 assert_eq!(bounds.inset(-2.0), Rect::new(8.0, 18.0, 42.0, 32.0));
378 let inverted = bounds.inset(20.0);
379 assert!(inverted.is_empty());
380 assert!(!inverted.contains(inverted.center()));
381 assert!(bounds.intersect(inverted).is_empty());
382 assert_eq!(bounds.union(inverted), bounds);
383 assert_eq!(
384 bounds.intersect(Rect::new(50.0, 50.0, 60.0, 60.0)),
385 Rect::new(50.0, 50.0, 50.0, 50.0)
386 );
387 }
388
389 #[test]
390 fn extreme_midpoints_remain_finite_and_nonfinite_bounds_are_not_silently_discarded() {
391 let large = Rect::new(1e308, -1.4e308, 1.4e308, -1e308);
392 assert_eq!(large.center(), Point::new(1.2e308, -1.2e308));
393 assert_eq!(
394 Rect::new(-f64::MAX, 0.0, f64::MAX, 2.0).center(),
395 Point::new(0.0, 1.0)
396 );
397 let smallest = f64::from_bits(1);
398 assert_eq!(
399 Rect::new(smallest, smallest, smallest, smallest).center(),
400 Point::new(smallest, smallest)
401 );
402 assert_eq!(
403 Rect::new(smallest, smallest, smallest * 2.0, smallest * 2.0).center(),
404 Point::new(smallest * 2.0, smallest * 2.0)
405 );
406 let valid = Rect::new(0.0, 0.0, 10.0, 10.0);
407 for invalid in [
408 Rect::new(f64::NAN, 0.0, 10.0, 10.0),
409 Rect::new(0.0, 0.0, f64::INFINITY, 10.0),
410 ] {
411 assert!(!invalid.is_finite());
412 assert!(!invalid.is_empty());
413 assert!(!invalid.contains(Point::ORIGIN));
414 assert!(!valid.union(invalid).is_finite());
415 assert!(!invalid.union(valid).is_finite());
416 assert!(!valid.intersect(invalid).is_finite());
417 assert!(!invalid.intersect(valid).is_finite());
418 }
419 }
420
421 #[test]
422 fn interpolation_retains_f64_progress_and_signed_extrapolated_values() {
423 let progress = 0.500_000_000_1;
424 let size = Size::default().interpolate(&Size::new(1.0, 2.0), progress);
425 assert_eq!(size, Size::new(progress, 2.0 * progress));
426 assert_ne!(size.width, f64::from(progress as f32));
427 let bounds = Rect::default().interpolate(&Rect::new(1.0, 2.0, 3.0, 4.0), progress);
428 assert_eq!(
429 bounds,
430 Rect::new(progress, 2.0 * progress, 3.0 * progress, 4.0 * progress)
431 );
432 let inverted = Rect::new(0.0, 0.0, 10.0, 20.0).interpolate(&Rect::default(), 2.0);
433 assert_eq!(inverted.size(), Size::new(-10.0, -20.0));
434 assert!(inverted.is_empty());
435 assert_eq!(
436 Size::new(10.0, 20.0).interpolate(&Size::default(), 2.0),
437 Size::new(-10.0, -20.0)
438 );
439 }
440}