pdfium_render/pdf/document/
pages.rs

1//! Defines the [PdfPages] struct, a collection of all the `PdfPage` objects in a
2//! `PdfDocument`.
3
4use crate::bindgen::{
5    size_t, FPDF_DOCUMENT, FPDF_FORMHANDLE, FPDF_PAGE, FS_SIZEF, PAGEMODE_FULLSCREEN,
6    PAGEMODE_UNKNOWN, PAGEMODE_USEATTACHMENTS, PAGEMODE_USENONE, PAGEMODE_USEOC,
7    PAGEMODE_USEOUTLINES, PAGEMODE_USETHUMBS,
8};
9use crate::bindings::PdfiumLibraryBindings;
10use crate::error::{PdfiumError, PdfiumInternalError};
11use crate::pdf::document::page::index_cache::PdfPageIndexCache;
12use crate::pdf::document::page::object::group::PdfPageGroupObject;
13use crate::pdf::document::page::size::PdfPagePaperSize;
14use crate::pdf::document::page::PdfPage;
15use crate::pdf::document::PdfDocument;
16use crate::pdf::points::PdfPoints;
17use crate::pdf::rect::PdfRect;
18use crate::utils::mem::create_byte_buffer;
19use crate::utils::utf16le::get_string_from_pdfium_utf16le_bytes;
20use std::ops::{Range, RangeInclusive};
21use std::os::raw::{c_double, c_int, c_void};
22
23/// The zero-based index of a single [PdfPage] inside its containing [PdfPages] collection.
24pub type PdfPageIndex = u16;
25
26/// A hint to a PDF document reader (such as Adobe Acrobat) as to how the creator intended
27/// the [PdfPage] objects in a [PdfDocument] to be displayed to the viewer when the document is opened.
28#[derive(Debug, Copy, Clone)]
29pub enum PdfPageMode {
30    /// No known page mode is set for this [PdfDocument].
31    UnsetOrUnknown = PAGEMODE_UNKNOWN as isize,
32
33    /// No page mode, i.e. neither the document outline nor thumbnail images should be visible,
34    /// no side panels should be visible, and the document should not be displayed in full screen mode.
35    None = PAGEMODE_USENONE as isize,
36
37    /// Outline page mode: the document outline should be visible.
38    ShowDocumentOutline = PAGEMODE_USEOUTLINES as isize,
39
40    /// Thumbnail page mode: page thumbnails should be visible.
41    ShowPageThumbnails = PAGEMODE_USETHUMBS as isize,
42
43    /// Fullscreen page mode: no menu bar, window controls, or other windows should be visible.
44    Fullscreen = PAGEMODE_FULLSCREEN as isize,
45
46    /// The optional content group panel should be visible.
47    ShowContentGroupPanel = PAGEMODE_USEOC as isize,
48
49    /// The attachments panel should be visible.
50    ShowAttachmentsPanel = PAGEMODE_USEATTACHMENTS as isize,
51}
52
53impl PdfPageMode {
54    #[inline]
55    pub(crate) fn from_pdfium(page_mode: i32) -> Option<Self> {
56        // The PAGEMODE_* enum constants are a mixture of i32 and u32 values :/
57
58        if page_mode == PAGEMODE_UNKNOWN {
59            return Some(PdfPageMode::UnsetOrUnknown);
60        }
61
62        match page_mode as u32 {
63            PAGEMODE_USENONE => Some(PdfPageMode::None),
64            PAGEMODE_USEOUTLINES => Some(PdfPageMode::ShowDocumentOutline),
65            PAGEMODE_USETHUMBS => Some(PdfPageMode::ShowPageThumbnails),
66            PAGEMODE_FULLSCREEN => Some(PdfPageMode::Fullscreen),
67            PAGEMODE_USEOC => Some(PdfPageMode::ShowContentGroupPanel),
68            PAGEMODE_USEATTACHMENTS => Some(PdfPageMode::ShowAttachmentsPanel),
69            _ => None,
70        }
71    }
72}
73
74/// The collection of [PdfPage] objects inside a [PdfDocument].
75pub struct PdfPages<'a> {
76    document_handle: FPDF_DOCUMENT,
77    form_handle: Option<FPDF_FORMHANDLE>,
78    bindings: &'a dyn PdfiumLibraryBindings,
79}
80
81impl<'a> PdfPages<'a> {
82    #[inline]
83    pub(crate) fn from_pdfium(
84        document_handle: FPDF_DOCUMENT,
85        form_handle: Option<FPDF_FORMHANDLE>,
86        bindings: &'a dyn PdfiumLibraryBindings,
87    ) -> Self {
88        PdfPages {
89            document_handle,
90            form_handle,
91            bindings,
92        }
93    }
94
95    /// Returns the [PdfiumLibraryBindings] used by this [PdfPages] collection.
96    #[inline]
97    pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
98        self.bindings
99    }
100
101    /// Returns the number of pages in this [PdfPages] collection.
102    pub fn len(&self) -> PdfPageIndex {
103        self.bindings.FPDF_GetPageCount(self.document_handle) as PdfPageIndex
104    }
105
106    /// Returns `true` if this [PdfPages] collection is empty.
107    #[inline]
108    pub fn is_empty(&self) -> bool {
109        self.len() == 0
110    }
111
112    /// Returns a Range from `0..(number of pages)` for this [PdfPages] collection.
113    #[inline]
114    pub fn as_range(&self) -> Range<PdfPageIndex> {
115        0..self.len()
116    }
117
118    /// Returns an inclusive Range from `0..=(number of pages - 1)` for this [PdfPages] collection.
119    #[inline]
120    pub fn as_range_inclusive(&self) -> RangeInclusive<PdfPageIndex> {
121        if self.is_empty() {
122            0..=0
123        } else {
124            0..=(self.len() - 1)
125        }
126    }
127
128    /// Returns a single [PdfPage] from this [PdfPages] collection.
129    pub fn get(&self, index: PdfPageIndex) -> Result<PdfPage<'a>, PdfiumError> {
130        if index >= self.len() {
131            return Err(PdfiumError::PageIndexOutOfBounds);
132        }
133
134        let page_handle = self
135            .bindings
136            .FPDF_LoadPage(self.document_handle, index as c_int);
137
138        let result = self.pdfium_page_handle_to_result(index, page_handle);
139
140        if let Ok(page) = result.as_ref() {
141            PdfPageIndexCache::cache_props_for_page(
142                self.document_handle,
143                page_handle,
144                index,
145                page.content_regeneration_strategy(),
146            );
147        }
148
149        result
150    }
151
152    /// Returns the size of a single [PdfPage] without loading it into memory.
153    /// This is considerably faster than loading the page first via [PdfPages::get()] and then
154    /// retrieving the page size using [PdfPage::page_size()].
155    pub fn page_size(&self, index: PdfPageIndex) -> Result<PdfRect, PdfiumError> {
156        if index >= self.len() {
157            return Err(PdfiumError::PageIndexOutOfBounds);
158        }
159
160        let mut size = FS_SIZEF {
161            width: 0.0,
162            height: 0.0,
163        };
164
165        if self
166            .bindings
167            .is_true(self.bindings.FPDF_GetPageSizeByIndexF(
168                self.document_handle,
169                index.into(),
170                &mut size,
171            ))
172        {
173            Ok(PdfRect::new(
174                PdfPoints::ZERO,
175                PdfPoints::ZERO,
176                PdfPoints::new(size.height),
177                PdfPoints::new(size.width),
178            ))
179        } else {
180            Err(PdfiumError::PdfiumLibraryInternalError(
181                PdfiumInternalError::Unknown,
182            ))
183        }
184    }
185
186    /// Returns the size of every [PdfPage] in this [PdfPages] collection.
187    #[inline]
188    pub fn page_sizes(&self) -> Result<Vec<PdfRect>, PdfiumError> {
189        let mut sizes = Vec::with_capacity(self.len() as usize);
190
191        for i in self.as_range() {
192            sizes.push(self.page_size(i)?);
193        }
194
195        Ok(sizes)
196    }
197
198    /// Returns the first [PdfPage] in this [PdfPages] collection.
199    #[inline]
200    pub fn first(&self) -> Result<PdfPage<'a>, PdfiumError> {
201        if !self.is_empty() {
202            self.get(0)
203        } else {
204            Err(PdfiumError::NoPagesInDocument)
205        }
206    }
207
208    /// Returns the last [PdfPage] in this [PdfPages] collection.
209    #[inline]
210    pub fn last(&self) -> Result<PdfPage<'a>, PdfiumError> {
211        if !self.is_empty() {
212            self.get(self.len() - 1)
213        } else {
214            Err(PdfiumError::NoPagesInDocument)
215        }
216    }
217
218    /// Creates a new, empty [PdfPage] with the given [PdfPagePaperSize] and inserts it
219    /// at the start of this [PdfPages] collection, shuffling down all other pages.
220    #[inline]
221    pub fn create_page_at_start(
222        &mut self,
223        size: PdfPagePaperSize,
224    ) -> Result<PdfPage<'a>, PdfiumError> {
225        self.create_page_at_index(size, 0)
226    }
227
228    /// Creates a new, empty [PdfPage] with the given [PdfPagePaperSize] and adds it
229    /// to the end of this [PdfPages] collection.
230    #[inline]
231    pub fn create_page_at_end(
232        &mut self,
233        size: PdfPagePaperSize,
234    ) -> Result<PdfPage<'a>, PdfiumError> {
235        self.create_page_at_index(size, self.len())
236    }
237
238    /// Creates a new, empty [PdfPage] with the given [PdfPagePaperSize] and inserts it
239    /// into this [PdfPages] collection at the given page index.
240    pub fn create_page_at_index(
241        &mut self,
242        size: PdfPagePaperSize,
243        index: PdfPageIndex,
244    ) -> Result<PdfPage<'a>, PdfiumError> {
245        let result = self.pdfium_page_handle_to_result(
246            index,
247            self.bindings.FPDFPage_New(
248                self.document_handle,
249                index as c_int,
250                size.width().value as c_double,
251                size.height().value as c_double,
252            ),
253        );
254
255        if let Ok(page) = result.as_ref() {
256            PdfPageIndexCache::insert_pages_at_index(self.document_handle, index, 1);
257            PdfPageIndexCache::cache_props_for_page(
258                self.document_handle,
259                page.page_handle(),
260                index,
261                page.content_regeneration_strategy(),
262            );
263        }
264
265        result
266    }
267
268    // TODO: AJRC - 5/2/23 - remove deprecated PdfPages::delete_page_range() function in 0.9.0
269    // as part of tracking issue: https://github.com/ajrcarey/pdfium-render/issues/36
270    // TODO: AJRC - 5/2/23 - if PdfDocument::pages() returned a &PdfPages reference (rather than an
271    // owned PdfPages instance), and if PdfPages::get() returned a &PdfPage reference (rather than an
272    // owned PdfPage instance), then it might be possible to reinstate this function, as Rust
273    // would be able to manage the reference lifetimes safely. Tracking issue:
274    // https://github.com/ajrcarey/pdfium-render/issues/47
275    /// Deletes the page at the given index from this [PdfPages] collection.
276    #[deprecated(
277        since = "0.7.30",
278        note = "This function has been deprecated. Use the PdfPage::delete() function instead."
279    )]
280    #[doc(hidden)]
281    pub fn delete_page_at_index(&mut self, index: PdfPageIndex) -> Result<(), PdfiumError> {
282        if index >= self.len() {
283            return Err(PdfiumError::PageIndexOutOfBounds);
284        }
285
286        self.bindings
287            .FPDFPage_Delete(self.document_handle, index as c_int);
288
289        PdfPageIndexCache::delete_pages_at_index(self.document_handle, index, 1);
290
291        Ok(())
292    }
293
294    // TODO: AJRC - 5/2/23 - remove deprecated PdfPages::delete_page_range() function in 0.9.0
295    // as part of tracking issue: https://github.com/ajrcarey/pdfium-render/issues/36
296    // TODO: AJRC - 5/2/23 - if PdfDocument::pages() returned a &PdfPages reference (rather than an
297    // owned PdfPages instance), and if PdfPages::get() returned a &PdfPage reference (rather than an
298    // owned PdfPage instance), then it might be possible to reinstate this function, as Rust
299    // would be able to manage the reference lifetimes safely. Tracking issue:
300    // https://github.com/ajrcarey/pdfium-render/issues/47
301    /// Deletes all pages in the given range from this [PdfPages] collection.
302    #[deprecated(
303        since = "0.7.30",
304        note = "This function has been deprecated. Use the PdfPage::delete() function instead."
305    )]
306    #[doc(hidden)]
307    pub fn delete_page_range(&mut self, range: Range<PdfPageIndex>) -> Result<(), PdfiumError> {
308        for index in range.rev() {
309            #[allow(deprecated)] // Both functions will be removed at the same time.
310            self.delete_page_at_index(index)?;
311        }
312
313        Ok(())
314    }
315
316    /// Copies a single page with the given source page index from the given
317    /// source [PdfDocument], inserting it at the given destination page index
318    /// in this [PdfPages] collection.
319    pub fn copy_page_from_document(
320        &mut self,
321        source: &PdfDocument,
322        source_page_index: PdfPageIndex,
323        destination_page_index: PdfPageIndex,
324    ) -> Result<(), PdfiumError> {
325        self.copy_page_range_from_document(
326            source,
327            source_page_index..=source_page_index,
328            destination_page_index,
329        )
330    }
331
332    /// Copies one or more pages, specified using a user-friendly page range string,
333    /// from the given source [PdfDocument], inserting the pages sequentially starting at the given
334    /// destination page index in this [PdfPages] collection.
335    ///
336    /// The page range string should be in a comma-separated list of indexes and ranges,
337    /// for example \"1,3,5-7\". Pages are indexed starting at one, not zero.
338    #[inline]
339    pub fn copy_pages_from_document(
340        &mut self,
341        source: &PdfDocument,
342        pages: &str,
343        destination_page_index: PdfPageIndex,
344    ) -> Result<(), PdfiumError> {
345        Self::copy_pages_between_documents(
346            source.handle(),
347            pages,
348            self.document_handle,
349            destination_page_index,
350            self.bindings(),
351        )
352    }
353
354    /// Copies one or more pages, specified using a user-friendly page range string,
355    /// from one raw document handle to another, inserting the pages sequentially
356    /// starting at the given destination page index.
357    pub(crate) fn copy_pages_between_documents(
358        source: FPDF_DOCUMENT,
359        pages: &str,
360        destination: FPDF_DOCUMENT,
361        destination_page_index: PdfPageIndex,
362        bindings: &dyn PdfiumLibraryBindings,
363    ) -> Result<(), PdfiumError> {
364        let destination_page_count_before_import = bindings.FPDF_GetPageCount(destination);
365
366        if bindings.is_true(bindings.FPDF_ImportPages(
367            destination,
368            source,
369            pages,
370            destination_page_index as c_int,
371        )) {
372            let destination_page_count_after_import = bindings.FPDF_GetPageCount(destination);
373
374            PdfPageIndexCache::insert_pages_at_index(
375                destination,
376                destination_page_index,
377                (destination_page_count_after_import - destination_page_count_before_import)
378                    as PdfPageIndex,
379            );
380
381            Ok(())
382        } else {
383            Err(PdfiumError::PdfiumLibraryInternalError(
384                PdfiumInternalError::Unknown,
385            ))
386        }
387    }
388
389    /// Copies one or more pages with the given range of indices from the given
390    /// source [PdfDocument], inserting the pages sequentially starting at the given
391    /// destination page index in this [PdfPages] collection.
392    #[inline]
393    pub fn copy_page_range_from_document(
394        &mut self,
395        source: &PdfDocument,
396        source_page_range: RangeInclusive<PdfPageIndex>,
397        destination_page_index: PdfPageIndex,
398    ) -> Result<(), PdfiumError> {
399        Self::copy_page_range_between_documents(
400            source.handle(),
401            source_page_range,
402            self.document_handle,
403            destination_page_index,
404            self.bindings(),
405        )
406    }
407
408    /// Copies one or more pages with the given range of indices from one raw document handle
409    /// to another, inserting the pages sequentially starting at the given destination page index.
410    pub(crate) fn copy_page_range_between_documents(
411        source: FPDF_DOCUMENT,
412        source_page_range: RangeInclusive<PdfPageIndex>,
413        destination: FPDF_DOCUMENT,
414        destination_page_index: PdfPageIndex,
415        bindings: &dyn PdfiumLibraryBindings,
416    ) -> Result<(), PdfiumError> {
417        let no_of_pages_to_import = source_page_range.len() as PdfPageIndex;
418
419        if bindings.is_true(
420            bindings.FPDF_ImportPagesByIndex_vec(
421                destination,
422                source,
423                source_page_range
424                    .map(|index| index as c_int)
425                    .collect::<Vec<_>>(),
426                destination_page_index as c_int,
427            ),
428        ) {
429            PdfPageIndexCache::insert_pages_at_index(
430                destination,
431                destination_page_index,
432                no_of_pages_to_import,
433            );
434
435            Ok(())
436        } else {
437            Err(PdfiumError::PdfiumLibraryInternalError(
438                PdfiumInternalError::Unknown,
439            ))
440        }
441    }
442
443    /// Copies all pages in the given source [PdfDocument], appending them sequentially
444    /// to the end of this [PdfPages] collection.
445    ///
446    /// For finer control over which pages are imported, and where they should be inserted,
447    /// use one of the [PdfPages::copy_page_from_document()], [PdfPages::copy_pages_from_document()],
448    ///  or [PdfPages::copy_page_range_from_document()] functions.
449    #[inline]
450    pub fn append(&mut self, document: &PdfDocument) -> Result<(), PdfiumError> {
451        self.copy_page_range_from_document(
452            document,
453            document.pages().as_range_inclusive(),
454            self.len(),
455        )
456    }
457
458    /// Creates a new [PdfDocument] by copying the pages in this [PdfPages] collection
459    /// into tiled grids, the size of each tile shrinking or expanding as necessary to fit
460    /// the given [PdfPagePaperSize].
461    ///
462    /// For example, to output all pages in a [PdfPages] collection into a new
463    /// A3 landscape document with six source pages tiled on each destination page arranged
464    /// into a 2 row x 3 column grid, you would call:
465    ///
466    /// ```
467    /// PdfPages::tile_into_new_document(2, 3, PdfPagePaperSize::a3().to_landscape())
468    /// ```
469    pub fn tile_into_new_document(
470        &self,
471        rows_per_page: u8,
472        columns_per_row: u8,
473        size: PdfPagePaperSize,
474    ) -> Result<PdfDocument, PdfiumError> {
475        let handle = self.bindings.FPDF_ImportNPagesToOne(
476            self.document_handle,
477            size.width().value,
478            size.height().value,
479            columns_per_row as size_t,
480            rows_per_page as size_t,
481        );
482
483        if handle.is_null() {
484            Err(PdfiumError::PdfiumLibraryInternalError(
485                PdfiumInternalError::Unknown,
486            ))
487        } else {
488            Ok(PdfDocument::from_pdfium(handle, self.bindings))
489        }
490    }
491
492    /// Returns a [PdfPage] from the given `FPDF_PAGE` handle, if possible.
493    pub(crate) fn pdfium_page_handle_to_result(
494        &self,
495        index: PdfPageIndex,
496        page_handle: FPDF_PAGE,
497    ) -> Result<PdfPage<'a>, PdfiumError> {
498        if page_handle.is_null() {
499            Err(PdfiumError::PdfiumLibraryInternalError(
500                PdfiumInternalError::Unknown,
501            ))
502        } else {
503            // The page's label (if any) is retrieved by index rather than by using the
504            // FPDF_PAGE handle. Since the index of any particular page can change
505            // (if other pages are inserted or removed), it's better if we don't treat the
506            // page index as an immutable property of the PdfPage; instead, we look up the label now.
507
508            // (Pdfium does not currently include an FPDF_SetPageLabel() function, so the label
509            // _will_ be an immutable property of the PdfPage for its entire lifetime.)
510
511            let label = {
512                // Retrieving the label text from Pdfium is a two-step operation. First, we call
513                // FPDF_GetPageLabel() with a null buffer; this will retrieve the length of
514                // the label text in bytes. If the length is zero, then there is no such tag.
515
516                // If the length is non-zero, then we reserve a byte buffer of the given
517                // length and call FPDF_GetPageLabel() again with a pointer to the buffer;
518                // this will write the label text to the buffer in UTF16LE format.
519
520                let buffer_length = self.bindings.FPDF_GetPageLabel(
521                    self.document_handle,
522                    index as c_int,
523                    std::ptr::null_mut(),
524                    0,
525                );
526
527                if buffer_length == 0 {
528                    // The label is not present.
529
530                    None
531                } else {
532                    let mut buffer = create_byte_buffer(buffer_length as usize);
533
534                    let result = self.bindings.FPDF_GetPageLabel(
535                        self.document_handle,
536                        index as c_int,
537                        buffer.as_mut_ptr() as *mut c_void,
538                        buffer_length,
539                    );
540
541                    debug_assert_eq!(result, buffer_length);
542
543                    get_string_from_pdfium_utf16le_bytes(buffer)
544                }
545            };
546
547            Ok(PdfPage::from_pdfium(
548                self.document_handle,
549                page_handle,
550                self.form_handle,
551                label,
552                self.bindings,
553            ))
554        }
555    }
556
557    /// Returns the [PdfPageMode] setting embedded in the containing [PdfDocument].
558    pub fn page_mode(&self) -> PdfPageMode {
559        PdfPageMode::from_pdfium(self.bindings.FPDFDoc_GetPageMode(self.document_handle))
560            .unwrap_or(PdfPageMode::UnsetOrUnknown)
561    }
562
563    /// Applies the given watermarking closure to each [PdfPage] in this [PdfPages] collection.
564    ///
565    /// The closure receives four arguments:
566    /// * An empty [PdfPageGroupObject] for you to populate with the page objects that make up your watermark.
567    /// * The zero-based index of the [PdfPage] currently being processed.
568    /// * The width of the [PdfPage] currently being processed, in [PdfPoints].
569    /// * The height of the [PdfPage] currently being processed, in [PdfPoints].
570    ///
571    /// If the current page should not be watermarked, simply leave the group empty.
572    ///
573    /// The closure can return a `Result<(), PdfiumError>`; this makes it easy to use the `?` unwrapping
574    /// operator within the closure.
575    ///
576    /// For example, the following snippet adds a page number to the very top of every page in a document
577    /// except for the first page.
578    ///
579    /// ```
580    ///     document.pages().watermark(|group, index, width, height| {
581    ///         if index == 0 {
582    ///             // Don't watermark the first page.
583    ///
584    ///             Ok(())
585    ///         } else {
586    ///             let mut page_number = PdfPageTextObject::new(
587    ///                 &document,
588    ///                 format!("Page {}", index + 1),
589    ///                 &PdfFont::helvetica(&document),
590    ///                 PdfPoints::new(14.0),
591    ///             )?;
592    ///
593    ///             page_number.translate(
594    ///                 (width - page_number.width()?) / 2.0, // Horizontally center the page number...
595    ///                 height - page_number.height()?, // ... and vertically position it at the page top.
596    ///             )?;
597    ///
598    ///             group.push(&mut page_number.into())
599    ///         }
600    ///     })?;
601    /// ```
602    pub fn watermark<F>(&self, watermarker: F) -> Result<(), PdfiumError>
603    where
604        F: Fn(
605            &mut PdfPageGroupObject<'a>,
606            PdfPageIndex,
607            PdfPoints,
608            PdfPoints,
609        ) -> Result<(), PdfiumError>,
610    {
611        for (index, page) in self.iter().enumerate() {
612            let mut group = PdfPageGroupObject::from_pdfium(
613                self.document_handle,
614                page.page_handle(),
615                self.bindings,
616            );
617
618            watermarker(
619                &mut group,
620                index as PdfPageIndex,
621                page.width(),
622                page.height(),
623            )?;
624        }
625
626        Ok(())
627    }
628
629    /// Returns an iterator over all the pages in this [PdfPages] collection.
630    #[inline]
631    pub fn iter(&self) -> PdfPagesIterator {
632        PdfPagesIterator::new(self)
633    }
634}
635
636/// An iterator over all the [PdfPage] objects in a [PdfPages] collection.
637pub struct PdfPagesIterator<'a> {
638    pages: &'a PdfPages<'a>,
639    next_index: PdfPageIndex,
640}
641
642impl<'a> PdfPagesIterator<'a> {
643    #[inline]
644    pub(crate) fn new(pages: &'a PdfPages<'a>) -> Self {
645        PdfPagesIterator {
646            pages,
647            next_index: 0,
648        }
649    }
650}
651
652impl<'a> Iterator for PdfPagesIterator<'a> {
653    type Item = PdfPage<'a>;
654
655    fn next(&mut self) -> Option<Self::Item> {
656        let next = self.pages.get(self.next_index);
657
658        self.next_index += 1;
659
660        next.ok()
661    }
662}
663
664#[cfg(test)]
665mod tests {
666    use crate::prelude::*;
667    use crate::utils::test::test_bind_to_pdfium;
668
669    #[test]
670    fn test_page_size() -> Result<(), PdfiumError> {
671        // Tests the dimensions of each page in a sample file.
672
673        let pdfium = test_bind_to_pdfium();
674
675        let document = pdfium.load_pdf_from_file("./test/page-sizes-test.pdf", None)?;
676
677        assert_eq!(document.pages().page_size(0)?, expected_page_0_size());
678        assert_eq!(document.pages().page_size(1)?, expected_page_1_size());
679        assert_eq!(document.pages().page_size(2)?, expected_page_2_size());
680        assert_eq!(document.pages().page_size(3)?, expected_page_3_size());
681        assert_eq!(document.pages().page_size(4)?, expected_page_4_size());
682        assert!(document.pages().page_size(5).is_err());
683
684        Ok(())
685    }
686
687    #[test]
688    fn test_page_sizes() -> Result<(), PdfiumError> {
689        // Tests the dimensions of all pages in a sample file.
690
691        let pdfium = test_bind_to_pdfium();
692
693        let document = pdfium.load_pdf_from_file("./test/page-sizes-test.pdf", None)?;
694
695        assert_eq!(
696            document.pages().page_sizes()?,
697            vec!(
698                expected_page_0_size(),
699                expected_page_1_size(),
700                expected_page_2_size(),
701                expected_page_3_size(),
702                expected_page_4_size(),
703            ),
704        );
705
706        Ok(())
707    }
708
709    const fn expected_page_0_size() -> PdfRect {
710        PdfRect::new_from_values(0.0, 0.0, 841.8898, 595.30396)
711    }
712
713    const fn expected_page_1_size() -> PdfRect {
714        PdfRect::new_from_values(0.0, 0.0, 595.30396, 841.8898)
715    }
716
717    const fn expected_page_2_size() -> PdfRect {
718        PdfRect::new_from_values(0.0, 0.0, 1190.5511, 841.8898)
719    }
720
721    const fn expected_page_3_size() -> PdfRect {
722        PdfRect::new_from_values(0.0, 0.0, 419.5559, 595.30396)
723    }
724
725    const fn expected_page_4_size() -> PdfRect {
726        expected_page_0_size()
727    }
728}