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 | |