Skip to main content

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