1use 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
22pub type PdfPageIndex = c_int;
24
25#[derive(Debug, Copy, Clone)]
28pub enum PdfPageMode {
29 UnsetOrUnknown = PAGEMODE_UNKNOWN as isize,
31
32 None = PAGEMODE_USENONE as isize,
35
36 ShowDocumentOutline = PAGEMODE_USEOUTLINES as isize,
38
39 ShowPageThumbnails = PAGEMODE_USETHUMBS as isize,
41
42 Fullscreen = PAGEMODE_FULLSCREEN as isize,
44
45 ShowContentGroupPanel = PAGEMODE_USEOC as isize,
47
48 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
71pub 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 #[inline]
94 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
95 self.bindings
96 }
97
98 pub fn len(&self) -> PdfPageIndex {
100 self.bindings.FPDF_GetPageCount(self.document_handle) as PdfPageIndex
101 }
102
103 #[inline]
105 pub fn is_empty(&self) -> bool {
106 self.len() == 0
107 }
108
109 #[inline]
111 pub fn as_range(&self) -> Range<PdfPageIndex> {
112 0..self.len()
113 }
114
115 #[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 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 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 #[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 #[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 #[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 #[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 #[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 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 #[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 #[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 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 #[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 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 #[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 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 #[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 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 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 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 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 #[inline]
544 pub fn iter(&self) -> PdfPagesIterator<'_> {
545 PdfPagesIterator::new(self)
546 }
547}
548
549pub 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}