1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10use super::UnknownUnit;
11use crate::box2d::Box2D;
12use crate::num::*;
13use crate::point::Point2D;
14use crate::scale::Scale;
15use crate::side_offsets::SideOffsets2D;
16use crate::size::Size2D;
17use crate::vector::Vector2D;
18
19use num_traits::{NumCast, Float};
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22#[cfg(feature = "bytemuck")]
23use bytemuck::{Zeroable, Pod};
24
25use core::borrow::Borrow;
26use core::cmp::PartialOrd;
27use core::fmt;
28use core::hash::{Hash, Hasher};
29use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Range, Sub};
30
31/// A 2d Rectangle optionally tagged with a unit.
32///
33/// # Representation
34///
35/// `Rect` is represented by an origin point and a size.
36///
37/// See [`Box2D`] for a rectangle represented by two endpoints.
38///
39/// # Empty rectangle
40///
41/// A rectangle is considered empty (see [`is_empty`]) if any of the following is true:
42/// - it's area is empty,
43/// - it's area is negative (`size.x < 0` or `size.y < 0`),
44/// - it contains NaNs.
45///
46/// [`is_empty`]: #method.is_empty
47/// [`Box2D`]: struct.Box2D.html
48#[repr(C)]
49#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50#[cfg_attr(
51 feature = "serde",
52 serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
53)]
54pub struct Rect<T, U> {
55 pub origin: Point2D<T, U>,
56 pub size: Size2D<T, U>,
57}
58
59#[cfg(feature = "arbitrary")]
60impl<'a, T, U> arbitrary::Arbitrary<'a> for Rect<T, U>
61where
62 T: arbitrary::Arbitrary<'a>,
63{
64 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>
65 {
66 let (origin, size) = arbitrary::Arbitrary::arbitrary(u)?;
67 Ok(Rect {
68 origin,
69 size,
70 })
71 }
72}
73
74#[cfg(feature = "bytemuck")]
75unsafe impl<T: Zeroable, U> Zeroable for Rect<T, U> {}
76
77#[cfg(feature = "bytemuck")]
78unsafe impl<T: Pod, U: 'static> Pod for Rect<T, U> {}
79
80impl<T: Hash, U> Hash for Rect<T, U> {
81 fn hash<H: Hasher>(&self, h: &mut H) {
82 self.origin.hash(state:h);
83 self.size.hash(state:h);
84 }
85}
86
87impl<T: Copy, U> Copy for Rect<T, U> {}
88
89impl<T: Clone, U> Clone for Rect<T, U> {
90 fn clone(&self) -> Self {
91 Self::new(self.origin.clone(), self.size.clone())
92 }
93}
94
95impl<T: PartialEq, U> PartialEq for Rect<T, U> {
96 fn eq(&self, other: &Self) -> bool {
97 self.origin.eq(&other.origin) && self.size.eq(&other.size)
98 }
99}
100
101impl<T: Eq, U> Eq for Rect<T, U> {}
102
103impl<T: fmt::Debug, U> fmt::Debug for Rect<T, U> {
104 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105 write!(f, "Rect(")?;
106 fmt::Debug::fmt(&self.size, f)?;
107 write!(f, " at ")?;
108 fmt::Debug::fmt(&self.origin, f)?;
109 write!(f, ")")
110 }
111}
112
113impl<T: Default, U> Default for Rect<T, U> {
114 fn default() -> Self {
115 Rect::new(origin:Default::default(), size:Default::default())
116 }
117}
118
119impl<T, U> Rect<T, U> {
120 /// Constructor.
121 #[inline]
122 pub const fn new(origin: Point2D<T, U>, size: Size2D<T, U>) -> Self {
123 Rect { origin, size }
124 }
125}
126
127impl<T, U> Rect<T, U>
128where
129 T: Zero,
130{
131 /// Constructor, setting all sides to zero.
132 #[inline]
133 pub fn zero() -> Self {
134 Rect::new(Point2D::origin(), size:Size2D::zero())
135 }
136
137 /// Creates a rect of the given size, at offset zero.
138 #[inline]
139 pub fn from_size(size: Size2D<T, U>) -> Self {
140 Rect {
141 origin: Point2D::zero(),
142 size,
143 }
144 }
145}
146
147impl<T, U> Rect<T, U>
148where
149 T: Copy + Add<T, Output = T>,
150{
151 #[inline]
152 pub fn min(&self) -> Point2D<T, U> {
153 self.origin
154 }
155
156 #[inline]
157 pub fn max(&self) -> Point2D<T, U> {
158 self.origin + self.size
159 }
160
161 #[inline]
162 pub fn max_x(&self) -> T {
163 self.origin.x + self.size.width
164 }
165
166 #[inline]
167 pub fn min_x(&self) -> T {
168 self.origin.x
169 }
170
171 #[inline]
172 pub fn max_y(&self) -> T {
173 self.origin.y + self.size.height
174 }
175
176 #[inline]
177 pub fn min_y(&self) -> T {
178 self.origin.y
179 }
180
181 #[inline]
182 pub fn width(&self) -> T {
183 self.size.width
184 }
185
186 #[inline]
187 pub fn height(&self) -> T {
188 self.size.height
189 }
190
191 #[inline]
192 pub fn x_range(&self) -> Range<T> {
193 self.min_x()..self.max_x()
194 }
195
196 #[inline]
197 pub fn y_range(&self) -> Range<T> {
198 self.min_y()..self.max_y()
199 }
200
201 /// Returns the same rectangle, translated by a vector.
202 #[inline]
203 #[must_use]
204 pub fn translate(&self, by: Vector2D<T, U>) -> Self {
205 Self::new(self.origin + by, self.size)
206 }
207
208 #[inline]
209 pub fn to_box2d(&self) -> Box2D<T, U> {
210 Box2D {
211 min: self.min(),
212 max: self.max(),
213 }
214 }
215}
216
217impl<T, U> Rect<T, U>
218where
219 T: Copy + PartialOrd + Add<T, Output = T>,
220{
221 /// Returns true if this rectangle contains the point. Points are considered
222 /// in the rectangle if they are on the left or top edge, but outside if they
223 /// are on the right or bottom edge.
224 #[inline]
225 pub fn contains(&self, p: Point2D<T, U>) -> bool {
226 self.to_box2d().contains(p)
227 }
228
229 #[inline]
230 pub fn intersects(&self, other: &Self) -> bool {
231 self.to_box2d().intersects(&other.to_box2d())
232 }
233}
234
235impl<T, U> Rect<T, U>
236where
237 T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>,
238{
239 #[inline]
240 pub fn intersection(&self, other: &Self) -> Option<Self> {
241 let box2d: Box2D = self.to_box2d().intersection_unchecked(&other.to_box2d());
242
243 if box2d.is_empty() {
244 return None;
245 }
246
247 Some(box2d.to_rect())
248 }
249}
250
251impl<T, U> Rect<T, U>
252where
253 T: Copy + Add<T, Output = T> + Sub<T, Output = T>,
254{
255 #[inline]
256 #[must_use]
257 pub fn inflate(&self, width: T, height: T) -> Self {
258 Rect::new(
259 origin:Point2D::new(self.origin.x - width, self.origin.y - height),
260 size:Size2D::new(
261 self.size.width + width + width,
262 self.size.height + height + height,
263 ),
264 )
265 }
266}
267
268impl<T, U> Rect<T, U>
269where
270 T: Copy + Zero + PartialOrd + Add<T, Output = T>,
271{
272 /// Returns true if this rectangle contains the interior of rect. Always
273 /// returns true if rect is empty, and always returns false if rect is
274 /// nonempty but this rectangle is empty.
275 #[inline]
276 pub fn contains_rect(&self, rect: &Self) -> bool {
277 rect.is_empty()
278 || (self.min_x() <= rect.min_x()
279 && rect.max_x() <= self.max_x()
280 && self.min_y() <= rect.min_y()
281 && rect.max_y() <= self.max_y())
282 }
283}
284
285impl<T, U> Rect<T, U>
286where
287 T: Copy + Zero + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>,
288{
289 /// Calculate the size and position of an inner rectangle.
290 ///
291 /// Subtracts the side offsets from all sides. The horizontal and vertical
292 /// offsets must not be larger than the original side length.
293 /// This method assumes y oriented downward.
294 pub fn inner_rect(&self, offsets: SideOffsets2D<T, U>) -> Self {
295 let rect: Rect = Rect::new(
296 origin:Point2D::new(self.origin.x + offsets.left, self.origin.y + offsets.top),
297 size:Size2D::new(
298 self.size.width - offsets.horizontal(),
299 self.size.height - offsets.vertical(),
300 ),
301 );
302 debug_assert!(rect.size.width >= Zero::zero());
303 debug_assert!(rect.size.height >= Zero::zero());
304 rect
305 }
306}
307
308impl<T, U> Rect<T, U>
309where
310 T: Copy + Add<T, Output = T> + Sub<T, Output = T>,
311{
312 /// Calculate the size and position of an outer rectangle.
313 ///
314 /// Add the offsets to all sides. The expanded rectangle is returned.
315 /// This method assumes y oriented downward.
316 pub fn outer_rect(&self, offsets: SideOffsets2D<T, U>) -> Self {
317 Rect::new(
318 origin:Point2D::new(self.origin.x - offsets.left, self.origin.y - offsets.top),
319 size:Size2D::new(
320 self.size.width + offsets.horizontal(),
321 self.size.height + offsets.vertical(),
322 ),
323 )
324 }
325}
326
327impl<T, U> Rect<T, U>
328where
329 T: Copy + Zero + PartialOrd + Sub<T, Output = T>,
330{
331 /// Returns the smallest rectangle defined by the top/bottom/left/right-most
332 /// points provided as parameter.
333 ///
334 /// Note: This function has a behavior that can be surprising because
335 /// the right-most and bottom-most points are exactly on the edge
336 /// of the rectangle while the `contains` function is has exclusive
337 /// semantic on these edges. This means that the right-most and bottom-most
338 /// points provided to `from_points` will count as not contained by the rect.
339 /// This behavior may change in the future.
340 pub fn from_points<I>(points: I) -> Self
341 where
342 I: IntoIterator,
343 I::Item: Borrow<Point2D<T, U>>,
344 {
345 Box2D::from_points(points).to_rect()
346 }
347}
348
349impl<T, U> Rect<T, U>
350where
351 T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
352{
353 /// Linearly interpolate between this rectangle and another rectangle.
354 #[inline]
355 pub fn lerp(&self, other: Self, t: T) -> Self {
356 Self::new(
357 self.origin.lerp(other.origin, t),
358 self.size.lerp(other:other.size, t),
359 )
360 }
361}
362
363impl<T, U> Rect<T, U>
364where
365 T: Copy + One + Add<Output = T> + Div<Output = T>,
366{
367 pub fn center(&self) -> Point2D<T, U> {
368 let two: T = T::one() + T::one();
369 self.origin + self.size.to_vector() / two
370 }
371}
372
373impl<T, U> Rect<T, U>
374where
375 T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T> + Zero,
376{
377 #[inline]
378 pub fn union(&self, other: &Self) -> Self {
379 self.to_box2d().union(&other.to_box2d()).to_rect()
380 }
381}
382
383impl<T, U> Rect<T, U> {
384 #[inline]
385 pub fn scale<S: Copy>(&self, x: S, y: S) -> Self
386 where
387 T: Copy + Mul<S, Output = T>,
388 {
389 Rect::new(
390 origin:Point2D::new(self.origin.x * x, self.origin.y * y),
391 size:Size2D::new(self.size.width * x, self.size.height * y),
392 )
393 }
394}
395
396impl<T: Copy + Mul<T, Output = T>, U> Rect<T, U> {
397 #[inline]
398 pub fn area(&self) -> T {
399 self.size.area()
400 }
401}
402
403impl<T: Copy + Zero + PartialOrd, U> Rect<T, U> {
404 #[inline]
405 pub fn is_empty(&self) -> bool {
406 self.size.is_empty()
407 }
408}
409
410impl<T: Copy + Zero + PartialOrd, U> Rect<T, U> {
411 #[inline]
412 pub fn to_non_empty(&self) -> Option<Self> {
413 if self.is_empty() {
414 return None;
415 }
416
417 Some(*self)
418 }
419}
420
421impl<T: Copy + Mul, U> Mul<T> for Rect<T, U> {
422 type Output = Rect<T::Output, U>;
423
424 #[inline]
425 fn mul(self, scale: T) -> Self::Output {
426 Rect::new(self.origin * scale, self.size * scale)
427 }
428}
429
430impl<T: Copy + MulAssign, U> MulAssign<T> for Rect<T, U> {
431 #[inline]
432 fn mul_assign(&mut self, scale: T) {
433 *self *= Scale::new(scale);
434 }
435}
436
437impl<T: Copy + Div, U> Div<T> for Rect<T, U> {
438 type Output = Rect<T::Output, U>;
439
440 #[inline]
441 fn div(self, scale: T) -> Self::Output {
442 Rect::new(self.origin / scale.clone(), self.size / scale)
443 }
444}
445
446impl<T: Copy + DivAssign, U> DivAssign<T> for Rect<T, U> {
447 #[inline]
448 fn div_assign(&mut self, scale: T) {
449 *self /= Scale::new(scale);
450 }
451}
452
453impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Rect<T, U1> {
454 type Output = Rect<T::Output, U2>;
455
456 #[inline]
457 fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
458 Rect::new(self.origin * scale.clone(), self.size * scale)
459 }
460}
461
462impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Rect<T, U> {
463 #[inline]
464 fn mul_assign(&mut self, scale: Scale<T, U, U>) {
465 self.origin *= scale.clone();
466 self.size *= scale;
467 }
468}
469
470impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Rect<T, U2> {
471 type Output = Rect<T::Output, U1>;
472
473 #[inline]
474 fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
475 Rect::new(self.origin / scale.clone(), self.size / scale)
476 }
477}
478
479impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Rect<T, U> {
480 #[inline]
481 fn div_assign(&mut self, scale: Scale<T, U, U>) {
482 self.origin /= scale.clone();
483 self.size /= scale;
484 }
485}
486
487impl<T: Copy, U> Rect<T, U> {
488 /// Drop the units, preserving only the numeric value.
489 #[inline]
490 pub fn to_untyped(&self) -> Rect<T, UnknownUnit> {
491 Rect::new(self.origin.to_untyped(), self.size.to_untyped())
492 }
493
494 /// Tag a unitless value with units.
495 #[inline]
496 pub fn from_untyped(r: &Rect<T, UnknownUnit>) -> Rect<T, U> {
497 Rect::new(
498 origin:Point2D::from_untyped(r.origin),
499 size:Size2D::from_untyped(r.size),
500 )
501 }
502
503 /// Cast the unit
504 #[inline]
505 pub fn cast_unit<V>(&self) -> Rect<T, V> {
506 Rect::new(self.origin.cast_unit(), self.size.cast_unit())
507 }
508}
509
510impl<T: NumCast + Copy, U> Rect<T, U> {
511 /// Cast from one numeric representation to another, preserving the units.
512 ///
513 /// When casting from floating point to integer coordinates, the decimals are truncated
514 /// as one would expect from a simple cast, but this behavior does not always make sense
515 /// geometrically. Consider using round(), round_in or round_out() before casting.
516 #[inline]
517 pub fn cast<NewT: NumCast>(&self) -> Rect<NewT, U> {
518 Rect::new(self.origin.cast(), self.size.cast())
519 }
520
521 /// Fallible cast from one numeric representation to another, preserving the units.
522 ///
523 /// When casting from floating point to integer coordinates, the decimals are truncated
524 /// as one would expect from a simple cast, but this behavior does not always make sense
525 /// geometrically. Consider using round(), round_in or round_out() before casting.
526 pub fn try_cast<NewT: NumCast>(&self) -> Option<Rect<NewT, U>> {
527 match (self.origin.try_cast(), self.size.try_cast()) {
528 (Some(origin), Some(size)) => Some(Rect::new(origin, size)),
529 _ => None,
530 }
531 }
532
533 // Convenience functions for common casts
534
535 /// Cast into an `f32` rectangle.
536 #[inline]
537 pub fn to_f32(&self) -> Rect<f32, U> {
538 self.cast()
539 }
540
541 /// Cast into an `f64` rectangle.
542 #[inline]
543 pub fn to_f64(&self) -> Rect<f64, U> {
544 self.cast()
545 }
546
547 /// Cast into an `usize` rectangle, truncating decimals if any.
548 ///
549 /// When casting from floating point rectangles, it is worth considering whether
550 /// to `round()`, `round_in()` or `round_out()` before the cast in order to
551 /// obtain the desired conversion behavior.
552 #[inline]
553 pub fn to_usize(&self) -> Rect<usize, U> {
554 self.cast()
555 }
556
557 /// Cast into an `u32` rectangle, truncating decimals if any.
558 ///
559 /// When casting from floating point rectangles, it is worth considering whether
560 /// to `round()`, `round_in()` or `round_out()` before the cast in order to
561 /// obtain the desired conversion behavior.
562 #[inline]
563 pub fn to_u32(&self) -> Rect<u32, U> {
564 self.cast()
565 }
566
567 /// Cast into an `u64` rectangle, truncating decimals if any.
568 ///
569 /// When casting from floating point rectangles, it is worth considering whether
570 /// to `round()`, `round_in()` or `round_out()` before the cast in order to
571 /// obtain the desired conversion behavior.
572 #[inline]
573 pub fn to_u64(&self) -> Rect<u64, U> {
574 self.cast()
575 }
576
577 /// Cast into an `i32` rectangle, truncating decimals if any.
578 ///
579 /// When casting from floating point rectangles, it is worth considering whether
580 /// to `round()`, `round_in()` or `round_out()` before the cast in order to
581 /// obtain the desired conversion behavior.
582 #[inline]
583 pub fn to_i32(&self) -> Rect<i32, U> {
584 self.cast()
585 }
586
587 /// Cast into an `i64` rectangle, truncating decimals if any.
588 ///
589 /// When casting from floating point rectangles, it is worth considering whether
590 /// to `round()`, `round_in()` or `round_out()` before the cast in order to
591 /// obtain the desired conversion behavior.
592 #[inline]
593 pub fn to_i64(&self) -> Rect<i64, U> {
594 self.cast()
595 }
596}
597
598impl<T: Float, U> Rect<T, U> {
599 /// Returns true if all members are finite.
600 #[inline]
601 pub fn is_finite(self) -> bool {
602 self.origin.is_finite() && self.size.is_finite()
603 }
604}
605
606impl<T: Floor + Ceil + Round + Add<T, Output = T> + Sub<T, Output = T>, U> Rect<T, U> {
607 /// Return a rectangle with edges rounded to integer coordinates, such that
608 /// the returned rectangle has the same set of pixel centers as the original
609 /// one.
610 /// Edges at offset 0.5 round up.
611 /// Suitable for most places where integral device coordinates
612 /// are needed, but note that any translation should be applied first to
613 /// avoid pixel rounding errors.
614 /// Note that this is *not* rounding to nearest integer if the values are negative.
615 /// They are always rounding as floor(n + 0.5).
616 ///
617 /// # Usage notes
618 /// Note, that when using with floating-point `T` types that method can significantly
619 /// loose precision for large values, so if you need to call this method very often it
620 /// is better to use [`Box2D`].
621 ///
622 /// [`Box2D`]: struct.Box2D.html
623 #[must_use]
624 pub fn round(&self) -> Self {
625 self.to_box2d().round().to_rect()
626 }
627
628 /// Return a rectangle with edges rounded to integer coordinates, such that
629 /// the original rectangle contains the resulting rectangle.
630 ///
631 /// # Usage notes
632 /// Note, that when using with floating-point `T` types that method can significantly
633 /// loose precision for large values, so if you need to call this method very often it
634 /// is better to use [`Box2D`].
635 ///
636 /// [`Box2D`]: struct.Box2D.html
637 #[must_use]
638 pub fn round_in(&self) -> Self {
639 self.to_box2d().round_in().to_rect()
640 }
641
642 /// Return a rectangle with edges rounded to integer coordinates, such that
643 /// the original rectangle is contained in the resulting rectangle.
644 ///
645 /// # Usage notes
646 /// Note, that when using with floating-point `T` types that method can significantly
647 /// loose precision for large values, so if you need to call this method very often it
648 /// is better to use [`Box2D`].
649 ///
650 /// [`Box2D`]: struct.Box2D.html
651 #[must_use]
652 pub fn round_out(&self) -> Self {
653 self.to_box2d().round_out().to_rect()
654 }
655}
656
657impl<T, U> From<Size2D<T, U>> for Rect<T, U>
658where
659 T: Zero,
660{
661 fn from(size: Size2D<T, U>) -> Self {
662 Self::from_size(size)
663 }
664}
665
666/// Shorthand for `Rect::new(Point2D::new(x, y), Size2D::new(w, h))`.
667pub const fn rect<T, U>(x: T, y: T, w: T, h: T) -> Rect<T, U> {
668 Rect::new(origin:Point2D::new(x, y), size:Size2D::new(width:w, height:h))
669}
670
671#[cfg(test)]
672mod tests {
673 use crate::default::{Point2D, Rect, Size2D};
674 use crate::side_offsets::SideOffsets2D;
675 use crate::{point2, rect, size2, vec2};
676
677 #[test]
678 fn test_translate() {
679 let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
680 let pp = p.translate(vec2(10, 15));
681
682 assert!(pp.size.width == 50);
683 assert!(pp.size.height == 40);
684 assert!(pp.origin.x == 10);
685 assert!(pp.origin.y == 15);
686
687 let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
688 let rr = r.translate(vec2(0, -10));
689
690 assert!(rr.size.width == 50);
691 assert!(rr.size.height == 40);
692 assert!(rr.origin.x == -10);
693 assert!(rr.origin.y == -15);
694 }
695
696 #[test]
697 fn test_union() {
698 let p = Rect::new(Point2D::new(0, 0), Size2D::new(50, 40));
699 let q = Rect::new(Point2D::new(20, 20), Size2D::new(5, 5));
700 let r = Rect::new(Point2D::new(-15, -30), Size2D::new(200, 15));
701 let s = Rect::new(Point2D::new(20, -15), Size2D::new(250, 200));
702
703 let pq = p.union(&q);
704 assert!(pq.origin == Point2D::new(0, 0));
705 assert!(pq.size == Size2D::new(50, 40));
706
707 let pr = p.union(&r);
708 assert!(pr.origin == Point2D::new(-15, -30));
709 assert!(pr.size == Size2D::new(200, 70));
710
711 let ps = p.union(&s);
712 assert!(ps.origin == Point2D::new(0, -15));
713 assert!(ps.size == Size2D::new(270, 200));
714 }
715
716 #[test]
717 fn test_intersection() {
718 let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
719 let q = Rect::new(Point2D::new(5, 15), Size2D::new(10, 10));
720 let r = Rect::new(Point2D::new(-5, -5), Size2D::new(8, 8));
721
722 let pq = p.intersection(&q);
723 assert!(pq.is_some());
724 let pq = pq.unwrap();
725 assert!(pq.origin == Point2D::new(5, 15));
726 assert!(pq.size == Size2D::new(5, 5));
727
728 let pr = p.intersection(&r);
729 assert!(pr.is_some());
730 let pr = pr.unwrap();
731 assert!(pr.origin == Point2D::new(0, 0));
732 assert!(pr.size == Size2D::new(3, 3));
733
734 let qr = q.intersection(&r);
735 assert!(qr.is_none());
736 }
737
738 #[test]
739 fn test_intersection_overflow() {
740 // test some scenarios where the intersection can overflow but
741 // the min_x() and max_x() don't. Gecko currently fails these cases
742 let p = Rect::new(Point2D::new(-2147483648, -2147483648), Size2D::new(0, 0));
743 let q = Rect::new(
744 Point2D::new(2136893440, 2136893440),
745 Size2D::new(279552, 279552),
746 );
747 let r = Rect::new(Point2D::new(-2147483648, -2147483648), Size2D::new(1, 1));
748
749 assert!(p.is_empty());
750 let pq = p.intersection(&q);
751 assert!(pq.is_none());
752
753 let qr = q.intersection(&r);
754 assert!(qr.is_none());
755 }
756
757 #[test]
758 fn test_contains() {
759 let r = Rect::new(Point2D::new(-20, 15), Size2D::new(100, 200));
760
761 assert!(r.contains(Point2D::new(0, 50)));
762 assert!(r.contains(Point2D::new(-10, 200)));
763
764 // The `contains` method is inclusive of the top/left edges, but not the
765 // bottom/right edges.
766 assert!(r.contains(Point2D::new(-20, 15)));
767 assert!(!r.contains(Point2D::new(80, 15)));
768 assert!(!r.contains(Point2D::new(80, 215)));
769 assert!(!r.contains(Point2D::new(-20, 215)));
770
771 // Points beyond the top-left corner.
772 assert!(!r.contains(Point2D::new(-25, 15)));
773 assert!(!r.contains(Point2D::new(-15, 10)));
774
775 // Points beyond the top-right corner.
776 assert!(!r.contains(Point2D::new(85, 20)));
777 assert!(!r.contains(Point2D::new(75, 10)));
778
779 // Points beyond the bottom-right corner.
780 assert!(!r.contains(Point2D::new(85, 210)));
781 assert!(!r.contains(Point2D::new(75, 220)));
782
783 // Points beyond the bottom-left corner.
784 assert!(!r.contains(Point2D::new(-25, 210)));
785 assert!(!r.contains(Point2D::new(-15, 220)));
786
787 let r = Rect::new(Point2D::new(-20.0, 15.0), Size2D::new(100.0, 200.0));
788 assert!(r.contains_rect(&r));
789 assert!(!r.contains_rect(&r.translate(vec2(0.1, 0.0))));
790 assert!(!r.contains_rect(&r.translate(vec2(-0.1, 0.0))));
791 assert!(!r.contains_rect(&r.translate(vec2(0.0, 0.1))));
792 assert!(!r.contains_rect(&r.translate(vec2(0.0, -0.1))));
793 // Empty rectangles are always considered as contained in other rectangles,
794 // even if their origin is not.
795 let p = Point2D::new(1.0, 1.0);
796 assert!(!r.contains(p));
797 assert!(r.contains_rect(&Rect::new(p, Size2D::zero())));
798 }
799
800 #[test]
801 fn test_scale() {
802 let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
803 let pp = p.scale(10, 15);
804
805 assert!(pp.size.width == 500);
806 assert!(pp.size.height == 600);
807 assert!(pp.origin.x == 0);
808 assert!(pp.origin.y == 0);
809
810 let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
811 let rr = r.scale(1, 20);
812
813 assert!(rr.size.width == 50);
814 assert!(rr.size.height == 800);
815 assert!(rr.origin.x == -10);
816 assert!(rr.origin.y == -100);
817 }
818
819 #[test]
820 fn test_inflate() {
821 let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 10));
822 let pp = p.inflate(10, 20);
823
824 assert!(pp.size.width == 30);
825 assert!(pp.size.height == 50);
826 assert!(pp.origin.x == -10);
827 assert!(pp.origin.y == -20);
828
829 let r = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
830 let rr = r.inflate(-2, -5);
831
832 assert!(rr.size.width == 6);
833 assert!(rr.size.height == 10);
834 assert!(rr.origin.x == 2);
835 assert!(rr.origin.y == 5);
836 }
837
838 #[test]
839 fn test_inner_outer_rect() {
840 let inner_rect = Rect::new(point2(20, 40), size2(80, 100));
841 let offsets = SideOffsets2D::new(20, 10, 10, 10);
842 let outer_rect = inner_rect.outer_rect(offsets);
843 assert_eq!(outer_rect.origin.x, 10);
844 assert_eq!(outer_rect.origin.y, 20);
845 assert_eq!(outer_rect.size.width, 100);
846 assert_eq!(outer_rect.size.height, 130);
847 assert_eq!(outer_rect.inner_rect(offsets), inner_rect);
848 }
849
850 #[test]
851 fn test_min_max_x_y() {
852 let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
853 assert!(p.max_y() == 40);
854 assert!(p.min_y() == 0);
855 assert!(p.max_x() == 50);
856 assert!(p.min_x() == 0);
857
858 let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
859 assert!(r.max_y() == 35);
860 assert!(r.min_y() == -5);
861 assert!(r.max_x() == 40);
862 assert!(r.min_x() == -10);
863 }
864
865 #[test]
866 fn test_width_height() {
867 let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
868 assert!(r.width() == 50);
869 assert!(r.height() == 40);
870 }
871
872 #[test]
873 fn test_is_empty() {
874 assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 0u32)).is_empty());
875 assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(10u32, 0u32)).is_empty());
876 assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 10u32)).is_empty());
877 assert!(!Rect::new(Point2D::new(0u32, 0u32), Size2D::new(1u32, 1u32)).is_empty());
878 assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 0u32)).is_empty());
879 assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(10u32, 0u32)).is_empty());
880 assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 10u32)).is_empty());
881 assert!(!Rect::new(Point2D::new(10u32, 10u32), Size2D::new(1u32, 1u32)).is_empty());
882 }
883
884 #[test]
885 fn test_round() {
886 let mut x = -2.0;
887 let mut y = -2.0;
888 let mut w = -2.0;
889 let mut h = -2.0;
890 while x < 2.0 {
891 while y < 2.0 {
892 while w < 2.0 {
893 while h < 2.0 {
894 let rect = Rect::new(Point2D::new(x, y), Size2D::new(w, h));
895
896 assert!(rect.contains_rect(&rect.round_in()));
897 assert!(rect.round_in().inflate(1.0, 1.0).contains_rect(&rect));
898
899 assert!(rect.round_out().contains_rect(&rect));
900 assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round_out()));
901
902 assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round()));
903 assert!(rect.round().inflate(1.0, 1.0).contains_rect(&rect));
904
905 h += 0.1;
906 }
907 w += 0.1;
908 }
909 y += 0.1;
910 }
911 x += 0.1
912 }
913 }
914
915 #[test]
916 fn test_center() {
917 let r: Rect<i32> = rect(-2, 5, 4, 10);
918 assert_eq!(r.center(), point2(0, 10));
919
920 let r: Rect<f32> = rect(1.0, 2.0, 3.0, 4.0);
921 assert_eq!(r.center(), point2(2.5, 4.0));
922 }
923
924 #[test]
925 fn test_nan() {
926 let r1: Rect<f32> = rect(-2.0, 5.0, 4.0, std::f32::NAN);
927 let r2: Rect<f32> = rect(std::f32::NAN, -1.0, 3.0, 10.0);
928
929 assert_eq!(r1.intersection(&r2), None);
930 }
931}
932