kurbo/
rect.rs

1// Copyright 2019 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A rectangle.
5
6use core::fmt;
7use core::ops::{Add, Sub};
8
9use crate::{Ellipse, Insets, PathEl, Point, RoundedRect, RoundedRectRadii, Shape, Size, Vec2};
10
11#[cfg(not(feature = "std"))]
12use crate::common::FloatFuncs;
13
14/// A rectangle.
15#[derive(Clone, Copy, Default, PartialEq)]
16#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct Rect {
19    /// The minimum x coordinate (left edge).
20    pub x0: f64,
21    /// The minimum y coordinate (top edge in y-down spaces).
22    pub y0: f64,
23    /// The maximum x coordinate (right edge).
24    pub x1: f64,
25    /// The maximum y coordinate (bottom edge in y-down spaces).
26    pub y1: f64,
27}
28
29impl Rect {
30    /// The empty rectangle at the origin.
31    pub const ZERO: Rect = Rect::new(0., 0., 0., 0.);
32
33    /// A new rectangle from minimum and maximum coordinates.
34    #[inline(always)]
35    pub const fn new(x0: f64, y0: f64, x1: f64, y1: f64) -> Rect {
36        Rect { x0, y0, x1, y1 }
37    }
38
39    /// A new rectangle from two points.
40    ///
41    /// The result will have non-negative width and height.
42    #[inline]
43    pub fn from_points(p0: impl Into<Point>, p1: impl Into<Point>) -> Rect {
44        let p0 = p0.into();
45        let p1 = p1.into();
46        Rect::new(p0.x, p0.y, p1.x, p1.y).abs()
47    }
48
49    /// A new rectangle from origin and size.
50    ///
51    /// The result will have non-negative width and height.
52    #[inline]
53    pub fn from_origin_size(origin: impl Into<Point>, size: impl Into<Size>) -> Rect {
54        let origin = origin.into();
55        Rect::from_points(origin, origin + size.into().to_vec2())
56    }
57
58    /// A new rectangle from center and size.
59    #[inline]
60    pub fn from_center_size(center: impl Into<Point>, size: impl Into<Size>) -> Rect {
61        let center = center.into();
62        let size = 0.5 * size.into();
63        Rect::new(
64            center.x - size.width,
65            center.y - size.height,
66            center.x + size.width,
67            center.y + size.height,
68        )
69    }
70
71    /// Create a new `Rect` with the same size as `self` and a new origin.
72    #[inline]
73    pub fn with_origin(self, origin: impl Into<Point>) -> Rect {
74        Rect::from_origin_size(origin, self.size())
75    }
76
77    /// Create a new `Rect` with the same origin as `self` and a new size.
78    #[inline]
79    pub fn with_size(self, size: impl Into<Size>) -> Rect {
80        Rect::from_origin_size(self.origin(), size)
81    }
82
83    /// Create a new `Rect` by applying the [`Insets`].
84    ///
85    /// This will not preserve negative width and height.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use kurbo::Rect;
91    /// let inset_rect = Rect::new(0., 0., 10., 10.,).inset(2.);
92    /// assert_eq!(inset_rect.width(), 14.0);
93    /// assert_eq!(inset_rect.x0, -2.0);
94    /// assert_eq!(inset_rect.x1, 12.0);
95    /// ```
96    #[inline]
97    pub fn inset(self, insets: impl Into<Insets>) -> Rect {
98        self + insets.into()
99    }
100
101    /// The width of the rectangle.
102    ///
103    /// Note: nothing forbids negative width.
104    #[inline]
105    pub fn width(&self) -> f64 {
106        self.x1 - self.x0
107    }
108
109    /// The height of the rectangle.
110    ///
111    /// Note: nothing forbids negative height.
112    #[inline]
113    pub fn height(&self) -> f64 {
114        self.y1 - self.y0
115    }
116
117    /// Returns the minimum value for the x-coordinate of the rectangle.
118    #[inline]
119    pub fn min_x(&self) -> f64 {
120        self.x0.min(self.x1)
121    }
122
123    /// Returns the maximum value for the x-coordinate of the rectangle.
124    #[inline]
125    pub fn max_x(&self) -> f64 {
126        self.x0.max(self.x1)
127    }
128
129    /// Returns the minimum value for the y-coordinate of the rectangle.
130    #[inline]
131    pub fn min_y(&self) -> f64 {
132        self.y0.min(self.y1)
133    }
134
135    /// Returns the maximum value for the y-coordinate of the rectangle.
136    #[inline]
137    pub fn max_y(&self) -> f64 {
138        self.y0.max(self.y1)
139    }
140
141    /// The origin of the rectangle.
142    ///
143    /// This is the top left corner in a y-down space and with
144    /// non-negative width and height.
145    #[inline(always)]
146    pub fn origin(&self) -> Point {
147        Point::new(self.x0, self.y0)
148    }
149
150    /// The size of the rectangle.
151    #[inline]
152    pub fn size(&self) -> Size {
153        Size::new(self.width(), self.height())
154    }
155
156    /// The area of the rectangle.
157    #[inline]
158    pub fn area(&self) -> f64 {
159        self.width() * self.height()
160    }
161
162    /// Whether this rectangle has zero area.
163    #[doc(alias = "is_empty")]
164    #[inline]
165    pub fn is_zero_area(&self) -> bool {
166        self.area() == 0.0
167    }
168
169    /// Whether this rectangle has zero area.
170    ///
171    /// Note: a rectangle with negative area is not considered empty.
172    #[inline]
173    #[deprecated(since = "0.11.1", note = "use is_zero_area instead")]
174    pub fn is_empty(&self) -> bool {
175        self.is_zero_area()
176    }
177
178    /// The center point of the rectangle.
179    #[inline]
180    pub fn center(&self) -> Point {
181        Point::new(0.5 * (self.x0 + self.x1), 0.5 * (self.y0 + self.y1))
182    }
183
184    /// Returns `true` if `point` lies within `self`.
185    #[inline]
186    pub fn contains(&self, point: impl Into<Point>) -> bool {
187        let point = point.into();
188        point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1
189    }
190
191    /// Take absolute value of width and height.
192    ///
193    /// The resulting rect has the same extents as the original, but is
194    /// guaranteed to have non-negative width and height.
195    #[inline]
196    pub fn abs(&self) -> Rect {
197        let Rect { x0, y0, x1, y1 } = *self;
198        Rect::new(x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1))
199    }
200
201    /// The smallest rectangle enclosing two rectangles.
202    ///
203    /// Results are valid only if width and height are non-negative.
204    #[inline]
205    pub fn union(&self, other: Rect) -> Rect {
206        Rect::new(
207            self.x0.min(other.x0),
208            self.y0.min(other.y0),
209            self.x1.max(other.x1),
210            self.y1.max(other.y1),
211        )
212    }
213
214    /// Compute the union with one point.
215    ///
216    /// This method includes the perimeter of zero-area rectangles.
217    /// Thus, a succession of `union_pt` operations on a series of
218    /// points yields their enclosing rectangle.
219    ///
220    /// Results are valid only if width and height are non-negative.
221    pub fn union_pt(&self, pt: impl Into<Point>) -> Rect {
222        let pt = pt.into();
223        Rect::new(
224            self.x0.min(pt.x),
225            self.y0.min(pt.y),
226            self.x1.max(pt.x),
227            self.y1.max(pt.y),
228        )
229    }
230
231    /// The intersection of two rectangles.
232    ///
233    /// The result is zero-area if either input has negative width or
234    /// height. The result always has non-negative width and height.
235    ///
236    /// If you want to determine whether two rectangles intersect, use the
237    /// [`overlaps`] method instead.
238    ///
239    /// [`overlaps`]: Rect::overlaps
240    #[inline]
241    pub fn intersect(&self, other: Rect) -> Rect {
242        let x0 = self.x0.max(other.x0);
243        let y0 = self.y0.max(other.y0);
244        let x1 = self.x1.min(other.x1);
245        let y1 = self.y1.min(other.y1);
246        Rect::new(x0, y0, x1.max(x0), y1.max(y0))
247    }
248
249    /// Determines whether this rectangle overlaps with another in any way.
250    ///
251    /// Note that the edge of the rectangle is considered to be part of itself, meaning
252    /// that two rectangles that share an edge are considered to overlap.
253    ///
254    /// Returns `true` if the rectangles overlap, `false` otherwise.
255    ///
256    /// If you want to compute the *intersection* of two rectangles, use the
257    /// [`intersect`] method instead.
258    ///
259    /// [`intersect`]: Rect::intersect
260    ///
261    /// # Examples
262    ///
263    /// ```
264    /// use kurbo::Rect;
265    ///
266    /// let rect1 = Rect::new(0.0, 0.0, 10.0, 10.0);
267    /// let rect2 = Rect::new(5.0, 5.0, 15.0, 15.0);
268    /// assert!(rect1.overlaps(rect2));
269    ///
270    /// let rect1 = Rect::new(0.0, 0.0, 10.0, 10.0);
271    /// let rect2 = Rect::new(10.0, 0.0, 20.0, 10.0);
272    /// assert!(rect1.overlaps(rect2));
273    /// ```
274    #[inline]
275    pub fn overlaps(&self, other: Rect) -> bool {
276        self.x0 <= other.x1 && self.x1 >= other.x0 && self.y0 <= other.y1 && self.y1 >= other.y0
277    }
278
279    /// Returns whether this rectangle contains another rectangle.
280    ///
281    /// A rectangle is considered to contain another rectangle if the other
282    /// rectangle is fully enclosed within the bounds of this rectangle.
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use kurbo::Rect;
288    ///
289    /// let rect1 = Rect::new(0.0, 0.0, 10.0, 10.0);
290    /// let rect2 = Rect::new(2.0, 2.0, 4.0, 4.0);
291    /// assert!(rect1.contains_rect(rect2));
292    /// ```
293    ///
294    /// Two equal rectangles are considered to contain each other.
295    ///
296    /// ```
297    /// use kurbo::Rect;
298    ///
299    /// let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
300    /// assert!(rect.contains_rect(rect));
301    /// ```
302    #[inline]
303    pub fn contains_rect(&self, other: Rect) -> bool {
304        self.x0 <= other.x0 && self.y0 <= other.y0 && self.x1 >= other.x1 && self.y1 >= other.y1
305    }
306
307    /// Expand a rectangle by a constant amount in both directions.
308    ///
309    /// The logic simply applies the amount in each direction. If rectangle
310    /// area or added dimensions are negative, this could give odd results.
311    pub fn inflate(&self, width: f64, height: f64) -> Rect {
312        Rect::new(
313            self.x0 - width,
314            self.y0 - height,
315            self.x1 + width,
316            self.y1 + height,
317        )
318    }
319
320    /// Returns a new `Rect`,
321    /// with each coordinate value [rounded] to the nearest integer.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// use kurbo::Rect;
327    /// let rect = Rect::new(3.3, 3.6, 3.0, -3.1).round();
328    /// assert_eq!(rect.x0, 3.0);
329    /// assert_eq!(rect.y0, 4.0);
330    /// assert_eq!(rect.x1, 3.0);
331    /// assert_eq!(rect.y1, -3.0);
332    /// ```
333    ///
334    /// [rounded]: f64::round
335    #[inline]
336    pub fn round(self) -> Rect {
337        Rect::new(
338            self.x0.round(),
339            self.y0.round(),
340            self.x1.round(),
341            self.y1.round(),
342        )
343    }
344
345    /// Returns a new `Rect`,
346    /// with each coordinate value [rounded up] to the nearest integer,
347    /// unless they are already an integer.
348    ///
349    /// # Examples
350    ///
351    /// ```
352    /// use kurbo::Rect;
353    /// let rect = Rect::new(3.3, 3.6, 3.0, -3.1).ceil();
354    /// assert_eq!(rect.x0, 4.0);
355    /// assert_eq!(rect.y0, 4.0);
356    /// assert_eq!(rect.x1, 3.0);
357    /// assert_eq!(rect.y1, -3.0);
358    /// ```
359    ///
360    /// [rounded up]: f64::ceil
361    #[inline]
362    pub fn ceil(self) -> Rect {
363        Rect::new(
364            self.x0.ceil(),
365            self.y0.ceil(),
366            self.x1.ceil(),
367            self.y1.ceil(),
368        )
369    }
370
371    /// Returns a new `Rect`,
372    /// with each coordinate value [rounded down] to the nearest integer,
373    /// unless they are already an integer.
374    ///
375    /// # Examples
376    ///
377    /// ```
378    /// use kurbo::Rect;
379    /// let rect = Rect::new(3.3, 3.6, 3.0, -3.1).floor();
380    /// assert_eq!(rect.x0, 3.0);
381    /// assert_eq!(rect.y0, 3.0);
382    /// assert_eq!(rect.x1, 3.0);
383    /// assert_eq!(rect.y1, -4.0);
384    /// ```
385    ///
386    /// [rounded down]: f64::floor
387    #[inline]
388    pub fn floor(self) -> Rect {
389        Rect::new(
390            self.x0.floor(),
391            self.y0.floor(),
392            self.x1.floor(),
393            self.y1.floor(),
394        )
395    }
396
397    /// Returns a new `Rect`,
398    /// with each coordinate value rounded away from the center of the `Rect`
399    /// to the nearest integer, unless they are already an integer.
400    /// That is to say this function will return the smallest possible `Rect`
401    /// with integer coordinates that is a superset of `self`.
402    ///
403    /// # Examples
404    ///
405    /// ```
406    /// use kurbo::Rect;
407    ///
408    /// // In positive space
409    /// let rect = Rect::new(3.3, 3.6, 5.6, 4.1).expand();
410    /// assert_eq!(rect.x0, 3.0);
411    /// assert_eq!(rect.y0, 3.0);
412    /// assert_eq!(rect.x1, 6.0);
413    /// assert_eq!(rect.y1, 5.0);
414    ///
415    /// // In both positive and negative space
416    /// let rect = Rect::new(-3.3, -3.6, 5.6, 4.1).expand();
417    /// assert_eq!(rect.x0, -4.0);
418    /// assert_eq!(rect.y0, -4.0);
419    /// assert_eq!(rect.x1, 6.0);
420    /// assert_eq!(rect.y1, 5.0);
421    ///
422    /// // In negative space
423    /// let rect = Rect::new(-5.6, -4.1, -3.3, -3.6).expand();
424    /// assert_eq!(rect.x0, -6.0);
425    /// assert_eq!(rect.y0, -5.0);
426    /// assert_eq!(rect.x1, -3.0);
427    /// assert_eq!(rect.y1, -3.0);
428    ///
429    /// // Inverse orientation
430    /// let rect = Rect::new(5.6, -3.6, 3.3, -4.1).expand();
431    /// assert_eq!(rect.x0, 6.0);
432    /// assert_eq!(rect.y0, -3.0);
433    /// assert_eq!(rect.x1, 3.0);
434    /// assert_eq!(rect.y1, -5.0);
435    /// ```
436    #[inline]
437    pub fn expand(self) -> Rect {
438        // The compiler optimizer will remove the if branching.
439        let (x0, x1) = if self.x0 < self.x1 {
440            (self.x0.floor(), self.x1.ceil())
441        } else {
442            (self.x0.ceil(), self.x1.floor())
443        };
444        let (y0, y1) = if self.y0 < self.y1 {
445            (self.y0.floor(), self.y1.ceil())
446        } else {
447            (self.y0.ceil(), self.y1.floor())
448        };
449        Rect::new(x0, y0, x1, y1)
450    }
451
452    /// Returns a new `Rect`,
453    /// with each coordinate value rounded towards the center of the `Rect`
454    /// to the nearest integer, unless they are already an integer.
455    /// That is to say this function will return the biggest possible `Rect`
456    /// with integer coordinates that is a subset of `self`.
457    ///
458    /// # Examples
459    ///
460    /// ```
461    /// use kurbo::Rect;
462    ///
463    /// // In positive space
464    /// let rect = Rect::new(3.3, 3.6, 5.6, 4.1).trunc();
465    /// assert_eq!(rect.x0, 4.0);
466    /// assert_eq!(rect.y0, 4.0);
467    /// assert_eq!(rect.x1, 5.0);
468    /// assert_eq!(rect.y1, 4.0);
469    ///
470    /// // In both positive and negative space
471    /// let rect = Rect::new(-3.3, -3.6, 5.6, 4.1).trunc();
472    /// assert_eq!(rect.x0, -3.0);
473    /// assert_eq!(rect.y0, -3.0);
474    /// assert_eq!(rect.x1, 5.0);
475    /// assert_eq!(rect.y1, 4.0);
476    ///
477    /// // In negative space
478    /// let rect = Rect::new(-5.6, -4.1, -3.3, -3.6).trunc();
479    /// assert_eq!(rect.x0, -5.0);
480    /// assert_eq!(rect.y0, -4.0);
481    /// assert_eq!(rect.x1, -4.0);
482    /// assert_eq!(rect.y1, -4.0);
483    ///
484    /// // Inverse orientation
485    /// let rect = Rect::new(5.6, -3.6, 3.3, -4.1).trunc();
486    /// assert_eq!(rect.x0, 5.0);
487    /// assert_eq!(rect.y0, -4.0);
488    /// assert_eq!(rect.x1, 4.0);
489    /// assert_eq!(rect.y1, -4.0);
490    /// ```
491    #[inline]
492    pub fn trunc(self) -> Rect {
493        // The compiler optimizer will remove the if branching.
494        let (x0, x1) = if self.x0 < self.x1 {
495            (self.x0.ceil(), self.x1.floor())
496        } else {
497            (self.x0.floor(), self.x1.ceil())
498        };
499        let (y0, y1) = if self.y0 < self.y1 {
500            (self.y0.ceil(), self.y1.floor())
501        } else {
502            (self.y0.floor(), self.y1.ceil())
503        };
504        Rect::new(x0, y0, x1, y1)
505    }
506
507    /// Scales the `Rect` by `factor` with respect to the origin (the point `(0, 0)`).
508    ///
509    /// # Examples
510    ///
511    /// ```
512    /// use kurbo::Rect;
513    ///
514    /// let rect = Rect::new(2., 2., 4., 6.).scale_from_origin(2.);
515    /// assert_eq!(rect.x0, 4.);
516    /// assert_eq!(rect.x1, 8.);
517    /// ```
518    #[inline]
519    pub fn scale_from_origin(self, factor: f64) -> Rect {
520        Rect {
521            x0: self.x0 * factor,
522            y0: self.y0 * factor,
523            x1: self.x1 * factor,
524            y1: self.y1 * factor,
525        }
526    }
527
528    /// Creates a new [`RoundedRect`] from this `Rect` and the provided
529    /// corner [radius](RoundedRectRadii).
530    #[inline]
531    pub fn to_rounded_rect(self, radii: impl Into<RoundedRectRadii>) -> RoundedRect {
532        RoundedRect::from_rect(self, radii)
533    }
534
535    /// Returns the [`Ellipse`] that is bounded by this `Rect`.
536    #[inline]
537    pub fn to_ellipse(self) -> Ellipse {
538        Ellipse::from_rect(self)
539    }
540
541    /// The aspect ratio of the `Rect`.
542    ///
543    /// This is defined as the height divided by the width. It measures the
544    /// "squareness" of the rectangle (a value of `1` is square).
545    ///
546    /// If the width is `0` the output will be `sign(y1 - y0) * infinity`.
547    ///
548    /// If The width and height are `0`, the result will be `NaN`.
549    #[inline]
550    pub fn aspect_ratio(&self) -> f64 {
551        self.size().aspect_ratio()
552    }
553
554    /// Returns the largest possible `Rect` that is fully contained in `self`
555    /// with the given `aspect_ratio`.
556    ///
557    /// The aspect ratio is specified fractionally, as `height / width`.
558    ///
559    /// The resulting rectangle will be centered if it is smaller than the
560    /// input rectangle.
561    ///
562    /// For the special case where the aspect ratio is `1.0`, the resulting
563    /// `Rect` will be square.
564    ///
565    /// # Examples
566    ///
567    /// ```
568    /// # use kurbo::Rect;
569    /// let outer = Rect::new(0.0, 0.0, 10.0, 20.0);
570    /// let inner = outer.contained_rect_with_aspect_ratio(1.0);
571    /// // The new `Rect` is a square centered at the center of `outer`.
572    /// assert_eq!(inner, Rect::new(0.0, 5.0, 10.0, 15.0));
573    /// ```
574    ///
575    pub fn contained_rect_with_aspect_ratio(&self, aspect_ratio: f64) -> Rect {
576        let (width, height) = (self.width(), self.height());
577        let self_aspect = height / width;
578
579        // TODO the parameter `1e-9` was chosen quickly and may not be optimal.
580        if (self_aspect - aspect_ratio).abs() < 1e-9 {
581            // short circuit
582            *self
583        } else if self_aspect.abs() < aspect_ratio.abs() {
584            // shrink x to fit
585            let new_width = height * aspect_ratio.recip();
586            let gap = (width - new_width) * 0.5;
587            let x0 = self.x0 + gap;
588            let x1 = self.x1 - gap;
589            Rect::new(x0, self.y0, x1, self.y1)
590        } else {
591            // shrink y to fit
592            let new_height = width * aspect_ratio;
593            let gap = (height - new_height) * 0.5;
594            let y0 = self.y0 + gap;
595            let y1 = self.y1 - gap;
596            Rect::new(self.x0, y0, self.x1, y1)
597        }
598    }
599
600    /// Is this rectangle [finite]?
601    ///
602    /// [finite]: f64::is_finite
603    #[inline]
604    pub fn is_finite(&self) -> bool {
605        self.x0.is_finite() && self.x1.is_finite() && self.y0.is_finite() && self.y1.is_finite()
606    }
607
608    /// Is this rectangle [NaN]?
609    ///
610    /// [NaN]: f64::is_nan
611    #[inline]
612    pub fn is_nan(&self) -> bool {
613        self.x0.is_nan() || self.y0.is_nan() || self.x1.is_nan() || self.y1.is_nan()
614    }
615}
616
617impl From<(Point, Point)> for Rect {
618    #[inline(always)]
619    fn from(points: (Point, Point)) -> Rect {
620        Rect::from_points(points.0, points.1)
621    }
622}
623
624impl From<(Point, Size)> for Rect {
625    fn from(params: (Point, Size)) -> Rect {
626        Rect::from_origin_size(params.0, params.1)
627    }
628}
629
630impl Add<Vec2> for Rect {
631    type Output = Rect;
632
633    #[inline]
634    fn add(self, v: Vec2) -> Rect {
635        Rect::new(self.x0 + v.x, self.y0 + v.y, self.x1 + v.x, self.y1 + v.y)
636    }
637}
638
639impl Sub<Vec2> for Rect {
640    type Output = Rect;
641
642    #[inline]
643    fn sub(self, v: Vec2) -> Rect {
644        Rect::new(self.x0 - v.x, self.y0 - v.y, self.x1 - v.x, self.y1 - v.y)
645    }
646}
647
648impl Sub for Rect {
649    type Output = Insets;
650
651    #[inline]
652    fn sub(self, other: Rect) -> Insets {
653        let x0 = other.x0 - self.x0;
654        let y0 = other.y0 - self.y0;
655        let x1 = self.x1 - other.x1;
656        let y1 = self.y1 - other.y1;
657        Insets { x0, y0, x1, y1 }
658    }
659}
660
661#[doc(hidden)]
662pub struct RectPathIter {
663    rect: Rect,
664    ix: usize,
665}
666
667impl Shape for Rect {
668    type PathElementsIter<'iter> = RectPathIter;
669
670    fn path_elements(&self, _tolerance: f64) -> RectPathIter {
671        RectPathIter { rect: *self, ix: 0 }
672    }
673
674    // It's a bit of duplication having both this and the impl method, but
675    // removing that would require using the trait. We'll leave it for now.
676    #[inline]
677    fn area(&self) -> f64 {
678        Rect::area(self)
679    }
680
681    #[inline]
682    fn perimeter(&self, _accuracy: f64) -> f64 {
683        2.0 * (self.width().abs() + self.height().abs())
684    }
685
686    /// Note: this function is carefully designed so that if the plane is
687    /// tiled with rectangles, the winding number will be nonzero for exactly
688    /// one of them.
689    #[inline]
690    fn winding(&self, pt: Point) -> i32 {
691        let xmin = self.x0.min(self.x1);
692        let xmax = self.x0.max(self.x1);
693        let ymin = self.y0.min(self.y1);
694        let ymax = self.y0.max(self.y1);
695        if pt.x >= xmin && pt.x < xmax && pt.y >= ymin && pt.y < ymax {
696            if (self.x1 > self.x0) ^ (self.y1 > self.y0) {
697                -1
698            } else {
699                1
700            }
701        } else {
702            0
703        }
704    }
705
706    #[inline]
707    fn bounding_box(&self) -> Rect {
708        self.abs()
709    }
710
711    #[inline(always)]
712    fn as_rect(&self) -> Option<Rect> {
713        Some(*self)
714    }
715
716    #[inline]
717    fn contains(&self, pt: Point) -> bool {
718        self.contains(pt)
719    }
720}
721
722// This is clockwise in a y-down coordinate system for positive area.
723impl Iterator for RectPathIter {
724    type Item = PathEl;
725
726    fn next(&mut self) -> Option<PathEl> {
727        self.ix += 1;
728        match self.ix {
729            1 => Some(PathEl::MoveTo(Point::new(self.rect.x0, self.rect.y0))),
730            2 => Some(PathEl::LineTo(Point::new(self.rect.x1, self.rect.y0))),
731            3 => Some(PathEl::LineTo(Point::new(self.rect.x1, self.rect.y1))),
732            4 => Some(PathEl::LineTo(Point::new(self.rect.x0, self.rect.y1))),
733            5 => Some(PathEl::ClosePath),
734            _ => None,
735        }
736    }
737}
738
739impl fmt::Debug for Rect {
740    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
741        if f.alternate() {
742            write!(
743                f,
744                "Rect {{ origin: {:?}, size: {:?} }}",
745                self.origin(),
746                self.size()
747            )
748        } else {
749            write!(
750                f,
751                "Rect {{ x0: {:?}, y0: {:?}, x1: {:?}, y1: {:?} }}",
752                self.x0, self.y0, self.x1, self.y1
753            )
754        }
755    }
756}
757
758impl fmt::Display for Rect {
759    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
760        write!(f, "Rect {{ ")?;
761        fmt::Display::fmt(&self.origin(), f)?;
762        write!(f, " ")?;
763        fmt::Display::fmt(&self.size(), f)?;
764        write!(f, " }}")
765    }
766}
767
768#[cfg(test)]
769mod tests {
770    use crate::{Point, Rect, Shape};
771
772    fn assert_approx_eq(x: f64, y: f64) {
773        assert!((x - y).abs() < 1e-7);
774    }
775
776    #[test]
777    fn area_sign() {
778        let r = Rect::new(0.0, 0.0, 10.0, 10.0);
779        let center = r.center();
780        assert_approx_eq(r.area(), 100.0);
781
782        assert_eq!(r.winding(center), 1);
783
784        let p = r.to_path(1e-9);
785        assert_approx_eq(r.area(), p.area());
786        assert_eq!(r.winding(center), p.winding(center));
787
788        let r_flip = Rect::new(0.0, 10.0, 10.0, 0.0);
789        assert_approx_eq(r_flip.area(), -100.0);
790
791        assert_eq!(r_flip.winding(Point::new(5.0, 5.0)), -1);
792        let p_flip = r_flip.to_path(1e-9);
793        assert_approx_eq(r_flip.area(), p_flip.area());
794        assert_eq!(r_flip.winding(center), p_flip.winding(center));
795    }
796
797    #[test]
798    fn display() {
799        let r = Rect::from_origin_size((10., 12.23214), (22.222222222, 23.1));
800        assert_eq!(
801            format!("{r}"),
802            "Rect { (10, 12.23214) (22.222222222×23.1) }"
803        );
804        assert_eq!(format!("{r:.2}"), "Rect { (10.00, 12.23) (22.22×23.10) }");
805    }
806
807    /* TODO uncomment when a (possibly approximate) equality test has been decided on
808    #[test]
809    fn rect_from_center_size() {
810        assert_eq!(
811            Rect::from_center_size(Point::new(3.0, 2.0), Size::new(2.0, 4.0)),
812            Rect::new(2.0, 0.0, 4.0, 4.0)
813        );
814    }
815    */
816
817    #[test]
818    fn contained_rect_with_aspect_ratio() {
819        fn case(outer: [f64; 4], aspect_ratio: f64, expected: [f64; 4]) {
820            let outer = Rect::new(outer[0], outer[1], outer[2], outer[3]);
821            let expected = Rect::new(expected[0], expected[1], expected[2], expected[3]);
822            assert_eq!(
823                outer.contained_rect_with_aspect_ratio(aspect_ratio),
824                expected
825            );
826        }
827        // squares (different point orderings)
828        case([0.0, 0.0, 10.0, 20.0], 1.0, [0.0, 5.0, 10.0, 15.0]);
829        case([0.0, 20.0, 10.0, 0.0], 1.0, [0.0, 5.0, 10.0, 15.0]);
830        case([10.0, 0.0, 0.0, 20.0], 1.0, [10.0, 15.0, 0.0, 5.0]);
831        case([10.0, 20.0, 0.0, 0.0], 1.0, [10.0, 15.0, 0.0, 5.0]);
832        // non-square
833        case([0.0, 0.0, 10.0, 20.0], 0.5, [0.0, 7.5, 10.0, 12.5]);
834        // same aspect ratio
835        case([0.0, 0.0, 10.0, 20.0], 2.0, [0.0, 0.0, 10.0, 20.0]);
836        // negative aspect ratio
837        case([0.0, 0.0, 10.0, 20.0], -1.0, [0.0, 15.0, 10.0, 5.0]);
838        // infinite aspect ratio
839        case([0.0, 0.0, 10.0, 20.0], f64::INFINITY, [5.0, 0.0, 5.0, 20.0]);
840        // zero aspect ratio
841        case([0.0, 0.0, 10.0, 20.0], 0.0, [0.0, 10.0, 10.0, 10.0]);
842        // zero width rect
843        case([0.0, 0.0, 0.0, 20.0], 1.0, [0.0, 10.0, 0.0, 10.0]);
844        // many zeros
845        case([0.0, 0.0, 0.0, 20.0], 0.0, [0.0, 10.0, 0.0, 10.0]);
846        // everything zero
847        case([0.0, 0.0, 0.0, 0.0], 0.0, [0.0, 0.0, 0.0, 0.0]);
848    }
849
850    #[test]
851    fn aspect_ratio() {
852        let test = Rect::new(0.0, 0.0, 1.0, 1.0);
853        assert!((test.aspect_ratio() - 1.0).abs() < 1e-6);
854    }
855
856    #[test]
857    fn contained_rect_overlaps() {
858        let outer = Rect::new(0.0, 0.0, 10.0, 10.0);
859        let inner = Rect::new(2.0, 2.0, 4.0, 4.0);
860        assert!(outer.overlaps(inner));
861    }
862
863    #[test]
864    fn overlapping_rect_overlaps() {
865        let a = Rect::new(0.0, 0.0, 10.0, 10.0);
866        let b = Rect::new(5.0, 5.0, 15.0, 15.0);
867        assert!(a.overlaps(b));
868    }
869
870    #[test]
871    fn disjoint_rect_overlaps() {
872        let a = Rect::new(0.0, 0.0, 10.0, 10.0);
873        let b = Rect::new(11.0, 11.0, 15.0, 15.0);
874        assert!(!a.overlaps(b));
875    }
876
877    #[test]
878    fn sharing_edge_overlaps() {
879        let a = Rect::new(0.0, 0.0, 10.0, 10.0);
880        let b = Rect::new(10.0, 0.0, 20.0, 10.0);
881        assert!(a.overlaps(b));
882    }
883
884    // Test the two other directions in case there is a bug that only appears in one direction.
885    #[test]
886    fn disjoint_rect_overlaps_negative() {
887        let a = Rect::new(0.0, 0.0, 10.0, 10.0);
888        let b = Rect::new(-10.0, -10.0, -5.0, -5.0);
889        assert!(!a.overlaps(b));
890    }
891
892    #[test]
893    fn contained_rectangle_contains() {
894        let outer = Rect::new(0.0, 0.0, 10.0, 10.0);
895        let inner = Rect::new(2.0, 2.0, 4.0, 4.0);
896        assert!(outer.contains_rect(inner));
897    }
898
899    #[test]
900    fn overlapping_rectangle_contains() {
901        let outer = Rect::new(0.0, 0.0, 10.0, 10.0);
902        let inner = Rect::new(5.0, 5.0, 15.0, 15.0);
903        assert!(!outer.contains_rect(inner));
904    }
905
906    #[test]
907    fn disjoint_rectangle_contains() {
908        let outer = Rect::new(0.0, 0.0, 10.0, 10.0);
909        let inner = Rect::new(11.0, 11.0, 15.0, 15.0);
910        assert!(!outer.contains_rect(inner));
911    }
912}