1use 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
23pub type PdfPageIndex = u16;
25
26#[derive(Debug, Copy, Clone)]
29pub enum PdfPageMode {
30 UnsetOrUnknown = PAGEMODE_UNKNOWN as isize,
32
33 None = PAGEMODE_USENONE as isize,
36
37 ShowDocumentOutline = PAGEMODE_USEOUTLINES as isize,
39
40 ShowPageThumbnails = PAGEMODE_USETHUMBS as isize,
42
43 Fullscreen = PAGEMODE_FULLSCREEN as isize,
45
46 ShowContentGroupPanel = PAGEMODE_USEOC as isize,
48
49 ShowAttachmentsPanel = PAGEMODE_USEATTACHMENTS as isize,
51}
52
53impl PdfPageMode {
54 #[inline]
55 pub(crate) fn from_pdfium(page_mode: i32) -> Option<Self> {
56 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
74pub 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 #[inline]
97 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
98 self.bindings
99 }
100
101 pub fn len(&self) -> PdfPageIndex {
103 self.bindings.FPDF_GetPageCount(self.document_handle) as PdfPageIndex
104 }
105
106 #[inline]
108 pub fn is_empty(&self) -> bool {
109 self.len() == 0
110 }
111
112 #[inline]
114 pub fn as_range(&self) -> Range<PdfPageIndex> {
115 0..self.len()
116 }
117
118 #[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 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 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 #[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 #[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 #[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 #[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 #[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 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 #[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 #[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)] self.delete_page_at_index(index)?;
311 }
312
313 Ok(())
314 }
315
316 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 #[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 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 #[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 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 #[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 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 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 let label = {
512 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 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 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 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 #[inline]
631 pub fn iter(&self) -> PdfPagesIterator {
632 PdfPagesIterator::new(self)
633 }
634}
635
636pub 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 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 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}