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