Coverage Report

Created: 2026-10-28 07:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/kurbo-0.13.1/src/rect.rs
Line
Count
Source
1
// Copyright 2019 the Kurbo Authors
2
// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4
//! A rectangle.
5
6
use core::fmt;
7
use core::ops::{Add, Sub};
8
9
use crate::{
10
    Axis, Ellipse, Insets, PathEl, Point, RoundedRect, RoundedRectRadii, Shape, Size, Vec2,
11
};
12
13
#[cfg(not(feature = "std"))]
14
use 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))]
20
pub 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
31
impl 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
0
    pub const fn new(x0: f64, y0: f64, x1: f64, y1: f64) -> Rect {
38
0
        Rect { x0, y0, x1, y1 }
39
0
    }
40
41
    /// A new rectangle from two points.
42
    ///
43
    /// The result will have non-negative width and height.
44
    #[inline]
45
0
    pub fn from_points(p0: impl Into<Point>, p1: impl Into<Point>) -> Rect {
46
0
        let p0 = p0.into();
47
0
        let p1 = p1.into();
48
0
        Rect::new(p0.x, p0.y, p1.x, p1.y).abs()
49
0
    }
50
51
    /// A new rectangle from origin and size.
52
    ///
53
    /// The result will have non-negative width and height.
54
    #[inline]
55
0
    pub fn from_origin_size(origin: impl Into<Point>, size: impl Into<Size>) -> Rect {
56
0
        let origin = origin.into();
57
0
        Rect::from_points(origin, origin + size.into().to_vec2())
58
0
    }
59
60
    /// A new rectangle from center and size.
61
    #[inline]
62
0
    pub fn from_center_size(center: impl Into<Point>, size: impl Into<Size>) -> Rect {
63
0
        let center = center.into();
64
0
        let size = 0.5 * size.into();
65
0
        Rect::new(
66
0
            center.x - size.width,
67
0
            center.y - size.height,
68
0
            center.x + size.width,
69
0
            center.y + size.height,
70
        )
71
0
    }
72
73
    /// Create a new `Rect` with the same size as `self` and a new origin.
74
    #[inline]
75
0
    pub fn with_origin(self, origin: impl Into<Point>) -> Rect {
76
0
        Rect::from_origin_size(origin, self.size())
77
0
    }
78
79
    /// Create a new `Rect` with the same origin as `self` and a new size.
80
    #[inline]
81
0
    pub fn with_size(self, size: impl Into<Size>) -> Rect {
82
0
        Rect::from_origin_size(self.origin(), size)
83
0
    }
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
0
    pub fn inset(self, insets: impl Into<Insets>) -> Rect {
100
0
        self + insets.into()
101
0
    }
102
103
    /// The width of the rectangle.
104
    ///
105
    /// Note: nothing forbids negative width.
106
    #[inline]
107
0
    pub const fn width(&self) -> f64 {
108
0
        self.x1 - self.x0
109
0
    }
110
111
    /// The height of the rectangle.
112
    ///
113
    /// Note: nothing forbids negative height.
114
    #[inline]
115
0
    pub const fn height(&self) -> f64 {
116
0
        self.y1 - self.y0
117
0
    }
118
119
    /// Returns the minimum value for the x-coordinate of the rectangle.
120
    #[inline]
121
0
    pub const fn min_x(&self) -> f64 {
122
0
        self.x0.min(self.x1)
123
0
    }
Unexecuted instantiation: <kurbo::rect::Rect>::min_x
Unexecuted instantiation: <kurbo::rect::Rect>::min_x
124
125
    /// Returns the maximum value for the x-coordinate of the rectangle.
126
    #[inline]
127
0
    pub const fn max_x(&self) -> f64 {
128
0
        self.x0.max(self.x1)
129
0
    }
Unexecuted instantiation: <kurbo::rect::Rect>::max_x
Unexecuted instantiation: <kurbo::rect::Rect>::max_x
130
131
    /// Returns the minimum value for the y-coordinate of the rectangle.
132
    #[inline]
133
0
    pub const fn min_y(&self) -> f64 {
134
0
        self.y0.min(self.y1)
135
0
    }
