1use 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 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 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 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
206pub 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}