1use core::convert::TryFrom;
7
8use crate::{FiniteF32, IntSize, LengthU32, Point, SaturateRound, Size, Transform};
9
10#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
11use crate::NoStdFloat;
12
13#[allow(missing_docs)]
20#[derive(Copy, Clone, PartialEq, Debug)]
21pub struct IntRect {
22 x: i32,
23 y: i32,
24 width: LengthU32,
25 height: LengthU32,
26}
27
28impl IntRect {
29 pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Option<Self> {
31 x.checked_add(i32::try_from(width).ok()?)?;
32 y.checked_add(i32::try_from(height).ok()?)?;
33
34 Some(IntRect {
35 x,
36 y,
37 width: LengthU32::new(width)?,
38 height: LengthU32::new(height)?,
39 })
40 }
41
42 pub fn from_ltrb(left: i32, top: i32, right: i32, bottom: i32) -> Option<Self> {
44 let width = u32::try_from(right.checked_sub(left)?).ok()?;
45 let height = u32::try_from(bottom.checked_sub(top)?).ok()?;
46 IntRect::from_xywh(left, top, width, height)
47 }
48
49 pub fn x(&self) -> i32 {
51 self.x
52 }
53
54 pub fn y(&self) -> i32 {
56 self.y
57 }
58
59 pub fn width(&self) -> u32 {
61 self.width.get()
62 }
63
64 pub fn height(&self) -> u32 {
66 self.height.get()
67 }
68
69 pub fn left(&self) -> i32 {
71 self.x
72 }
73
74 pub fn top(&self) -> i32 {
76 self.y
77 }
78
79 pub fn right(&self) -> i32 {
81 self.x + self.width.get() as i32
83 }
84
85 pub fn bottom(&self) -> i32 {
87 self.y + self.height.get() as i32
89 }
90
91 pub fn size(&self) -> IntSize {
93 IntSize::from_wh_safe(self.width, self.height)
94 }
95
96 pub fn contains(&self, other: &Self) -> bool {
98 self.x <= other.x
99 && self.y <= other.y
100 && self.right() >= other.right()
101 && self.bottom() >= other.bottom()
102 }
103
104 pub fn intersect(&self, other: &Self) -> Option<Self> {
108 let left = self.x.max(other.x);
109 let top = self.y.max(other.y);
110
111 let right = self.right().min(other.right());
112 let bottom = self.bottom().min(other.bottom());
113
114 let w = u32::try_from(right.checked_sub(left)?).ok()?;
115 let h = u32::try_from(bottom.checked_sub(top)?).ok()?;
116
117 IntRect::from_xywh(left, top, w, h)
118 }
119
120 pub fn inset(&self, dx: i32, dy: i32) -> Option<Self> {
122 IntRect::from_ltrb(
123 self.left() + dx,
124 self.top() + dy,
125 self.right() - dx,
126 self.bottom() - dy,
127 )
128 }
129
130 pub fn make_outset(&self, dx: i32, dy: i32) -> Option<Self> {
132 IntRect::from_ltrb(
133 self.left().saturating_sub(dx),
134 self.top().saturating_sub(dy),
135 self.right().saturating_add(dx),
136 self.bottom().saturating_add(dy),
137 )
138 }
139
140 pub fn translate(&self, tx: i32, ty: i32) -> Option<Self> {
142 IntRect::from_xywh(self.x() + tx, self.y() + ty, self.width(), self.height())
143 }
144
145 pub fn translate_to(&self, x: i32, y: i32) -> Option<Self> {
147 IntRect::from_xywh(x, y, self.width(), self.height())
148 }
149
150 pub fn to_rect(&self) -> Rect {
152 Rect::from_ltrb(
154 self.x as f32,
155 self.y as f32,
156 self.x as f32 + self.width.get() as f32,
157 self.y as f32 + self.height.get() as f32,
158 )
159 .unwrap()
160 }
161}
162
163#[cfg(test)]
164mod int_rect_tests {
165 use super::*;
166
167 #[test]
168 fn tests() {
169 assert_eq!(IntRect::from_xywh(0, 0, 0, 0), None);
170 assert_eq!(IntRect::from_xywh(0, 0, 1, 0), None);
171 assert_eq!(IntRect::from_xywh(0, 0, 0, 1), None);
172
173 assert_eq!(IntRect::from_xywh(0, 0, u32::MAX, u32::MAX), None);
174 assert_eq!(IntRect::from_xywh(0, 0, 1, u32::MAX), None);
175 assert_eq!(IntRect::from_xywh(0, 0, u32::MAX, 1), None);
176
177 assert_eq!(IntRect::from_xywh(i32::MAX, 0, 1, 1), None);
178 assert_eq!(IntRect::from_xywh(0, i32::MAX, 1, 1), None);
179
180 {
181 let r1 = IntRect::from_xywh(1, 2, 3, 4).unwrap();
183 let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
184 assert_eq!(r1.intersect(&r2), None);
185 }
186
187 {
188 let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
190 let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
191 assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 13, 14));
192 }
193
194 {
195 let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
197 let r2 = IntRect::from_xywh(11, 12, 50, 60).unwrap();
198 assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 20, 30));
199 }
200 }
201}
202
203#[allow(missing_docs)]
214#[derive(Copy, Clone, PartialEq, Hash, Eq)]
215pub struct Rect {
216 left: FiniteF32,
217 top: FiniteF32,
218 right: FiniteF32,
219 bottom: FiniteF32,
220}
221
222impl core::fmt::Debug for Rect {
223 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
224 f.debug_struct("Rect")
225 .field("left", &self.left.get())
226 .field("top", &self.top.get())
227 .field("right", &self.right.get())
228 .field("bottom", &self.bottom.get())
229 .finish()
230 }
231}
232
233impl Rect {
234 pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> {
236 let left = FiniteF32::new(left)?;
237 let top = FiniteF32::new(top)?;
238 let right = FiniteF32::new(right)?;
239 let bottom = FiniteF32::new(bottom)?;
240
241 if left.get() <= right.get() && top.get() <= bottom.get() {
242 checked_f32_sub(right.get(), left.get())?;
244 checked_f32_sub(bottom.get(), top.get())?;
245
246 Some(Rect {
247 left,
248 top,
249 right,
250 bottom,
251 })
252 } else {
253 None
254 }
255 }
256
257 pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
259 Rect::from_ltrb(x, y, w + x, h + y)
260 }
261
262 pub fn left(&self) -> f32 {
264 self.left.get()
265 }
266
267 pub fn top(&self) -> f32 {
269 self.top.get()
270 }
271
272 pub fn right(&self) -> f32 {
274 self.right.get()
275 }
276
277 pub fn bottom(&self) -> f32 {
279 self.bottom.get()
280 }
281
282 pub fn x(&self) -> f32 {
284 self.left.get()
285 }
286
287 pub fn y(&self) -> f32 {
289 self.top.get()
290 }
291
292 #[inline]
294 pub fn width(&self) -> f32 {
295 self.right.get() - self.left.get()
296 }
297
298 #[inline]
300 pub fn height(&self) -> f32 {
301 self.bottom.get() - self.top.get()
302 }
303
304 #[inline]
306 pub fn is_empty(&self) -> bool {
307 self.left == self.right || self.top == self.bottom
308 }
309
310 pub fn round(&self) -> Option<IntRect> {
314 let left = i32::saturate_round(self.left());
315 let top = i32::saturate_round(self.top());
316 let right = i32::saturate_round(self.right());
317 let bottom = i32::saturate_round(self.bottom());
318 IntRect::from_xywh(
319 left,
320 top,
321 core::cmp::max(1, right.saturating_sub(left)) as u32,
322 core::cmp::max(1, bottom.saturating_sub(top)) as u32,
323 )
324 }
325
326 pub fn round_out(&self) -> Option<IntRect> {
330 let left = i32::saturate_floor(self.left());
331 let top = i32::saturate_floor(self.top());
332 let right = i32::saturate_ceil(self.right());
333 let bottom = i32::saturate_ceil(self.bottom());
334 IntRect::from_xywh(
335 left,
336 top,
337 core::cmp::max(1, right.saturating_sub(left)) as u32,
338 core::cmp::max(1, bottom.saturating_sub(top)) as u32,
339 )
340 }
341
342 pub fn intersect(&self, other: &Self) -> Option<Self> {
346 let left = self.x().max(other.x());
347 let top = self.y().max(other.y());
348
349 let right = self.right().min(other.right());
350 let bottom = self.bottom().min(other.bottom());
351
352 Rect::from_ltrb(left, top, right, bottom)
353 }
354
355 pub fn join(&self, other: &Self) -> Option<Self> {
359 if other.is_empty() {
360 return Some(*self);
361 }
362 if self.is_empty() {
363 return Some(*other);
364 }
365
366 let left = self.x().min(other.x());
367 let top = self.y().min(other.y());
368
369 let right = self.right().max(other.right());
370 let bottom = self.bottom().max(other.bottom());
371
372 Rect::from_ltrb(left, top, right, bottom)
373 }
374
375 pub fn from_points(points: &[Point]) -> Option<Self> {
379 use crate::f32x4_t::f32x4;
380
381 let mut offset;
382 let mut min;
383 let mut max;
384
385 match points.len() {
388 0 => return None,
389 1 => {
390 let Point { x, y } = points[0];
391 return Rect::from_xywh(x, y, 0.0, 0.0);
392 }
393 2 => {
394 let Point { x: x0, y: y0 } = points[0];
395 let Point { x: x1, y: y1 } = points[1];
396 let (l, r) = if x0 < x1 { (x0, x1) } else { (x1, x0) };
398 let (t, b) = if y0 < y1 { (y0, y1) } else { (y1, y0) };
399 return Rect::from_ltrb(l, t, r, b);
400 }
401 i if i & 1 != 0 => {
402 let pt = points[0];
403 min = f32x4([pt.x, pt.y, pt.x, pt.y]);
404 max = min;
405 offset = 1;
406 }
407 _ => {
408 let pt0 = points[0];
409 let pt1 = points[1];
410 min = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
411 max = min;
412 offset = 2;
413 }
414 }
415
416 let mut accum = f32x4::default();
417 while offset != points.len() {
418 let pt0 = points[offset + 0];
419 let pt1 = points[offset + 1];
420 let xy = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
421
422 accum *= xy;
423 min = min.min(xy);
424 max = max.max(xy);
425 offset += 2;
426 }
427
428 let all_finite = accum * f32x4::default() == f32x4::default();
429 let min: [f32; 4] = min.0;
430 let max: [f32; 4] = max.0;
431 if all_finite {
432 Rect::from_ltrb(
433 min[0].min(min[2]),
434 min[1].min(min[3]),
435 max[0].max(max[2]),
436 max[1].max(max[3]),
437 )
438 } else {
439 None
440 }
441 }
442
443 pub fn inset(&self, dx: f32, dy: f32) -> Option<Self> {
445 Rect::from_ltrb(
446 self.left() + dx,
447 self.top() + dy,
448 self.right() - dx,
449 self.bottom() - dy,
450 )
451 }
452
453 pub fn outset(&self, dx: f32, dy: f32) -> Option<Self> {
455 self.inset(-dx, -dy)
456 }
457
458 pub fn transform(&self, ts: Transform) -> Option<Self> {
462 if ts.is_identity() {
463 Some(*self)
464 } else if ts.has_skew() {
465 let lt = Point::from_xy(self.left(), self.top());
467 let rt = Point::from_xy(self.right(), self.top());
468 let lb = Point::from_xy(self.left(), self.bottom());
469 let rb = Point::from_xy(self.right(), self.bottom());
470 let mut pts = [lt, rt, lb, rb];
471 ts.map_points(&mut pts);
472 Self::from_points(&pts)
473 } else {
474 let lt = Point::from_xy(self.left(), self.top());
476 let rb = Point::from_xy(self.right(), self.bottom());
477 let mut pts = [lt, rb];
478 ts.map_points(&mut pts);
479 Self::from_points(&pts)
480 }
481 }
482
483 pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self {
485 let x = self.x() * bbox.width() + bbox.x();
486 let y = self.y() * bbox.height() + bbox.y();
487 let w = self.width() * bbox.width();
488 let h = self.height() * bbox.height();
489 Self::from_xywh(x, y, w, h).unwrap()
490 }
491
492 pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> {
494 NonZeroRect::from_xywh(self.x(), self.y(), self.width(), self.height())
495 }
496}
497
498fn checked_f32_sub(a: f32, b: f32) -> Option<f32> {
499 debug_assert!(a.is_finite());
500 debug_assert!(b.is_finite());
501
502 let n = a as f64 - b as f64;
503 if n > f32::MIN as f64 && n < f32::MAX as f64 {
505 Some(n as f32)
506 } else {
507 None
508 }
509}
510
511#[cfg(test)]
512mod rect_tests {
513 use super::*;
514
515 #[test]
516 fn tests() {
517 assert_eq!(Rect::from_ltrb(10.0, 10.0, 5.0, 10.0), None);
518 assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 5.0), None);
519 assert_eq!(Rect::from_ltrb(f32::NAN, 10.0, 10.0, 10.0), None);
520 assert_eq!(Rect::from_ltrb(10.0, f32::NAN, 10.0, 10.0), None);
521 assert_eq!(Rect::from_ltrb(10.0, 10.0, f32::NAN, 10.0), None);
522 assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::NAN), None);
523 assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::INFINITY), None);
524
525 let rect = Rect::from_ltrb(10.0, 20.0, 30.0, 40.0).unwrap();
526 assert_eq!(rect.left(), 10.0);
527 assert_eq!(rect.top(), 20.0);
528 assert_eq!(rect.right(), 30.0);
529 assert_eq!(rect.bottom(), 40.0);
530 assert_eq!(rect.width(), 20.0);
531 assert_eq!(rect.height(), 20.0);
532
533 let rect = Rect::from_ltrb(-30.0, 20.0, -10.0, 40.0).unwrap();
534 assert_eq!(rect.width(), 20.0);
535 assert_eq!(rect.height(), 20.0);
536 }
537
538 #[test]
539 fn transform() {
540 let rect = Rect::from_ltrb(1.0, 2.0, 3.0, 4.0).unwrap();
542
543 let ts = Transform::identity();
544 assert_eq!(rect.transform(ts).unwrap(), rect);
545
546 let ts = Transform::from_scale(1.0, 2.0);
548 let rect_ts: Rect = Rect::from_ltrb(1.0, 4.0, 3.0, 8.0).unwrap();
549 assert_eq!(rect.transform(ts).unwrap(), rect_ts);
550
551 let ts = Transform::from_skew(1.0, 0.0);
554 let bounding_rect: Rect = Rect::from_ltrb(3.0, 2.0, 7.0, 4.0).unwrap();
555 assert_eq!(rect.transform(ts).unwrap(), bounding_rect);
556
557 let ts = Transform::from_skew(0.0, 2.0);
559 let bounding_rect: Rect = Rect::from_ltrb(1.0, 4.0, 3.0, 10.0).unwrap();
560 assert_eq!(rect.transform(ts).unwrap(), bounding_rect);
561 }
562}
563
564#[allow(missing_docs)]
576#[derive(Copy, Clone, PartialEq)]
577pub struct NonZeroRect {
578 left: FiniteF32,
579 top: FiniteF32,
580 right: FiniteF32,
581 bottom: FiniteF32,
582}
583
584impl core::fmt::Debug for NonZeroRect {
585 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
586 f.debug_struct("NonZeroRect")
587 .field("left", &self.left.get())
588 .field("top", &self.top.get())
589 .field("right", &self.right.get())
590 .field("bottom", &self.bottom.get())
591 .finish()
592 }
593}
594
595impl NonZeroRect {
596 pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> {
598 let left = FiniteF32::new(left)?;
599 let top = FiniteF32::new(top)?;
600 let right = FiniteF32::new(right)?;
601 let bottom = FiniteF32::new(bottom)?;
602
603 if left.get() < right.get() && top.get() < bottom.get() {
604 checked_f32_sub(right.get(), left.get())?;
606 checked_f32_sub(bottom.get(), top.get())?;
607
608 Some(Self {
609 left,
610 top,
611 right,
612 bottom,
613 })
614 } else {
615 None
616 }
617 }
618
619 pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
621 Self::from_ltrb(x, y, w + x, h + y)
622 }
623
624 pub fn left(&self) -> f32 {
626 self.left.get()
627 }
628
629 pub fn top(&self) -> f32 {
631 self.top.get()
632 }
633
634 pub fn right(&self) -> f32 {
636 self.right.get()
637 }
638
639 pub fn bottom(&self) -> f32 {
641 self.bottom.get()
642 }
643
644 pub fn x(&self) -> f32 {
646 self.left.get()
647 }
648
649 pub fn y(&self) -> f32 {
651 self.top.get()
652 }
653
654 pub fn width(&self) -> f32 {
656 self.right.get() - self.left.get()
657 }
658
659 pub fn height(&self) -> f32 {
661 self.bottom.get() - self.top.get()
662 }
663
664 pub fn size(&self) -> Size {
666 Size::from_wh(self.width(), self.height()).unwrap()
667 }
668
669 pub fn translate_to(&self, x: f32, y: f32) -> Option<Self> {
671 Self::from_xywh(x, y, self.width(), self.height())
672 }
673
674 pub fn transform(&self, ts: Transform) -> Option<Self> {
678 if ts.is_identity() {
679 Some(*self)
680 } else {
681 self.to_rect().transform(ts)?.to_non_zero_rect()
682 }
683 }
684
685 pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self {
687 let x = self.x() * bbox.width() + bbox.x();
688 let y = self.y() * bbox.height() + bbox.y();
689 let w = self.width() * bbox.width();
690 let h = self.height() * bbox.height();
691 Self::from_xywh(x, y, w, h).unwrap()
692 }
693
694 pub fn to_rect(&self) -> Rect {
696 Rect::from_xywh(self.x(), self.y(), self.width(), self.height()).unwrap()
697 }
698
699 pub fn to_int_rect(&self) -> IntRect {
701 IntRect::from_xywh(
702 self.x().floor() as i32,
703 self.y().floor() as i32,
704 core::cmp::max(1, self.width().ceil() as u32),
705 core::cmp::max(1, self.height().ceil() as u32),
706 )
707 .unwrap()
708 }
709}