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