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::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
25pub type PdfPageIndex = c_int;
27
28#[derive(Debug, Copy, Clone)]
31pub enum PdfPageMode {
32 UnsetOrUnknown = PAGEMODE_UNKNOWN as isize,
34
35 None = PAGEMODE_USENONE as isize,
38
39 ShowDocumentOutline = PAGEMODE_USEOUTLINES as isize,
41
42 ShowPageThumbnails = PAGEMODE_USETHUMBS as isize,
44
45 Fullscreen = PAGEMODE_FULLSCREEN as isize,
47
48 ShowContentGroupPanel = PAGEMODE_USEOC as isize,
50
51 ShowAttachmentsPanel = PAGEMODE_USEATTACHMENTS as isize,
53}
54
55impl PdfPageMode {
56 #[inline]
57 pub(crate) fn from_pdfium(page_mode: i32) -> Option<Self> {
58 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
76pub 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 pub fn len(&self) -> PdfPageIndex {
98 (unsafe { self.bindings().FPDF_GetPageCount(self.document_handle) }) as PdfPageIndex
99 }
100
101 #[inline]
103 pub fn is_empty(&self) -> bool {
104 self.len() == 0
105 }
106
107 #[inline]
109 pub fn as_range(&self) -> Range<PdfPageIndex> {
110 0..self.len()
111 }
112
113 #[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 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 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 #[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 #[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 #[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 #[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 #[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 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 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 #[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 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 #[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 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 #[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 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 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 let label = {
455 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 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 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 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 #[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
589pub 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 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 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 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; 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}