1use crate::bindgen::{FPDF_BOOL, FS_RECTF};
4use crate::bindings::PdfiumLibraryBindings;
5use crate::error::{PdfiumError, PdfiumInternalError};
6use crate::pdf::matrix::PdfMatrix;
7use crate::pdf::points::PdfPoints;
8use crate::pdf::quad_points::PdfQuadPoints;
9use itertools::{max, min};
10use std::fmt::{Display, Formatter};
11use std::hash::{Hash, Hasher};
12
13#[derive(Debug, Copy, Clone)]
19pub struct PdfRect {
20 #[deprecated(
22 since = "0.8.28",
23 note = "Use the PdfRect::bottom() function instead of direct field access. Direct field access will be removed in release 0.9.0."
24 )]
25 pub bottom: PdfPoints,
26
27 #[deprecated(
28 since = "0.8.28",
29 note = "Use the PdfRect::left() function instead of direct field access. Direct field access will be removed in release 0.9.0."
30 )]
31 pub left: PdfPoints,
32
33 #[deprecated(
34 since = "0.8.28",
35 note = "Use the PdfRect::top() function instead of direct field access. Direct field access will be removed in release 0.9.0."
36 )]
37 pub top: PdfPoints,
38
39 #[deprecated(
40 since = "0.8.28",
41 note = "Use the PdfRect::left() function instead of direct field access. Direct field access will be removed in release 0.9.0."
42 )]
43 pub right: PdfPoints,
44}
45
46impl PdfRect {
47 pub const ZERO: PdfRect = PdfRect::zero();
49
50 pub const MAX: PdfRect = PdfRect::new(
53 PdfPoints::MIN,
54 PdfPoints::MIN,
55 PdfPoints::MAX,
56 PdfPoints::MAX,
57 );
58
59 #[inline]
60 pub(crate) fn from_pdfium(rect: FS_RECTF) -> Self {
61 #[allow(deprecated)]
62 Self {
63 bottom: PdfPoints::new(rect.bottom),
64 left: PdfPoints::new(rect.left),
65 top: PdfPoints::new(rect.top),
66 right: PdfPoints::new(rect.right),
67 }
68 }
69
70 #[inline]
71 pub(crate) fn from_pdfium_as_result(
72 result: FPDF_BOOL,
73 rect: FS_RECTF,
74 bindings: &dyn PdfiumLibraryBindings,
75 ) -> Result<PdfRect, PdfiumError> {
76 if !bindings.is_true(result) {
77 Err(PdfiumError::PdfiumLibraryInternalError(
78 PdfiumInternalError::Unknown,
79 ))
80 } else {
81 Ok(PdfRect::from_pdfium(rect))
82 }
83 }
84
85 #[inline]
91 pub const fn new(bottom: PdfPoints, left: PdfPoints, top: PdfPoints, right: PdfPoints) -> Self {
92 #[allow(deprecated)]
93 Self {
94 bottom,
95 left,
96 top,
97 right,
98 }
99 }
100
101 #[inline]
107 pub const fn new_from_values(bottom: f32, left: f32, top: f32, right: f32) -> Self {
108 Self::new(
109 PdfPoints::new(bottom),
110 PdfPoints::new(left),
111 PdfPoints::new(top),
112 PdfPoints::new(right),
113 )
114 }
115
116 #[inline]
121 pub const fn zero() -> Self {
122 Self::new_from_values(0.0, 0.0, 0.0, 0.0)
123 }
124
125 #[inline]
127 pub const fn left(&self) -> PdfPoints {
128 #[allow(deprecated)]
129 self.left
130 }
131
132 #[inline]
134 pub const fn right(&self) -> PdfPoints {
135 #[allow(deprecated)]
136 self.right
137 }
138
139 #[inline]
141 pub const fn bottom(&self) -> PdfPoints {
142 #[allow(deprecated)]
143 self.bottom
144 }
145
146 #[inline]
148 pub const fn top(&self) -> PdfPoints {
149 #[allow(deprecated)]
150 self.top
151 }
152
153 #[inline]
155 pub fn width(&self) -> PdfPoints {
156 self.right() - self.left()
157 }
158
159 #[inline]
161 pub fn height(&self) -> PdfPoints {
162 self.top() - self.bottom()
163 }
164
165 #[inline]
166 pub fn contains(&self, x: PdfPoints, y: PdfPoints) -> bool {
168 self.contains_x(x) && self.contains_y(y)
169 }
170
171 #[inline]
173 pub fn contains_x(&self, x: PdfPoints) -> bool {
174 self.left() <= x && self.right() >= x
175 }
176
177 #[inline]
179 pub fn contains_y(&self, y: PdfPoints) -> bool {
180 self.bottom() <= y && self.top() >= y
181 }
182
183 #[inline]
185 pub fn is_inside(&self, other: &PdfRect) -> bool {
186 self.left() >= other.left()
187 && self.right() <= other.right()
188 && self.top() <= other.top()
189 && self.bottom() >= other.bottom()
190 }
191
192 #[inline]
195 pub fn does_overlap(&self, other: &PdfRect) -> bool {
196 self.left() < other.right()
199 && self.right() > other.left()
200 && self.top() > other.bottom()
201 && self.bottom() < other.top()
202 }
203
204 #[inline]
206 pub fn transform(&self, matrix: PdfMatrix) -> PdfRect {
207 let (x1, y1) = matrix.apply_to_points(self.left(), self.top());
208 let (x2, y2) = matrix.apply_to_points(self.left(), self.bottom());
209 let (x3, y3) = matrix.apply_to_points(self.right(), self.top());
210 let (x4, y4) = matrix.apply_to_points(self.right(), self.bottom());
211
212 PdfRect::new(
213 min([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
214 min([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
215 max([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
216 max([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
217 )
218 }
219
220 #[inline]
222 pub fn to_quad_points(&self) -> PdfQuadPoints {
223 PdfQuadPoints::from_rect(self)
224 }
225
226 #[inline]
227 pub(crate) fn as_pdfium(&self) -> FS_RECTF {
228 FS_RECTF {
229 left: self.left().value,
230 top: self.top().value,
231 right: self.right().value,
232 bottom: self.bottom().value,
233 }
234 }
235}
236
237impl PartialEq for PdfRect {
241 fn eq(&self, other: &Self) -> bool {
242 self.bottom() == other.bottom()
243 && self.left() == other.left()
244 && self.top() == other.top()
245 && self.right() == other.right()
246 }
247}
248
249impl Eq for PdfRect {}
253
254impl Hash for PdfRect {
255 fn hash<H: Hasher>(&self, state: &mut H) {
256 state.write_u32(self.bottom().value.to_bits());
257 state.write_u32(self.left().value.to_bits());
258 state.write_u32(self.top().value.to_bits());
259 state.write_u32(self.right().value.to_bits());
260 }
261}
262
263impl Display for PdfRect {
264 #[inline]
265 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
266 f.write_fmt(format_args!(
267 "PdfRect(bottom: {}, left: {}, top: {}, right: {})",
268 self.bottom().value,
269 self.left().value,
270 self.top().value,
271 self.right().value
272 ))
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use crate::prelude::*;
279
280 #[test]
281 fn test_rect_is_inside() {
282 assert!(PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0)
283 .is_inside(&PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0)));
284
285 assert!(!PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0)
286 .is_inside(&PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0)));
287
288 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
289 .is_inside(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0)));
290
291 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
292 .is_inside(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0)));
293
294 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
295 .is_inside(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0)));
296 }
297
298 #[test]
299 fn test_rect_does_overlap() {
300 assert!(PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
301 .does_overlap(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0)));
302
303 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
304 .does_overlap(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0)));
305
306 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
307 .does_overlap(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0)));
308 }
309
310 #[test]
311 fn test_transform_rect() {
312 let delta_x = PdfPoints::new(50.0);
313 let delta_y = PdfPoints::new(-25.0);
314
315 let matrix = PdfMatrix::identity().translate(delta_x, delta_y).unwrap();
316
317 let bottom = PdfPoints::new(100.0);
318 let top = PdfPoints::new(200.0);
319 let left = PdfPoints::new(300.0);
320 let right = PdfPoints::new(400.0);
321
322 let rect = PdfRect::new(bottom, left, top, right);
323
324 let result = rect.transform(matrix);
325
326 assert_eq!(result.bottom(), bottom + delta_y);
327 assert_eq!(result.top(), top + delta_y);
328 assert_eq!(result.left(), left + delta_x);
329 assert_eq!(result.right(), right + delta_x);
330 }
331}