Skip to main content

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