Unexecuted instantiation: <kurbo::rect::Rect>::min_y
Unexecuted instantiation: <kurbo::rect::Rect>::min_y
136
137
    /// Returns the maximum value for the y-coordinate of the rectangle.
138
    #[inline]
139
0
    pub const fn max_y(&self) -> f64 {
140
0
        self.y0.max(self.y1)
141
0
    }
Unexecuted instantiation: <kurbo::rect::Rect>::max_y
Unexecuted instantiation: <kurbo::rect::Rect>::max_y
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
0
    pub const fn origin(&self) -> Point {
149
0
        Point::new(self.x0, self.y0)
150
0
    }
151
152
    /// The size of the rectangle.
153
    #[inline]
154
0
    pub const fn size(&self) -> Size {
155
0
        Size::new(self.width(), self.height())
156
0
    }
157
158
    /// The area of the rectangle.
159
    #[inline]
160
0
    pub const fn area(&self) -> f64 {
161
0
        self.width() * self.height()
162
0
    }
163
164
    /// Whether this rectangle has zero area.
165
    #[doc(alias = "is_empty")]
166
    #[inline]
167
0
    pub const fn is_zero_area(&self) -> bool {
168
0
        self.area() == 0.0
169
0
    }
170
171
    /// The center point of the rectangle.
172
    #[inline]
173
0
    pub const fn center(&self) -> Point {
174
0
        Point::new(0.5 * (self.x0 + self.x1), 0.5 * (self.y0 + self.y1))
175
0
    }
176
177
    /// Returns `true` if `point` lies within `self`.
178
    #[inline]
179
0
    pub fn contains(&self, point: impl Into<Point>) -> bool {
180
0
        let point = point.into();
181
0
        point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1
182
0
    }
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
0
    pub const fn abs(&self) -> Rect {
190
0
        let Rect { x0, y0, x1, y1 } = *self;
191
0
        Rect::new(x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1))
192
0
    }
193
194
    /// The smallest rectangle enclosing two rectangles.
195
    ///
196
    /// Results are valid only if width and height are non-negative.
197
    #[inline]
