Skip to main content

pdfium_render/pdf/
document.rs

1//! Defines the [PdfDocument] struct, the entry point to all Pdfium functionality
2//! related to a single PDF file.
3
4pub mod attachment;
5pub mod attachments;
6pub mod bookmark;
7pub mod bookmarks;
8pub mod catalog;
9pub mod fonts;
10pub mod form;
11pub mod metadata;
12pub mod page;
13pub mod pages;
14pub mod permissions;
15pub mod signature;
16pub mod signatures;
17
18use crate::bindgen::FPDF_DOCUMENT;
19use crate::error::PdfiumError;
20use crate::error::PdfiumInternalError;
21use crate::pdf::document::attachments::PdfAttachments;
22use crate::pdf::document::bookmarks::PdfBookmarks;
23use crate::pdf::document::catalog::PdfCatalog;
24use crate::pdf::document::fonts::PdfFonts;
25use crate::pdf::document::form::PdfForm;
26use crate::pdf::document::metadata::PdfMetadata;
27use crate::pdf::document::pages::PdfPages;
28use crate::pdf::document::permissions::PdfPermissions;
29use crate::pdf::document::signatures::PdfSignatures;
30use crate::pdfium::PdfiumLibraryBindingsAccessor;
31use crate::utils::files::get_pdfium_file_writer_from_writer;
32use crate::utils::files::FpdfFileAccessExt;
33use std::fmt::{Debug, Formatter};
34use std::io::Cursor;
35use std::io::Write;
36
37#[cfg(not(target_arch = "wasm32"))]
38use std::fs::File;
39
40use std::marker::PhantomData;
41#[cfg(not(target_arch = "wasm32"))]
42use std::path::Path;
43
44#[cfg(target_arch = "wasm32")]
45use js_sys::{Array, Uint8Array};
46
47#[cfg(target_arch = "wasm32")]
48use wasm_bindgen::JsValue;
49
50#[cfg(target_arch = "wasm32")]
51use web_sys::Blob;
52
53// The following dummy declaration is used only when running cargo doc.
54// It allows documentation of WASM-specific functionality to be included
55// in documentation generated on non-WASM targets.
56
57#[cfg(doc)]
58struct Blob;
59
60/// The file version of a [PdfDocument].
61///
62/// A list of PDF file versions is available at <https://en.wikipedia.org/wiki/History_of_PDF>.
63#[derive(Debug, Copy, Clone, PartialEq)]
64pub enum PdfDocumentVersion {
65    /// No version information is available. This is the case if the [PdfDocument]
66    /// was created via a call to `Pdfium::create_new_pdf()` rather than loaded from a file.
67    Unset,
68
69    /// PDF 1.0, first published in 1993, supported by Acrobat Reader Carousel (1.0) onwards.
70    Pdf1_0,
71
72    /// PDF 1.1, first published in 1994, supported by Acrobat Reader 2.0 onwards.
73    Pdf1_1,
74
75    /// PDF 1.2, first published in 1996, supported by Acrobat Reader 3.0 onwards.
76    Pdf1_2,
77
78    /// PDF 1.3, first published in 2000, supported by Acrobat Reader 4.0 onwards.
79    Pdf1_3,
80
81    /// PDF 1.4, first published in 2001, supported by Acrobat Reader 5.0 onwards.
82    Pdf1_4,
83
84    /// PDF 1.5, first published in 2003, supported by Acrobat Reader 6.0 onwards.
85    Pdf1_5,
86
87    /// PDF 1.6, first published in 2004, supported by Acrobat Reader 7.0 onwards.
88    Pdf1_6,
89
90    /// PDF 1.7, first published in 2006, supported by Acrobat Reader 8.0 onwards,
91    /// adopted as ISO open standard 32000-1 in 2008. Certain proprietary Adobe
92    /// extensions to PDF 1.7 are only fully supported in Acrobat Reader X (10.0)
93    /// and later.
94    Pdf1_7,
95
96    /// PDF 2.0, first published in 2017, ISO open standard 32000-2.
97    Pdf2_0,
98
99    /// A two-digit raw file version number. For instance, a value of 21 would indicate
100    /// PDF version 2.1, a value of 34 would indicate PDF version 3.4, and so on.
101    /// Only used when the file version number is not directly recognized by
102    /// pdfium-render.
103    Other(i32),
104}
105
106impl PdfDocumentVersion {
107    /// The default [PdfDocumentVersion] applied to new documents.
108    pub const DEFAULT_VERSION: PdfDocumentVersion = PdfDocumentVersion::Pdf1_7;
109
110    #[inline]
111    pub(crate) fn from_pdfium(version: i32) -> Self {
112        match version {
113            10 => PdfDocumentVersion::Pdf1_0,
114            11 => PdfDocumentVersion::Pdf1_1,
115            12 => PdfDocumentVersion::Pdf1_2,
116            13 => PdfDocumentVersion::Pdf1_3,
117            14 => PdfDocumentVersion::Pdf1_4,
118            15 => PdfDocumentVersion::Pdf1_5,
119            16 => PdfDocumentVersion::Pdf1_6,
120            17 => PdfDocumentVersion::Pdf1_7,
121            20 => PdfDocumentVersion::Pdf2_0,
122            _ => PdfDocumentVersion::Other(version),
123        }
124    }
125
126    #[inline]
127    pub(crate) fn as_pdfium(&self) -> Option<i32> {
128        match self {
129            PdfDocumentVersion::Pdf1_0 => Some(10),
130            PdfDocumentVersion::Pdf1_1 => Some(11),
131            PdfDocumentVersion::Pdf1_2 => Some(12),
132            PdfDocumentVersion::Pdf1_3 => Some(13),
133            PdfDocumentVersion::Pdf1_4 => Some(14),
134            PdfDocumentVersion::Pdf1_5 => Some(15),
135            PdfDocumentVersion::Pdf1_6 => Some(16),
136            PdfDocumentVersion::Pdf1_7 => Some(17),
137            PdfDocumentVersion::Pdf2_0 => Some(20),
138            PdfDocumentVersion::Other(value) => Some(*value),
139            PdfDocumentVersion::Unset => None,
140        }
141    }
142}
143
144/// An entry point to all the various object collections contained in a single PDF file.
145/// These collections include:
146/// * [PdfDocument::attachments()], an immutable collection of all the [PdfAttachments] in the document.
147/// * [PdfDocument::attachments_mut()], a mutable collection of all the [PdfAttachments] in the document.
148/// * [PdfDocument::bookmarks()], an immutable collection of all the [PdfBookmarks] in the document.
149/// * [PdfDocument::fonts()], an immutable collection of all the [PdfFonts] in the document.
150/// * [PdfDocument::fonts_mut()], a mutable collection of all the [PdfFonts] in the document.
151/// * [PdfDocument::form()], an immutable reference to the [PdfForm] embedded in the document, if any.
152/// * [PdfDocument::metadata()], an immutable collection of all the [PdfMetadata] tags in the document.
153/// * [PdfDocument::pages()], an immutable collection of all the [PdfPages] in the document.
154/// * [PdfDocument::pages_mut()], a mutable collection of all the [PdfPages] in the document.
155/// * [PdfDocument::permissions()], settings relating to security handlers and document permissions
156///   for the document.
157/// * [PdfDocument::signatures()], an immutable collection of all the [PdfSignatures] in the document.
158pub struct PdfDocument<'a> {
159    handle: FPDF_DOCUMENT,
160    output_version: Option<PdfDocumentVersion>,
161    attachments: PdfAttachments<'a>,
162    bookmarks: PdfBookmarks<'a>,
163    catalog: PdfCatalog<'a>,
164    form: Option<PdfForm<'a>>,
165    fonts: PdfFonts<'a>,
166    metadata: PdfMetadata<'a>,
167    pages: PdfPages<'a>,
168    permissions: PdfPermissions<'a>,
169    signatures: PdfSignatures<'a>,
170    source_byte_buffer: Option<Vec<u8>>,
171
172    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
173    // This field is never used when compiling to WASM.
174    file_access_reader: Option<Box<FpdfFileAccessExt<'a>>>,
175
176    lifetime: PhantomData<&'a FPDF_DOCUMENT>,
177}
178
179impl<'a> PdfDocument<'a> {
180    #[inline]
181    pub(crate) fn from_pdfium(handle: FPDF_DOCUMENT) -> Self {
182        let form = PdfForm::from_pdfium(handle);
183
184        let pages = PdfPages::from_pdfium(handle, form.as_ref().map(|form| form.handle()));
185
186        PdfDocument {
187            handle,
188            output_version: None,
189            attachments: PdfAttachments::from_pdfium(handle),
190            bookmarks: PdfBookmarks::from_pdfium(handle),
191            catalog: PdfCatalog::from_pdfium(handle),
192            form,
193            fonts: PdfFonts::from_pdfium(handle),
194            metadata: PdfMetadata::from_pdfium(handle),
195            pages,
196            permissions: PdfPermissions::from_pdfium(handle),
197            signatures: PdfSignatures::from_pdfium(handle),
198            source_byte_buffer: None,
199            file_access_reader: None,
200            lifetime: PhantomData,
201        }
202    }
203
204    /// Returns the internal `FPDF_DOCUMENT` handle for this [PdfDocument].
205    #[inline]
206    pub(crate) fn handle(&self) -> FPDF_DOCUMENT {
207        self.handle
208    }
209
210    /// Transfers ownership of the byte buffer containing the binary data of this [PdfDocument],
211    /// so that it will always be available for Pdfium to read data from as needed.
212    #[inline]
213    pub(crate) fn set_source_byte_buffer(&mut self, bytes: Vec<u8>) {
214        self.source_byte_buffer = Some(bytes);
215    }
216
217    /// Binds an `FPDF_FILEACCESS` reader to the lifetime of this [PdfDocument], so that
218    /// it will always be available for Pdfium to read data from as needed.
219    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
220    // This function is never used when compiling to WASM.
221    #[inline]
222    pub(crate) fn set_file_access_reader(&mut self, reader: Box<FpdfFileAccessExt<'a>>) {
223        self.file_access_reader = Some(reader);
224    }
225
226    /// Returns the file version of this [PdfDocument].
227    pub fn version(&self) -> PdfDocumentVersion {
228        let mut version = 0;
229
230        if unsafe {
231            self.bindings()
232                .FPDF_GetFileVersion(self.handle, &mut version)
233        } != 0
234        {
235            PdfDocumentVersion::from_pdfium(version)
236        } else {
237            PdfDocumentVersion::Unset
238        }
239    }
240
241    /// Sets the file version that will be used the next time this [PdfDocument] is saved.
242    pub fn set_version(&mut self, version: PdfDocumentVersion) {
243        self.output_version = Some(version);
244    }
245
246    /// Returns an immutable collection of all the [PdfAttachments] embedded in this [PdfDocument].
247    #[inline]
248    pub fn attachments(&self) -> &PdfAttachments<'_> {
249        &self.attachments
250    }
251
252    /// Returns a mutable collection of all the [PdfAttachments] embedded in this [PdfDocument].
253    #[inline]
254    pub fn attachments_mut(&mut self) -> &mut PdfAttachments<'a> {
255        &mut self.attachments
256    }
257
258    /// Returns an immutable collection of all the [PdfBookmarks] in this [PdfDocument].
259    #[inline]
260    pub fn bookmarks(&self) -> &PdfBookmarks<'_> {
261        &self.bookmarks
262    }
263
264    /// Returns an immutable reference to the [PdfCatalog] properties for this [PdfDocument].
265    #[inline]
266    pub fn catalog(&self) -> &PdfCatalog<'_> {
267        &self.catalog
268    }
269
270    /// Returns a mutable reference to the [PdfCatalog] properties for this [PdfDocument].
271    #[inline]
272    pub fn catalog_mut(&mut self) -> &mut PdfCatalog<'a> {
273        &mut self.catalog
274    }
275
276    /// Returns an immutable reference to the [PdfForm] embedded in this [PdfDocument], if any.
277    #[inline]
278    pub fn form(&self) -> Option<&PdfForm<'_>> {
279        self.form.as_ref()
280    }
281
282    /// Returns an immutable collection of all the [PdfFonts] in this [PdfDocument].
283    #[inline]
284    pub fn fonts(&self) -> &PdfFonts<'_> {
285        &self.fonts
286    }
287
288    /// Returns a mutable collection of all the [PdfFonts] in this [PdfDocument].
289    #[inline]
290    pub fn fonts_mut(&mut self) -> &mut PdfFonts<'a> {
291        &mut self.fonts
292    }
293
294    /// Returns an immutable collection of all the [PdfMetadata] tags in this [PdfDocument].
295    #[inline]
296    pub fn metadata(&self) -> &PdfMetadata<'_> {
297        &self.metadata
298    }
299
300    /// Returns an immutable collection of all the [PdfPages] in this [PdfDocument].
301    #[inline]
302    pub fn pages(&self) -> &PdfPages<'a> {
303        &self.pages
304    }
305
306    /// Returns a mutable collection of all the [PdfPages] in this [PdfDocument].
307    #[inline]
308    pub fn pages_mut(&mut self) -> &mut PdfPages<'a> {
309        &mut self.pages
310    }
311
312    /// Returns an immutable collection of all the [PdfPermissions] applied to this [PdfDocument].
313    #[inline]
314    pub fn permissions(&self) -> &PdfPermissions<'_> {
315        &self.permissions
316    }
317
318    /// Returns an immutable collection of all the [PdfSignatures] attached to this [PdfDocument].
319    #[inline]
320    pub fn signatures(&self) -> &PdfSignatures<'_> {
321        &self.signatures
322    }
323
324    /// Writes this [PdfDocument] to the given writer.
325    ///
326    /// If the given writer implementation itself calls Pdfium functions, then it will block
327    /// when used in conjunction with this crate's `thread_safe` feature.
328    pub fn save_to_writer<W: Write + 'static>(&self, writer: &mut W) -> Result<(), PdfiumError> {
329        // TODO: AJRC - 25/5/22 - investigate supporting the FPDF_INCREMENTAL, FPDF_NO_INCREMENTAL,
330        // and FPDF_REMOVE_SECURITY flags defined in fpdf_save.h. There's not a lot of information
331        // on what they actually do, however.
332        // Some small info at https://forum.patagames.com/posts/t155-PDF-SaveFlags.
333
334        let flags = 0;
335
336        let mut pdfium_file_writer = get_pdfium_file_writer_from_writer(writer);
337
338        let result = match self.output_version {
339            Some(version) => unsafe {
340                self.bindings().FPDF_SaveWithVersion(
341                    self.handle,
342                    pdfium_file_writer.as_fpdf_file_write_mut_ptr(),
343                    flags,
344                    version.as_pdfium().unwrap_or_else(|| {
345                        PdfDocumentVersion::DEFAULT_VERSION.as_pdfium().unwrap()
346                    }),
347                )
348            },
349            None => unsafe {
350                self.bindings().FPDF_SaveAsCopy(
351                    self.handle,
352                    pdfium_file_writer.as_fpdf_file_write_mut_ptr(),
353                    flags,
354                )
355            },
356        };
357
358        match self.bindings().is_true(result) {
359            true => {
360                // Pdfium's return value indicated success. Flush the buffer.
361
362                pdfium_file_writer.flush().map_err(PdfiumError::IoError)
363            }
364            false => {
365                // Pdfium's return value indicated failure.
366
367                Err(PdfiumError::PdfiumLibraryInternalError(
368                    PdfiumInternalError::Unknown,
369                ))
370            }
371        }
372    }
373
374    /// Writes this [PdfDocument] to the file at the given path.
375    ///
376    /// This function is not available when compiling to WASM. You have several options for
377    /// saving your PDF document data in WASM:
378    /// * Use either the [PdfDocument::save_to_writer()] or the [PdfDocument::save_to_bytes()] functions,
379    ///   both of which are available when compiling to WASM.
380    /// * Use the [PdfDocument::save_to_blob()] function to save document data directly into a new
381    ///   Javascript `Blob` object. This function is only available when compiling to WASM.
382    #[cfg(not(target_arch = "wasm32"))]
383    pub fn save_to_file(&self, path: &(impl AsRef<Path> + ?Sized)) -> Result<(), PdfiumError> {
384        self.save_to_writer(&mut File::create(path).map_err(PdfiumError::IoError)?)
385    }
386
387    /// Writes this [PdfDocument] to a new byte buffer, returning the byte buffer.
388    pub fn save_to_bytes(&self) -> Result<Vec<u8>, PdfiumError> {
389        let mut cursor = Cursor::new(Vec::new());
390
391        self.save_to_writer(&mut cursor)?;
392
393        Ok(cursor.into_inner())
394    }
395
396    /// Writes this [PdfDocument] to a new `Blob`, returning the `Blob`.
397    ///
398    /// This function is only available when compiling to WASM.
399    #[cfg(any(doc, target_arch = "wasm32"))]
400    pub fn save_to_blob(&self) -> Result<Blob, PdfiumError> {
401        let bytes = self.save_to_bytes()?;
402
403        let array = Uint8Array::new_with_length(bytes.len() as u32);
404
405        array.copy_from(bytes.as_slice());
406
407        let blob =
408            Blob::new_with_u8_array_sequence(&JsValue::from(Array::of1(&JsValue::from(array))))
409                .map_err(|_| PdfiumError::JsSysErrorConstructingBlobFromBytes)?;
410
411        Ok(blob)
412    }
413}
414
415impl<'a> Drop for PdfDocument<'a> {
416    /// Closes this [PdfDocument], releasing held memory and, if the document was loaded
417    /// from a file, the file handle on the document.
418    #[inline]
419    fn drop(&mut self) {
420        // Drop this document's PdfForm, if any, before we close the document itself.
421        // This ensures that FPDFDOC_ExitFormFillEnvironment() is called _before_ FPDF_CloseDocument(),
422        // avoiding a segmentation fault when using Pdfium builds compiled with V8/XFA support.
423
424        self.form = None;
425
426        // Close all fonts loaded into this document's font registry _before_ closing the
427        // document itself. Struct fields are dropped only _after_ this Drop impl runs, so
428        // without this, each font's FPDFFont_Close() would execute against an already-closed
429        // document, reading freed memory inside Pdfium and corrupting the process heap.
430        self.fonts.clear();
431
432        unsafe {
433            self.bindings().FPDF_CloseDocument(self.handle);
434        }
435    }
436}
437
438impl<'a> Debug for PdfDocument<'a> {
439    #[inline]
440    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
441        f.debug_struct("PdfDocument")
442            .field("FPDF_DOCUMENT", &format!("{:?}", self.handle))
443            .finish()
444    }
445}
446
447impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfDocument<'a> {}
448
449#[cfg(feature = "thread_safe")]
450unsafe impl<'a> Send for PdfDocument<'a> {}
451
452#[cfg(feature = "thread_safe")]
453unsafe impl<'a> Sync for PdfDocument<'a> {}