pdfium_render/pdf/path/
segment.rs

1//! Defines the [PdfPathSegment] struct, exposing functionality related to a single
2//! path segment in a `PdfPathSegments` collection.
3
4use crate::bindgen::{
5    FPDF_PATHSEGMENT, FPDF_SEGMENT_BEZIERTO, FPDF_SEGMENT_LINETO, FPDF_SEGMENT_MOVETO,
6    FPDF_SEGMENT_UNKNOWN,
7};
8use crate::bindings::PdfiumLibraryBindings;
9use crate::error::PdfiumError;
10use crate::pdf::matrix::PdfMatrix;
11use crate::pdf::points::PdfPoints;
12use std::os::raw::c_float;
13
14/// The type of a single [PdfPathSegment].
15#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
16pub enum PdfPathSegmentType {
17    Unknown = FPDF_SEGMENT_UNKNOWN as isize,
18    LineTo = FPDF_SEGMENT_LINETO as isize,
19    BezierTo = FPDF_SEGMENT_BEZIERTO as isize,
20    MoveTo = FPDF_SEGMENT_MOVETO as isize,
21}
22
23impl PdfPathSegmentType {
24    #[inline]
25    pub(crate) fn from_pdfium(segment_type: i32) -> Result<PdfPathSegmentType, PdfiumError> {
26        if segment_type == FPDF_SEGMENT_UNKNOWN {
27            return Ok(PdfPathSegmentType::Unknown);
28        }
29
30        match segment_type as u32 {
31            FPDF_SEGMENT_LINETO => Ok(PdfPathSegmentType::LineTo),
32            FPDF_SEGMENT_BEZIERTO => Ok(PdfPathSegmentType::BezierTo),
33            FPDF_SEGMENT_MOVETO => Ok(PdfPathSegmentType::MoveTo),
34            _ => Err(PdfiumError::UnknownPathSegmentType),
35        }
36    }
37}
38
39/// A single [PdfPathSegment] in a `PdfPathSegments` collection.
40pub struct PdfPathSegment<'a> {
41    handle: FPDF_PATHSEGMENT,
42    matrix: Option<PdfMatrix>,
43    bindings: &'a dyn PdfiumLibraryBindings,
44}
45
46impl<'a> PdfPathSegment<'a> {
47    #[inline]
48    pub(crate) fn from_pdfium(
49        handle: FPDF_PATHSEGMENT,
50        matrix: Option<PdfMatrix>,
51        bindings: &'a dyn PdfiumLibraryBindings,
52    ) -> Self {
53        Self {
54            handle,
55            matrix,
56            bindings,
57        }
58    }
59
60    /// Returns the internal `FPDF_PATHSEGMENT` handle for this [PdfPathSegment].
61    #[inline]
62    pub(crate) fn handle(&self) -> FPDF_PATHSEGMENT {
63        self.handle
64    }
65
66    /// Returns the [PdfiumLibraryBindings] used by this [PdfPathSegment].
67    #[inline]
68    pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
69        self.bindings
70    }
71
72    /// Returns the [PdfPathSegmentType] of this [PdfPathSegment].
73    #[inline]
74    pub fn segment_type(&self) -> PdfPathSegmentType {
75        PdfPathSegmentType::from_pdfium(self.bindings().FPDFPathSegment_GetType(self.handle))
76            .unwrap_or(PdfPathSegmentType::Unknown)
77    }
78
79    /// Returns `true` if this [PdfPathSegment] closes the current sub-path.
80    #[inline]
81    pub fn is_close(&self) -> bool {
82        self.bindings()
83            .is_true(self.bindings().FPDFPathSegment_GetClose(self.handle()))
84    }
85
86    /// Returns the horizontal and vertical destination positions of this [PdfPathSegment].
87    pub fn point(&self) -> (PdfPoints, PdfPoints) {
88        let mut x: c_float = 0.0;
89
90        let mut y: c_float = 0.0;
91
92        if self
93            .bindings()
94            .is_true(
95                self.bindings()
96                    .FPDFPathSegment_GetPoint(self.handle(), &mut x, &mut y),
97            )
98        {
99            let x = PdfPoints::new(x as f32);
100
101            let y = PdfPoints::new(y as f32);
102
103            match self.matrix.as_ref() {
104                None => (x, y),
105                Some(matrix) => matrix.apply_to_points(x, y),
106            }
107        } else {
108            (PdfPoints::ZERO, PdfPoints::ZERO)
109        }
110    }
111
112    /// Returns the horizontal destination position of this [PdfPathSegment].
113    #[inline]
114    pub fn x(&self) -> PdfPoints {
115        self.point().0
116    }
117
118    /// Returns the vertical destination position of this [PdfPathSegment].
119    #[inline]
120    pub fn y(&self) -> PdfPoints {
121        self.point().1
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use crate::prelude::*;
128    use crate::utils::test::test_bind_to_pdfium;
129
130    #[test]
131    fn test_point_transform() {
132        let pdfium = test_bind_to_pdfium();
133
134        let mut document = pdfium.create_new_pdf().unwrap();
135
136        let mut page = document
137            .pages_mut()
138            .create_page_at_start(PdfPagePaperSize::a4())
139            .unwrap();
140
141        let object = page
142            .objects_mut()
143            .create_path_object_line(
144                PdfPoints::new(100.0),
145                PdfPoints::new(200.0),
146                PdfPoints::new(300.0),
147                PdfPoints::new(400.0),
148                PdfColor::BEIGE,
149                PdfPoints::new(1.0),
150            )
151            .unwrap();
152
153        let delta_x = PdfPoints::new(50.0);
154        let delta_y = PdfPoints::new(-25.0);
155
156        let matrix = PdfMatrix::identity().translate(delta_x, delta_y).unwrap();
157
158        let raw_segment_0 = object.as_path_object().unwrap().segments().get(0).unwrap();
159        let raw_segment_1 = object.as_path_object().unwrap().segments().get(1).unwrap();
160
161        let transformed_segment_0 = object
162            .as_path_object()
163            .unwrap()
164            .segments()
165            .transform(matrix)
166            .get(0)
167            .unwrap();
168
169        let transformed_segment_1 = object
170            .as_path_object()
171            .unwrap()
172            .segments()
173            .transform(matrix)
174            .get(1)
175            .unwrap();
176
177        assert_eq!(transformed_segment_0.x(), raw_segment_0.x() + delta_x);
178        assert_eq!(transformed_segment_0.y(), raw_segment_0.y() + delta_y);
179        assert_eq!(transformed_segment_1.x(), raw_segment_1.x() + delta_x);
180        assert_eq!(transformed_segment_1.y(), raw_segment_1.y() + delta_y);
181    }
182
183    #[test]
184    fn test_point_transform_during_iteration() {
185        let pdfium = test_bind_to_pdfium();
186
187        let mut document = pdfium.create_new_pdf().unwrap();
188
189        let mut page = document
190            .pages_mut()
191            .create_page_at_start(PdfPagePaperSize::a4())
192            .unwrap();
193
194        let object = page
195            .objects_mut()
196            .create_path_object_line(
197                PdfPoints::new(100.0),
198                PdfPoints::new(200.0),
199                PdfPoints::new(300.0),
200                PdfPoints::new(400.0),
201                PdfColor::BEIGE,
202                PdfPoints::new(1.0),
203            )
204            .unwrap();
205
206        let raw_points: Vec<(PdfPoints, PdfPoints)> = object
207            .as_path_object()
208            .unwrap()
209            .segments()
210            .iter()
211            .map(|segment| segment.point())
212            .collect();
213
214        let delta_x = PdfPoints::new(50.0);
215        let delta_y = PdfPoints::new(-25.0);
216
217        let matrix = PdfMatrix::identity().translate(delta_x, delta_y).unwrap();
218
219        let transformed_points: Vec<(PdfPoints, PdfPoints)> = object
220            .as_path_object()
221            .unwrap()
222            .segments()
223            .transform(matrix)
224            .iter()
225            .map(|segment| segment.point())
226            .collect();
227
228        for (raw, transformed) in raw_points.iter().zip(transformed_points) {
229            assert_eq!(transformed.0, raw.0 + delta_x);
230            assert_eq!(transformed.1, raw.1 + delta_y);
231        }
232    }
233}