198
0
    pub const fn union(&self, other: Rect) -> Rect {
199
0
        Rect::new(
200
0
            self.x0.min(other.x0),
201
0
            self.y0.min(other.y0),
202
0
            self.x1.max(other.x1),
203
0
            self.y1.max(other.y1),
204
        )
205
0
    }
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
0
    pub fn union_pt(&self, pt: impl Into<Point>) -> Rect {
215
0
        let pt = pt.into();
216
0
        Rect::new(
217
0
            self.x0.min(pt.x),
218
0
            self.y0.min(pt.y),
219
0
            self.x1.max(pt.x),
220
0
            self.y1.max(pt.y),
221
        )
222
0
    }
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
0
    pub const fn intersect(&self, other: Rect) -> Rect {
235
0
        let x0 = self.x0.max(other.x0);
236
0
        let y0 = self.y0.max(other.y0);
237
0
        let x1 = self.x1.min(other.x1);
238
0
        let y1 = self.y1.min(other.y1);
239
0
        Rect::new(x0, y0, x1.max(x0), y1.max(y0))
240
0
    }
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
0
    pub const fn overlaps(&self, other: Rect) -> bool {
269
0
        self.x0 <= other.x1 && self.x1 >= other.x0 && self.y0 <= other.y1 && self.y1 >= other.y0
270
0
    }
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
0
    pub const fn contains_rect(&self, other: Rect) -> bool {
297
0
        self.x0 <= other.x0 && self.y0 <= other.y0 && self.x1 >= other.x1 && self.y1 >= other.y1
298
0
    }
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
0
    pub const fn inflate(&self, width: f64, height: f64) -> Rect {
305
0
        Rect::new(
306
0
            self.x0 - width,
307
0
            self.y0 - height,
308
0
            self.x1 + width,
309
0
            self.y1 + height,
310
        )
311
0
    }
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
0
    pub fn round(self) -> Rect {
330
0
        Rect::new(
331
0
            self.x0.round(),
332
0
            self.y0.round(),
333
0
            self.x1.round(),
334
0
            self.y1.round(),
335
        )
336
0
    }
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
0
    pub fn ceil(self) -> Rect {
356
0
        Rect::new(
357
0
            self.x0.ceil(),
358
0
            self.y0.ceil(),
359
0
            self.x1.ceil(),
360
0
            self.y1.ceil(),
361
        )
362
0
    }
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
0
    pub fn floor(self) -> Rect {
382
0
        Rect::new(
383
0
            self.x0.floor(),
384
0
            self.y0.floor(),
385
0
            self.x1.floor(),
386
0
            self.y1.floor(),
387
        )
388
0
    }
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
0
    pub fn expand(self) -> Rect {
431
        // The compiler optimizer will remove the if branching.
432
0
        let (x0, x1) = if self.x0 < self.x1 {
433
0
            (self.x0.floor(), self.x1.ceil())
434
        } else {
435
0
            (self.x0.ceil(), self.x1.floor())
436
        };
437
0
        let (y0, y1) = if self.y0 < self.y1 {
438
0
            (self.y0.floor(), self.y1.ceil())
439
        } else {
440
0
            (self.y0.ceil(), self.y1.floor())
441
        };
442
0
        Rect::new(x0, y0, x1, y1)
443
0
    }
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
0
    pub fn trunc(self) -> Rect {
486
        // The compiler optimizer will remove the if branching.
487
0
        let (x0, x1) = if self.x0 < self.x1 {
488
0
            (self.x0.ceil(), self.x1.floor())
489
        } else {
490
0
            (self.x0.floor(), self.x1.ceil())
491
        };
492
0
        let (y0, y1) = if self.y0 < self.y1 {
493
0
            (self.y0.ceil(), self.y1.floor())
494
        } else {
495
0
            (self.y0.floor(), self.y1.ceil())
496
        };
497
0
        Rect::new(x0, y0, x1, y1)
498
0
    }
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
0
    pub const fn scale_from_origin(self, factor: f64) -> Rect {
513
0
        Rect {
514
0
            x0: self.x0 * factor,
515
0
            y0: self.y0 * factor,
516
0
            x1: self.x1 * factor,
517
0
            y1: self.y1 * factor,
518
0
        }
519
0
    }
520
521
    /// Creates a new [`RoundedRect`] from this `Rect` and the provided
522
    /// corner [radius](RoundedRectRadii).
523
    #[inline]
524
0
    pub fn to_rounded_rect(self, radii: impl Into<RoundedRectRadii>) -> RoundedRect {
525
0
        RoundedRect::from_rect(self, radii)
526
0
    }
527
528
    /// Returns the [`Ellipse`] that is bounded by this `Rect`.
529
    #[inline]
530
0
    pub fn to_ellipse(self) -> Ellipse {
531
0
        Ellipse::from_rect(self)
532
0
    }
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
0
    pub const fn aspect_ratio_width(self) -> f64 {
543
0
        self.size().aspect_ratio_width()
544
0
    }
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
0
    pub fn aspect_ratio(&self) -> f64 {
561
        #[expect(deprecated, reason = "This method is deprecated for the same reason.")]
562
0
        self.size().aspect_ratio()
563
0
    }
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
0
    pub const fn inscribed_rect_with_aspect_ratio(&self, aspect_ratio: f64) -> Rect {
582
0
        let self_size @ Size { width, height } = self.size();
583
0
        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
0
        if self_aspect.is_nan() || (self_aspect - aspect_ratio).abs() < 1e-9 {
589
            // short circuit
590
0
            *self
591
0
        } 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
0
            let new_height = width * aspect_ratio.recip();
596
0
            let gap = (height - new_height) * 0.5;
597
0
            let y0 = self.y0 + gap;
598
0
            let y1 = self.y1 - gap;
599
0
            Rect::new(self.x0, y0, self.x1, y1)
600
        } else {
601
            // shrink x to fit
602
0
            let new_width = height * aspect_ratio;
603
0
            let gap = (width - new_width) * 0.5;
604
0
            let x0 = self.x0 + gap;
605
0
            let x1 = self.x1 - gap;
606
0
            Rect::new(x0, self.y0, x1, self.y1)
607
        }
