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