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