Skip to main content

pdfium_render/pdf/document/page/object/
path.rs

1//! Defines the [PdfPagePathObject] struct, exposing functionality related to a single
2//! page object defining a path.
3
4use crate::bindgen::{
5    FPDF_BOOL, FPDF_FILLMODE_ALTERNATE, FPDF_FILLMODE_NONE, FPDF_FILLMODE_WINDING, FPDF_PAGEOBJECT,
6};
7use crate::bindings::PdfiumLibraryBindings;
8use crate::error::{PdfiumError, PdfiumInternalError};
9use crate::pdf::color::PdfColor;
10use crate::pdf::document::page::object::private::internal::PdfPageObjectPrivate;
11use crate::pdf::document::page::object::{PdfPageObjectCommon, PdfPageObjectOwnership};
12use crate::pdf::document::PdfDocument;
13use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
14use crate::pdf::path::segment::PdfPathSegment;
15use crate::pdf::path::segments::{PdfPathSegmentIndex, PdfPathSegments, PdfPathSegmentsIterator};
16use crate::pdf::points::PdfPoints;
17use crate::pdf::rect::PdfRect;
18use crate::pdfium::PdfiumLibraryBindingsAccessor;
19use crate::{create_transform_getters, create_transform_setters};
20use std::convert::TryInto;
21use std::marker::PhantomData;
22use std::os::raw::{c_int, c_uint};
23
24#[cfg(doc)]
25use {
26    crate::pdf::document::page::object::PdfPageObject,
27    crate::pdf::document::page::object::PdfPageObjectType,
28    crate::pdf::document::page::objects::common::PdfPageObjectsCommon,
29    crate::pdf::document::page::PdfPage,
30};
31
32/// Sets the method used to determine the path region to fill.
33///
34/// The default fill mode used by `pdfium-render` when creating new [PdfPagePathObject]
35/// instances is [PdfPathFillMode::Winding]. The fill mode can be changed on an
36/// object-by-object basis by calling the [PdfPagePathObject::set_fill_and_stroke_mode()] function.
37#[derive(Copy, Clone, Debug, PartialEq)]
38pub enum PdfPathFillMode {
39    /// The path will not be filled.
40    None = FPDF_FILLMODE_NONE as isize,
41
42    /// The even-odd rule will be used to determine the path region to fill.
43    ///
44    /// The even-odd rule determines whether a point is inside a path by drawing a ray from that
45    /// point in any direction and simply counting the number of path segments that cross the
46    /// ray, regardless of direction. If this number is odd, the point is inside; if even, the
47    /// point is outside. This yields the same results as the nonzero winding number rule
48    /// for paths with simple shapes, but produces different results for more complex shapes.
49    ///
50    /// More information, including visual examples, can be found in Section 4.4.2 of
51    /// the PDF Reference Manual, version 1.7, on page 233.
52    EvenOdd = FPDF_FILLMODE_ALTERNATE as isize,
53
54    /// The non-zero winding number rule will be used to determine the path region to fill.
55    ///
56    /// The nonzero winding number rule determines whether a given point is inside a
57    /// path by conceptually drawing a ray from that point to infinity in any direction
58    /// and then examining the places where a segment of the path crosses the ray. Start-
59    /// ing with a count of 0, the rule adds 1 each time a path segment crosses the ray
60    /// from left to right and subtracts 1 each time a segment crosses from right to left.
61    /// After counting all the crossings, if the result is 0, the point is outside the path;
62    /// otherwise, it is inside.
63    ///
64    /// This is the default fill mode used by `pdfium-render` when creating new [PdfPagePathObject]
65    /// instances. The fill mode can be changed on an object-by-object basis by calling the
66    /// [PdfPagePathObject::set_fill_and_stroke_mode()] function.
67    ///
68    /// More information, including visual examples, can be found in Section 4.4.2 of
69    /// the PDF Reference Manual, version 1.7, on page 232.
70    Winding = FPDF_FILLMODE_WINDING as isize,
71}
72
73impl PdfPathFillMode {
74    #[inline]
75    pub(crate) fn from_pdfium(value: c_int) -> Result<PdfPathFillMode, PdfiumError> {
76        match value as u32 {
77            FPDF_FILLMODE_NONE => Ok(PdfPathFillMode::None),
78            FPDF_FILLMODE_ALTERNATE => Ok(PdfPathFillMode::EvenOdd),
79            FPDF_FILLMODE_WINDING => Ok(PdfPathFillMode::Winding),
80            _ => Err(PdfiumError::UnknownPdfPagePathFillMode),
81        }
82    }
83
84    #[inline]
85    #[allow(dead_code)]
86    // The as_pdfium() function is not currently used, but we expect it to be in future
87    pub(crate) fn as_pdfium(&self) -> c_uint {
88        match self {
89            PdfPathFillMode::None => FPDF_FILLMODE_NONE,
90            PdfPathFillMode::EvenOdd => FPDF_FILLMODE_ALTERNATE,
91            PdfPathFillMode::Winding => FPDF_FILLMODE_WINDING,
92        }
93    }
94}
95
96impl Default for PdfPathFillMode {
97    /// Returns the default fill mode used when creating new [PdfPagePathObject]
98    /// instances. The fill mode can be changed on an object-by-object basis by calling the
99    /// [PdfPagePathObject::set_fill_and_stroke_mode()] function.
100    #[inline]
101    fn default() -> Self {
102        PdfPathFillMode::Winding
103    }
104}
105
106/// A single [PdfPageObject] of type [PdfPageObjectType::Path]. The page object defines a path.
107///
108/// Paths define shapes, trajectories, and regions of all sorts. They are used to draw
109/// lines, define the shapes of filled areas, and specify boundaries for clipping other
110/// graphics. A path is composed of one or more _path segments_, each specifying
111/// a straight or curved line segment. Each segment may connect to one another, forming a
112/// _closed sub-path_, or may be disconnected from one another, forming one or more
113/// _open sub-paths_. A path therefore is made up of one or more disconnected sub-paths, each
114/// comprising a sequence of connected segments. Closed sub-paths can be filled;
115/// both closed and open sub-paths can be stroked. The topology of the path is unrestricted;
116/// it may be concave or convex, may contain multiple sub-paths representing disjoint areas,
117/// and may intersect itself in arbitrary ways.
118///
119/// Page objects can be created either attached to a `PdfPage` (in which case the page object's
120/// memory is owned by the containing page) or detached from any page (in which case the page
121/// object's memory is owned by the object). Page objects are not rendered until they are
122/// attached to a page; page objects that are never attached to a page will be lost when they
123/// fall out of scope.
124///
125/// The simplest way to create a path object that is immediately attached to a page is to call
126/// one of the `PdfPageObjects::create_path_object_*()` functions to create lines, cubic Bézier curves,
127/// rectangles, circles, and ellipses. Alternatively you can create a detached path object using
128/// one of the following functions, but you must add the object to a containing `PdfPageObjects`
129/// collection manually.
130///
131/// * [PdfPagePathObject::new()]: creates an empty detached path object. Segments can be added to the
132///   path by sequentially calling one or more of the [PdfPagePathObject::move_to()],
133///   [PdfPagePathObject::line_to()], or [PdfPagePathObject::bezier_to()] functions.
134///   A closed sub-path can be created by calling the [PdfPagePathObject::close_path()]
135///   function. Convenience functions for adding rectangles, circles, and ellipses are also
136///   available with the [PdfPagePathObject::rect_to()], [PdfPagePathObject::circle_to()],
137///   and [PdfPagePathObject::ellipse_to()] functions, which create the desired shapes by
138///   constructing closed sub-paths from other path segments.
139/// * [PdfPagePathObject::new_line()]: creates a detached path object initialized with a single straight line.
140/// * [PdfPagePathObject::new_bezier()]: creates a detached path object initialized with a single cubic Bézier curve.
141/// * [PdfPagePathObject::new_rect()]: creates a detached path object initialized with a rectangular path.
142/// * [PdfPagePathObject::new_circle()]: creates a detached path object initialized with a circular path,
143///   filling the given rectangle.
144/// * [PdfPagePathObject::new_circle_at()]: creates a detached path object initialized with a circular path,
145///   centered at a particular origin point with a given radius.
146/// * [PdfPagePathObject::new_ellipse()]: creates a detached path object initialized with an elliptical path,
147///   filling the given rectangle.
148/// * [PdfPagePathObject::new_ellipse_at()]: creates a detached path object initialized with an elliptical path,
149///   centered at a particular origin point with given horizontal and vertical radii.
150///
151/// The detached path object can later be attached to a page by calling the
152/// [PdfPageObjectsCommon::add_path_object()] function.
153pub struct PdfPagePathObject<'a> {
154    object_handle: FPDF_PAGEOBJECT,
155    ownership: PdfPageObjectOwnership,
156    current_point_x: PdfPoints,
157    current_point_y: PdfPoints,
158    lifetime: PhantomData<&'a FPDF_PAGEOBJECT>,
159}
160
161impl<'a> PdfPagePathObject<'a> {
162    #[inline]
163    pub(crate) fn from_pdfium(
164        object_handle: FPDF_PAGEOBJECT,
165        ownership: PdfPageObjectOwnership,
166    ) -> Self {
167        PdfPagePathObject {
168            object_handle,
169            ownership,
170            current_point_x: PdfPoints::ZERO,
171            current_point_y: PdfPoints::ZERO,
172            lifetime: PhantomData,
173        }
174    }
175
176    /// Creates a new [PdfPagePathObject] from the given arguments. The returned page object
177    /// will not be rendered until it is added to a [PdfPage] using the
178    /// [PdfPageObjectsCommon::add_path_object()] function.
179    ///
180    /// The new path will be created with the given initial position and with the given fill and stroke
181    /// settings applied. Both the stroke color and the stroke width must be provided for the
182    /// path to be stroked.
183    ///
184    /// Other than setting the initial position, this path will be empty. Add additional segments
185    /// to this path by calling one or more of the [PdfPagePathObject::move_to()],
186    /// [PdfPagePathObject::line_to()], or [PdfPagePathObject::bezier_to()]
187    /// functions. A closed sub-path can be created by calling the [PdfPagePathObject::close_path()]
188    /// function. Convenience functions for adding rectangles, circles, and ellipses are also
189    /// available with the [PdfPagePathObject::rect_to()], [PdfPagePathObject::circle_to()],
190    /// and [PdfPagePathObject::ellipse_to()] functions, which create the desired shapes by
191    /// constructing closed sub-paths from other path segments.
192    #[inline]
193    pub fn new(
194        document: &PdfDocument<'a>,
195        x: PdfPoints,
196        y: PdfPoints,
197        stroke_color: Option<PdfColor>,
198        stroke_width: Option<PdfPoints>,
199        fill_color: Option<PdfColor>,
200    ) -> Result<Self, PdfiumError> {
201        Self::new_from_bindings(
202            document.bindings(),
203            x,
204            y,
205            stroke_color,
206            stroke_width,
207            fill_color,
208        )
209    }
210
211    pub(crate) fn new_from_bindings(
212        bindings: &'a dyn PdfiumLibraryBindings,
213        x: PdfPoints,
214        y: PdfPoints,
215        stroke_color: Option<PdfColor>,
216        stroke_width: Option<PdfPoints>,
217        fill_color: Option<PdfColor>,
218    ) -> Result<Self, PdfiumError> {
219        let handle = unsafe { bindings.FPDFPageObj_CreateNewPath(x.value, y.value) };
220
221        if handle.is_null() {
222            Err(PdfiumError::PdfiumLibraryInternalError(
223                PdfiumInternalError::Unknown,
224            ))
225        } else {
226            let mut result = PdfPagePathObject {
227                object_handle: handle,
228                ownership: PdfPageObjectOwnership::unowned(),
229                current_point_x: x,
230                current_point_y: y,
231                lifetime: PhantomData,
232            };
233
234            result.move_to(x, y)?;
235
236            let do_stroke = if let Some(stroke_color) = stroke_color {
237                if let Some(stroke_width) = stroke_width {
238                    result.set_stroke_color(stroke_color)?;
239                    result.set_stroke_width(stroke_width)?;
240
241                    true
242                } else {
243                    false
244                }
245            } else {
246                false
247            };
248
249            let fill_mode = if let Some(fill_color) = fill_color {
250                result.set_fill_color(fill_color)?;
251
252                PdfPathFillMode::default()
253            } else {
254                PdfPathFillMode::None
255            };
256
257            result.set_fill_and_stroke_mode(fill_mode, do_stroke)?;
258
259            Ok(result)
260        }
261    }
262
263    #[inline]
264    pub(crate) fn new_line_from_bindings(
265        bindings: &'a dyn PdfiumLibraryBindings,
266        x1: PdfPoints,
267        y1: PdfPoints,
268        x2: PdfPoints,
269        y2: PdfPoints,
270        stroke_color: PdfColor,
271        stroke_width: PdfPoints,
272    ) -> Result<Self, PdfiumError> {
273        let mut result = Self::new_from_bindings(
274            bindings,
275            x1,
276            y1,
277            Some(stroke_color),
278            Some(stroke_width),
279            None,
280        )?;
281
282        result.line_to(x2, y2)?;
283
284        Ok(result)
285    }
286
287    /// Creates a new [PdfPagePathObject] from the given arguments. The returned page object
288    /// will not be rendered until it is added to a [PdfPage] using the
289    /// [PdfPageObjectsCommon::add_path_object()] function.
290    ///
291    /// The new path will be created with a line with the given start and end coordinates,
292    /// and with the given stroke settings applied.
293    #[inline]
294    pub fn new_line(
295        document: &PdfDocument<'a>,
296        x1: PdfPoints,
297        y1: PdfPoints,
298        x2: PdfPoints,
299        y2: PdfPoints,
300        stroke_color: PdfColor,
301        stroke_width: PdfPoints,
302    ) -> Result<Self, PdfiumError> {
303        Self::new_line_from_bindings(
304            document.bindings(),
305            x1,
306            y1,
307            x2,
308            y2,
309            stroke_color,
310            stroke_width,
311        )
312    }
313
314    #[allow(clippy::too_many_arguments)]
315    #[inline]
316    pub(crate) fn new_bezier_from_bindings(
317        bindings: &'a dyn PdfiumLibraryBindings,
318        x1: PdfPoints,
319        y1: PdfPoints,
320        x2: PdfPoints,
321        y2: PdfPoints,
322        control1_x: PdfPoints,
323        control1_y: PdfPoints,
324        control2_x: PdfPoints,
325        control2_y: PdfPoints,
326        stroke_color: PdfColor,
327        stroke_width: PdfPoints,
328    ) -> Result<Self, PdfiumError> {
329        let mut result = Self::new_from_bindings(
330            bindings,
331            x1,
332            y1,
333            Some(stroke_color),
334            Some(stroke_width),
335            None,
336        )?;
337
338        result.bezier_to(x2, y2, control1_x, control1_y, control2_x, control2_y)?;
339
340        Ok(result)
341    }
342
343    /// Creates a new [PdfPagePathObject] from the given arguments. The returned page object
344    /// will not be rendered until it is added to a [PdfPage] using the
345    /// [PdfPageObjectsCommon::add_path_object()] function.
346    ///
347    /// The new path will be created with a cubic Bézier curve with the given start, end,
348    /// and control point coordinates, and with the given stroke settings applied.
349    #[allow(clippy::too_many_arguments)]
350    #[inline]
351    pub fn new_bezier(
352        document: &PdfDocument<'a>,
353        x1: PdfPoints,
354        y1: PdfPoints,
355        x2: PdfPoints,
356        y2: PdfPoints,
357        control1_x: PdfPoints,
358        control1_y: PdfPoints,
359        control2_x: PdfPoints,
360        control2_y: PdfPoints,
361        stroke_color: PdfColor,
362        stroke_width: PdfPoints,
363    ) -> Result<Self, PdfiumError> {
364        Self::new_bezier_from_bindings(
365            document.bindings(),
366            x1,
367            y1,
368            x2,
369            y2,
370            control1_x,
371            control1_y,
372            control2_x,
373            control2_y,
374            stroke_color,
375            stroke_width,
376        )
377    }
378
379    #[inline]
380    pub(crate) fn new_rect_from_bindings(
381        bindings: &'a dyn PdfiumLibraryBindings,
382        rect: PdfRect,
383        stroke_color: Option<PdfColor>,
384        stroke_width: Option<PdfPoints>,
385        fill_color: Option<PdfColor>,
386    ) -> Result<Self, PdfiumError> {
387        let mut result = Self::new_from_bindings(
388            bindings,
389            rect.left(),
390            rect.bottom(),
391            stroke_color,
392            stroke_width,
393            fill_color,
394        )?;
395
396        result.rect_to(rect.right(), rect.top())?;
397
398        Ok(result)
399    }
400
401    /// Creates a new [PdfPagePathObject] from the given arguments. The returned page object
402    /// will not be rendered until it is added to a [PdfPage] using the
403    /// [PdfPageObjectsCommon::add_path_object()] function.
404    ///
405    /// The new path will be created with a path for the given rectangle, with the given
406    /// fill and stroke settings applied. Both the stroke color and the stroke width must be
407    /// provided for the rectangle to be stroked.
408    #[inline]
409    pub fn new_rect(
410        document: &PdfDocument<'a>,
411        rect: PdfRect,
412        stroke_color: Option<PdfColor>,
413        stroke_width: Option<PdfPoints>,
414        fill_color: Option<PdfColor>,
415    ) -> Result<Self, PdfiumError> {
416        Self::new_rect_from_bindings(
417            document.bindings(),
418            rect,
419            stroke_color,
420            stroke_width,
421            fill_color,
422        )
423    }
424
425    #[inline]
426    pub(crate) fn new_circle_from_bindings(
427        bindings: &'a dyn PdfiumLibraryBindings,
428        rect: PdfRect,
429        stroke_color: Option<PdfColor>,
430        stroke_width: Option<PdfPoints>,
431        fill_color: Option<PdfColor>,
432    ) -> Result<Self, PdfiumError> {
433        let mut result = Self::new_from_bindings(
434            bindings,
435            rect.left(),
436            rect.bottom(),
437            stroke_color,
438            stroke_width,
439            fill_color,
440        )?;
441
442        result.circle_to(rect.right(), rect.top())?;
443
444        Ok(result)
445    }
446
447    /// Creates a new [PdfPagePathObject] from the given arguments. The returned page object
448    /// will not be rendered until it is added to a [PdfPage] using the
449    /// [PdfPageObjectsCommon::add_path_object()] function.
450    ///
451    /// The new path will be created with a circle that fills the given rectangle, with the given
452    /// fill and stroke settings applied. Both the stroke color and the stroke width must be
453    /// provided for the circle to be stroked.
454    #[inline]
455    pub fn new_circle(
456        document: &PdfDocument<'a>,
457        rect: PdfRect,
458        stroke_color: Option<PdfColor>,
459        stroke_width: Option<PdfPoints>,
460        fill_color: Option<PdfColor>,
461    ) -> Result<Self, PdfiumError> {
462        Self::new_circle_from_bindings(
463            document.bindings(),
464            rect,
465            stroke_color,
466            stroke_width,
467            fill_color,
468        )
469    }
470
471    #[inline]
472    pub(crate) fn new_circle_at_from_bindings(
473        bindings: &'a dyn PdfiumLibraryBindings,
474        center_x: PdfPoints,
475        center_y: PdfPoints,
476        radius: PdfPoints,
477        stroke_color: Option<PdfColor>,
478        stroke_width: Option<PdfPoints>,
479        fill_color: Option<PdfColor>,
480    ) -> Result<Self, PdfiumError> {
481        Self::new_circle_from_bindings(
482            bindings,
483            PdfRect::new(
484                center_y - radius,
485                center_x - radius,
486                center_y + radius,
487                center_x + radius,
488            ),
489            stroke_color,
490            stroke_width,
491            fill_color,
492        )
493    }
494
495    /// Creates a new [PdfPagePathObject] from the given arguments. The returned page object
496    /// will not be rendered until it is added to a [PdfPage] using the
497    /// [PdfPageObjectsCommon::add_path_object()] function.
498    ///
499    /// The new path will be created with a circle centered at the given coordinates, with the
500    /// given radius, and with the given fill and stroke settings applied. Both the stroke color
501    /// and the stroke width must be provided for the circle to be stroked.
502    #[inline]
503    pub fn new_circle_at(
504        document: &PdfDocument<'a>,
505        center_x: PdfPoints,
506        center_y: PdfPoints,
507        radius: PdfPoints,
508        stroke_color: Option<PdfColor>,
509        stroke_width: Option<PdfPoints>,
510        fill_color: Option<PdfColor>,
511    ) -> Result<Self, PdfiumError> {
512        Self::new_circle_at_from_bindings(
513            document.bindings(),
514            center_x,
515            center_y,
516            radius,
517            stroke_color,
518            stroke_width,
519            fill_color,
520        )
521    }
522
523    #[inline]
524    pub(crate) fn new_ellipse_from_bindings(
525        bindings: &'a dyn PdfiumLibraryBindings,
526        rect: PdfRect,
527        stroke_color: Option<PdfColor>,
528        stroke_width: Option<PdfPoints>,
529        fill_color: Option<PdfColor>,
530    ) -> Result<Self, PdfiumError> {
531        let mut result = Self::new_from_bindings(
532            bindings,
533            rect.left(),
534            rect.bottom(),
535            stroke_color,
536            stroke_width,
537            fill_color,
538        )?;
539
540        result.ellipse_to(rect.right(), rect.top())?;
541
542        Ok(result)
543    }
544
545    /// Creates a new [PdfPagePathObject] from the given arguments. The returned page object
546    /// will not be rendered until it is added to a [PdfPage] using the
547    /// [PdfPageObjectsCommon::add_path_object()] function.
548    ///
549    /// The new path will be created with an ellipse that fills the given rectangle, with the given
550    /// fill and stroke settings applied. Both the stroke color and the stroke width must be
551    /// provided for the ellipse to be stroked.
552    #[inline]
553    pub fn new_ellipse(
554        document: &PdfDocument<'a>,
555        rect: PdfRect,
556        stroke_color: Option<PdfColor>,
557        stroke_width: Option<PdfPoints>,
558        fill_color: Option<PdfColor>,
559    ) -> Result<Self, PdfiumError> {
560        Self::new_ellipse_from_bindings(
561            document.bindings(),
562            rect,
563            stroke_color,
564            stroke_width,
565            fill_color,
566        )
567    }
568
569    #[allow(clippy::too_many_arguments)]
570    #[inline]
571    pub(crate) fn new_ellipse_at_from_bindings(
572        bindings: &'a dyn PdfiumLibraryBindings,
573        center_x: PdfPoints,
574        center_y: PdfPoints,
575        x_radius: PdfPoints,
576        y_radius: PdfPoints,
577        stroke_color: Option<PdfColor>,
578        stroke_width: Option<PdfPoints>,
579        fill_color: Option<PdfColor>,
580    ) -> Result<Self, PdfiumError> {
581        Self::new_ellipse_from_bindings(
582            bindings,
583            PdfRect::new(
584                center_y - y_radius,
585                center_x - x_radius,
586                center_y + y_radius,
587                center_x + x_radius,
588            ),
589            stroke_color,
590            stroke_width,
591            fill_color,
592        )
593    }
594
595    /// Creates a new [PdfPagePathObject] from the given arguments. The returned page object
596    /// will not be rendered until it is added to a [PdfPage] using the
597    /// [PdfPageObjectsCommon::add_path_object()] function.
598    ///
599    /// The new path will be created with an ellipse centered at the given coordinates, with the
600    /// given horizontal and vertical radii, and with the given fill and stroke settings applied.
601    /// Both the stroke color and the stroke width must be provided for the ellipse to be stroked.
602    #[allow(clippy::too_many_arguments)]
603    #[inline]
604    pub fn new_ellipse_at(
605        document: &PdfDocument<'a>,
606        center_x: PdfPoints,
607        center_y: PdfPoints,
608        x_radius: PdfPoints,
609        y_radius: PdfPoints,
610        stroke_color: Option<PdfColor>,
611        stroke_width: Option<PdfPoints>,
612        fill_color: Option<PdfColor>,
613    ) -> Result<Self, PdfiumError> {
614        Self::new_ellipse_at_from_bindings(
615            document.bindings(),
616            center_x,
617            center_y,
618            x_radius,
619            y_radius,
620            stroke_color,
621            stroke_width,
622            fill_color,
623        )
624    }
625
626    /// Begins a new sub-path in this [PdfPagePathObject] by moving the current point to the
627    /// given coordinates, omitting any connecting line segment.
628    pub fn move_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
629        if self.bindings().is_true(unsafe {
630            self.bindings()
631                .FPDFPath_MoveTo(self.object_handle(), x.value, y.value)
632        }) {
633            self.current_point_x = x;
634            self.current_point_y = y;
635
636            Ok(())
637        } else {
638            Err(PdfiumError::PdfiumLibraryInternalError(
639                PdfiumInternalError::Unknown,
640            ))
641        }
642    }
643
644    /// Appends a straight line segment to this [PdfPagePathObject] from the current point to the
645    /// given coordinates. The new current point is set to the given coordinates.
646    pub fn line_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
647        if self.bindings().is_true(unsafe {
648            self.bindings()
649                .FPDFPath_LineTo(self.object_handle(), x.value, y.value)
650        }) {
651            self.current_point_x = x;
652            self.current_point_y = y;
653
654            Ok(())
655        } else {
656            Err(PdfiumError::PdfiumLibraryInternalError(
657                PdfiumInternalError::Unknown,
658            ))
659        }
660    }
661
662    /// Appends a cubic Bézier curve to this [PdfPagePathObject] from the current point to the
663    /// given coordinates, using the two given Bézier control points. The new current point
664    /// is set to the given coordinates.
665    pub fn bezier_to(
666        &mut self,
667        x: PdfPoints,
668        y: PdfPoints,
669        control1_x: PdfPoints,
670        control1_y: PdfPoints,
671        control2_x: PdfPoints,
672        control2_y: PdfPoints,
673    ) -> Result<(), PdfiumError> {
674        if self.bindings().is_true(unsafe {
675            self.bindings().FPDFPath_BezierTo(
676                self.object_handle(),
677                control1_x.value,
678                control1_y.value,
679                control2_x.value,
680                control2_y.value,
681                x.value,
682                y.value,
683            )
684        }) {
685            self.current_point_x = x;
686            self.current_point_y = y;
687
688            Ok(())
689        } else {
690            Err(PdfiumError::PdfiumLibraryInternalError(
691                PdfiumInternalError::Unknown,
692            ))
693        }
694    }
695
696    /// Appends a rectangle to this [PdfPagePathObject] by drawing four line segments
697    /// from the current point, ending at the given coordinates. The current sub-path will be closed.
698    /// The new current point is set to the given coordinates.
699    pub fn rect_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
700        let orig_x = self.current_point_x;
701
702        let orig_y = self.current_point_y;
703
704        self.close_path()?;
705        self.line_to(orig_x, y)?;
706        self.line_to(x, y)?;
707        self.line_to(x, orig_y)?;
708        self.close_path()?;
709        self.move_to(x, y)
710    }
711
712    /// Appends an ellipse to this [PdfPagePathObject] by drawing four Bézier curves approximating
713    /// an ellipse filling a rectangle from the current point to the given coordinates.
714    /// The current sub-path will be closed. The new current point is set to the given coordinates.
715    pub fn ellipse_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
716        let x_radius = (x - self.current_point_x) / 2.0;
717
718        let y_radius = (y - self.current_point_y) / 2.0;
719
720        self.close_path()?;
721        self.move_to(
722            self.current_point_x + x_radius,
723            self.current_point_y + y_radius,
724        )?;
725        self.ellipse(x_radius, y_radius)?;
726        self.move_to(x, y)
727    }
728
729    /// Appends a circle to this [PdfPagePathObject] by drawing four Bézier curves approximating
730    /// a circle filling a rectangle from the current point to the given coordinates.
731    /// The current sub-path will be closed. The new current point is set to the given coordinates.
732    ///
733    /// Note that perfect circles cannot be represented exactly using Bézier curves. However,
734    /// a very close approximation, more than sufficient to please the human eye, can be achieved
735    /// using four Bézier curves, one for each quadrant of the circle.
736    pub fn circle_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
737        let radius = (x - self.current_point_x) / 2.0;
738
739        self.move_to(self.current_point_x + radius, self.current_point_y + radius)?;
740        self.ellipse(radius, radius)?;
741        self.move_to(x, y)
742    }
743
744    /// Draws an ellipse at the current point using the given horizontal and vertical radii.
745    /// The ellipse will be constructed using four Bézier curves, one for each quadrant.
746    fn ellipse(&mut self, x_radius: PdfPoints, y_radius: PdfPoints) -> Result<(), PdfiumError> {
747        // Ellipse approximation method: https://spencermortensen.com/articles/bezier-circle/
748        // Implementation based on: https://stackoverflow.com/a/2007782
749
750        const C: f32 = 0.551915;
751
752        let x_c = x_radius * C;
753        let y_c = y_radius * C;
754        let orig_x = self.current_point_x;
755        let orig_y = self.current_point_y;
756
757        self.move_to(orig_x - x_radius, orig_y)?;
758        self.bezier_to(
759            orig_x,
760            orig_y + y_radius,
761            orig_x - x_radius,
762            orig_y + y_c,
763            orig_x - x_c,
764            orig_y + y_radius,
765        )?;
766        self.bezier_to(
767            orig_x + x_radius,
768            orig_y,
769            orig_x + x_c,
770            orig_y + y_radius,
771            orig_x + x_radius,
772            orig_y + y_c,
773        )?;
774        self.bezier_to(
775            orig_x,
776            orig_y - y_radius,
777            orig_x + x_radius,
778            orig_y - y_c,
779            orig_x + x_c,
780            orig_y - y_radius,
781        )?;
782        self.bezier_to(
783            orig_x - x_radius,
784            orig_y,
785            orig_x - x_c,
786            orig_y - y_radius,
787            orig_x - x_radius,
788            orig_y - y_c,
789        )?;
790        self.close_path()
791    }
792
793    /// Closes the current sub-path in this [PdfPagePathObject] by appending a straight line segment
794    /// from the current point to the starting point of the sub-path.
795    pub fn close_path(&mut self) -> Result<(), PdfiumError> {
796        if self
797            .bindings()
798            .is_true(unsafe { self.bindings().FPDFPath_Close(self.object_handle) })
799        {
800            Ok(())
801        } else {
802            Err(PdfiumError::PdfiumLibraryInternalError(
803                PdfiumInternalError::Unknown,
804            ))
805        }
806    }
807
808    /// Returns the method used to determine which sub-paths of any path in this [PdfPagePathObject]
809    /// should be filled.
810    pub fn fill_mode(&self) -> Result<PdfPathFillMode, PdfiumError> {
811        let mut raw_fill_mode: c_int = 0;
812
813        let mut _raw_stroke: FPDF_BOOL = self.bindings().FALSE();
814
815        if self.bindings().is_true(unsafe {
816            self.bindings().FPDFPath_GetDrawMode(
817                self.object_handle(),
818                &mut raw_fill_mode,
819                &mut _raw_stroke,
820            )
821        }) {
822            PdfPathFillMode::from_pdfium(raw_fill_mode)
823        } else {
824            Err(PdfiumError::PdfiumLibraryInternalError(
825                PdfiumInternalError::Unknown,
826            ))
827        }
828    }
829
830    /// Returns `true` if this [PdfPagePathObject] will be stroked, regardless of the path's
831    /// stroke settings.
832    ///
833    /// Even if this path is set to be stroked, the stroke must be configured with a visible color
834    /// and a non-zero width in order to actually be visible.
835    pub fn is_stroked(&self) -> Result<bool, PdfiumError> {
836        let mut _raw_fill_mode: c_int = 0;
837
838        let mut raw_stroke: FPDF_BOOL = self.bindings().FALSE();
839
840        if self.bindings().is_true(unsafe {
841            self.bindings().FPDFPath_GetDrawMode(
842                self.object_handle(),
843                &mut _raw_fill_mode,
844                &mut raw_stroke,
845            )
846        }) {
847            Ok(self.bindings().is_true(raw_stroke))
848        } else {
849            Err(PdfiumError::PdfiumLibraryInternalError(
850                PdfiumInternalError::Unknown,
851            ))
852        }
853    }
854
855    /// Sets the method used to determine which sub-paths of any path in this [PdfPagePathObject]
856    /// should be filled, and whether or not any path in this [PdfPagePathObject] should be stroked.
857    ///
858    /// Even if this object's path is set to be stroked, the stroke must be configured with
859    /// a visible color and a non-zero width in order to actually be visible.
860    pub fn set_fill_and_stroke_mode(
861        &mut self,
862        fill_mode: PdfPathFillMode,
863        do_stroke: bool,
864    ) -> Result<(), PdfiumError> {
865        if self.bindings().is_true(unsafe {
866            self.bindings().FPDFPath_SetDrawMode(
867                self.object_handle(),
868                fill_mode.as_pdfium() as c_int,
869                self.bindings().bool_to_pdfium(do_stroke),
870            )
871        }) {
872            Ok(())
873        } else {
874            Err(PdfiumError::PdfiumLibraryInternalError(
875                PdfiumInternalError::Unknown,
876            ))
877        }
878    }
879
880    /// Returns the collection of path segments currently defined by this [PdfPagePathObject].
881    #[inline]
882    pub fn segments(&self) -> PdfPagePathObjectSegments<'_> {
883        PdfPagePathObjectSegments::from_pdfium(self.object_handle())
884    }
885
886    create_transform_setters!(
887        &mut Self,
888        Result<(), PdfiumError>,
889        "this [PdfPagePathObject]",
890        "this [PdfPagePathObject].",
891        "this [PdfPagePathObject],"
892    );
893
894    // The transform_impl() function required by the create_transform_setters!() macro
895    // is provided by the PdfPageObjectPrivate trait.
896
897    create_transform_getters!(
898        "this [PdfPagePathObject]",
899        "this [PdfPagePathObject].",
900        "this [PdfPagePathObject],"
901    );
902
903    // The get_matrix_impl() function required by the create_transform_getters!() macro
904    // is provided by the PdfPageObjectPrivate trait.
905}
906
907impl<'a> PdfPageObjectPrivate<'a> for PdfPagePathObject<'a> {
908    #[inline]
909    fn object_handle(&self) -> FPDF_PAGEOBJECT {
910        self.object_handle
911    }
912
913    #[inline]
914    fn ownership(&self) -> &PdfPageObjectOwnership {
915        &self.ownership
916    }
917
918    #[inline]
919    fn set_ownership(&mut self, ownership: PdfPageObjectOwnership) {
920        self.ownership = ownership;
921    }
922}
923
924impl<'a> Drop for PdfPagePathObject<'a> {
925    /// Closes this [PdfPagePathObject], releasing held memory.
926    fn drop(&mut self) {
927        self.drop_impl();
928    }
929}
930
931impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPagePathObject<'a> {}
932
933#[cfg(feature = "thread_safe")]
934unsafe impl<'a> Send for PdfPagePathObject<'a> {}
935
936#[cfg(feature = "thread_safe")]
937unsafe impl<'a> Sync for PdfPagePathObject<'a> {}
938
939/// The collection of [PdfPathSegment] objects inside a path page object.
940///
941/// The coordinates of each segment in the returned iterator will be the untransformed,
942/// raw values supplied at the time the segment was created. Use the
943/// [PdfPagePathObjectSegments::transform()] function to apply a [PdfMatrix] transformation matrix
944/// to the coordinates of each segment as it is returned.
945pub struct PdfPagePathObjectSegments<'a> {
946    handle: FPDF_PAGEOBJECT,
947    matrix: Option<PdfMatrix>,
948    lifetime: PhantomData<&'a FPDF_PAGEOBJECT>,
949}
950
951impl<'a> PdfPagePathObjectSegments<'a> {
952    #[inline]
953    pub(crate) fn from_pdfium(handle: FPDF_PAGEOBJECT) -> Self {
954        Self {
955            handle,
956            matrix: None,
957            lifetime: PhantomData,
958        }
959    }
960
961    /// Returns a new iterator over this collection of [PdfPathSegment] objects that applies
962    /// the given [PdfMatrix] to the points in each returned segment.
963    #[inline]
964    pub fn transform(&self, matrix: PdfMatrix) -> PdfPagePathObjectSegments<'a> {
965        Self {
966            handle: self.handle,
967            matrix: Some(matrix),
968            lifetime: PhantomData,
969        }
970    }
971
972    /// Returns a new iterator over this collection of [PdfPathSegment] objects that ensures
973    /// the points of each returned segment are untransformed raw values.
974    #[inline]
975    pub fn raw(&self) -> PdfPagePathObjectSegments<'a> {
976        Self {
977            handle: self.handle,
978            matrix: None,
979            lifetime: PhantomData,
980        }
981    }
982}
983
984impl<'a> PdfPathSegments<'a> for PdfPagePathObjectSegments<'a> {
985    #[inline]
986    fn len(&self) -> PdfPathSegmentIndex {
987        unsafe {
988            self.bindings()
989                .FPDFPath_CountSegments(self.handle)
990                .try_into()
991                .unwrap_or(0)
992        }
993    }
994
995    fn get(&self, index: PdfPathSegmentIndex) -> Result<PdfPathSegment<'a>, PdfiumError> {
996        let handle = unsafe {
997            self.bindings()
998                .FPDFPath_GetPathSegment(self.handle, index as c_int)
999        };
1000
1001        if handle.is_null() {
1002            Err(PdfiumError::PdfiumLibraryInternalError(
1003                PdfiumInternalError::Unknown,
1004            ))
1005        } else {
1006            Ok(PdfPathSegment::from_pdfium(handle, self.matrix))
1007        }
1008    }
1009
1010    #[inline]
1011    fn iter(&'a self) -> PdfPathSegmentsIterator<'a> {
1012        PdfPathSegmentsIterator::new(self)
1013    }
1014}
1015
1016impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPagePathObjectSegments<'a> {}
1017
1018#[cfg(feature = "thread_safe")]
1019unsafe impl<'a> Send for PdfPagePathObjectSegments<'a> {}
1020
1021#[cfg(feature = "thread_safe")]
1022unsafe impl<'a> Sync for PdfPagePathObjectSegments<'a> {}