1// Copyright 2020 Yevhenii Reizner
2//
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5
6use core::convert::TryFrom;
7
8use crate::{FiniteF32, IntSize, LengthU32, PathBuilder, Point, SaturateRound, Size, Transform};
9
10#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
11use crate::NoStdFloat;
12
13/// An integer rectangle.
14///
15/// # Guarantees
16///
17/// - Width and height are in 1..=i32::MAX range.
18/// - x+width and y+height does not overflow.
19#[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 /// Creates a new `IntRect`.
30 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 /// Creates a new `IntRect`.
43 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 /// Returns rect's X position.
50 pub fn x(&self) -> i32 {
51 self.x
52 }
53
54 /// Returns rect's Y position.
55 pub fn y(&self) -> i32 {
56 self.y
57 }
58
59 /// Returns rect's width.
60 pub fn width(&self) -> u32 {
61 self.width.get()
62 }
63
64 /// Returns rect's height.
65 pub fn height(&self) -> u32 {
66 self.height.get()
67 }
68
69 /// Returns rect's left edge.
70 pub fn left(&self) -> i32 {
71 self.x
72 }
73
74 /// Returns rect's top edge.
75 pub fn top(&self) -> i32 {
76 self.y
77 }
78
79 /// Returns rect's right edge.
80 pub fn right(&self) -> i32 {
81 // No overflow is guaranteed by constructors.
82 self.x + self.width.get() as i32
83 }
84
85 /// Returns rect's bottom edge.
86 pub fn bottom(&self) -> i32 {
87 // No overflow is guaranteed by constructors.
88 self.y + self.height.get() as i32
89 }
90
91 /// Returns rect's size.
92 pub fn size(&self) -> IntSize {
93 IntSize::from_wh_safe(self.width, self.height)
94 }
95
96 /// Checks that the rect is completely includes `other` Rect.
97 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 /// Returns an intersection of two rectangles.
105 ///
106 /// Returns `None` otherwise.
107 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 /// Insets the rectangle.
121 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 /// Outsets the rectangle.
131 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 /// Translates the rect by the specified offset.
141 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 /// Translates the rect to the specified position.
146 pub fn translate_to(&self, x: i32, y: i32) -> Option<Self> {
147 IntRect::from_xywh(x, y, self.width(), self.height())
148 }
149
150 /// Converts into `Rect`.
151 pub fn to_rect(&self) -> Rect {
152 // Can't fail, because `IntRect` is always valid.
153 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 // No intersection.
182 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 // Second inside the first one.
189 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 // Partial overlap.
196 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/// A rectangle defined by left, top, right and bottom edges.
204///
205/// Can have zero width and/or height. But not a negative one.
206///
207/// # Guarantees
208///
209/// - All values are finite.
210/// - Left edge is <= right.
211/// - Top edge is <= bottom.
212/// - Width and height are <= f32::MAX.
213#[allow(missing_docs)]
214#[derive(Copy, Clone, PartialEq)]
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&mut DebugStruct<'_, '_>.debug_struct("Rect")
225 .field("left", &self.left.get())
226 .field("top", &self.top.get())
227 .field("right", &self.right.get())
228 .field(name:"bottom", &self.bottom.get())
229 .finish()
230 }
231}
232
233impl Rect {
234 /// Creates new `Rect`.
235 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 // Width and height must not overflow.
243 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 /// Creates new `Rect`.
258 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 /// Returns the left edge.
263 pub fn left(&self) -> f32 {
264 self.left.get()
265 }
266
267 /// Returns the top edge.
268 pub fn top(&self) -> f32 {
269 self.top.get()
270 }
271
272 /// Returns the right edge.
273 pub fn right(&self) -> f32 {
274 self.right.get()
275 }
276
277 /// Returns the bottom edge.
278 pub fn bottom(&self) -> f32 {
279 self.bottom.get()
280 }
281
282 /// Returns rect's X position.
283 pub fn x(&self) -> f32 {
284 self.left.get()
285 }
286
287 /// Returns rect's Y position.
288 pub fn y(&self) -> f32 {
289 self.top.get()
290 }
291
292 /// Returns rect's width.
293 #[inline]
294 pub fn width(&self) -> f32 {
295 self.right.get() - self.left.get()
296 }
297
298 /// Returns rect's height.
299 #[inline]
300 pub fn height(&self) -> f32 {
301 self.bottom.get() - self.top.get()
302 }
303
304 /// Converts into an `IntRect` by adding 0.5 and discarding the fractional portion.
305 ///
306 /// Width and height are guarantee to be >= 1.
307 pub fn round(&self) -> Option<IntRect> {
308 IntRect::from_xywh(
309 i32::saturate_round(self.x()),
310 i32::saturate_round(self.y()),
311 core::cmp::max(1, i32::saturate_round(self.width()) as u32),
312 core::cmp::max(1, i32::saturate_round(self.height()) as u32),
313 )
314 }
315
316 /// Converts into an `IntRect` rounding outwards.
317 ///
318 /// Width and height are guarantee to be >= 1.
319 pub fn round_out(&self) -> Option<IntRect> {
320 IntRect::from_xywh(
321 i32::saturate_floor(self.x()),
322 i32::saturate_floor(self.y()),
323 core::cmp::max(1, i32::saturate_ceil(self.width()) as u32),
324 core::cmp::max(1, i32::saturate_ceil(self.height()) as u32),
325 )
326 }
327
328 /// Returns an intersection of two rectangles.
329 ///
330 /// Returns `None` otherwise.
331 pub fn intersect(&self, other: &Self) -> Option<Self> {
332 let left = self.x().max(other.x());
333 let top = self.y().max(other.y());
334
335 let right = self.right().min(other.right());
336 let bottom = self.bottom().min(other.bottom());
337
338 Rect::from_ltrb(left, top, right, bottom)
339 }
340
341 /// Creates a Rect from Point array.
342 ///
343 /// Returns None if count is zero or if Point array contains an infinity or NaN.
344 pub fn from_points(points: &[Point]) -> Option<Self> {
345 use crate::f32x4_t::f32x4;
346
347 if points.is_empty() {
348 return None;
349 }
350
351 let mut offset = 0;
352 let mut min;
353 let mut max;
354 if points.len() & 1 != 0 {
355 let pt = points[0];
356 min = f32x4([pt.x, pt.y, pt.x, pt.y]);
357 max = min;
358 offset += 1;
359 } else {
360 let pt0 = points[0];
361 let pt1 = points[1];
362 min = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
363 max = min;
364 offset += 2;
365 }
366
367 let mut accum = f32x4::default();
368 while offset != points.len() {
369 let pt0 = points[offset + 0];
370 let pt1 = points[offset + 1];
371 let xy = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
372
373 accum *= xy;
374 min = min.min(xy);
375 max = max.max(xy);
376 offset += 2;
377 }
378
379 let all_finite = accum * f32x4::default() == f32x4::default();
380 let min: [f32; 4] = min.0;
381 let max: [f32; 4] = max.0;
382 if all_finite {
383 Rect::from_ltrb(
384 min[0].min(min[2]),
385 min[1].min(min[3]),
386 max[0].max(max[2]),
387 max[1].max(max[3]),
388 )
389 } else {
390 None
391 }
392 }
393
394 /// Insets the rectangle by the specified offset.
395 pub fn inset(&self, dx: f32, dy: f32) -> Option<Self> {
396 Rect::from_ltrb(
397 self.left() + dx,
398 self.top() + dy,
399 self.right() - dx,
400 self.bottom() - dy,
401 )
402 }
403
404 /// Outsets the rectangle by the specified offset.
405 pub fn outset(&self, dx: f32, dy: f32) -> Option<Self> {
406 self.inset(-dx, -dy)
407 }
408
409 /// Transforms the rect using the provided `Transform`.
410 ///
411 /// This method is expensive.
412 pub fn transform(&self, ts: Transform) -> Option<Self> {
413 if !ts.is_identity() {
414 // TODO: remove allocation
415 let mut path = PathBuilder::from_rect(*self);
416 path = path.transform(ts)?;
417 Some(path.bounds())
418 } else {
419 Some(*self)
420 }
421 }
422
423 /// Applies a bounding box transform.
424 pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self {
425 let x = self.x() * bbox.width() + bbox.x();
426 let y = self.y() * bbox.height() + bbox.y();
427 let w = self.width() * bbox.width();
428 let h = self.height() * bbox.height();
429 Self::from_xywh(x, y, w, h).unwrap()
430 }
431
432 /// Converts into [`NonZeroRect`].
433 pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> {
434 NonZeroRect::from_xywh(self.x(), self.y(), self.width(), self.height())
435 }
436}
437
438fn checked_f32_sub(a: f32, b: f32) -> Option<f32> {
439 debug_assert!(a.is_finite());
440 debug_assert!(b.is_finite());
441
442 let n: f64 = a as f64 - b as f64;
443 // Not sure if this is perfectly correct.
444 if n > f32::MIN as f64 && n < f32::MAX as f64 {
445 Some(n as f32)
446 } else {
447 None
448 }
449}
450
451#[cfg(test)]
452mod rect_tests {
453 use super::*;
454
455 #[test]
456 fn tests() {
457 assert_eq!(Rect::from_ltrb(10.0, 10.0, 5.0, 10.0), None);
458 assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 5.0), None);
459 assert_eq!(Rect::from_ltrb(f32::NAN, 10.0, 10.0, 10.0), None);
460 assert_eq!(Rect::from_ltrb(10.0, f32::NAN, 10.0, 10.0), None);
461 assert_eq!(Rect::from_ltrb(10.0, 10.0, f32::NAN, 10.0), None);
462 assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::NAN), None);
463 assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::INFINITY), None);
464
465 let rect = Rect::from_ltrb(10.0, 20.0, 30.0, 40.0).unwrap();
466 assert_eq!(rect.left(), 10.0);
467 assert_eq!(rect.top(), 20.0);
468 assert_eq!(rect.right(), 30.0);
469 assert_eq!(rect.bottom(), 40.0);
470 assert_eq!(rect.width(), 20.0);
471 assert_eq!(rect.height(), 20.0);
472
473 let rect = Rect::from_ltrb(-30.0, 20.0, -10.0, 40.0).unwrap();
474 assert_eq!(rect.width(), 20.0);
475 assert_eq!(rect.height(), 20.0);
476 }
477
478 #[test]
479 fn round_overflow() {
480 // minimum value that cause overflow
481 // because i32::MAX has no exact conversion to f32
482 let x = 128.0;
483 // maximum width
484 let width = i32::MAX as f32;
485
486 let rect = Rect::from_xywh(x, 0.0, width, 1.0).unwrap();
487 assert_eq!(rect.round(), None);
488 assert_eq!(rect.round_out(), None);
489 }
490}
491
492/// A rectangle defined by left, top, right and bottom edges.
493///
494/// Similar to [`Rect`], but width and height guarantee to be non-zero and positive.
495///
496/// # Guarantees
497///
498/// - All values are finite.
499/// - Left edge is < right.
500/// - Top edge is < bottom.
501/// - Width and height are <= f32::MAX.
502/// - Width and height are > 0.0
503#[allow(missing_docs)]
504#[derive(Copy, Clone, PartialEq)]
505pub struct NonZeroRect {
506 left: FiniteF32,
507 top: FiniteF32,
508 right: FiniteF32,
509 bottom: FiniteF32,
510}
511
512impl core::fmt::Debug for NonZeroRect {
513 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
514 f&mut DebugStruct<'_, '_>.debug_struct("NonZeroRect")
515 .field("left", &self.left.get())
516 .field("top", &self.top.get())
517 .field("right", &self.right.get())
518 .field(name:"bottom", &self.bottom.get())
519 .finish()
520 }
521}
522
523impl NonZeroRect {
524 /// Creates new `NonZeroRect`.
525 pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> {
526 let left = FiniteF32::new(left)?;
527 let top = FiniteF32::new(top)?;
528 let right = FiniteF32::new(right)?;
529 let bottom = FiniteF32::new(bottom)?;
530
531 if left.get() < right.get() && top.get() < bottom.get() {
532 // Width and height must not overflow.
533 checked_f32_sub(right.get(), left.get())?;
534 checked_f32_sub(bottom.get(), top.get())?;
535
536 Some(Self {
537 left,
538 top,
539 right,
540 bottom,
541 })
542 } else {
543 None
544 }
545 }
546
547 /// Creates new `NonZeroRect`.
548 pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
549 Self::from_ltrb(x, y, w + x, h + y)
550 }
551
552 /// Returns the left edge.
553 pub fn left(&self) -> f32 {
554 self.left.get()
555 }
556
557 /// Returns the top edge.
558 pub fn top(&self) -> f32 {
559 self.top.get()
560 }
561
562 /// Returns the right edge.
563 pub fn right(&self) -> f32 {
564 self.right.get()
565 }
566
567 /// Returns the bottom edge.
568 pub fn bottom(&self) -> f32 {
569 self.bottom.get()
570 }
571
572 /// Returns rect's X position.
573 pub fn x(&self) -> f32 {
574 self.left.get()
575 }
576
577 /// Returns rect's Y position.
578 pub fn y(&self) -> f32 {
579 self.top.get()
580 }
581
582 /// Returns rect's width.
583 pub fn width(&self) -> f32 {
584 self.right.get() - self.left.get()
585 }
586
587 /// Returns rect's height.
588 pub fn height(&self) -> f32 {
589 self.bottom.get() - self.top.get()
590 }
591
592 /// Returns rect's size.
593 pub fn size(&self) -> Size {
594 Size::from_wh(self.width(), self.height()).unwrap()
595 }
596
597 /// Translates the rect to the specified position.
598 pub fn translate_to(&self, x: f32, y: f32) -> Option<Self> {
599 Self::from_xywh(x, y, self.width(), self.height())
600 }
601
602 /// Transforms the rect using the provided `Transform`.
603 ///
604 /// This method is expensive.
605 pub fn transform(&self, ts: Transform) -> Option<Self> {
606 if !ts.is_identity() {
607 // TODO: remove allocation
608 let mut path = PathBuilder::from_rect(self.to_rect());
609 path = path.transform(ts)?;
610 path.bounds().to_non_zero_rect()
611 } else {
612 Some(*self)
613 }
614 }
615
616 /// Applies a bounding box transform.
617 pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self {
618 let x = self.x() * bbox.width() + bbox.x();
619 let y = self.y() * bbox.height() + bbox.y();
620 let w = self.width() * bbox.width();
621 let h = self.height() * bbox.height();
622 Self::from_xywh(x, y, w, h).unwrap()
623 }
624
625 /// Converts into [`Rect`].
626 pub fn to_rect(&self) -> Rect {
627 Rect::from_xywh(self.x(), self.y(), self.width(), self.height()).unwrap()
628 }
629
630 /// Converts into [`IntRect`].
631 pub fn to_int_rect(&self) -> IntRect {
632 IntRect::from_xywh(
633 self.x().floor() as i32,
634 self.y().floor() as i32,
635 core::cmp::max(1, self.width().ceil() as u32),
636 core::cmp::max(1, self.height().ceil() as u32),
637 )
638 .unwrap()
639 }
640}
641