Skip to main content

tiny_skia_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, 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, 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    /// 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    /// Returns true if the rect is empty.
305    #[inline]
306    pub fn is_empty(&self) -> bool {
307        self.left == self.right || self.top == self.bottom
308    }
309
310    /// Converts into an `IntRect` by adding 0.5 and discarding the fractional portion.
311    ///
312    /// Width and height are guarantee to be >= 1.
313    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    /// Converts into an `IntRect` rounding outwards.
327    ///
328    /// Width and height are guarantee to be >= 1.
329    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    /// Returns an intersection of two rectangles.
343    ///
344    /// Returns `None` otherwise.
345    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    /// Returns the union of two rectangles.
356    ///
357    /// Returns `None` if the width or height would overflow.
358    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    /// Creates a Rect from Point array.
376    ///
377    /// Returns None if count is zero or if Point array contains an infinity or NaN.
378    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        // Handle trivial-size lists without creating vectors, since they won't
386        // actually use the vector min/max code
387        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                // Avoid min/max to preserve a NaN for rejection by from_ltrb
397                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    /// Insets the rectangle by the specified offset.
444    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    /// Outsets the rectangle by the specified offset.
454    pub fn outset(&self, dx: f32, dy: f32) -> Option<Self> {
455        self.inset(-dx, -dy)
456    }
457
458    /// Transforms the rect using the provided `Transform`.
459    ///
460    /// If the transform is a skew, the result will be a bounding box around the skewed rectangle.
461    pub fn transform(&self, ts: Transform) -> Option<Self> {
462        if ts.is_identity() {
463            Some(*self)
464        } else if ts.has_skew() {
465            // we need to transform all 4 corners
466            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            // Faster (more common) case
475            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    /// Applies a bounding box transform.
484    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    /// Converts into [`NonZeroRect`].
493    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    // Not sure if this is perfectly correct.
504    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        // Tests based on 2x2 rectangle
541        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        // Scale x by 1x, y by 2x
547        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        // Skew box along x-axis - vertical lines at y=c go to y=c+x and
552        // horizontal lines stay put. Result is bounding box
553        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        // Skew box along y-axis - horizontal lines at x=c go to x=c+2y
558        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/// A rectangle defined by left, top, right and bottom edges.
565///
566/// Similar to [`Rect`], but width and height guarantee to be non-zero and positive.
567///
568/// # Guarantees
569///
570/// - All values are finite.
571/// - Left edge is < right.
572/// - Top edge is < bottom.
573/// - Width and height are <= f32::MAX.
574/// - Width and height are > 0.0
575#[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    /// Creates new `NonZeroRect`.
597    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            // Width and height must not overflow.
605            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    /// Creates new `NonZeroRect`.
620    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    /// Returns the left edge.
625    pub fn left(&self) -> f32 {
626        self.left.get()
627    }
628
629    /// Returns the top edge.
630    pub fn top(&self) -> f32 {
631        self.top.get()
632    }
633
634    /// Returns the right edge.
635    pub fn right(&self) -> f32 {
636        self.right.get()
637    }
638
639    /// Returns the bottom edge.
640    pub fn bottom(&self) -> f32 {
641        self.bottom.get()
642    }
643
644    /// Returns rect's X position.
645    pub fn x(&self) -> f32 {
646        self.left.get()
647    }
648
649    /// Returns rect's Y position.
650    pub fn y(&self) -> f32 {
651        self.top.get()
652    }
653
654    /// Returns rect's width.
655    pub fn width(&self) -> f32 {
656        self.right.get() - self.left.get()
657    }
658
659    /// Returns rect's height.
660    pub fn height(&self) -> f32 {
661        self.bottom.get() - self.top.get()
662    }
663
664    /// Returns rect's size.
665    pub fn size(&self) -> Size {
666        Size::from_wh(self.width(), self.height()).unwrap()
667    }
668
669    /// Translates the rect to the specified position.
670    pub fn translate_to(&self, x: f32, y: f32) -> Option<Self> {
671        Self::from_xywh(x, y, self.width(), self.height())
672    }
673
674    /// Transforms the rect using the provided `Transform`.
675    ///
676    /// If the transform is a skew, the result will be a bounding box around the skewed rectangle.
677    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    /// Applies a bounding box transform.
686    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    /// Converts into [`Rect`].
695    pub fn to_rect(&self) -> Rect {
696        Rect::from_xywh(self.x(), self.y(), self.width(), self.height()).unwrap()
697    }
698
699    /// Converts into [`IntRect`].
700    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}