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