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