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