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 | /// |
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 | |
534 | impl 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 | |
540 | impl 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 | |
546 | impl 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 | |
555 | impl 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 | |
564 | impl 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)] |
578 | pub struct RectPathIter { |
579 | rect: Rect, |
580 | ix: usize, |
581 | } |
582 | |
583 | impl 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. |
639 | impl 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 | |
655 | impl 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 | |
674 | impl 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)] |
685 | mod 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 | |