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}