1use core::fmt;
6
7use crate::{
8 impl_option, impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_mut, impl_vec_partialeq,
9 impl_vec_partialord,
10};
11
12#[derive(Copy, Default, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
14#[repr(C)]
15pub struct LayoutPoint {
16 pub x: isize,
17 pub y: isize,
18}
19
20impl fmt::Debug for LayoutPoint {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 write!(f, "{self}")
23 }
24}
25impl fmt::Display for LayoutPoint {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "({}, {})", self.x, self.y)
28 }
29}
30
31impl LayoutPoint {
32 #[inline]
33 #[must_use] pub const fn new(x: isize, y: isize) -> Self {
34 Self { x, y }
35 }
36 #[inline]
37 #[must_use] pub const fn zero() -> Self {
38 Self::new(0, 0)
39 }
40}
41
42impl_option!(
43 LayoutPoint,
44 OptionLayoutPoint,
45 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd]
46);
47
48#[derive(Copy, Default, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
50#[repr(C)]
51pub struct LayoutSize {
52 pub width: isize,
53 pub height: isize,
54}
55
56impl fmt::Debug for LayoutSize {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "{self}")
59 }
60}
61impl fmt::Display for LayoutSize {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "{}x{}", self.width, self.height)
64 }
65}
66
67impl LayoutSize {
68 #[inline]
69 #[must_use] pub const fn new(width: isize, height: isize) -> Self {
70 Self { width, height }
71 }
72 #[inline]
73 #[must_use] pub const fn zero() -> Self {
74 Self::new(0, 0)
75 }
76 #[inline]
77 #[must_use] pub fn round(width: f32, height: f32) -> Self {
78 Self {
79 width: crate::cast::f32_to_isize(libm::roundf(width)),
80 height: crate::cast::f32_to_isize(libm::roundf(height)),
81 }
82 }
83}
84
85impl_option!(
86 LayoutSize,
87 OptionLayoutSize,
88 [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
89);
90
91#[derive(Copy, Clone, PartialEq, Eq, PartialOrd)]
93#[repr(C)]
94pub struct LayoutRect {
95 pub origin: LayoutPoint,
96 pub size: LayoutSize,
97}
98
99impl_option!(
100 LayoutRect,
101 OptionLayoutRect,
102 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd]
103);
104impl_vec!(LayoutRect, LayoutRectVec, LayoutRectVecDestructor, LayoutRectVecDestructorType, LayoutRectVecSlice, OptionLayoutRect);
105impl_vec_clone!(LayoutRect, LayoutRectVec, LayoutRectVecDestructor);
106impl_vec_debug!(LayoutRect, LayoutRectVec);
107impl_vec_mut!(LayoutRect, LayoutRectVec);
108impl_vec_partialeq!(LayoutRect, LayoutRectVec);
109impl_vec_partialord!(LayoutRect, LayoutRectVec);
110
111impl fmt::Debug for LayoutRect {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "{self}")
114 }
115}
116impl fmt::Display for LayoutRect {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "{} @ {}", self.size, self.origin)
119 }
120}
121
122impl LayoutRect {
123 #[inline]
124 #[must_use] pub const fn new(origin: LayoutPoint, size: LayoutSize) -> Self {
125 Self { origin, size }
126 }
127 #[inline]
128 #[must_use] pub const fn zero() -> Self {
129 Self::new(LayoutPoint::zero(), LayoutSize::zero())
130 }
131 #[inline]
132 #[must_use] pub const fn max_x(&self) -> isize {
133 self.origin.x.saturating_add(self.size.width)
134 }
135 #[inline]
136 #[must_use] pub const fn min_x(&self) -> isize {
137 self.origin.x
138 }
139 #[inline]
140 #[must_use] pub const fn max_y(&self) -> isize {
141 self.origin.y.saturating_add(self.size.height)
142 }
143 #[inline]
144 #[must_use] pub const fn min_y(&self) -> isize {
145 self.origin.y
146 }
147 #[inline]
148 #[must_use] pub const fn width(&self) -> isize {
149 self.size.width
150 }
151 #[inline]
152 #[must_use] pub const fn height(&self) -> isize {
153 self.size.height
154 }
155
156 #[must_use] pub const fn contains(&self, other: &LayoutPoint) -> bool {
157 self.min_x() <= other.x
158 && other.x < self.max_x()
159 && self.min_y() <= other.y
160 && other.y < self.max_y()
161 }
162
163 #[must_use] pub fn contains_f32(&self, other_x: f32, other_y: f32) -> bool {
164 crate::cast::isize_to_f32(self.min_x()) <= other_x
165 && other_x < crate::cast::isize_to_f32(self.max_x())
166 && crate::cast::isize_to_f32(self.min_y()) <= other_y
167 && other_y < crate::cast::isize_to_f32(self.max_y())
168 }
169
170 #[inline]
174 #[must_use] pub const fn hit_test(&self, other: &LayoutPoint) -> Option<LayoutPoint> {
175 let dx_left_edge = other.x.saturating_sub(self.min_x());
176 let dx_right_edge = self.max_x().saturating_sub(other.x);
177 let dy_top_edge = other.y.saturating_sub(self.min_y());
178 let dy_bottom_edge = self.max_y().saturating_sub(other.y);
179 if dx_left_edge > 0 && dx_right_edge > 0 && dy_top_edge > 0 && dy_bottom_edge > 0 {
180 Some(LayoutPoint::new(dx_left_edge, dy_top_edge))
181 } else {
182 None
183 }
184 }
185
186 #[inline]
189 #[must_use] pub fn union(rects: LayoutRectVecSlice) -> OptionLayoutRect {
190 let mut iter = rects.as_slice().iter().copied();
191 let Some(first) = iter.next() else {
192 return OptionLayoutRect::None;
193 };
194
195 let mut min_x = first.origin.x;
196 let mut min_y = first.origin.y;
197 let mut max_x = first.origin.x.saturating_add(first.size.width);
198 let mut max_y = first.origin.y.saturating_add(first.size.height);
199
200 for Self {
201 origin: LayoutPoint { x, y },
202 size: LayoutSize { width, height },
203 } in iter
204 {
205 max_x = max_x.max(x.saturating_add(width));
206 max_y = max_y.max(y.saturating_add(height));
207 min_x = min_x.min(x);
208 min_y = min_y.min(y);
209 }
210
211 OptionLayoutRect::Some(Self {
212 origin: LayoutPoint { x: min_x, y: min_y },
213 size: LayoutSize {
214 width: max_x.saturating_sub(min_x),
215 height: max_y.saturating_sub(min_y),
216 },
217 })
218 }
219
220 #[inline]
222 #[allow(clippy::suspicious_operation_groupings)]
226 #[must_use] pub const fn contains_rect(&self, b: &Self) -> bool {
227 let a = self;
228
229 let a_x = a.origin.x;
230 let a_y = a.origin.y;
231 let a_width = a.size.width;
232 let a_height = a.size.height;
233
234 let b_x = b.origin.x;
235 let b_y = b.origin.y;
236 let b_width = b.size.width;
237 let b_height = b.size.height;
238
239 b_x >= a_x
240 && b_y >= a_y
241 && b_x.saturating_add(b_width) <= a_x.saturating_add(a_width)
242 && b_y.saturating_add(b_height) <= a_y.saturating_add(a_height)
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 fn rect(x: isize, y: isize, w: isize, h: isize) -> LayoutRect {
251 LayoutRect::new(LayoutPoint::new(x, y), LayoutSize::new(w, h))
252 }
253
254 #[test]
255 fn union_slice_returns_bounding_rect() {
256 let vec: LayoutRectVec =
257 alloc::vec![rect(0, 0, 10, 10), rect(20, -5, 5, 30), rect(-3, 15, 4, 4)].into();
258 let slice = vec.as_c_slice();
259
260 match LayoutRect::union(slice) {
261 OptionLayoutRect::Some(r) => {
262 assert_eq!(r, rect(-3, -5, 28, 30));
263 }
264 OptionLayoutRect::None => panic!("expected Some bounding rect"),
265 }
266 }
267
268 #[test]
269 fn union_empty_slice_returns_none() {
270 let vec: LayoutRectVec = LayoutRectVec::new();
271 let slice = vec.as_c_slice();
272 assert!(matches!(LayoutRect::union(slice), OptionLayoutRect::None));
273 }
274}
275
276#[cfg(test)]
277#[allow(clippy::float_cmp, clippy::unreadable_literal, clippy::cognitive_complexity)]
278mod autotest_generated {
279 use core::hash::{Hash, Hasher};
280
281 use super::*;
282 use crate::cast::{f32_to_isize, isize_to_f32};
283
284 fn point(x: isize, y: isize) -> LayoutPoint {
287 LayoutPoint::new(x, y)
288 }
289
290 fn size(w: isize, h: isize) -> LayoutSize {
291 LayoutSize::new(w, h)
292 }
293
294 fn rect(x: isize, y: isize, w: isize, h: isize) -> LayoutRect {
295 LayoutRect::new(point(x, y), size(w, h))
296 }
297
298 fn rect_vec(rects: &[LayoutRect]) -> LayoutRectVec {
299 rects.to_vec().into()
300 }
301
302 struct FnvHasher(u64);
304 impl Hasher for FnvHasher {
305 fn finish(&self) -> u64 {
306 self.0
307 }
308 fn write(&mut self, bytes: &[u8]) {
309 for b in bytes {
310 self.0 ^= u64::from(*b);
311 self.0 = self.0.wrapping_mul(0x0100_0000_01b3);
312 }
313 }
314 }
315
316 fn hash_of<T: Hash>(v: &T) -> u64 {
317 let mut h = FnvHasher(0xcbf2_9ce4_8422_2325);
318 v.hash(&mut h);
319 h.finish()
320 }
321
322 fn parse_point(s: &str) -> LayoutPoint {
326 let inner = s
327 .strip_prefix('(')
328 .and_then(|s| s.strip_suffix(')'))
329 .expect("LayoutPoint should be parenthesised");
330 let (x, y) = inner.split_once(", ").expect("LayoutPoint needs a `, `");
331 LayoutPoint::new(x.parse().expect("x"), y.parse().expect("y"))
332 }
333
334 fn parse_size(s: &str) -> LayoutSize {
335 let (w, h) = s.split_once('x').expect("LayoutSize needs an `x`");
336 LayoutSize::new(w.parse().expect("width"), h.parse().expect("height"))
337 }
338
339 fn parse_rect(s: &str) -> LayoutRect {
340 let (sz, origin) = s.split_once(" @ ").expect("LayoutRect needs a ` @ `");
341 LayoutRect::new(parse_point(origin), parse_size(sz))
342 }
343
344 const EXTREMES: [isize; 9] = [
346 isize::MIN,
347 isize::MIN + 1,
348 -1_000_000,
349 -1,
350 0,
351 1,
352 1_000_000,
353 isize::MAX - 1,
354 isize::MAX,
355 ];
356
357 #[test]
360 fn point_new_stores_every_extreme_verbatim() {
361 for x in EXTREMES {
362 for y in EXTREMES {
363 let p = LayoutPoint::new(x, y);
364 assert_eq!(p.x, x);
365 assert_eq!(p.y, y);
366 assert_eq!(p, LayoutPoint::new(x, y), "construction is not stable");
367 }
368 }
369 }
370
371 #[test]
372 fn size_new_stores_every_extreme_verbatim_including_negative_sizes() {
373 for w in EXTREMES {
375 for h in EXTREMES {
376 let s = LayoutSize::new(w, h);
377 assert_eq!(s.width, w);
378 assert_eq!(s.height, h);
379 }
380 }
381 }
382
383 #[test]
384 fn rect_new_stores_origin_and_size_verbatim() {
385 let r = LayoutRect::new(point(isize::MIN, isize::MAX), size(isize::MAX, isize::MIN));
386 assert_eq!(r.origin, point(isize::MIN, isize::MAX));
387 assert_eq!(r.size, size(isize::MAX, isize::MIN));
388 assert_eq!(r.min_x(), isize::MIN);
390 assert_eq!(r.min_y(), isize::MAX);
391 assert_eq!(r.width(), isize::MAX);
392 assert_eq!(r.height(), isize::MIN);
393 }
394
395 #[test]
396 fn zero_constructors_are_the_neutral_element_and_match_default() {
397 assert_eq!(LayoutPoint::zero(), LayoutPoint::new(0, 0));
398 assert_eq!(LayoutPoint::zero(), LayoutPoint::default());
399 assert_eq!(LayoutSize::zero(), LayoutSize::new(0, 0));
400 assert_eq!(LayoutSize::zero(), LayoutSize::default());
401
402 let z = LayoutRect::zero();
404 assert_eq!(z.origin, LayoutPoint::zero());
405 assert_eq!(z.size, LayoutSize::zero());
406 assert_eq!(z.min_x(), 0);
407 assert_eq!(z.max_x(), 0);
408 assert_eq!(z.min_y(), 0);
409 assert_eq!(z.max_y(), 0);
410 assert_eq!(z.width(), 0);
411 assert_eq!(z.height(), 0);
412 }
413
414 #[test]
415 fn zero_rect_is_empty_it_contains_no_point_not_even_its_own_origin() {
416 let z = LayoutRect::zero();
418 assert!(!z.contains(&LayoutPoint::zero()));
419 assert!(!z.contains_f32(0.0, 0.0));
420 assert_eq!(z.hit_test(&LayoutPoint::zero()), None);
421 assert!(z.contains_rect(&z));
423 }
424
425 #[test]
426 fn constructors_are_usable_in_const_context() {
427 const P: LayoutPoint = LayoutPoint::new(isize::MIN, isize::MAX);
428 const S: LayoutSize = LayoutSize::new(-1, -2);
429 const R: LayoutRect = LayoutRect::new(P, S);
430 const Z: LayoutRect = LayoutRect::zero();
431 const W: isize = R.width();
432
433 assert_eq!(P.x, isize::MIN);
434 assert_eq!(S.height, -2);
435 assert_eq!(R.origin, P);
436 assert_eq!(W, -1);
437 assert_eq!(Z, LayoutRect::new(LayoutPoint::zero(), LayoutSize::zero()));
438 }
439
440 #[test]
443 fn display_of_extremes_is_well_formed_and_debug_delegates_to_it() {
444 for x in EXTREMES {
445 for y in EXTREMES {
446 let p = LayoutPoint::new(x, y);
447 let s = LayoutSize::new(x, y);
448 let r = LayoutRect::new(p, s);
449
450 let p_str = alloc::format!("{p}");
451 let s_str = alloc::format!("{s}");
452 let r_str = alloc::format!("{r}");
453
454 assert_eq!(p_str, alloc::format!("({x}, {y})"));
455 assert_eq!(s_str, alloc::format!("{x}x{y}"));
456 assert_eq!(r_str, alloc::format!("{x}x{y} @ ({x}, {y})"));
457
458 assert!(!p_str.is_empty() && !s_str.is_empty() && !r_str.is_empty());
459 assert_eq!(alloc::format!("{p:?}"), p_str);
461 assert_eq!(alloc::format!("{s:?}"), s_str);
462 assert_eq!(alloc::format!("{r:?}"), r_str);
463 }
464 }
465 }
466
467 #[test]
468 fn display_of_the_zero_values_does_not_panic_and_is_canonical() {
469 assert_eq!(alloc::format!("{}", LayoutPoint::zero()), "(0, 0)");
470 assert_eq!(alloc::format!("{}", LayoutSize::zero()), "0x0");
471 assert_eq!(alloc::format!("{}", LayoutRect::zero()), "0x0 @ (0, 0)");
472 assert_eq!(alloc::format!("{:?}", LayoutRect::zero()), "0x0 @ (0, 0)");
473 }
474
475 #[test]
476 fn display_ignores_format_flags_rather_than_panicking() {
477 let p = point(1, -2);
480 assert_eq!(alloc::format!("{p:>40}"), "(1, -2)");
481 assert_eq!(alloc::format!("{p:.1}"), "(1, -2)");
482 assert_eq!(alloc::format!("{:#?}", size(3, 4)), "3x4");
483 }
484
485 #[test]
488 fn display_round_trips_through_a_parser_for_every_extreme() {
489 for a in EXTREMES {
490 for b in EXTREMES {
491 let p = LayoutPoint::new(a, b);
492 let s = LayoutSize::new(a, b);
493 let r = LayoutRect::new(p, s);
494
495 assert_eq!(parse_point(&alloc::format!("{p}")), p, "point {p} decoded wrong");
496 assert_eq!(parse_size(&alloc::format!("{s}")), s, "size {s} decoded wrong");
497 assert_eq!(parse_rect(&alloc::format!("{r}")), r, "rect {r} decoded wrong");
498 }
499 }
500 }
501
502 #[test]
503 fn display_round_trips_for_a_negative_size_rect_where_the_x_separator_is_ambiguous_looking() {
504 let r = rect(-7, -8, -1, -2);
506 assert_eq!(alloc::format!("{r}"), "-1x-2 @ (-7, -8)");
507 assert_eq!(parse_rect("-1x-2 @ (-7, -8)"), r);
508 }
509
510 #[test]
513 fn getters_return_the_construction_values() {
514 let r = rect(3, -4, 10, 20);
515 assert_eq!(r.min_x(), 3);
516 assert_eq!(r.min_y(), -4);
517 assert_eq!(r.max_x(), 13);
518 assert_eq!(r.max_y(), 16);
519 assert_eq!(r.width(), 10);
520 assert_eq!(r.height(), 20);
521 }
522
523 #[test]
524 fn max_minus_min_is_the_extent_whenever_the_sum_does_not_overflow() {
525 for x in [isize::MIN, -1, 0, 1, isize::MAX] {
526 for w in [-1_000, -1, 0, 1, 1_000] {
527 let Some(expected_max) = x.checked_add(w) else {
529 continue;
530 };
531 let r = rect(x, x, w, w);
532 assert_eq!(r.max_x(), expected_max);
533 assert_eq!(r.max_y(), expected_max);
534 assert_eq!(r.max_x() - r.min_x(), r.width());
535 assert_eq!(r.max_y() - r.min_y(), r.height());
536 }
537 }
538 }
539
540 #[test]
541 fn getters_survive_the_widest_non_overflowing_rect() {
542 let r = rect(isize::MIN, isize::MIN, isize::MAX, isize::MAX);
545 assert_eq!(r.min_x(), isize::MIN);
546 assert_eq!(r.min_y(), isize::MIN);
547 assert_eq!(r.max_x(), -1);
548 assert_eq!(r.max_y(), -1);
549 assert_eq!(r.width(), isize::MAX);
550 assert_eq!(r.height(), isize::MAX);
551
552 assert!(r.contains(&point(isize::MIN, isize::MIN)));
554 assert!(r.contains(&point(-2, -2)));
555 assert!(!r.contains(&point(-1, -1)));
557 assert!(!r.contains(&point(0, 0)));
558 }
559
560 #[test]
561 fn max_getters_do_not_overflow_when_the_size_is_zero() {
562 let r = rect(isize::MAX, isize::MAX, 0, 0);
563 assert_eq!(r.max_x(), isize::MAX);
564 assert_eq!(r.max_y(), isize::MAX);
565
566 let r = rect(isize::MIN, isize::MIN, 0, 0);
567 assert_eq!(r.max_x(), isize::MIN);
568 assert_eq!(r.max_y(), isize::MIN);
569 }
570
571 #[test]
575 fn max_x_saturates_instead_of_overflowing() {
576 let r = core::hint::black_box(rect(isize::MAX, 0, 1, 0));
577 assert_eq!(r.max_x(), isize::MAX);
578 }
579
580 #[test]
581 fn max_y_saturates_instead_of_overflowing() {
582 let r = core::hint::black_box(rect(0, isize::MIN, 0, -1));
583 assert_eq!(r.max_y(), isize::MIN);
584 }
585
586 #[test]
589 fn contains_is_min_inclusive_and_max_exclusive_on_every_edge() {
590 let r = rect(10, 20, 5, 5); assert!(r.contains(&point(10, 20))); assert!(r.contains(&point(14, 24))); assert!(!r.contains(&point(15, 24))); assert!(!r.contains(&point(14, 25))); assert!(!r.contains(&point(15, 25))); assert!(!r.contains(&point(9, 20)));
597 assert!(!r.contains(&point(10, 19)));
598 }
599
600 #[test]
601 fn contains_handles_negative_coordinates_deterministically() {
602 let r = rect(-10, -10, 5, 5); assert!(r.contains(&point(-10, -10)));
604 assert!(r.contains(&point(-6, -6)));
605 assert!(!r.contains(&point(-5, -5)));
606 assert!(!r.contains(&point(-11, -10)));
607 }
608
609 #[test]
610 fn a_negative_size_rect_contains_nothing() {
611 let r = rect(0, 0, -5, -5);
613 for x in -8..8 {
614 for y in -8..8 {
615 assert!(!r.contains(&point(x, y)), "({x}, {y}) must not be inside {r}");
616 assert_eq!(r.hit_test(&point(x, y)), None);
617 }
618 }
619 }
620
621 #[test]
622 fn contains_does_not_panic_at_the_isize_extremes_it_can_reach() {
623 let r = rect(isize::MAX, isize::MAX, 1, 1);
626 assert!(!r.contains(&point(0, 0)));
627 assert!(!r.contains(&point(isize::MIN, isize::MIN)));
628
629 let r = rect(isize::MIN, isize::MIN, 1, 1);
630 assert!(r.contains(&point(isize::MIN, isize::MIN)));
631 assert!(!r.contains(&point(isize::MAX, isize::MAX)));
632 assert!(!r.contains(&point(isize::MIN + 1, isize::MIN)));
633 }
634
635 #[test]
639 fn contains_does_not_panic_on_a_rect_whose_right_edge_overflows() {
640 let r = core::hint::black_box(rect(1, 0, isize::MAX, 10));
641 let p = core::hint::black_box(point(5, 5));
642 assert!(r.contains(&p));
643 }
644
645 #[test]
648 fn contains_f32_matches_contains_on_integer_coordinates() {
649 for r in [rect(0, 0, 10, 10), rect(-5, -5, 3, 4), rect(0, 0, 0, 0)] {
650 for x in -8..=12_isize {
651 for y in -8..=12_isize {
652 assert_eq!(
653 r.contains_f32(isize_to_f32(x), isize_to_f32(y)),
654 r.contains(&point(x, y)),
655 "{r} disagrees about ({x}, {y})"
656 );
657 }
658 }
659 }
660 }
661
662 #[test]
663 fn contains_f32_is_min_inclusive_max_exclusive_for_fractional_points() {
664 let r = rect(0, 0, 10, 10);
665 assert!(r.contains_f32(0.0, 0.0));
666 assert!(r.contains_f32(-0.0, -0.0)); assert!(r.contains_f32(9.999_999, 9.999_999));
668 assert!(!r.contains_f32(10.0, 5.0)); assert!(!r.contains_f32(-0.000_001, 5.0));
670 assert!(!r.contains_f32(5.0, 10.0));
671 }
672
673 #[test]
674 fn contains_f32_returns_false_for_nan_and_never_panics() {
675 let r = rect(0, 0, 10, 10);
676 assert!(!r.contains_f32(f32::NAN, 5.0));
678 assert!(!r.contains_f32(5.0, f32::NAN));
679 assert!(!r.contains_f32(f32::NAN, f32::NAN));
680 assert!(!r.contains_f32(-f32::NAN, 5.0));
681 assert!(!r.contains_f32(f32::from_bits(0x7fc0_1234), 5.0));
682 }
683
684 #[test]
685 fn contains_f32_treats_infinities_as_outside() {
686 let r = rect(0, 0, 10, 10);
687 assert!(!r.contains_f32(f32::INFINITY, 5.0));
688 assert!(!r.contains_f32(f32::NEG_INFINITY, 5.0));
689 assert!(!r.contains_f32(5.0, f32::INFINITY));
690 assert!(!r.contains_f32(5.0, f32::NEG_INFINITY));
691 assert!(!r.contains_f32(f32::MAX, f32::MAX));
692 assert!(!r.contains_f32(f32::MIN, f32::MIN));
693 }
694
695 #[test]
696 fn contains_f32_survives_the_widest_non_overflowing_rect() {
697 let r = rect(isize::MIN, isize::MIN, isize::MAX, isize::MAX);
698 assert!(r.contains_f32(-1.0e18, -1.0e18));
699 assert!(!r.contains_f32(0.0, 0.0));
700 assert!(!r.contains_f32(f32::INFINITY, f32::INFINITY));
701 }
702
703 #[cfg(target_pointer_width = "64")]
707 #[test]
708 fn contains_f32_reports_a_point_left_of_the_rect_as_inside_past_2_pow_24() {
709 const TWO_POW_40: isize = 1 << 40; let r = rect(TWO_POW_40 + 1, 0, 1_000_000, 1_000_000);
713 let p = point(TWO_POW_40, 1);
714
715 assert!(!r.contains(&p), "exact integer math: the point is left of the rect");
716 assert!(
717 r.contains_f32(isize_to_f32(TWO_POW_40), 1.0),
718 "f32 math: the rounded-down left edge swallows the point"
719 );
720 assert_eq!(isize_to_f32(TWO_POW_40 + 1), isize_to_f32(TWO_POW_40));
722 }
723
724 #[test]
727 fn contains_f32_does_not_panic_on_a_rect_whose_right_edge_overflows() {
728 let r = core::hint::black_box(rect(1, 0, isize::MAX, 10));
729 assert!(r.contains_f32(core::hint::black_box(5.0), 5.0));
730 }
731
732 #[test]
735 fn hit_test_excludes_every_boundary_and_returns_the_origin_relative_offset() {
736 let r = rect(10, 20, 5, 5); assert_eq!(r.hit_test(&point(11, 21)), Some(point(1, 1)));
738 assert_eq!(r.hit_test(&point(14, 24)), Some(point(4, 4)));
739
740 assert!(r.contains(&point(10, 20)));
742 assert_eq!(r.hit_test(&point(10, 20)), None);
743 assert_eq!(r.hit_test(&point(10, 22)), None);
744 assert_eq!(r.hit_test(&point(12, 20)), None);
745 assert_eq!(r.hit_test(&point(15, 22)), None);
747 assert_eq!(r.hit_test(&point(12, 25)), None);
748 }
749
750 #[test]
751 fn hit_test_some_always_implies_contains_and_the_offset_is_exact() {
752 for r in [rect(0, 0, 10, 10), rect(-5, -5, 3, 4), rect(2, 2, 1, 1), rect(0, 0, 0, 0)] {
753 for x in -8..=12_isize {
754 for y in -8..=12_isize {
755 let p = point(x, y);
756 let strictly_inside =
757 r.min_x() < x && x < r.max_x() && r.min_y() < y && y < r.max_y();
758 assert_eq!(
759 r.hit_test(&p).is_some(),
760 strictly_inside,
761 "{r} hit_test({p}) disagrees with the strict-interior predicate"
762 );
763 if let Some(offset) = r.hit_test(&p) {
764 assert_eq!(offset, point(x - r.min_x(), y - r.min_y()));
765 assert!(r.contains(&p), "hit_test hit a point outside contains()");
766 assert!(offset.x > 0 && offset.x < r.width());
768 assert!(offset.y > 0 && offset.y < r.height());
769 }
770 }
771 }
772 }
773 }
774
775 #[test]
776 fn hit_test_of_a_one_by_one_rect_is_always_none_because_it_has_no_interior() {
777 let r = rect(0, 0, 1, 1);
778 assert!(r.contains(&point(0, 0)));
779 for x in -2..=2 {
780 for y in -2..=2 {
781 assert_eq!(r.hit_test(&point(x, y)), None);
782 }
783 }
784 }
785
786 #[test]
790 fn hit_test_of_a_point_far_left_of_a_perfectly_ordinary_rect_is_none() {
791 let r = core::hint::black_box(rect(0, 0, 10, 10));
792 let p = core::hint::black_box(point(isize::MIN, 0));
793 assert_eq!(r.hit_test(&p), None);
794 }
795
796 #[test]
797 fn hit_test_of_a_rect_whose_right_edge_overflows_returns_the_interior_offset() {
798 let r = core::hint::black_box(rect(1, 0, isize::MAX, 10));
799 let p = core::hint::black_box(point(5, 5));
800 assert_eq!(r.hit_test(&p), Some(point(4, 5)));
801 }
802
803 #[test]
806 fn contains_rect_is_reflexive_and_uses_inclusive_edges() {
807 let a = rect(0, 0, 10, 10);
808 assert!(a.contains_rect(&a));
809 assert!(a.contains_rect(&rect(0, 0, 5, 5)));
810 assert!(a.contains_rect(&rect(5, 5, 5, 5))); assert!(!a.contains_rect(&rect(5, 5, 6, 5))); assert!(!a.contains_rect(&rect(-1, 0, 5, 5)));
813 assert!(!a.contains_rect(&rect(0, -1, 5, 5)));
814
815 assert!(a.contains_rect(&rect(10, 10, 0, 0)));
818 assert!(!a.contains(&point(10, 10)));
819 }
820
821 #[test]
822 fn contains_rect_is_not_symmetric() {
823 let big = rect(0, 0, 10, 10);
824 let small = rect(2, 2, 2, 2);
825 assert!(big.contains_rect(&small));
826 assert!(!small.contains_rect(&big));
827 }
828
829 #[test]
830 fn contains_rect_wrongly_accepts_a_negative_size_rect_that_extends_far_outside() {
831 let a = rect(0, 0, 10, 10);
835 let b = rect(5, 5, -100, -100);
836 assert!(a.contains_rect(&b));
837 }
838
839 #[test]
840 fn contains_rect_does_not_panic_on_the_extremes_it_can_reach() {
841 let full = rect(0, 0, isize::MAX, isize::MAX);
842 assert!(full.contains_rect(&full)); assert!(full.contains_rect(&rect(0, 0, 0, 0)));
844 assert!(!full.contains_rect(&rect(-1, 0, 0, 0)));
845
846 let half = rect(isize::MIN, isize::MIN, isize::MAX, isize::MAX);
849 assert!(!half.contains_rect(&rect(0, 0, 0, 0)));
850 assert!(half.contains_rect(&rect(isize::MIN, isize::MIN, 0, 0)));
851 }
852
853 #[test]
856 fn contains_rect_does_not_panic_when_the_inner_rects_far_edge_overflows() {
857 let a = core::hint::black_box(rect(0, 0, isize::MAX, isize::MAX));
858 let b = core::hint::black_box(rect(1, 1, isize::MAX, 1));
859 assert!(a.contains_rect(&b));
860 }
861
862 #[test]
865 fn union_of_a_single_rect_is_that_rect_even_at_the_extremes() {
866 for r in [
867 rect(0, 0, 0, 0),
868 rect(-7, -8, 1, 2),
869 rect(3, 4, -5, -6), rect(isize::MIN, isize::MIN, isize::MAX, isize::MAX),
871 rect(isize::MAX, isize::MAX, 0, 0),
872 ] {
873 let vec = rect_vec(&[r]);
874 assert_eq!(
875 LayoutRect::union(vec.as_c_slice()),
876 OptionLayoutRect::Some(r),
877 "union([{r}]) is not the identity"
878 );
879 }
880 }
881
882 #[test]
883 fn union_is_idempotent_and_order_independent_for_well_formed_rects() {
884 let a = rect(-3, 15, 4, 4);
885 let b = rect(20, -5, 5, 30);
886
887 let ab = rect_vec(&[a, b]);
888 let ba = rect_vec(&[b, a]);
889 assert_eq!(
890 LayoutRect::union(ab.as_c_slice()),
891 LayoutRect::union(ba.as_c_slice())
892 );
893
894 let aa = rect_vec(&[a, a, a]);
895 assert_eq!(LayoutRect::union(aa.as_c_slice()), OptionLayoutRect::Some(a));
896 }
897
898 #[test]
899 fn union_covers_every_input_rect() {
900 let rects = [rect(0, 0, 10, 10), rect(20, -5, 5, 30), rect(-3, 15, 4, 4)];
901 let vec = rect_vec(&rects);
902 let OptionLayoutRect::Some(u) = LayoutRect::union(vec.as_c_slice()) else {
903 panic!("expected Some for a non-empty slice");
904 };
905 for r in rects {
906 assert!(u.contains_rect(&r), "{u} does not cover {r}");
907 }
908 let tight = rect(u.min_x() + 1, u.min_y(), u.width() - 1, u.height());
910 assert!(rects.iter().any(|r| !tight.contains_rect(r)));
911 }
912
913 #[test]
914 fn union_only_reads_the_slice_it_was_given() {
915 let vec = rect_vec(&[rect(0, 0, 1, 1), rect(100, 100, 1, 1), rect(-100, -100, 1, 1)]);
916 assert_eq!(
918 LayoutRect::union(vec.as_c_slice_range(0, 1)),
919 OptionLayoutRect::Some(rect(0, 0, 1, 1))
920 );
921 assert_eq!(
922 LayoutRect::union(vec.as_c_slice_range(0, 2)),
923 OptionLayoutRect::Some(rect(0, 0, 101, 101))
924 );
925 assert!(LayoutRect::union(vec.as_c_slice_range(1, 1)).is_none());
927 }
928
929 #[test]
930 fn union_of_an_empty_and_a_default_constructed_vec_is_none() {
931 let empty = LayoutRectVec::new();
932 assert!(empty.is_empty());
933 assert_eq!(LayoutRect::union(empty.as_c_slice()), OptionLayoutRect::None);
934 assert!(LayoutRect::union(LayoutRectVecSlice::empty()).is_none());
935 assert_eq!(OptionLayoutRect::default(), OptionLayoutRect::None);
936 }
937
938 #[test]
939 fn union_with_negative_size_rects_folds_them_into_a_smaller_box() {
940 let vec = rect_vec(&[rect(10, 10, -5, -5), rect(0, 0, 2, 2)]);
943 assert_eq!(
944 LayoutRect::union(vec.as_c_slice()),
945 OptionLayoutRect::Some(rect(0, 0, 5, 5))
946 );
947 }
948
949 #[test]
950 fn union_handles_all_negative_coordinates() {
951 let vec = rect_vec(&[rect(-10, -10, 2, 2), rect(-30, -5, 1, 1)]);
952 assert_eq!(
953 LayoutRect::union(vec.as_c_slice()),
954 OptionLayoutRect::Some(rect(-30, -10, 22, 6))
955 );
956 }
957
958 #[test]
959 fn union_survives_the_widest_non_overflowing_pair() {
960 let vec = rect_vec(&[rect(isize::MIN, isize::MIN, 0, 0), rect(-1, -1, 0, 0)]);
961 assert_eq!(
962 LayoutRect::union(vec.as_c_slice()),
963 OptionLayoutRect::Some(rect(isize::MIN, isize::MIN, isize::MAX, isize::MAX))
964 );
965 }
966
967 #[test]
971 fn union_spanning_the_whole_isize_range_saturates_the_extent() {
972 let vec = rect_vec(&[rect(isize::MIN, 0, 0, 0), rect(isize::MAX, 0, 0, 0)]);
973 assert_eq!(
974 LayoutRect::union(vec.as_c_slice()),
975 OptionLayoutRect::Some(rect(isize::MIN, 0, isize::MAX, 0))
976 );
977 }
978
979 #[test]
980 fn union_of_a_rect_whose_far_edge_overflows_saturates() {
981 let vec = rect_vec(&[rect(isize::MAX, 0, 1, 0)]);
982 assert_eq!(
983 LayoutRect::union(vec.as_c_slice()),
984 OptionLayoutRect::Some(rect(isize::MAX, 0, 0, 0))
985 );
986 }
987
988 #[test]
991 fn round_of_zero_and_negative_zero_is_the_zero_size() {
992 assert_eq!(LayoutSize::round(0.0, 0.0), LayoutSize::zero());
993 assert_eq!(LayoutSize::round(-0.0, -0.0), LayoutSize::zero());
994 assert_eq!(LayoutSize::round(0.0, -0.0), LayoutSize::zero());
995 }
996
997 #[test]
998 fn round_goes_half_away_from_zero_not_half_to_even() {
999 assert_eq!(LayoutSize::round(0.5, -0.5), size(1, -1));
1000 assert_eq!(LayoutSize::round(1.5, -1.5), size(2, -2));
1001 assert_eq!(LayoutSize::round(2.5, -2.5), size(3, -3));
1003 assert_eq!(LayoutSize::round(3.5, -3.5), size(4, -4));
1004 }
1005
1006 #[test]
1007 fn round_truncates_toward_zero_just_below_the_half() {
1008 let just_below_half = f32::from_bits(0x3eff_ffff);
1010 assert!(just_below_half < 0.5);
1011 assert_eq!(
1012 LayoutSize::round(just_below_half, -just_below_half),
1013 LayoutSize::zero()
1014 );
1015 assert_eq!(LayoutSize::round(0.49, -0.49), LayoutSize::zero());
1016 assert_eq!(LayoutSize::round(1.49, -1.49), size(1, -1));
1017 }
1018
1019 #[test]
1020 fn round_of_nan_is_zero_and_does_not_panic() {
1021 assert_eq!(LayoutSize::round(f32::NAN, f32::NAN), LayoutSize::zero());
1022 assert_eq!(LayoutSize::round(f32::NAN, 5.0), size(0, 5));
1023 assert_eq!(LayoutSize::round(5.0, -f32::NAN), size(5, 0));
1024 assert_eq!(
1025 LayoutSize::round(f32::from_bits(0x7fc0_1234), 1.0),
1026 size(0, 1)
1027 );
1028 }
1029
1030 #[test]
1031 fn round_saturates_the_infinities_to_the_isize_bounds() {
1032 assert_eq!(
1033 LayoutSize::round(f32::INFINITY, f32::NEG_INFINITY),
1034 size(isize::MAX, isize::MIN)
1035 );
1036 assert_eq!(
1037 LayoutSize::round(f32::NEG_INFINITY, f32::INFINITY),
1038 size(isize::MIN, isize::MAX)
1039 );
1040 }
1041
1042 #[test]
1043 fn round_saturates_out_of_range_finite_floats_rather_than_wrapping() {
1044 assert_eq!(
1045 LayoutSize::round(f32::MAX, f32::MIN),
1046 size(isize::MAX, isize::MIN)
1047 );
1048 assert_eq!(LayoutSize::round(1.0e30, -1.0e30), size(isize::MAX, isize::MIN));
1049 }
1050
1051 #[test]
1052 fn round_flushes_subnormals_and_tiny_magnitudes_to_zero() {
1053 assert_eq!(
1054 LayoutSize::round(f32::MIN_POSITIVE, -f32::MIN_POSITIVE),
1055 LayoutSize::zero()
1056 );
1057 assert_eq!(LayoutSize::round(f32::EPSILON, f32::from_bits(1)), LayoutSize::zero());
1058 }
1059
1060 #[test]
1061 fn round_is_exact_for_values_inside_the_f32_integer_range() {
1062 assert_eq!(LayoutSize::round(1.0e9, -1.0e9), size(1_000_000_000, -1_000_000_000));
1063 assert_eq!(LayoutSize::round(16_777_216.0, -16_777_216.0), size(1 << 24, -(1 << 24)));
1064 assert_eq!(LayoutSize::round(-1.0, 1.0), size(-1, 1));
1065 }
1066
1067 #[test]
1068 fn round_agrees_with_roundf_then_cast_across_a_wide_sample() {
1069 let samples = [
1070 0.0,
1071 -0.0,
1072 0.5,
1073 -0.5,
1074 2.5,
1075 -2.5,
1076 1.4999999,
1077 -1.4999999,
1078 42.7,
1079 -42.7,
1080 16_777_215.5,
1081 -16_777_215.5,
1082 1.0e18,
1083 -1.0e18,
1084 f32::MAX,
1085 f32::MIN,
1086 f32::INFINITY,
1087 f32::NEG_INFINITY,
1088 f32::NAN,
1089 f32::MIN_POSITIVE,
1090 ];
1091 for w in samples {
1092 for h in samples {
1093 let got = LayoutSize::round(w, h);
1094 assert_eq!(got.width, f32_to_isize(libm::roundf(w)));
1095 assert_eq!(got.height, f32_to_isize(libm::roundf(h)));
1096 }
1097 }
1098 }
1099
1100 #[test]
1101 fn round_round_trips_through_f32_for_layout_sized_values() {
1102 let mut v: isize = -4_000_000;
1105 while v <= 4_000_000 {
1106 assert_eq!(
1107 LayoutSize::round(isize_to_f32(v), isize_to_f32(-v)),
1108 size(v, -v),
1109 "round-trip broke at {v}"
1110 );
1111 v += 40_009; }
1113 }
1114
1115 #[test]
1118 fn point_and_size_ordering_is_lexicographic_on_their_fields() {
1119 assert!(point(0, 1) < point(1, 0));
1120 assert!(point(1, 1) < point(1, 2));
1121 assert_eq!(point(1, 2).cmp(&point(1, 2)), core::cmp::Ordering::Equal);
1122 assert!(point(isize::MIN, isize::MAX) < point(isize::MAX, isize::MIN));
1123
1124 assert!(size(0, 1) < size(1, 0));
1125 assert!(size(-1, 0) < size(0, -1));
1126
1127 assert!(rect(0, 0, 1, 1) < rect(0, 0, 1, 2));
1129 assert!(rect(0, 0, 9, 9) < rect(0, 1, 0, 0));
1130 }
1131
1132 #[test]
1133 fn hash_agrees_with_eq_for_points_and_sizes() {
1134 assert_eq!(hash_of(&point(3, -4)), hash_of(&point(3, -4)));
1135 assert_eq!(hash_of(&size(3, -4)), hash_of(&size(3, -4)));
1136 assert_ne!(hash_of(&point(3, -4)), hash_of(&point(-4, 3)));
1138 assert_ne!(hash_of(&point(0, 0)), hash_of(&point(0, 1)));
1139 assert_eq!(hash_of(&LayoutPoint::zero()), hash_of(&LayoutPoint::default()));
1140 }
1141
1142 #[test]
1143 fn option_wrappers_default_to_none_and_round_trip_through_core_option() {
1144 assert!(OptionLayoutPoint::default().is_none());
1145 assert!(OptionLayoutSize::default().is_none());
1146 assert!(OptionLayoutRect::default().is_none());
1147
1148 let r = rect(1, 2, 3, 4);
1149 let o: OptionLayoutRect = Some(r).into();
1150 assert!(o.is_some());
1151 assert_eq!(o.into_option(), Some(r));
1152 assert_eq!(Option::<LayoutRect>::from(OptionLayoutRect::None), None);
1153
1154 let p: OptionLayoutPoint = Some(point(-1, -2)).into();
1155 assert_eq!(p.as_ref(), Some(&point(-1, -2)));
1156 }
1157}