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