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