1 | // Copyright 2019 the Kurbo Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | //! A rectangle. |
5 | |
6 | use core::fmt; |
7 | use core::ops::{Add, Sub}; |
8 | |
9 | use crate::{Ellipse, Insets, PathEl, Point, RoundedRect, RoundedRectRadii, Shape, Size, Vec2}; |
10 | |
11 | #[cfg (not(feature = "std" ))] |
12 | use 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))] |
18 | pub 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 | |
29 | impl 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 | #[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 | |
615 | impl From<(Point, Point)> for Rect { |
616 | fn from(points: (Point, Point)) -> Rect { |
617 | Rect::from_points(p0:points.0, p1:points.1) |
618 | } |
619 | } |
620 | |
621 | impl From<(Point, Size)> for Rect { |
622 | fn from(params: (Point, Size)) -> Rect { |
623 | Rect::from_origin_size(origin:params.0, size:params.1) |
624 | } |
625 | } |
626 | |
627 | impl Add<Vec2> for Rect { |
628 | type Output = Rect; |
629 | |
630 | #[inline ] |
631 | fn add(self, v: Vec2) -> Rect { |
632 | Rect::new(self.x0 + v.x, self.y0 + v.y, self.x1 + v.x, self.y1 + v.y) |
633 | } |
634 | } |
635 | |
636 | impl Sub<Vec2> for Rect { |
637 | type Output = Rect; |
638 | |
639 | #[inline ] |
640 | fn sub(self, v: Vec2) -> Rect { |
641 | Rect::new(self.x0 - v.x, self.y0 - v.y, self.x1 - v.x, self.y1 - v.y) |
642 | } |
643 | } |
644 | |
645 | impl Sub for Rect { |
646 | type Output = Insets; |
647 | |
648 | #[inline ] |
649 | fn sub(self, other: Rect) -> Insets { |
650 | let x0: f64 = other.x0 - self.x0; |
651 | let y0: f64 = other.y0 - self.y0; |
652 | let x1: f64 = self.x1 - other.x1; |
653 | let y1: f64 = self.y1 - other.y1; |
654 | Insets { x0, y0, x1, y1 } |
655 | } |
656 | } |
657 | |
658 | #[doc (hidden)] |
659 | pub struct RectPathIter { |
660 | rect: Rect, |
661 | ix: usize, |
662 | } |
663 | |
664 | impl Shape for Rect { |
665 | type PathElementsIter<'iter> = RectPathIter; |
666 | |
667 | fn path_elements(&self, _tolerance: f64) -> RectPathIter { |
668 | RectPathIter { rect: *self, ix: 0 } |
669 | } |
670 | |
671 | // It's a bit of duplication having both this and the impl method, but |
672 | // removing that would require using the trait. We'll leave it for now. |
673 | #[inline ] |
674 | fn area(&self) -> f64 { |
675 | Rect::area(self) |
676 | } |
677 | |
678 | #[inline ] |
679 | fn perimeter(&self, _accuracy: f64) -> f64 { |
680 | 2.0 * (self.width().abs() + self.height().abs()) |
681 | } |
682 | |
683 | /// Note: this function is carefully designed so that if the plane is |
684 | /// tiled with rectangles, the winding number will be nonzero for exactly |
685 | /// one of them. |
686 | #[inline ] |
687 | fn winding(&self, pt: Point) -> i32 { |
688 | let xmin = self.x0.min(self.x1); |
689 | let xmax = self.x0.max(self.x1); |
690 | let ymin = self.y0.min(self.y1); |
691 | let ymax = self.y0.max(self.y1); |
692 | if pt.x >= xmin && pt.x < xmax && pt.y >= ymin && pt.y < ymax { |
693 | if (self.x1 > self.x0) ^ (self.y1 > self.y0) { |
694 | -1 |
695 | } else { |
696 | 1 |
697 | } |
698 | } else { |
699 | 0 |
700 | } |
701 | } |
702 | |
703 | #[inline ] |
704 | fn bounding_box(&self) -> Rect { |
705 | self.abs() |
706 | } |
707 | |
708 | #[inline ] |
709 | fn as_rect(&self) -> Option<Rect> { |
710 | Some(*self) |
711 | } |
712 | |
713 | #[inline ] |
714 | fn contains(&self, pt: Point) -> bool { |
715 | self.contains(pt) |
716 | } |
717 | } |
718 | |
719 | // This is clockwise in a y-down coordinate system for positive area. |
720 | impl Iterator for RectPathIter { |
721 | type Item = PathEl; |
722 | |
723 | fn next(&mut self) -> Option<PathEl> { |
724 | self.ix += 1; |
725 | match self.ix { |
726 | 1 => Some(PathEl::MoveTo(Point::new(self.rect.x0, self.rect.y0))), |
727 | 2 => Some(PathEl::LineTo(Point::new(self.rect.x1, self.rect.y0))), |
728 | 3 => Some(PathEl::LineTo(Point::new(self.rect.x1, self.rect.y1))), |
729 | 4 => Some(PathEl::LineTo(Point::new(self.rect.x0, self.rect.y1))), |
730 | 5 => Some(PathEl::ClosePath), |
731 | _ => None, |
732 | } |
733 | } |
734 | } |
735 | |
736 | impl fmt::Debug for Rect { |
737 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
738 | if f.alternate() { |
739 | write!( |
740 | f, |
741 | "Rect {{ origin: {:?}, size: {:?} }}" , |
742 | self.origin(), |
743 | self.size() |
744 | ) |
745 | } else { |
746 | write!( |
747 | f, |
748 | "Rect {{ x0: {:?}, y0: {:?}, x1: {:?}, y1: {:?} }}" , |
749 | self.x0, self.y0, self.x1, self.y1 |
750 | ) |
751 | } |
752 | } |
753 | } |
754 | |
755 | impl fmt::Display for Rect { |
756 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
757 | write!(f, "Rect {{ " )?; |
758 | fmt::Display::fmt(&self.origin(), f)?; |
759 | write!(f, " " )?; |
760 | fmt::Display::fmt(&self.size(), f)?; |
761 | write!(f, " }}" ) |
762 | } |
763 | } |
764 | |
765 | #[cfg (test)] |
766 | mod tests { |
767 | use crate::{Point, Rect, Shape}; |
768 | |
769 | fn assert_approx_eq(x: f64, y: f64) { |
770 | assert!((x - y).abs() < 1e-7); |
771 | } |
772 | |
773 | #[test ] |
774 | fn area_sign() { |
775 | let r = Rect::new(0.0, 0.0, 10.0, 10.0); |
776 | let center = r.center(); |
777 | assert_approx_eq(r.area(), 100.0); |
778 | |
779 | assert_eq!(r.winding(center), 1); |
780 | |
781 | let p = r.to_path(1e-9); |
782 | assert_approx_eq(r.area(), p.area()); |
783 | assert_eq!(r.winding(center), p.winding(center)); |
784 | |
785 | let r_flip = Rect::new(0.0, 10.0, 10.0, 0.0); |
786 | assert_approx_eq(r_flip.area(), -100.0); |
787 | |
788 | assert_eq!(r_flip.winding(Point::new(5.0, 5.0)), -1); |
789 | let p_flip = r_flip.to_path(1e-9); |
790 | assert_approx_eq(r_flip.area(), p_flip.area()); |
791 | assert_eq!(r_flip.winding(center), p_flip.winding(center)); |
792 | } |
793 | |
794 | #[test ] |
795 | fn display() { |
796 | let r = Rect::from_origin_size((10., 12.23214), (22.222222222, 23.1)); |
797 | assert_eq!( |
798 | format!("{r}" ), |
799 | "Rect { (10, 12.23214) (22.222222222×23.1) }" |
800 | ); |
801 | assert_eq!(format!("{r:.2}" ), "Rect { (10.00, 12.23) (22.22×23.10) }" ); |
802 | } |
803 | |
804 | /* TODO uncomment when a (possibly approximate) equality test has been decided on |
805 | #[test] |
806 | fn rect_from_center_size() { |
807 | assert_eq!( |
808 | Rect::from_center_size(Point::new(3.0, 2.0), Size::new(2.0, 4.0)), |
809 | Rect::new(2.0, 0.0, 4.0, 4.0) |
810 | ); |
811 | } |
812 | */ |
813 | |
814 | #[test ] |
815 | fn contained_rect_with_aspect_ratio() { |
816 | fn case(outer: [f64; 4], aspect_ratio: f64, expected: [f64; 4]) { |
817 | let outer = Rect::new(outer[0], outer[1], outer[2], outer[3]); |
818 | let expected = Rect::new(expected[0], expected[1], expected[2], expected[3]); |
819 | assert_eq!( |
820 | outer.contained_rect_with_aspect_ratio(aspect_ratio), |
821 | expected |
822 | ); |
823 | } |
824 | // squares (different point orderings) |
825 | case([0.0, 0.0, 10.0, 20.0], 1.0, [0.0, 5.0, 10.0, 15.0]); |
826 | case([0.0, 20.0, 10.0, 0.0], 1.0, [0.0, 5.0, 10.0, 15.0]); |
827 | case([10.0, 0.0, 0.0, 20.0], 1.0, [10.0, 15.0, 0.0, 5.0]); |
828 | case([10.0, 20.0, 0.0, 0.0], 1.0, [10.0, 15.0, 0.0, 5.0]); |
829 | // non-square |
830 | case([0.0, 0.0, 10.0, 20.0], 0.5, [0.0, 7.5, 10.0, 12.5]); |
831 | // same aspect ratio |
832 | case([0.0, 0.0, 10.0, 20.0], 2.0, [0.0, 0.0, 10.0, 20.0]); |
833 | // negative aspect ratio |
834 | case([0.0, 0.0, 10.0, 20.0], -1.0, [0.0, 15.0, 10.0, 5.0]); |
835 | // infinite aspect ratio |
836 | case([0.0, 0.0, 10.0, 20.0], f64::INFINITY, [5.0, 0.0, 5.0, 20.0]); |
837 | // zero aspect ratio |
838 | case([0.0, 0.0, 10.0, 20.0], 0.0, [0.0, 10.0, 10.0, 10.0]); |
839 | // zero width rect |
840 | case([0.0, 0.0, 0.0, 20.0], 1.0, [0.0, 10.0, 0.0, 10.0]); |
841 | // many zeros |
842 | case([0.0, 0.0, 0.0, 20.0], 0.0, [0.0, 10.0, 0.0, 10.0]); |
843 | // everything zero |
844 | case([0.0, 0.0, 0.0, 0.0], 0.0, [0.0, 0.0, 0.0, 0.0]); |
845 | } |
846 | |
847 | #[test ] |
848 | fn aspect_ratio() { |
849 | let test = Rect::new(0.0, 0.0, 1.0, 1.0); |
850 | assert!((test.aspect_ratio() - 1.0).abs() < 1e-6); |
851 | } |
852 | |
853 | #[test ] |
854 | fn contained_rect_overlaps() { |
855 | let outer = Rect::new(0.0, 0.0, 10.0, 10.0); |
856 | let inner = Rect::new(2.0, 2.0, 4.0, 4.0); |
857 | assert!(outer.overlaps(inner)); |
858 | } |
859 | |
860 | #[test ] |
861 | fn overlapping_rect_overlaps() { |
862 | let a = Rect::new(0.0, 0.0, 10.0, 10.0); |
863 | let b = Rect::new(5.0, 5.0, 15.0, 15.0); |
864 | assert!(a.overlaps(b)); |
865 | } |
866 | |
867 | #[test ] |
868 | fn disjoint_rect_overlaps() { |
869 | let a = Rect::new(0.0, 0.0, 10.0, 10.0); |
870 | let b = Rect::new(11.0, 11.0, 15.0, 15.0); |
871 | assert!(!a.overlaps(b)); |
872 | } |
873 | |
874 | #[test ] |
875 | fn sharing_edge_overlaps() { |
876 | let a = Rect::new(0.0, 0.0, 10.0, 10.0); |
877 | let b = Rect::new(10.0, 0.0, 20.0, 10.0); |
878 | assert!(a.overlaps(b)); |
879 | } |
880 | |
881 | // Test the two other directions in case there is a bug that only appears in one direction. |
882 | #[test ] |
883 | fn disjoint_rect_overlaps_negative() { |
884 | let a = Rect::new(0.0, 0.0, 10.0, 10.0); |
885 | let b = Rect::new(-10.0, -10.0, -5.0, -5.0); |
886 | assert!(!a.overlaps(b)); |
887 | } |
888 | |
889 | #[test ] |
890 | fn contained_rectangle_contains() { |
891 | let outer = Rect::new(0.0, 0.0, 10.0, 10.0); |
892 | let inner = Rect::new(2.0, 2.0, 4.0, 4.0); |
893 | assert!(outer.contains_rect(inner)); |
894 | } |
895 | |
896 | #[test ] |
897 | fn overlapping_rectangle_contains() { |
898 | let outer = Rect::new(0.0, 0.0, 10.0, 10.0); |
899 | let inner = Rect::new(5.0, 5.0, 15.0, 15.0); |
900 | assert!(!outer.contains_rect(inner)); |
901 | } |
902 | |
903 | #[test ] |
904 | fn disjoint_rectangle_contains() { |
905 | let outer = Rect::new(0.0, 0.0, 10.0, 10.0); |
906 | let inner = Rect::new(11.0, 11.0, 15.0, 15.0); |
907 | assert!(!outer.contains_rect(inner)); |
908 | } |
909 | } |
910 | |