rsvg/
rect.rs

1//! Types for rectangles.
2
3use crate::coord_units::CoordUnits;
4use crate::transform::Transform;
5
6#[allow(clippy::module_inception)]
7mod rect {
8    use crate::float_eq_cairo::ApproxEqCairo;
9    use core::ops::{Add, Range, Sub};
10    use float_cmp::approx_eq;
11    use num_traits::Zero;
12
13    // Use our own min() and max() that are acceptable for floating point
14
15    fn min<T: PartialOrd>(x: T, y: T) -> T {
16        if x <= y {
17            x
18        } else {
19            y
20        }
21    }
22
23    fn max<T: PartialOrd>(x: T, y: T) -> T {
24        if x >= y {
25            x
26        } else {
27            y
28        }
29    }
30
31    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
32    pub struct Rect<T> {
33        pub x0: T,
34        pub y0: T,
35        pub x1: T,
36        pub y1: T,
37    }
38
39    impl<T> Rect<T> {
40        #[inline]
41        pub fn new(x0: T, y0: T, x1: T, y1: T) -> Self {
42            Self { x0, y0, x1, y1 }
43        }
44    }
45
46    impl<T> Rect<T>
47    where
48        T: Copy + PartialOrd + PartialEq + Add<T, Output = T> + Sub<T, Output = T> + Zero,
49    {
50        #[inline]
51        pub fn from_size(w: T, h: T) -> Self {
52            Self {
53                x0: Zero::zero(),
54                y0: Zero::zero(),
55                x1: w,
56                y1: h,
57            }
58        }
59
60        #[inline]
61        pub fn width(&self) -> T {
62            self.x1 - self.x0
63        }
64
65        #[inline]
66        pub fn height(&self) -> T {
67            self.y1 - self.y0
68        }
69
70        #[inline]
71        pub fn size(&self) -> (T, T) {
72            (self.width(), self.height())
73        }
74
75        #[inline]
76        pub fn x_range(&self) -> Range<T> {
77            self.x0..self.x1
78        }
79
80        #[inline]
81        pub fn y_range(&self) -> Range<T> {
82            self.y0..self.y1
83        }
84
85        #[inline]
86        pub fn contains(self, x: T, y: T) -> bool {
87            x >= self.x0 && x < self.x1 && y >= self.y0 && y < self.y1
88        }
89
90        #[inline]
91        pub fn translate(&self, by: (T, T)) -> Self {
92            Self {
93                x0: self.x0 + by.0,
94                y0: self.y0 + by.1,
95                x1: self.x1 + by.0,
96                y1: self.y1 + by.1,
97            }
98        }
99
100        #[inline]
101        pub fn intersection(&self, rect: &Self) -> Option<Self> {
102            let (x0, y0, x1, y1) = (
103                max(self.x0, rect.x0),
104                max(self.y0, rect.y0),
105                min(self.x1, rect.x1),
106                min(self.y1, rect.y1),
107            );
108
109            if x1 > x0 && y1 > y0 {
110                Some(Self { x0, y0, x1, y1 })
111            } else {
112                None
113            }
114        }
115
116        #[inline]
117        pub fn union(&self, rect: &Self) -> Self {
118            Self {
119                x0: min(self.x0, rect.x0),
120                y0: min(self.y0, rect.y0),
121                x1: max(self.x1, rect.x1),
122                y1: max(self.y1, rect.y1),
123            }
124        }
125    }
126
127    impl Rect<i32> {
128        #[inline]
129        pub fn is_empty(&self) -> bool {
130            // Give an explicit type to the right hand side of the ==, since sometimes
131            // type inference fails to figure it out.  I have no idea why.
132            self.width() == <i32 as Zero>::zero() || self.height() == <i32 as Zero>::zero()
133        }
134
135        #[inline]
136        pub fn scale(self, x: f64, y: f64) -> Self {
137            Self {
138                x0: (f64::from(self.x0) * x).floor() as i32,
139                y0: (f64::from(self.y0) * y).floor() as i32,
140                x1: (f64::from(self.x1) * x).ceil() as i32,
141                y1: (f64::from(self.y1) * y).ceil() as i32,
142            }
143        }
144    }
145
146    impl Rect<f64> {
147        #[inline]
148        pub fn is_empty(&self) -> bool {
149            self.width().approx_eq_cairo(0.0) || self.height().approx_eq_cairo(0.0)
150        }
151
152        #[inline]
153        pub fn scale(self, x: f64, y: f64) -> Self {
154            Self {
155                x0: self.x0 * x,
156                y0: self.y0 * y,
157                x1: self.x1 * x,
158                y1: self.y1 * y,
159            }
160        }
161
162        pub fn approx_eq(&self, other: &Self) -> bool {
163            // FIXME: this is super fishy; shouldn't we be using 2x the epsilon against the width/height
164            // instead of the raw coordinates?
165            approx_eq!(f64, self.x0, other.x0, epsilon = 0.0001)
166                && approx_eq!(f64, self.y0, other.y0, epsilon = 0.0001)
167                && approx_eq!(f64, self.x1, other.x1, epsilon = 0.0001)
168                && approx_eq!(f64, self.y1, other.y1, epsilon = 0.00012)
169        }
170    }
171}
172
173pub type Rect = rect::Rect<f64>;
174
175impl From<Rect> for IRect {
176    #[inline]
177    fn from(r: Rect) -> Self {
178        Self {
179            x0: r.x0.floor() as i32,
180            y0: r.y0.floor() as i32,
181            x1: r.x1.ceil() as i32,
182            y1: r.y1.ceil() as i32,
183        }
184    }
185}
186
187impl From<cairo::Rectangle> for Rect {
188    #[inline]
189    fn from(r: cairo::Rectangle) -> Self {
190        Self {
191            x0: r.x(),
192            y0: r.y(),
193            x1: r.x() + r.width(),
194            y1: r.y() + r.height(),
195        }
196    }
197}
198
199impl From<Rect> for cairo::Rectangle {
200    #[inline]
201    fn from(r: Rect) -> Self {
202        Self::new(r.x0, r.y0, r.x1 - r.x0, r.y1 - r.y0)
203    }
204}
205
206/// Creates a transform to map to a rectangle.
207///
208/// The rectangle is an `Option<Rect>` to indicate the possibility that there is no
209/// bounding box from where the rectangle could be obtained.
210///
211/// This depends on a `CoordUnits` parameter.  When this is
212/// `CoordUnits::ObjectBoundingBox`, the bounding box must not be empty, since the calling
213/// code would then not have a usable size to work with.  In that case, if the bbox is
214/// empty, this function returns `Err(())`.
215///
216/// Usually calling code can simply ignore the action it was about to take if this
217/// function returns an error.
218pub fn rect_to_transform(rect: &Option<Rect>, units: CoordUnits) -> Result<Transform, ()> {
219    match units {
220        CoordUnits::UserSpaceOnUse => Ok(Transform::identity()),
221        CoordUnits::ObjectBoundingBox => {
222            if rect.as_ref().is_none_or(|r| r.is_empty()) {
223                Err(())
224            } else {
225                let r = rect.as_ref().unwrap();
226                let t = Transform::new_unchecked(r.width(), 0.0, 0.0, r.height(), r.x0, r.y0);
227
228                if t.is_invertible() {
229                    Ok(t)
230                } else {
231                    Err(())
232                }
233            }
234        }
235    }
236}
237
238pub type IRect = rect::Rect<i32>;
239
240impl From<IRect> for Rect {
241    #[inline]
242    fn from(r: IRect) -> Self {
243        Self {
244            x0: f64::from(r.x0),
245            y0: f64::from(r.y0),
246            x1: f64::from(r.x1),
247            y1: f64::from(r.y1),
248        }
249    }
250}
251
252impl From<cairo::Rectangle> for IRect {
253    #[inline]
254    fn from(r: cairo::Rectangle) -> Self {
255        Self {
256            x0: r.x().floor() as i32,
257            y0: r.y().floor() as i32,
258            x1: (r.x() + r.width()).ceil() as i32,
259            y1: (r.y() + r.height()).ceil() as i32,
260        }
261    }
262}
263
264impl From<IRect> for cairo::Rectangle {
265    #[inline]
266    fn from(r: IRect) -> Self {
267        Self::new(
268            f64::from(r.x0),
269            f64::from(r.y0),
270            f64::from(r.x1 - r.x0),
271            f64::from(r.y1 - r.y0),
272        )
273    }
274}