Skip to main content

pdfium_render/pdf/
quad_points.rs

1//! Defines the [PdfQuadPoints] struct, a set of four coordinates expressed in [PdfPoints]
2//! that outline the bounds of a four-sided quadrilateral.
3
4use crate::bindgen::{FPDF_BOOL, FS_QUADPOINTSF};
5use crate::bindings::PdfiumLibraryBindings;
6use crate::error::{PdfiumError, PdfiumInternalError};
7use crate::pdf::matrix::PdfMatrix;
8use crate::pdf::points::PdfPoints;
9use crate::pdf::rect::PdfRect;
10use std::fmt::{Display, Formatter};
11use std::hash::{Hash, Hasher};
12
13#[cfg(doc)]
14use crate::pdf::document::page::PdfPage;
15
16/// A set of four coordinates expressed in [PdfPoints] that outline the bounds of a
17/// four-sided quadrilateral. The coordinates specify the quadrilateral's four vertices
18/// in counter-clockwise order:
19/// ```
20/// (x4, y4)                     (x3, y3)
21///        ._____________________.
22///        |                     |
23///        |                     |
24///        !_____________________!
25/// (x1, y1)                     (x2, y2)
26/// ```
27/// More information on quad points can be found in Section 8.30 of the PDF Reference Manual,
28/// version 1.7, on page 634.
29#[derive(Debug, Copy, Clone)]
30pub struct PdfQuadPoints {
31    x1: PdfPoints,
32    y1: PdfPoints,
33    x2: PdfPoints,
34    y2: PdfPoints,
35    x3: PdfPoints,
36    y3: PdfPoints,
37    x4: PdfPoints,
38    y4: PdfPoints,
39}
40
41impl PdfQuadPoints {
42    /// A [PdfQuadPoints] object with the identity value (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0).
43    pub const ZERO: PdfQuadPoints = PdfQuadPoints::zero();
44
45    #[inline]
46    pub(crate) fn from_pdfium(points: FS_QUADPOINTSF) -> Self {
47        PdfQuadPoints::new_from_values(
48            points.x1, points.y1, points.x2, points.y2, points.x3, points.y3, points.x4, points.y4,
49        )
50    }
51
52    #[inline]
53    pub(crate) fn from_pdfium_as_result(
54        result: FPDF_BOOL,
55        points: FS_QUADPOINTSF,
56        bindings: &dyn PdfiumLibraryBindings,
57    ) -> Result<PdfQuadPoints, PdfiumError> {
58        if !bindings.is_true(result) {
59            Err(PdfiumError::PdfiumLibraryInternalError(
60                PdfiumInternalError::Unknown,
61            ))
62        } else {
63            Ok(PdfQuadPoints::from_pdfium(points))
64        }
65    }
66
67    /// Creates a new [PdfQuadPoints] object from the given [PdfPoints] coordinate pairs.
68    ///
69    /// The coordinate space of a [PdfPage] has its origin (0,0) at the bottom left of the page,
70    /// with x values increasing as coordinates move horizontally to the right and
71    /// y values increasing as coordinates move vertically up.
72    #[inline]
73    #[allow(clippy::too_many_arguments)]
74    pub const fn new(
75        x1: PdfPoints,
76        y1: PdfPoints,
77        x2: PdfPoints,
78        y2: PdfPoints,
79        x3: PdfPoints,
80        y3: PdfPoints,
81        x4: PdfPoints,
82        y4: PdfPoints,
83    ) -> Self {
84        Self {
85            x1,
86            y1,
87            x2,
88            y2,
89            x3,
90            y3,
91            x4,
92            y4,
93        }
94    }
95
96    /// Creates a new [PdfQuadPoints] object from the given raw points values.
97    ///
98    /// The coordinate space of a [PdfPage] has its origin (0,0) at the bottom left of the page,
99    /// with x values increasing as coordinates move horizontally to the right and
100    /// y values increasing as coordinates move vertically up.
101    #[inline]
102    #[allow(clippy::too_many_arguments)]
103    pub const fn new_from_values(
104        x1: f32,
105        y1: f32,
106        x2: f32,
107        y2: f32,
108        x3: f32,
109        y3: f32,
110        x4: f32,
111        y4: f32,
112    ) -> Self {
113        Self::new(
114            PdfPoints::new(x1),
115            PdfPoints::new(y1),
116            PdfPoints::new(x2),
117            PdfPoints::new(y2),
118            PdfPoints::new(x3),
119            PdfPoints::new(y3),
120            PdfPoints::new(x4),
121            PdfPoints::new(y4),
122        )
123    }
124
125    /// Creates a new [PdfQuadPoints] object with all values set to 0.0.
126    ///
127    /// Consider using the compile-time constant value [PdfQuadPoints::ZERO]
128    /// rather than calling this function directly.
129    #[inline]
130    pub const fn zero() -> Self {
131        Self::new_from_values(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
132    }
133
134    /// Creates a new [PdfQuadPoints] from the given [PdfRect].
135    #[inline]
136    pub fn from_rect(rect: &PdfRect) -> Self {
137        PdfQuadPoints::new(
138            rect.left(),
139            rect.bottom(),
140            rect.right(),
141            rect.bottom(),
142            rect.right(),
143            rect.top(),
144            rect.left(),
145            rect.top(),
146        )
147    }
148
149    /// Returns the `x1` point of this [PdfQuadPoints].
150    #[inline]
151    pub fn x1(&self) -> PdfPoints {
152        self.x1
153    }
154
155    /// Returns the `y1` point of this [PdfQuadPoints].
156    #[inline]
157    pub fn y1(&self) -> PdfPoints {
158        self.y1
159    }
160
161    /// Returns the `x2` point of this [PdfQuadPoints].
162    #[inline]
163    pub fn x2(&self) -> PdfPoints {
164        self.x2
165    }
166
167    /// Returns the `y2` point of this [PdfQuadPoints].
168    #[inline]
169    pub fn y2(&self) -> PdfPoints {
170        self.y2
171    }
172
173    /// Returns the `x3` point of this [PdfQuadPoints].
174    #[inline]
175    pub fn x3(&self) -> PdfPoints {
176        self.x3
177    }
178
179    /// Returns the `y3` point of this [PdfQuadPoints].
180    #[inline]
181    pub fn y3(&self) -> PdfPoints {
182        self.y3
183    }
184
185    /// Returns the `x4` point of this [PdfQuadPoints].
186    #[inline]
187    pub fn x4(&self) -> PdfPoints {
188        self.x4
189    }
190
191    /// Returns the `y4` point of this [PdfQuadPoints].
192    #[inline]
193    pub fn y4(&self) -> PdfPoints {
194        self.y4
195    }
196
197    /// Returns the left-most extent of this [PdfQuadPoints].
198    pub fn left(&self) -> PdfPoints {
199        *vec![self.x1, self.x2, self.x3, self.x4]
200            .iter()
201            .min()
202            .unwrap()
203    }
204
205    /// Returns the right-most extent of this [PdfQuadPoints].
206    pub fn right(&self) -> PdfPoints {
207        *vec![self.x1, self.x2, self.x3, self.x4]
208            .iter()
209            .max()
210            .unwrap()
211    }
212
213    /// Returns the bottom-most extent of this [PdfQuadPoints].
214    pub fn bottom(&self) -> PdfPoints {
215        *vec![self.y1, self.y2, self.y3, self.y4]
216            .iter()
217            .min()
218            .unwrap()
219    }
220
221    /// Returns the top-most extent of this [PdfQuadPoints].
222    pub fn top(&self) -> PdfPoints {
223        *vec![self.y1, self.y2, self.y3, self.y4]
224            .iter()
225            .max()
226            .unwrap()
227    }
228
229    /// Returns the width of this [PdfQuadPoints].
230    #[inline]
231    pub fn width(&self) -> PdfPoints {
232        self.right() - self.left()
233    }
234
235    /// Returns the height of this [PdfQuadPoints].
236    #[inline]
237    pub fn height(&self) -> PdfPoints {
238        self.top() - self.bottom()
239    }
240
241    /// Returns the result of applying the given [PdfMatrix] to each corner point
242    // of this [PdfQuadPoints].
243    #[inline]
244    pub fn transform(&self, matrix: PdfMatrix) -> PdfQuadPoints {
245        let (x1, y1) = matrix.apply_to_points(self.x1, self.y1);
246        let (x2, y2) = matrix.apply_to_points(self.x2, self.y2);
247        let (x3, y3) = matrix.apply_to_points(self.x3, self.y3);
248        let (x4, y4) = matrix.apply_to_points(self.x4, self.y4);
249
250        PdfQuadPoints::new(x1, y1, x2, y2, x3, y3, x4, y4)
251    }
252
253    /// Returns the smallest [PdfRect] that can completely enclose the quadrilateral
254    /// outlined by this [PdfQuadPoints].
255    pub fn to_rect(&self) -> PdfRect {
256        let xs = [self.x1, self.x2, self.x3, self.x4];
257        let ys = [self.y1, self.y2, self.y3, self.y4];
258
259        PdfRect::new(
260            *ys.iter().min().unwrap(),
261            *xs.iter().min().unwrap(),
262            *ys.iter().max().unwrap(),
263            *xs.iter().max().unwrap(),
264        )
265    }
266
267    #[inline]
268    pub(crate) fn as_pdfium(&self) -> FS_QUADPOINTSF {
269        FS_QUADPOINTSF {
270            x1: self.x1.value,
271            y1: self.y1.value,
272            x2: self.x2.value,
273            y2: self.y2.value,
274            x3: self.x3.value,
275            y3: self.y3.value,
276            x4: self.x4.value,
277            y4: self.y4.value,
278        }
279    }
280}
281
282// We could derive PartialEq automatically, but it's good practice to implement PartialEq
283// by hand when implementing Hash.
284
285impl PartialEq for PdfQuadPoints {
286    fn eq(&self, other: &Self) -> bool {
287        self.x1 == other.x1
288            && self.y1 == other.y1
289            && self.x2 == other.x2
290            && self.y2 == other.y2
291            && self.x3 == other.x3
292            && self.y3 == other.y3
293            && self.x4 == other.x4
294            && self.y4 == other.y4
295    }
296}
297
298// The f32 values inside PdfQuadPoints will never be NaN or Infinity, so these implementations
299// of Eq and Hash are safe.
300
301impl Eq for PdfQuadPoints {}
302
303impl Hash for PdfQuadPoints {
304    fn hash<H: Hasher>(&self, state: &mut H) {
305        state.write_u32(self.x1.value.to_bits());
306        state.write_u32(self.y1.value.to_bits());
307        state.write_u32(self.x2.value.to_bits());
308        state.write_u32(self.y2.value.to_bits());
309        state.write_u32(self.x3.value.to_bits());
310        state.write_u32(self.y3.value.to_bits());
311        state.write_u32(self.x4.value.to_bits());
312        state.write_u32(self.y4.value.to_bits());
313    }
314}
315
316impl Display for PdfQuadPoints {
317    #[inline]
318    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
319        f.write_fmt(format_args!(
320            "PdfQuadPoints(x1: {}, y1: {}, x2: {}, y2: {}, x3: {}, y3: {}, x4: {}, y4: {})",
321            self.x1.value,
322            self.y1.value,
323            self.x2.value,
324            self.y2.value,
325            self.x3.value,
326            self.y3.value,
327            self.x4.value,
328            self.y4.value
329        ))
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use crate::prelude::*;
336
337    #[test]
338    fn test_quadpoints_extents() {
339        let r = PdfRect::new_from_values(50.0, 100.0, 300.0, 200.0);
340
341        assert_eq!(r.to_quad_points().left().value, 100.0);
342        assert_eq!(r.to_quad_points().right().value, 200.0);
343        assert_eq!(r.to_quad_points().top().value, 300.0);
344        assert_eq!(r.to_quad_points().bottom().value, 50.0);
345
346        assert_eq!(r.to_quad_points().width().value, 100.0);
347        assert_eq!(r.to_quad_points().height().value, 250.0);
348    }
349
350    #[test]
351    fn test_quadpoints_to_rect() {
352        let r = PdfRect::new_from_values(100.0, 100.0, 200.0, 200.0);
353        assert_eq!(r.to_quad_points().to_rect(), r);
354
355        let m = PdfMatrix::identity()
356            .rotate_clockwise_degrees(45.0)
357            .unwrap();
358        let r45 = r.transform(m);
359
360        let q = r.to_quad_points();
361        let q45 = q.transform(m);
362
363        assert_eq!(q.to_rect(), r);
364        assert_eq!(q45.to_rect(), r45);
365
366        // It would be incredibly elegant to test
367
368        // assert_eq!(q45.transform(m.invert()).to_rect(), r);
369
370        // but sadly floating point rounding errors means the double-transformed values
371        // are ever-so-slightly off (by a fraction of a PdfPoint). Let's test manually
372        // so we can apply a comparison threshold.
373
374        let s = q45.transform(m.invert()).to_rect();
375        let threshold = PdfPoints::new(0.001);
376        assert!((s.top() - r.top()).abs() < threshold);
377        assert!((s.bottom() - r.bottom()).abs() < threshold);
378        assert!((s.left() - r.left()).abs() < threshold);
379        assert!((s.right() - r.right()).abs() < threshold);
380    }
381}