Skip to main content

folio_core/
rect.rs

1//! PDF rectangle type (lower-left x, lower-left y, upper-right x, upper-right y).
2
3/// A rectangle defined by two corner points.
4///
5/// In PDF coordinate space, (x1, y1) is typically the lower-left corner
6/// and (x2, y2) is the upper-right corner, though this is not enforced
7/// until `normalize()` is called.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct Rect {
10    pub x1: f64,
11    pub y1: f64,
12    pub x2: f64,
13    pub y2: f64,
14}
15
16impl Rect {
17    /// Create a new rectangle.
18    pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
19        Self { x1, y1, x2, y2 }
20    }
21
22    /// Create a zero-sized rectangle at the origin.
23    pub fn zero() -> Self {
24        Self::new(0.0, 0.0, 0.0, 0.0)
25    }
26
27    /// Width of the rectangle (may be negative if not normalized).
28    pub fn width(&self) -> f64 {
29        self.x2 - self.x1
30    }
31
32    /// Height of the rectangle (may be negative if not normalized).
33    pub fn height(&self) -> f64 {
34        self.y2 - self.y1
35    }
36
37    /// Normalize so that (x1,y1) is lower-left and (x2,y2) is upper-right.
38    pub fn normalize(&mut self) {
39        if self.x1 > self.x2 {
40            std::mem::swap(&mut self.x1, &mut self.x2);
41        }
42        if self.y1 > self.y2 {
43            std::mem::swap(&mut self.y1, &mut self.y2);
44        }
45    }
46
47    /// Return a normalized copy.
48    pub fn normalized(&self) -> Self {
49        let mut r = *self;
50        r.normalize();
51        r
52    }
53
54    /// Returns true if the point (x, y) is inside this rectangle.
55    /// The rectangle should be normalized first for correct results.
56    pub fn contains(&self, x: f64, y: f64) -> bool {
57        x >= self.x1 && x <= self.x2 && y >= self.y1 && y <= self.y2
58    }
59
60    /// Compute the intersection of two rectangles.
61    /// Returns None if they do not intersect.
62    pub fn intersect(&self, other: &Rect) -> Option<Rect> {
63        let x1 = self.x1.max(other.x1);
64        let y1 = self.y1.max(other.y1);
65        let x2 = self.x2.min(other.x2);
66        let y2 = self.y2.min(other.y2);
67        if x1 <= x2 && y1 <= y2 {
68            Some(Rect { x1, y1, x2, y2 })
69        } else {
70            None
71        }
72    }
73
74    /// Compute the union (bounding box) of two rectangles.
75    pub fn union(&self, other: &Rect) -> Rect {
76        Rect {
77            x1: self.x1.min(other.x1),
78            y1: self.y1.min(other.y1),
79            x2: self.x2.max(other.x2),
80            y2: self.y2.max(other.y2),
81        }
82    }
83
84    /// Inflate the rectangle by the given amounts on each side.
85    pub fn inflate(&mut self, dx: f64, dy: f64) {
86        self.x1 -= dx;
87        self.y1 -= dy;
88        self.x2 += dx;
89        self.y2 += dy;
90    }
91
92    /// Inflate and return a new rectangle.
93    pub fn inflated(&self, dx: f64, dy: f64) -> Self {
94        let mut r = *self;
95        r.inflate(dx, dy);
96        r
97    }
98}
99
100impl Default for Rect {
101    fn default() -> Self {
102        Self::zero()
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_dimensions() {
112        let r = Rect::new(10.0, 20.0, 110.0, 70.0);
113        assert_eq!(r.width(), 100.0);
114        assert_eq!(r.height(), 50.0);
115    }
116
117    #[test]
118    fn test_normalize() {
119        let r = Rect::new(100.0, 200.0, 10.0, 20.0);
120        let n = r.normalized();
121        assert_eq!(n.x1, 10.0);
122        assert_eq!(n.y1, 20.0);
123        assert_eq!(n.x2, 100.0);
124        assert_eq!(n.y2, 200.0);
125    }
126
127    #[test]
128    fn test_contains() {
129        let r = Rect::new(0.0, 0.0, 100.0, 100.0);
130        assert!(r.contains(50.0, 50.0));
131        assert!(!r.contains(150.0, 50.0));
132        assert!(r.contains(0.0, 0.0)); // boundary
133        assert!(r.contains(100.0, 100.0)); // boundary
134    }
135
136    #[test]
137    fn test_intersect() {
138        let a = Rect::new(0.0, 0.0, 100.0, 100.0);
139        let b = Rect::new(50.0, 50.0, 150.0, 150.0);
140        let i = a.intersect(&b).unwrap();
141        assert_eq!(i, Rect::new(50.0, 50.0, 100.0, 100.0));
142
143        let c = Rect::new(200.0, 200.0, 300.0, 300.0);
144        assert!(a.intersect(&c).is_none());
145    }
146
147    #[test]
148    fn test_union() {
149        let a = Rect::new(10.0, 10.0, 50.0, 50.0);
150        let b = Rect::new(30.0, 30.0, 80.0, 80.0);
151        let u = a.union(&b);
152        assert_eq!(u, Rect::new(10.0, 10.0, 80.0, 80.0));
153    }
154
155    #[test]
156    fn test_inflate() {
157        let r = Rect::new(10.0, 10.0, 50.0, 50.0);
158        let i = r.inflated(5.0, 5.0);
159        assert_eq!(i, Rect::new(5.0, 5.0, 55.0, 55.0));
160    }
161}