Skip to main content

pdfium_render/pdf/document/page/objects/
common.rs

1//! Defines the [PdfPageObjectsCommon] trait, providing functionality common to all
2//! containers of multiple [PdfPageObject] objects.
3
4use crate::error::{PdfiumError, PdfiumInternalError};
5use crate::pdf::color::PdfColor;
6use crate::pdf::document::fonts::ToPdfFontToken;
7use crate::pdf::document::page::PdfPageObjectOwnership;
8use crate::pdf::document::page::object::image::PdfPageImageObject;
9use crate::pdf::document::page::object::path::{BezierPoints, PathFillStroke, PathStroke, PdfPagePathObject};
10use crate::pdf::document::page::object::text::PdfPageTextObject;
11use crate::pdf::document::page::object::x_object_form::PdfPageXObjectFormObject;
12use crate::pdf::document::page::object::{PdfPageObject, PdfPageObjectCommon};
13use crate::pdf::document::page::objects::private::internal::PdfPageObjectsPrivate;
14use crate::pdf::points::PdfPoints;
15use crate::pdf::rect::PdfRect;
16use std::ops::{Range, RangeInclusive};
17
18#[cfg(feature = "image_025")]
19use image_025::DynamicImage;
20
21#[cfg(doc)]
22use {
23    crate::pdf::document::page::PdfPage, crate::pdf::document::page::PdfPageContentRegenerationStrategy,
24    crate::pdf::document::page::PdfPageObjects,
25};
26
27/// The zero-based index of a single [PdfPageObject] inside its containing [PdfPageObjects] collection.
28pub type PdfPageObjectIndex = usize;
29
30/// Functionality common to all containers of multiple [PdfPageObject] objects.
31/// Both pages and annotations can contain page objects.
32pub trait PdfPageObjectsCommon<'a> {
33    /// Returns the total number of page objects in the collection.
34    fn len(&self) -> PdfPageObjectIndex;
35
36    /// Returns true if this page objects collection is empty.
37    #[inline]
38    fn is_empty(&self) -> bool {
39        self.len() == 0
40    }
41
42    /// Returns a Range from `0..(number of objects)` for this page objects collection.
43    #[inline]
44    fn as_range(&self) -> Range<PdfPageObjectIndex> {
45        0..self.len()
46    }
47
48    /// Returns an inclusive Range from `0..=(number of objects - 1)` for this page objects collection.
49    #[inline]
50    fn as_range_inclusive(&self) -> RangeInclusive<PdfPageObjectIndex> {
51        if self.is_empty() { 0..=0 } else { 0..=(self.len() - 1) }
52    }
53
54    /// Returns a single [PdfPageObject] from this page objects collection.
55    fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError>;
56
57    /// Returns the first [PdfPageObject] in this page objects collection.
58    #[inline]
59    fn first(&self) -> Result<PdfPageObject<'a>, PdfiumError> {
60        first_of(self)
61    }
62
63    /// Returns the last [PdfPageObject] in this page objects collection.
64    #[inline]
65    fn last(&self) -> Result<PdfPageObject<'a>, PdfiumError> {
66        last_of(self)
67    }
68
69    /// Returns an iterator over all the [PdfPageObject] objects in this page objects collection.
70    fn iter(&'a self) -> PdfPageObjectsIterator<'a>;
71
72    /// Returns the smallest bounding box that contains all the [PdfPageObject] objects in this
73    /// page objects collection.
74    fn bounds(&'a self) -> PdfRect {
75        bounds_of(self)
76    }
77
78    /// Adds the given [PdfPageObject] to this page objects collection. The object's
79    /// memory ownership will be transferred to the [PdfPage] containing this page objects
80    /// collection, and the updated page object will be returned.
81    ///
82    /// If the containing [PdfPage] has a content regeneration strategy of
83    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
84    /// will be triggered on the page.
85    fn add_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError>;
86
87    /// Adds the given [PdfPageTextObject] to this page objects collection,
88    /// returning the text object wrapped inside a generic [PdfPageObject] wrapper.
89    ///
90    /// If the containing [PdfPage] has a content regeneration strategy of
91    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
92    /// will be triggered on the page.
93    #[inline]
94    fn add_text_object(&mut self, object: PdfPageTextObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
95        self.add_object(PdfPageObject::Text(object))
96    }
97
98    /// Creates a new [PdfPageTextObject] at the given x and y page co-ordinates
99    /// from the given arguments and adds it to this page objects collection,
100    /// returning the text object wrapped inside a generic [PdfPageObject] wrapper.
101    ///
102    /// If the containing [PdfPage] has a content regeneration strategy of
103    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
104    /// will be triggered on the page.
105    fn create_text_object(
106        &mut self,
107        x: PdfPoints,
108        y: PdfPoints,
109        text: impl ToString,
110        font: impl ToPdfFontToken,
111        font_size: PdfPoints,
112    ) -> Result<PdfPageObject<'a>, PdfiumError>;
113
114    /// Adds the given [PdfPagePathObject] to this page objects collection,
115    /// returning the path object wrapped inside a generic [PdfPageObject] wrapper.
116    ///
117    /// If the containing [PdfPage] has a content regeneration strategy of
118    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
119    /// will be triggered on the page.
120    #[inline]
121    fn add_path_object(&mut self, object: PdfPagePathObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
122        self.add_object(PdfPageObject::Path(object))
123    }
124
125    /// Adds the given [PdfPageXObjectFormObject] to this page objects collection,
126    /// returning the XObject form object wrapped inside a generic [PdfPageObject] wrapper.
127    ///
128    /// If the containing `PdfPage` has a content regeneration strategy of
129    /// `PdfPageContentRegenerationStrategy::AutomaticOnEveryChange` then content regeneration
130    /// will be triggered on the page.
131    #[inline]
132    fn add_x_object_form_object(
133        &mut self,
134        object: PdfPageXObjectFormObject<'a>,
135    ) -> Result<PdfPageObject<'a>, PdfiumError> {
136        self.add_object(PdfPageObject::XObjectForm(object))
137    }
138
139    /// Creates a new [PdfPagePathObject] for the given line, with the given
140    /// stroke settings applied. The new path object will be added to this page objects collection
141    /// and then returned, wrapped inside a generic [PdfPageObject] wrapper.
142    ///
143    /// If the containing [PdfPage] has a content regeneration strategy of
144    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
145    /// will be triggered on the page.
146    fn create_path_object_line(
147        &mut self,
148        x1: PdfPoints,
149        y1: PdfPoints,
150        x2: PdfPoints,
151        y2: PdfPoints,
152        stroke_color: PdfColor,
153        stroke_width: PdfPoints,
154    ) -> Result<PdfPageObject<'a>, PdfiumError>;
155
156    /// Creates a new [PdfPagePathObject] for the given cubic Bézier curve, with the given
157    /// stroke settings applied. The new path object will be added to this page objects collection
158    /// and then returned, wrapped inside a generic [PdfPageObject] wrapper.
159    ///
160    /// If the containing [PdfPage] has a content regeneration strategy of
161    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
162    /// will be triggered on the page.
163    #[allow(clippy::too_many_arguments)]
164    fn create_path_object_bezier(
165        &mut self,
166        x1: PdfPoints,
167        y1: PdfPoints,
168        x2: PdfPoints,
169        y2: PdfPoints,
170        control1_x: PdfPoints,
171        control1_y: PdfPoints,
172        control2_x: PdfPoints,
173        control2_y: PdfPoints,
174        stroke_color: PdfColor,
175        stroke_width: PdfPoints,
176    ) -> Result<PdfPageObject<'a>, PdfiumError>;
177
178    /// Creates a new [PdfPagePathObject] for the given rectangle, with the given
179    /// fill and stroke settings applied. Both the stroke color and the stroke width must be
180    /// provided for the rectangle to be stroked. The new path object will be added to
181    /// this page objects collection and then returned, wrapped inside a generic
182    /// [PdfPageObject] wrapper.
183    ///
184    /// If the containing [PdfPage] has a content regeneration strategy of
185    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
186    /// will be triggered on the page.
187    fn create_path_object_rect(
188        &mut self,
189        rect: PdfRect,
190        stroke_color: Option<PdfColor>,
191        stroke_width: Option<PdfPoints>,
192        fill_color: Option<PdfColor>,
193    ) -> Result<PdfPageObject<'a>, PdfiumError>;
194
195    /// Creates a new [PdfPagePathObject]. The new path will be created with a circle that fills
196    /// the given rectangle, with the given fill and stroke settings applied. Both the stroke color
197    /// and the stroke width must be provided for the circle to be stroked. The new path object
198    /// will be added to this page objects collection and then returned, wrapped inside a generic
199    /// [PdfPageObject] wrapper.
200    ///
201    /// If the containing [PdfPage] has a content regeneration strategy of
202    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
203    /// will be triggered on the page.
204    fn create_path_object_circle(
205        &mut self,
206        rect: PdfRect,
207        stroke_color: Option<PdfColor>,
208        stroke_width: Option<PdfPoints>,
209        fill_color: Option<PdfColor>,
210    ) -> Result<PdfPageObject<'a>, PdfiumError>;
211
212    /// Creates a new [PdfPagePathObject]. The new path will be created with a circle centered
213    /// at the given coordinates, with the given radius, and with the given fill and stroke settings
214    /// applied. Both the stroke color and the stroke width must be provided for the circle to be
215    /// stroked. The new path object will be added to this page objects collection and then
216    /// returned, wrapped inside a generic [PdfPageObject] wrapper.
217    ///
218    /// If the containing [PdfPage] has a content regeneration strategy of
219    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
220    /// will be triggered on the page.
221    fn create_path_object_circle_at(
222        &mut self,
223        center_x: PdfPoints,
224        center_y: PdfPoints,
225        radius: PdfPoints,
226        stroke_color: Option<PdfColor>,
227        stroke_width: Option<PdfPoints>,
228        fill_color: Option<PdfColor>,
229    ) -> Result<PdfPageObject<'a>, PdfiumError>;
230
231    /// Creates a new [PdfPagePathObject]. The new path will be created with an ellipse that fills
232    /// the given rectangle, with the given fill and stroke settings applied. Both the stroke color
233    /// and the stroke width must be provided for the ellipse to be stroked. The new path object
234    /// will be added to this page objects collection and then returned, wrapped inside a generic
235    /// [PdfPageObject] wrapper.
236    ///
237    /// If the containing [PdfPage] has a content regeneration strategy of
238    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
239    /// will be triggered on the page.
240    fn create_path_object_ellipse(
241        &mut self,
242        rect: PdfRect,
243        stroke_color: Option<PdfColor>,
244        stroke_width: Option<PdfPoints>,
245        fill_color: Option<PdfColor>,
246    ) -> Result<PdfPageObject<'a>, PdfiumError>;
247
248    /// Creates a new [PdfPagePathObject]. The new path will be created with an ellipse centered
249    /// at the given coordinates, with the given radii, and with the given fill and stroke settings
250    /// applied. Both the stroke color and the stroke width must be provided for the ellipse to be
251    /// stroked. The new path object will be added to this page objects collection and then
252    /// returned, wrapped inside a generic [PdfPageObject] wrapper.
253    ///
254    /// If the containing [PdfPage] has a content regeneration strategy of
255    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
256    /// will be triggered on the page.
257    #[allow(clippy::too_many_arguments)]
258    fn create_path_object_ellipse_at(
259        &mut self,
260        center_x: PdfPoints,
261        center_y: PdfPoints,
262        x_radius: PdfPoints,
263        y_radius: PdfPoints,
264        stroke_color: Option<PdfColor>,
265        stroke_width: Option<PdfPoints>,
266        fill_color: Option<PdfColor>,
267    ) -> Result<PdfPageObject<'a>, PdfiumError>;
268
269    /// Adds the given [PdfPageImageObject] to this page objects collection,
270    /// returning the image object wrapped inside a generic [PdfPageObject] wrapper.
271    ///
272    /// If the containing [PdfPage] has a content regeneration strategy of
273    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
274    /// will be triggered on the page.
275    #[inline]
276    fn add_image_object(&mut self, object: PdfPageImageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
277        self.add_object(PdfPageObject::Image(object))
278    }
279
280    /// Creates a new [PdfPageImageObject] at the given x and y page co-ordinates
281    /// from the given arguments and adds it to this page objects collection,
282    /// returning the image object wrapped inside a generic [PdfPageObject] wrapper.
283    ///
284    /// By default, new image objects have their width and height both set to 1.0 points.
285    /// If provided, the given width and/or height will be applied to the newly created object to
286    /// scale its size.
287    ///
288    /// If the containing [PdfPage] has a content regeneration strategy of
289    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
290    /// will be triggered on the page.
291    ///
292    /// This function is only available when this crate's `image` feature is enabled.
293    #[cfg(feature = "image_025")]
294    fn create_image_object(
295        &mut self,
296        x: PdfPoints,
297        y: PdfPoints,
298        image: &DynamicImage,
299        width: Option<PdfPoints>,
300        height: Option<PdfPoints>,
301    ) -> Result<PdfPageObject<'a>, PdfiumError>;
302
303    /// Removes the given [PdfPageObject] from this page objects collection. The object's
304    /// memory ownership will be removed from the [PdfPage] containing this page objects
305    /// collection, and the updated page object will be returned. It can be added back to a
306    /// page objects collection or dropped, at which point the memory owned by the object will
307    /// be freed.
308    ///
309    /// If the containing [PdfPage] has a content regeneration strategy of
310    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
311    /// will be triggered on the page.
312    fn remove_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError>;
313
314    /// Removes the [PdfPageObject] at the given index from this page objects collection.
315    /// The object's memory ownership will be removed from the [PdfPage] containing this page objects
316    /// collection, and the updated page object will be returned. It can be added back into a
317    /// page objects collection or discarded, at which point the memory owned by the object will
318    /// be freed.
319    ///
320    /// If the containing [PdfPage] has a content regeneration strategy of
321    /// [PdfPageContentRegenerationStrategy::AutomaticOnEveryChange] then content regeneration
322    /// will be triggered on the page.
323    fn remove_object_at_index(&mut self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError>;
324}
325
326impl<'a, T> PdfPageObjectsCommon<'a> for T
327where
328    T: PdfPageObjectsPrivate<'a>,
329{
330    #[inline]
331    fn len(&self) -> PdfPageObjectIndex {
332        self.len_impl()
333    }
334
335    #[inline]
336    fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError> {
337        self.get_impl(index)
338    }
339
340    #[inline]
341    fn iter(&'a self) -> PdfPageObjectsIterator<'a> {
342        self.iter_impl()
343    }
344
345    #[inline]
346    fn add_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
347        self.add_object_impl(object)
348    }
349
350    #[inline]
351    fn create_text_object(
352        &mut self,
353        x: PdfPoints,
354        y: PdfPoints,
355        text: impl ToString,
356        font: impl ToPdfFontToken,
357        font_size: PdfPoints,
358    ) -> Result<PdfPageObject<'a>, PdfiumError> {
359        let document_handle = match self.ownership() {
360            PdfPageObjectOwnership::Page(ownership) => Some(ownership.document_handle()),
361            PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.document_handle()),
362            PdfPageObjectOwnership::UnattachedAnnotation(ownership) => Some(ownership.document_handle()),
363            _ => None,
364        };
365
366        if let Some(document_handle) = document_handle {
367            let mut object = PdfPageTextObject::new_from_handles(
368                document_handle,
369                text,
370                font.token().handle(),
371                font_size,
372                self.bindings(),
373            )?;
374
375            object.translate(x, y)?;
376
377            self.add_text_object(object)
378        } else {
379            Err(PdfiumError::OwnershipNotAttachedToPage)
380        }
381    }
382
383    #[inline]
384    fn create_path_object_line(
385        &mut self,
386        x1: PdfPoints,
387        y1: PdfPoints,
388        x2: PdfPoints,
389        y2: PdfPoints,
390        stroke_color: PdfColor,
391        stroke_width: PdfPoints,
392    ) -> Result<PdfPageObject<'a>, PdfiumError> {
393        let object = PdfPagePathObject::new_line_from_bindings(
394            self.bindings(),
395            x1,
396            y1,
397            x2,
398            y2,
399            PathStroke {
400                color: stroke_color,
401                width: stroke_width,
402            },
403        )?;
404
405        self.add_path_object(object)
406    }
407
408    #[inline]
409    fn create_path_object_bezier(
410        &mut self,
411        x1: PdfPoints,
412        y1: PdfPoints,
413        x2: PdfPoints,
414        y2: PdfPoints,
415        control1_x: PdfPoints,
416        control1_y: PdfPoints,
417        control2_x: PdfPoints,
418        control2_y: PdfPoints,
419        stroke_color: PdfColor,
420        stroke_width: PdfPoints,
421    ) -> Result<PdfPageObject<'a>, PdfiumError> {
422        let object = PdfPagePathObject::new_bezier_from_bindings(
423            self.bindings(),
424            BezierPoints {
425                start: (x1, y1),
426                end: (x2, y2),
427                control1: (control1_x, control1_y),
428                control2: (control2_x, control2_y),
429            },
430            PathStroke {
431                color: stroke_color,
432                width: stroke_width,
433            },
434        )?;
435
436        self.add_path_object(object)
437    }
438
439    #[inline]
440    fn create_path_object_rect(
441        &mut self,
442        rect: PdfRect,
443        stroke_color: Option<PdfColor>,
444        stroke_width: Option<PdfPoints>,
445        fill_color: Option<PdfColor>,
446    ) -> Result<PdfPageObject<'a>, PdfiumError> {
447        let object =
448            PdfPagePathObject::new_rect_from_bindings(self.bindings(), rect, stroke_color, stroke_width, fill_color)?;
449
450        self.add_path_object(object)
451    }
452
453    #[inline]
454    fn create_path_object_circle(
455        &mut self,
456        rect: PdfRect,
457        stroke_color: Option<PdfColor>,
458        stroke_width: Option<PdfPoints>,
459        fill_color: Option<PdfColor>,
460    ) -> Result<PdfPageObject<'a>, PdfiumError> {
461        let object =
462            PdfPagePathObject::new_circle_from_bindings(self.bindings(), rect, stroke_color, stroke_width, fill_color)?;
463
464        self.add_path_object(object)
465    }
466
467    #[inline]
468    fn create_path_object_circle_at(
469        &mut self,
470        center_x: PdfPoints,
471        center_y: PdfPoints,
472        radius: PdfPoints,
473        stroke_color: Option<PdfColor>,
474        stroke_width: Option<PdfPoints>,
475        fill_color: Option<PdfColor>,
476    ) -> Result<PdfPageObject<'a>, PdfiumError> {
477        let object = PdfPagePathObject::new_circle_at_from_bindings(
478            self.bindings(),
479            center_x,
480            center_y,
481            radius,
482            PathFillStroke {
483                stroke_color,
484                stroke_width,
485                fill_color,
486            },
487        )?;
488
489        self.add_path_object(object)
490    }
491
492    #[inline]
493    fn create_path_object_ellipse(
494        &mut self,
495        rect: PdfRect,
496        stroke_color: Option<PdfColor>,
497        stroke_width: Option<PdfPoints>,
498        fill_color: Option<PdfColor>,
499    ) -> Result<PdfPageObject<'a>, PdfiumError> {
500        let object = PdfPagePathObject::new_ellipse_from_bindings(
501            self.bindings(),
502            rect,
503            stroke_color,
504            stroke_width,
505            fill_color,
506        )?;
507
508        self.add_path_object(object)
509    }
510
511    #[inline]
512    fn create_path_object_ellipse_at(
513        &mut self,
514        center_x: PdfPoints,
515        center_y: PdfPoints,
516        x_radius: PdfPoints,
517        y_radius: PdfPoints,
518        stroke_color: Option<PdfColor>,
519        stroke_width: Option<PdfPoints>,
520        fill_color: Option<PdfColor>,
521    ) -> Result<PdfPageObject<'a>, PdfiumError> {
522        let object = PdfPagePathObject::new_ellipse_at_from_bindings(
523            self.bindings(),
524            center_x,
525            center_y,
526            x_radius,
527            y_radius,
528            PathFillStroke {
529                stroke_color,
530                stroke_width,
531                fill_color,
532            },
533        )?;
534
535        self.add_path_object(object)
536    }
537
538    #[cfg(feature = "image_025")]
539    fn create_image_object(
540        &mut self,
541        x: PdfPoints,
542        y: PdfPoints,
543        image: &DynamicImage,
544        width: Option<PdfPoints>,
545        height: Option<PdfPoints>,
546    ) -> Result<PdfPageObject<'a>, PdfiumError> {
547        let document_handle = match self.ownership() {
548            PdfPageObjectOwnership::Page(ownership) => Some(ownership.document_handle()),
549            PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.document_handle()),
550            PdfPageObjectOwnership::UnattachedAnnotation(ownership) => Some(ownership.document_handle()),
551            _ => None,
552        };
553
554        if let Some(document_handle) = document_handle {
555            let image_width = image.width();
556
557            let image_height = image.height();
558
559            let mut object = PdfPageImageObject::new_from_handle(document_handle, self.bindings())?;
560
561            object.set_image(image)?;
562
563            match (width, height) {
564                (Some(width), Some(height)) => {
565                    object.scale(width.value, height.value)?;
566                }
567                (Some(width), None) => {
568                    let aspect_ratio = image_height as f32 / image_width as f32;
569
570                    let height = width * aspect_ratio;
571
572                    object.scale(width.value, height.value)?;
573                }
574                (None, Some(height)) => {
575                    let aspect_ratio = image_height as f32 / image_width as f32;
576
577                    let width = height / aspect_ratio;
578
579                    object.scale(width.value, height.value)?;
580                }
581                (None, None) => {}
582            }
583
584            object.translate(x, y)?;
585
586            self.add_image_object(object)
587        } else {
588            Err(PdfiumError::OwnershipNotAttachedToPage)
589        }
590    }
591
592    #[inline]
593    fn remove_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
594        self.remove_object_impl(object)
595    }
596
597    fn remove_object_at_index(&mut self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError> {
598        if index >= self.len() {
599            return Err(PdfiumError::PageObjectIndexOutOfBounds);
600        }
601
602        if let Ok(object) = self.get(index) {
603            self.remove_object(object)
604        } else {
605            Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
606        }
607    }
608}
609
610fn first_of<'a, T>(objects: &T) -> Result<PdfPageObject<'a>, PdfiumError>
611where
612    T: PdfPageObjectsCommon<'a> + ?Sized,
613{
614    if !objects.is_empty() {
615        objects.get(0)
616    } else {
617        Err(PdfiumError::NoPageObjectsInCollection)
618    }
619}
620
621fn last_of<'a, T>(objects: &T) -> Result<PdfPageObject<'a>, PdfiumError>
622where
623    T: PdfPageObjectsCommon<'a> + ?Sized,
624{
625    if !objects.is_empty() {
626        objects.get(objects.len() - 1)
627    } else {
628        Err(PdfiumError::NoPageObjectsInCollection)
629    }
630}
631
632fn bounds_of<'a, T>(objects: &'a T) -> PdfRect
633where
634    T: PdfPageObjectsCommon<'a> + ?Sized,
635{
636    let mut bottom: f32 = 0.0;
637    let mut top: f32 = 0.0;
638    let mut left: f32 = 0.0;
639    let mut right: f32 = 0.0;
640
641    for object in objects.iter() {
642        if let Ok(bounds) = object.bounds() {
643            bottom = bottom.min(bounds.bottom().value);
644            top = top.max(bounds.top().value);
645            left = left.min(bounds.left().value);
646            right = right.max(bounds.right().value);
647        }
648    }
649
650    PdfRect::new_from_values(bottom, left, top, right)
651}
652
653/// An iterator over all the [PdfPageObject] objects in a page objects collection.
654pub struct PdfPageObjectsIterator<'a> {
655    objects: &'a dyn PdfPageObjectsPrivate<'a>,
656    next_index: PdfPageObjectIndex,
657}
658
659impl<'a> PdfPageObjectsIterator<'a> {
660    #[inline]
661    pub(crate) fn new(objects: &'a dyn PdfPageObjectsPrivate<'a>) -> Self {
662        PdfPageObjectsIterator { objects, next_index: 0 }
663    }
664}
665
666impl<'a> Iterator for PdfPageObjectsIterator<'a> {
667    type Item = PdfPageObject<'a>;
668
669    fn next(&mut self) -> Option<Self::Item> {
670        let next = self.objects.get_impl(self.next_index);
671
672        self.next_index += 1;
673
674        next.ok()
675    }
676}