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