ruisa_path/
rect.rs

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, Point, SaturateRound};
9
10/// An integer rectangle.
11///
12/// # Guarantees
13///
14/// - Width and height are in 1..=i32::MAX range.
15/// - x+width and y+height does not overflow.
16#[allow(missing_docs)]
17#[derive(Copy, Clone, PartialEq, Debug)]
18pub struct IntRect {
19    x: i32,
20    y: i32,
21    width: LengthU32,
22    height: LengthU32,
23}
24
25impl IntRect {
26    /// Creates a new `IntRect`.
27    pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Option<Self> {
28        x.checked_add(i32::try_from(width).ok()?)?;
29        y.checked_add(i32::try_from(height).ok()?)?;
30
31        Some(IntRect {
32            x,
33            y,
34            width: LengthU32::new(width)?,
35            height: LengthU32::new(height)?,
36        })
37    }
38
39    /// Creates a new `IntRect`.
40    pub fn from_ltrb(left: i32, top: i32, right: i32, bottom: i32) -> Option<Self> {
41        let width = u32::try_from(right.checked_sub(left)?).ok()?;
42        let height = u32::try_from(bottom.checked_sub(top)?).ok()?;
43        IntRect::from_xywh(left, top, width, height)
44    }
45
46    /// Returns rect's X position.
47    pub fn x(&self) -> i32 {
48        self.x
49    }
50
51    /// Returns rect's Y position.
52    pub fn y(&self) -> i32 {
53        self.y
54    }
55
56    /// Returns rect's width.
57    pub fn width(&self) -> u32 {
58        self.width.get()
59    }
60
61    /// Returns rect's height.
62    pub fn height(&self) -> u32 {
63        self.height.get()
64    }
65
66    /// Returns rect's left edge.
67    pub fn left(&self) -> i32 {
68        self.x
69    }
70
71    /// Returns rect's top edge.
72    pub fn top(&self) -> i32 {
73        self.y
74    }
75
76    /// Returns rect's right edge.
77    pub fn right(&self) -> i32 {
78        // No overflow is guaranteed by constructors.
79        self.x + self.width.get() as i32
80    }
81
82    /// Returns rect's bottom edge.
83    pub fn bottom(&self) -> i32 {
84        // No overflow is guaranteed by constructors.
85        self.y + self.height.get() as i32
86    }
87
88    /// Returns rect's size.
89    pub fn size(&self) -> IntSize {
90        IntSize {
91            width: self.width,
92            height: self.height,
93        }
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    /// Converts into `Rect`.
141    pub fn to_rect(&self) -> Rect {
142        // Can't fail, because `IntRect` is always valid.
143        Rect::from_ltrb(
144            self.x as f32,
145            self.y as f32,
146            self.x as f32 + self.width.get() as f32,
147            self.y as f32 + self.height.get() as f32,
148        )
149        .unwrap()
150    }
151
152    /// Converts into `ScreenIntRect`.
153    ///
154    /// # Checks
155    ///
156    /// - x >= 0
157    /// - y >= 0
158    pub fn to_screen_int_rect(&self) -> Option<ScreenIntRect> {
159        let x = u32::try_from(self.x).ok()?;
160        let y = u32::try_from(self.y).ok()?;
161        Some(ScreenIntRect::from_xywh_safe(x, y, self.width, self.height))
162    }
163}
164
165#[cfg(test)]
166mod int_rect_tests {
167    use super::*;
168
169    #[test]
170    fn tests() {
171        assert_eq!(IntRect::from_xywh(0, 0, 0, 0), None);
172        assert_eq!(IntRect::from_xywh(0, 0, 1, 0), None);
173        assert_eq!(IntRect::from_xywh(0, 0, 0, 1), None);
174
175        assert_eq!(
176            IntRect::from_xywh(0, 0, core::u32::MAX, core::u32::MAX),
177            None
178        );
179        assert_eq!(IntRect::from_xywh(0, 0, 1, core::u32::MAX), None);
180        assert_eq!(IntRect::from_xywh(0, 0, core::u32::MAX, 1), None);
181
182        assert_eq!(IntRect::from_xywh(core::i32::MAX, 0, 1, 1), None);
183        assert_eq!(IntRect::from_xywh(0, core::i32::MAX, 1, 1), None);
184
185        let r = IntRect::from_xywh(1, 2, 3, 4).unwrap();
186        assert_eq!(
187            r.to_screen_int_rect().unwrap(),
188            ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap()
189        );
190
191        let r = IntRect::from_xywh(-1, -1, 3, 4).unwrap();
192        assert_eq!(r.to_screen_int_rect(), None);
193
194        {
195            // No intersection.
196            let r1 = IntRect::from_xywh(1, 2, 3, 4).unwrap();
197            let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
198            assert_eq!(r1.intersect(&r2), None);
199        }
200
201        {
202            // Second inside the first one.
203            let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
204            let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
205            assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 13, 14));
206        }
207
208        {
209            // Partial overlap.
210            let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
211            let r2 = IntRect::from_xywh(11, 12, 50, 60).unwrap();
212            assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 20, 30));
213        }
214    }
215}
216
217/// A screen `IntRect`.
218///
219/// # Guarantees
220///
221/// - X and Y are in 0..=i32::MAX range.
222/// - Width and height are in 1..=i32::MAX range.
223/// - x+width and y+height does not overflow.
224#[allow(missing_docs)]
225#[derive(Copy, Clone, PartialEq, Debug)]
226pub struct ScreenIntRect {
227    x: u32,
228    y: u32,
229    width: LengthU32,
230    height: LengthU32,
231}
232
233impl ScreenIntRect {
234    /// Creates a new `ScreenIntRect`.
235    pub fn from_xywh(x: u32, y: u32, width: u32, height: u32) -> Option<Self> {
236        i32::try_from(x).ok()?;
237        i32::try_from(y).ok()?;
238        i32::try_from(width).ok()?;
239        i32::try_from(height).ok()?;
240
241        x.checked_add(width)?;
242        y.checked_add(height)?;
243
244        let width = LengthU32::new(width)?;
245        let height = LengthU32::new(height)?;
246
247        Some(ScreenIntRect {
248            x,
249            y,
250            width,
251            height,
252        })
253    }
254
255    /// Creates a new `ScreenIntRect`.
256    pub const fn from_xywh_safe(x: u32, y: u32, width: LengthU32, height: LengthU32) -> Self {
257        ScreenIntRect {
258            x,
259            y,
260            width,
261            height,
262        }
263    }
264
265    /// Returns rect's X position.
266    pub fn x(&self) -> u32 {
267        self.x
268    }
269
270    /// Returns rect's Y position.
271    pub fn y(&self) -> u32 {
272        self.y
273    }
274
275    /// Returns rect's width.
276    pub fn width(&self) -> u32 {
277        self.width.get()
278    }
279
280    /// Returns rect's height.
281    pub fn height(&self) -> u32 {
282        self.height.get()
283    }
284
285    /// Returns rect's width.
286    pub fn width_safe(&self) -> LengthU32 {
287        self.width
288    }
289
290    /// Returns rect's left edge.
291    pub fn left(&self) -> u32 {
292        self.x
293    }
294
295    /// Returns rect's top edge.
296    pub fn top(&self) -> u32 {
297        self.y
298    }
299
300    /// Returns rect's right edge.
301    ///
302    /// The right edge is at least 1.
303    pub fn right(&self) -> u32 {
304        // No overflow is guaranteed by constructors.
305        self.x + self.width.get()
306    }
307
308    /// Returns rect's bottom edge.
309    ///
310    /// The bottom edge is at least 1.
311    pub fn bottom(&self) -> u32 {
312        // No overflow is guaranteed by constructors.
313        self.y + self.height.get()
314    }
315
316    /// Returns rect's size.
317    pub fn size(&self) -> IntSize {
318        IntSize {
319            width: self.width,
320            height: self.height,
321        }
322    }
323
324    /// Checks that the rect is completely includes `other` Rect.
325    pub fn contains(&self, other: &Self) -> bool {
326        self.x <= other.x
327            && self.y <= other.y
328            && self.right() >= other.right()
329            && self.bottom() >= other.bottom()
330    }
331
332    /// Converts into a `IntRect`.
333    pub fn to_int_rect(&self) -> IntRect {
334        // Everything is already checked by constructors.
335        IntRect::from_xywh(
336            self.x as i32,
337            self.y as i32,
338            self.width.get(),
339            self.height.get(),
340        )
341        .unwrap()
342    }
343
344    /// Converts into a `Rect`.
345    pub fn to_rect(&self) -> Rect {
346        // Can't fail, because `ScreenIntRect` is always valid.
347        // And u32 always fits into f32.
348        Rect::from_ltrb(
349            self.x as f32,
350            self.y as f32,
351            self.x as f32 + self.width.get() as f32,
352            self.y as f32 + self.height.get() as f32,
353        )
354        .unwrap()
355    }
356}
357
358#[cfg(test)]
359mod screen_int_rect_tests {
360    use super::*;
361
362    #[test]
363    fn tests() {
364        assert_eq!(ScreenIntRect::from_xywh(0, 0, 0, 0), None);
365        assert_eq!(ScreenIntRect::from_xywh(0, 0, 1, 0), None);
366        assert_eq!(ScreenIntRect::from_xywh(0, 0, 0, 1), None);
367
368        assert_eq!(
369            ScreenIntRect::from_xywh(0, 0, core::u32::MAX, core::u32::MAX),
370            None
371        );
372        assert_eq!(ScreenIntRect::from_xywh(0, 0, 1, core::u32::MAX), None);
373        assert_eq!(ScreenIntRect::from_xywh(0, 0, core::u32::MAX, 1), None);
374
375        assert_eq!(ScreenIntRect::from_xywh(core::u32::MAX, 0, 1, 1), None);
376        assert_eq!(ScreenIntRect::from_xywh(0, core::u32::MAX, 1, 1), None);
377
378        assert_eq!(
379            ScreenIntRect::from_xywh(
380                core::u32::MAX,
381                core::u32::MAX,
382                core::u32::MAX,
383                core::u32::MAX
384            ),
385            None
386        );
387
388        let r = ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap();
389        assert_eq!(r.x(), 1);
390        assert_eq!(r.y(), 2);
391        assert_eq!(r.width(), 3);
392        assert_eq!(r.height(), 4);
393        assert_eq!(r.right(), 4);
394        assert_eq!(r.bottom(), 6);
395    }
396}
397
398/// A rectangle defined by left, top, right and bottom edges.
399///
400/// Can have zero width and/or height. But not a negative one.
401///
402/// # Guarantees
403///
404/// - All values are finite.
405/// - Left edge is <= right.
406/// - Top edge is <= bottom.
407/// - Width and height are <= f32::MAX.
408#[allow(missing_docs)]
409#[derive(Copy, Clone, PartialEq)]
410pub struct Rect {
411    left: FiniteF32,
412    top: FiniteF32,
413    right: FiniteF32,
414    bottom: FiniteF32,
415}
416
417impl core::fmt::Debug for Rect {
418    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
419        f.debug_struct("Rect")
420            .field("left", &self.left.get())
421            .field("top", &self.top.get())
422            .field("right", &self.right.get())
423            .field("bottom", &self.bottom.get())
424            .finish()
425    }
426}
427
428impl Rect {
429    /// Creates new `Rect`.
430    pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> {
431        let left = FiniteF32::new(left)?;
432        let top = FiniteF32::new(top)?;
433        let right = FiniteF32::new(right)?;
434        let bottom = FiniteF32::new(bottom)?;
435
436        if left.get() <= right.get() && top.get() <= bottom.get() {
437            // Width and height must not overflow.
438            checked_f32_sub(right.get(), left.get())?;
439            checked_f32_sub(bottom.get(), top.get())?;
440
441            Some(Rect {
442                left,
443                top,
444                right,
445                bottom,
446            })
447        } else {
448            None
449        }
450    }
451
452    /// Creates new `Rect`.
453    pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
454        Rect::from_ltrb(x, y, w + x, h + y)
455    }
456
457    /// Returns the left edge.
458    pub fn left(&self) -> f32 {
459        self.left.get()
460    }
461
462    /// Returns the top edge.
463    pub fn top(&self) -> f32 {
464        self.top.get()
465    }
466
467    /// Returns the right edge.
468    pub fn right(&self) -> f32 {
469        self.right.get()
470    }
471
472    /// Returns the bottom edge.
473    pub fn bottom(&self) -> f32 {
474        self.bottom.get()
475    }
476
477    /// Returns rect's X position.
478    pub fn x(&self) -> f32 {
479        self.left.get()
480    }
481
482    /// Returns rect's Y position.
483    pub fn y(&self) -> f32 {
484        self.top.get()
485    }
486
487    /// Returns rect's width.
488    #[inline]
489    pub fn width(&self) -> f32 {
490        self.right.get() - self.left.get()
491    }
492
493    /// Returns rect's height.
494    #[inline]
495    pub fn height(&self) -> f32 {
496        self.bottom.get() - self.top.get()
497    }
498
499    /// Converts into an `IntRect` by adding 0.5 and discarding the fractional portion.
500    ///
501    /// Width and height are guarantee to be >= 1.
502    pub fn round(&self) -> Option<IntRect> {
503        IntRect::from_xywh(
504            i32::saturate_round(self.x()),
505            i32::saturate_round(self.y()),
506            core::cmp::max(1, i32::saturate_round(self.width()) as u32),
507            core::cmp::max(1, i32::saturate_round(self.height()) as u32),
508        )
509    }
510
511    /// Converts into an `IntRect` rounding outwards.
512    ///
513    /// Width and height are guarantee to be >= 1.
514    pub fn round_out(&self) -> Option<IntRect> {
515        IntRect::from_xywh(
516            i32::saturate_floor(self.x()),
517            i32::saturate_floor(self.y()),
518            core::cmp::max(1, i32::saturate_ceil(self.width()) as u32),
519            core::cmp::max(1, i32::saturate_ceil(self.height()) as u32),
520        )
521    }
522
523    /// Returns an intersection of two rectangles.
524    ///
525    /// Returns `None` otherwise.
526    pub fn intersect(&self, other: &Self) -> Option<Self> {
527        let left = self.x().max(other.x());
528        let top = self.y().max(other.y());
529
530        let right = self.right().min(other.right());
531        let bottom = self.bottom().min(other.bottom());
532
533        Rect::from_ltrb(left, top, right, bottom)
534    }
535
536    /// Creates a Rect from Point array.
537    ///
538    /// Returns None if count is zero or if Point array contains an infinity or NaN.
539    pub fn from_points(points: &[Point]) -> Option<Self> {
540        use crate::f32x4_t::f32x4;
541
542        if points.is_empty() {
543            return None;
544        }
545
546        let mut offset = 0;
547        let mut min;
548        let mut max;
549        if points.len() & 1 != 0 {
550            let pt = points[0];
551            min = f32x4([pt.x, pt.y, pt.x, pt.y]);
552            max = min;
553            offset += 1;
554        } else {
555            let pt0 = points[0];
556            let pt1 = points[1];
557            min = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
558            max = min;
559            offset += 2;
560        }
561
562        let mut accum = f32x4::default();
563        while offset != points.len() {
564            let pt0 = points[offset + 0];
565            let pt1 = points[offset + 1];
566            let xy = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
567
568            accum *= xy;
569            min = min.min(xy);
570            max = max.max(xy);
571            offset += 2;
572        }
573
574        let all_finite = accum * f32x4::default() == f32x4::default();
575        let min: [f32; 4] = min.0;
576        let max: [f32; 4] = max.0;
577        if all_finite {
578            Rect::from_ltrb(
579                min[0].min(min[2]),
580                min[1].min(min[3]),
581                max[0].max(max[2]),
582                max[1].max(max[3]),
583            )
584        } else {
585            None
586        }
587    }
588
589    /// Insets the rectangle by the specified offset.
590    pub fn inset(&mut self, dx: f32, dy: f32) -> Option<Self> {
591        Rect::from_ltrb(
592            self.left() + dx,
593            self.top() + dy,
594            self.right() - dx,
595            self.bottom() - dy,
596        )
597    }
598
599    /// Outsets the rectangle by the specified offset.
600    pub fn outset(&mut self, dx: f32, dy: f32) -> Option<Self> {
601        self.inset(-dx, -dy)
602    }
603}
604
605fn checked_f32_sub(a: f32, b: f32) -> Option<f32> {
606    debug_assert!(a.is_finite());
607    debug_assert!(b.is_finite());
608
609    let n = a as f64 - b as f64;
610    // Not sure if this is perfectly correct.
611    if n > core::f32::MIN as f64 && n < core::f32::MAX as f64 {
612        Some(n as f32)
613    } else {
614        None
615    }
616}
617
618#[cfg(test)]
619mod rect_tests {
620    use super::*;
621
622    #[test]
623    fn tests() {
624        assert_eq!(Rect::from_ltrb(10.0, 10.0, 5.0, 10.0), None);
625        assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 5.0), None);
626        assert_eq!(Rect::from_ltrb(core::f32::NAN, 10.0, 10.0, 10.0), None);
627        assert_eq!(Rect::from_ltrb(10.0, core::f32::NAN, 10.0, 10.0), None);
628        assert_eq!(Rect::from_ltrb(10.0, 10.0, core::f32::NAN, 10.0), None);
629        assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, core::f32::NAN), None);
630        assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, core::f32::INFINITY), None);
631
632        let rect = Rect::from_ltrb(10.0, 20.0, 30.0, 40.0).unwrap();
633        assert_eq!(rect.left(), 10.0);
634        assert_eq!(rect.top(), 20.0);
635        assert_eq!(rect.right(), 30.0);
636        assert_eq!(rect.bottom(), 40.0);
637        assert_eq!(rect.width(), 20.0);
638        assert_eq!(rect.height(), 20.0);
639
640        let rect = Rect::from_ltrb(-30.0, 20.0, -10.0, 40.0).unwrap();
641        assert_eq!(rect.width(), 20.0);
642        assert_eq!(rect.height(), 20.0);
643    }
644
645    #[test]
646    fn round_overflow() {
647        // minimum value that cause overflow
648        // because i32::MAX has no exact conversion to f32
649        let x = 128.0;
650        // maximum width
651        let width = i32::MAX as f32;
652
653        let rect = Rect::from_xywh(x, 0.0, width, 1.0).unwrap();
654        assert_eq!(rect.round(), None);
655        assert_eq!(rect.round_out(), None);
656    }
657}