608
0
    }
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
0
    pub fn contained_rect_with_aspect_ratio(&self, inverse_aspect_ratio: f64) -> Rect {
625
0
        self.inscribed_rect_with_aspect_ratio(1. / inverse_aspect_ratio)
626
0
    }
627
628
    /// Is this rectangle [finite]?
629
    ///
630
    /// [finite]: f64::is_finite
631
    #[inline]
632
0
    pub const fn is_finite(&self) -> bool {
633
0
        self.x0.is_finite() && self.x1.is_finite() && self.y0.is_finite() && self.y1.is_finite()
634
0
    }
635
636
    /// Is this rectangle [NaN]?
637
    ///
638
    /// [NaN]: f64::is_nan
639
    #[inline]
640
0
    pub const fn is_nan(&self) -> bool {
641
0
        self.x0.is_nan() || self.y0.is_nan() || self.x1.is_nan() || self.y1.is_nan()
642
0
    }
643
644
    /// Get the members matching the given axis.
645
    #[inline]
646
0
    pub const fn get_coords(self, axis: Axis) -> (f64, f64) {
647
0
        match axis {
648
0
            Axis::Horizontal => (self.x0, self.x1),
649
0
            Axis::Vertical => (self.y0, self.y1),
650
        }
651
0
    }
652
653
    /// Get a mutable reference to the members matching the given axis.
654
    #[inline]
655
0
    pub const fn get_coords_mut(&mut self, axis: Axis) -> (&mut f64, &mut f64) {
656
0
        match axis {
657
0
            Axis::Horizontal => (&mut self.x0, &mut self.x1),
658
0
            Axis::Vertical => (&mut self.y0, &mut self.y1),
659
        }
660
0
    }
661
662
    /// Set the members matching the given axis to the given values.
663
    #[inline]
664
0
    pub const fn set_coords(&mut self, axis: Axis, v0: f64, v1: f64) {
665
0
        match axis {
666
0
            Axis::Horizontal => (self.x0, self.x1) = (v0, v1),
667
0
            Axis::Vertical => (self.y0, self.y1) = (v0, v1),
668
        }
669
0
    }
