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