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