670
}
671
672
impl From<(Point, Point)> for Rect {
673
    #[inline(always)]
674
0
    fn from(points: (Point, Point)) -> Rect {
675
0
        Rect::from_points(points.0, points.1)
676
0
    }
677
}
678
679
impl From<(Point, Size)> for Rect {
680
0
    fn from(params: (Point, Size)) -> Rect {
681
0
        Rect::from_origin_size(params.0, params.1)
682
0
    }
683
}
684
685
impl Add<Vec2> for Rect {
686
    type Output = Rect;
687
688
    #[inline]
689
0
    fn add(self, v: Vec2) -> Rect {
690
0
        Rect::new(self.x0 + v.x, self.y0 + v.y, self.x1 + v.x, self.y1 + v.y)
691
0
    }
692
}
693
694
impl Sub<Vec2> for Rect {
695
    type Output = Rect;
696
697
    #[inline]
698
0
    fn sub(self, v: Vec2) -> Rect {
699
0
        Rect::new(self.x0 - v.x, self.y0 - v.y, self.x1 - v.x, self.y1 - v.y)
700
0
    }
701
}
702
703
impl Sub for Rect {
704
    type Output = Insets;
705
706
    #[inline]
707
0
    fn sub(self, other: Rect) -> Insets {
708
0
        let x0 = other.x0 - self.x0;
709
0
        let y0 = other.y0 - self.y0;
710
0
        let x1 = self.x1 - other.x1;
711
0
        let y1 = self.y1 - other.y1;
712
0
        Insets { x0, y0, x1, y1 }
713
0
    }
714
}
715
716
#[doc(hidden)]
717
pub struct RectPathIter {
718
    rect: Rect,
719
    ix: usize,
720
}
721
722
impl Shape for Rect {
723
    type PathElementsIter<'iter> = RectPathIter;
724
725
0
    fn path_elements(&self, _tolerance: f64) -> RectPathIter {
726
0
        RectPathIter { rect: *self, ix: 0 }
727
0
    }
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
0
    fn area(&self) -> f64 {
733
0
        Rect::area(self)
734
0
    }
735
736
    #[inline]
737
0
    fn perimeter(&self, _accuracy: f64) -> f64 {
738
0
        2.0 * (self.width().abs() + self.height().abs())
739
0
    }
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
0
    fn winding(&self, pt: Point) -> i32 {
746
0
        let xmin = self.x0.min(self.x1);
747
0
        let xmax = self.x0.max(self.x1);
748
0
        let ymin = self.y0.min(self.y1);
749
0
        let ymax = self.y0.max(self.y1);
750
0
        if pt.x >= xmin && pt.x < xmax && pt.y >= ymin && pt.y < ymax {
751
0
            if (self.x1 > self.x0) ^ (self.y1 > self.y0) {
752
0
                -1
753
            } else {
754
0
                1
755
            }
756
        } else {
757
0
            0
758
        }
759
0
    }
760
761
    #[inline]
762
0
    fn bounding_box(&self) -> Rect {
763
0
        self.abs()
764
0
    }
765
766
    #[inline(always)]
767
0
    fn as_rect(&self) -> Option<Rect> {
768
0
        Some(*self)
769
0
    }
770
771
    #[inline]
772
0
    fn contains(&self, pt: Point) -> bool {
773
0
        self.contains(pt)
774
0
    }
775
}
776
777
// This is clockwise in a y-down coordinate system for positive area.
778
impl Iterator for RectPathIter {
779
    type Item = PathEl;
780
781
0
    fn next(&mut self) -> Option<PathEl> {
782
0
        self.ix += 1;
783
0
        match self.ix {
784
0
            1 => Some(PathEl::MoveTo(Point::new(self.rect.x0, self.rect.y0))),
785
0
            2 => Some(PathEl::LineTo(Point::new(self.rect.x1, self.rect.y0))),
786
0
            3 => Some(PathEl::LineTo(Point::new(self.rect.x1, self.rect.y1))),
787
0
            4 => Some(PathEl::LineTo(Point::new(self.rect.x0, self.rect.y1))),
788
0
            5 => Some(PathEl::ClosePath),
789
0
            _ => None,
790
        }
791
0
    }
792
}
793
794
impl fmt::Debug for Rect {
795
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
796
0
        if f.alternate() {
797
0
            write!(
798
0
                f,
799
0
                "Rect {{ origin: {:?}, size: {:?} }}",
800
0
                self.origin(),
801
0
                self.size()
802
            )
803
        } else {
804
0
            write!(
805
0
                f,
806
0
                "Rect {{ x0: {:?}, y0: {:?}, x1: {:?}, y1: {:?} }}",
807
                self.x0, self.y0, self.x1, self.y1
808
            )
809
        }
810
0
    }
811
}
812
813
impl fmt::Display for Rect {
814
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
815
0
        write!(f, "Rect {{ ")?;
816
0
        fmt::Display::fmt(&self.origin(), f)?;
817
0
        write!(f, " ")?;
818
0
        fmt::Display::fmt(&self.size(), f)?;
819
0
        write!(f, " }}")
820
0
    }
821
}
822
823
#[cfg(test)]
824
mod 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
1036
    #[test]
1037
    fn rect_intersect_zero() {
1038
        // These rectangles don't overlap vertically.
1039
        let a = Rect::new(25., 101., 200., 130.);
1040
        let b = Rect::new(0., 0., 100., 100.);
1041
1042
        for intersection in [a.intersect(b), b.intersect(a)] {
1043
            assert_eq!(intersection.x0, 25.);
1044
            assert_eq!(intersection.x1, 100.);
1045
            assert_eq!(intersection.area(), 0.);
1046
            assert_eq!(intersection.y0, intersection.y1);
1047
1048
            // We don't guarantee this in the documentation, but the intersection's `y` values should
1049
            // be a sensible value taken from the input.
1050
            assert!([0., 100., 101., 130.].contains(&intersection.y0));
1051
        }
1052
    }
1053
}