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