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 fonts;
5pub mod form;
6pub mod metadata;
7pub mod page;
8pub mod pages;
9pub mod permissions;
10
11use crate::bindgen::FPDF_DOCUMENT;
12use crate::bindings::PdfiumLibraryBindings;
13use crate::error::PdfiumError;
14use crate::error::PdfiumInternalError;
15use crate::pdf::document::fonts::PdfFonts;
16use crate::pdf::document::form::PdfForm;
17use crate::pdf::document::metadata::PdfMetadata;
18use crate::pdf::document::page::index_cache::PdfPageIndexCache;
19use crate::pdf::document::pages::PdfPages;
20use crate::pdf::document::permissions::PdfPermissions;
21use crate::utils::files::FpdfFileAccessExt;
22use crate::utils::files::get_pdfium_file_writer_from_writer;
23use std::fmt::{Debug, Formatter};
24use std::io::Cursor;
25use std::io::Write;
26
27#[cfg(not(target_arch = "wasm32"))]
28use std::fs::File;
29
30#[cfg(not(target_arch = "wasm32"))]
31use std::path::Path;
32
33#[cfg(target_arch = "wasm32")]
34use js_sys::{Array, Uint8Array};
35
36#[cfg(target_arch = "wasm32")]
37use wasm_bindgen::JsValue;
38
39#[cfg(target_arch = "wasm32")]
40use web_sys::Blob;
41
42#[cfg(doc)]
43struct Blob;
44
45/// The file version of a [PdfDocument].
46///
47/// A list of PDF file versions is available at <https://en.wikipedia.org/wiki/History_of_PDF>.
48#[derive(Debug, Copy, Clone, PartialEq)]
49pub enum PdfDocumentVersion {
50    /// No version information is available. This is the case if the [PdfDocument]
51    /// was created via a call to `Pdfium::create_new_pdf()` rather than loaded from a file.
52    Unset,
53
54    /// PDF 1.0, first published in 1993, supported by Acrobat Reader Carousel (1.0) onwards.
55    Pdf1_0,
56
57    /// PDF 1.1, first published in 1994, supported by Acrobat Reader 2.0 onwards.
58    Pdf1_1,
59
60    /// PDF 1.2, first published in 1996, supported by Acrobat Reader 3.0 onwards.
61    Pdf1_2,
62
63    /// PDF 1.3, first published in 2000, supported by Acrobat Reader 4.0 onwards.
64    Pdf1_3,
65
66    /// PDF 1.4, first published in 2001, supported by Acrobat Reader 5.0 onwards.
67    Pdf1_4,
68
69    /// PDF 1.5, first published in 2003, supported by Acrobat Reader 6.0 onwards.
70    Pdf1_5,
71
72    /// PDF 1.6, first published in 2004, supported by Acrobat Reader 7.0 onwards.
73    Pdf1_6,
74
75    /// PDF 1.7, first published in 2006, supported by Acrobat Reader 8.0 onwards,
76    /// adopted as ISO open standard 32000-1 in 2008. Certain proprietary Adobe
77    /// extensions to PDF 1.7 are only fully supported in Acrobat Reader X (10.0)
78    /// and later.
79    Pdf1_7,
80
81    /// PDF 2.0, first published in 2017, ISO open standard 32000-2.
82    Pdf2_0,
83
84    /// A two-digit raw file version number. For instance, a value of 21 would indicate
85    /// PDF version 2.1, a value of 34 would indicate PDF version 3.4, and so on.
86    /// Only used when the file version number is not directly recognized by
87    /// pdfium-render.
88    Other(i32),
89}
90
91impl PdfDocumentVersion {
92    /// The default [PdfDocumentVersion] applied to new documents.
93    pub const DEFAULT_VERSION: PdfDocumentVersion = PdfDocumentVersion::Pdf1_7;
94
95    #[inline]
96    pub(crate) fn from_pdfium(version: i32) -> Self {
97        match version {
98            10 => PdfDocumentVersion::Pdf1_0,
99            11 => PdfDocumentVersion::Pdf1_1,
100            12 => PdfDocumentVersion::Pdf1_2,
101            13 => PdfDocumentVersion::Pdf1_3,
102            14 => PdfDocumentVersion::Pdf1_4,
103            15 => PdfDocumentVersion::Pdf1_5,
104            16 => PdfDocumentVersion::Pdf1_6,
105            17 => PdfDocumentVersion::Pdf1_7,
106            20 => PdfDocumentVersion::Pdf2_0,
107            _ => PdfDocumentVersion::Other(version),
108        }
109    }
110
111    #[inline]
112    pub(crate) fn as_pdfium(&self) -> Option<i32> {
113        match self {
114            PdfDocumentVersion::Pdf1_0 => Some(10),
115            PdfDocumentVersion::Pdf1_1 => Some(11),
116            PdfDocumentVersion::Pdf1_2 => Some(12),
117            PdfDocumentVersion::Pdf1_3 => Some(13),
118            PdfDocumentVersion::Pdf1_4 => Some(14),
119            PdfDocumentVersion::Pdf1_5 => Some(15),
120            PdfDocumentVersion::Pdf1_6 => Some(16),
121            PdfDocumentVersion::Pdf1_7 => Some(17),
122            PdfDocumentVersion::Pdf2_0 => Some(20),
123            PdfDocumentVersion::Other(value) => Some(*value),
124            PdfDocumentVersion::Unset => None,
125        }
126    }
127}
128
129/// An entry point to all the various object collections contained in a single PDF file.
130/// These collections include:
131/// * [PdfDocument::fonts()], an immutable collection of all the [PdfFonts] in the document.
132/// * [PdfDocument::fonts_mut()], a mutable collection of all the [PdfFonts] in the document.
133/// * [PdfDocument::form()], an immutable reference to the [PdfForm] embedded in the document, if any.
134/// * [PdfDocument::metadata()], an immutable collection of all the [PdfMetadata] tags in the document.
135/// * [PdfDocument::pages()], an immutable collection of all the [PdfPages] in the document.
136/// * [PdfDocument::pages_mut()], a mutable collection of all the [PdfPages] in the document.
137/// * [PdfDocument::permissions()], settings relating to security handlers and document permissions
138///   for the document.
139pub struct PdfDocument<'a> {
140    handle: FPDF_DOCUMENT,
141    output_version: Option<PdfDocumentVersion>,
142    form: Option<PdfForm<'a>>,
143    fonts: PdfFonts<'a>,
144    metadata: PdfMetadata<'a>,
145    pages: PdfPages<'a>,
146    permissions: PdfPermissions<'a>,
147    bindings: &'a dyn PdfiumLibraryBindings,
148    source_byte_buffer: Option<Vec<u8>>,
149
150    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
151    file_access_reader: Option<Box<FpdfFileAccessExt<'a>>>,
152}
153
154impl<'a> PdfDocument<'a> {
155    #[inline]
156    pub(crate) fn from_pdfium(handle: FPDF_DOCUMENT, bindings: &'a dyn PdfiumLibraryBindings) -> Self {
157        let form = PdfForm::from_pdfium(handle, bindings);
158
159        let pages = PdfPages::from_pdfium(handle, form.as_ref().map(|form| form.handle()), bindings);
160
161        PdfDocument {
162            handle,
163            output_version: None,
164            form,
165            fonts: PdfFonts::from_pdfium(handle, bindings),
166            metadata: PdfMetadata::from_pdfium(handle, bindings),
167            pages,
168            permissions: PdfPermissions::from_pdfium(handle, bindings),
169            bindings,
170            source_byte_buffer: None,
171            file_access_reader: None,
172        }
173    }
174
175    /// Returns the internal `FPDF_DOCUMENT` handle for this [PdfDocument].
176    #[inline]
177    pub(crate) fn handle(&self) -> FPDF_DOCUMENT {
178        self.handle
179    }
180
181    /// Returns the [PdfiumLibraryBindings] used by this [PdfDocument].
182    #[inline]
183    pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
184        self.bindings
185    }
186
187    /// Transfers ownership of the byte buffer containing the binary data of this [PdfDocument],
188    /// so that it will always be available for Pdfium to read data from as needed.
189    #[inline]
190    pub(crate) fn set_source_byte_buffer(&mut self, bytes: Vec<u8>) {
191        self.source_byte_buffer = Some(bytes);
192    }
193
194    /// Binds an `FPDF_FILEACCESS` reader to the lifetime of this [PdfDocument], so that
195    /// it will always be available for Pdfium to read data from as needed.
196    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
197    #[inline]
198    pub(crate) fn set_file_access_reader(&mut self, reader: Box<FpdfFileAccessExt<'a>>) {
199        self.file_access_reader = Some(reader);
200    }
201
202    /// Returns the file version of this [PdfDocument].
203    pub fn version(&self) -> PdfDocumentVersion {
204        let mut version = 0;
205
206        if self.bindings.FPDF_GetFileVersion(self.handle, &mut version) != 0 {
207            PdfDocumentVersion::from_pdfium(version)
208        } else {
209            PdfDocumentVersion::Unset
210        }
211    }
212
213    /// Sets the file version that will be used the next time this [PdfDocument] is saved.
214    pub fn set_version(&mut self, version: PdfDocumentVersion) {
215        self.output_version = Some(version);
216    }
217
218    /// Returns `true` if this [PdfDocument] is a tagged PDF.
219    pub fn is_tagged(&self) -> bool {
220        self.bindings.is_true(self.bindings.FPDFCatalog_IsTagged(self.handle))
221    }
222
223    /// Returns an immutable reference to the [PdfForm] embedded in this [PdfDocument], if any.
224    #[inline]
225    pub fn form(&self) -> Option<&PdfForm<'_>> {
226        self.form.as_ref()
227    }
228
229    /// Returns an immutable collection of all the [PdfFonts] in this [PdfDocument].
230    #[inline]
231    pub fn fonts(&self) -> &PdfFonts<'_> {
232        &self.fonts
233    }
234
235    /// Returns a mutable collection of all the [PdfFonts] in this [PdfDocument].
236    #[inline]
237    pub fn fonts_mut(&mut self) -> &mut PdfFonts<'a> {
238        &mut self.fonts
239    }
240
241    /// Returns an immutable collection of all the [PdfMetadata] tags in this [PdfDocument].
242    #[inline]
243    pub fn metadata(&self) -> &PdfMetadata<'_> {
244        &self.metadata
245    }
246
247    /// Returns an immutable collection of all the [PdfPages] in this [PdfDocument].
248    #[inline]
249    pub fn pages(&self) -> &PdfPages<'a> {
250        &self.pages
251    }
252
253    /// Returns a mutable collection of all the [PdfPages] in this [PdfDocument].
254    #[inline]
255    pub fn pages_mut(&mut self) -> &mut PdfPages<'a> {
256        &mut self.pages
257    }
258
259    /// Returns an immutable collection of all the [PdfPermissions] applied to this [PdfDocument].
260    #[inline]
261    pub fn permissions(&self) -> &PdfPermissions<'_> {
262        &self.permissions
263    }
264
265    /// Writes this [PdfDocument] to the given writer.
266    pub fn save_to_writer<W: Write + 'static>(&self, writer: &mut W) -> Result<(), PdfiumError> {
267        // ~keep TODO: AJRC - 25/5/22 - investigate supporting the FPDF_INCREMENTAL, FPDF_NO_INCREMENTAL,
268        // ~keep and FPDF_REMOVE_SECURITY flags defined in fpdf_save.h. There's not a lot of information
269        // ~keep on what they actually do, however.
270        // ~keep Some small info at https://forum.patagames.com/posts/t155-PDF-SaveFlags.
271
272        let flags = 0;
273
274        let mut pdfium_file_writer = get_pdfium_file_writer_from_writer(writer);
275
276        let result = match self.output_version {
277            Some(version) => self.bindings.FPDF_SaveWithVersion(
278                self.handle,
279                pdfium_file_writer.as_fpdf_file_write_mut_ptr(),
280                flags,
281                version
282                    .as_pdfium()
283                    .unwrap_or_else(|| PdfDocumentVersion::DEFAULT_VERSION.as_pdfium().unwrap()),
284            ),
285            None => self
286                .bindings
287                .FPDF_SaveAsCopy(self.handle, pdfium_file_writer.as_fpdf_file_write_mut_ptr(), flags),
288        };
289
290        match self.bindings.is_true(result) {
291            true => pdfium_file_writer.flush().map_err(PdfiumError::IoError),
292            false => Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown)),
293        }
294    }
295
296    /// Writes this [PdfDocument] to the file at the given path.
297    ///
298    /// This function is not available when compiling to WASM. You have several options for
299    /// saving your PDF document data in WASM:
300    /// * Use either the [PdfDocument::save_to_writer()] or the [PdfDocument::save_to_bytes()] functions,
301    ///   both of which are available when compiling to WASM.
302    /// * Use the [PdfDocument::save_to_blob()] function to save document data directly into a new
303    ///   Javascript `Blob` object. This function is only available when compiling to WASM.
304    #[cfg(not(target_arch = "wasm32"))]
305    pub fn save_to_file(&self, path: &(impl AsRef<Path> + ?Sized)) -> Result<(), PdfiumError> {
306        self.save_to_writer(&mut File::create(path).map_err(PdfiumError::IoError)?)
307    }
308
309    /// Writes this [PdfDocument] to a new byte buffer, returning the byte buffer.
310    pub fn save_to_bytes(&self) -> Result<Vec<u8>, PdfiumError> {
311        let mut cursor = Cursor::new(Vec::new());
312
313        self.save_to_writer(&mut cursor)?;
314
315        Ok(cursor.into_inner())
316    }
317
318    /// Writes this [PdfDocument] to a new `Blob`, returning the `Blob`.
319    ///
320    /// This function is only available when compiling to WASM.
321    #[cfg(any(doc, target_arch = "wasm32"))]
322    pub fn save_to_blob(&self) -> Result<Blob, PdfiumError> {
323        let bytes = self.save_to_bytes()?;
324
325        let array = Uint8Array::new_with_length(bytes.len() as u32);
326
327        array.copy_from(bytes.as_slice());
328
329        let blob = Blob::new_with_u8_array_sequence(&JsValue::from(Array::of1(&JsValue::from(array))))
330            .map_err(|_| PdfiumError::JsSysErrorConstructingBlobFromBytes)?;
331
332        Ok(blob)
333    }
334}
335
336impl<'a> Drop for PdfDocument<'a> {
337    /// Closes this [PdfDocument], releasing held memory and, if the document was loaded
338    /// from a file, the file handle on the document.
339    #[inline]
340    fn drop(&mut self) {
341        self.form = None;
342
343        PdfPageIndexCache::clear_document(self.handle);
344
345        self.bindings.FPDF_CloseDocument(self.handle);
346    }
347}
348
349impl<'a> Debug for PdfDocument<'a> {
350    #[inline]
351    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
352        f.debug_struct("PdfDocument")
353            .field("FPDF_DOCUMENT", &format!("{:?}", self.handle))
354            .finish()
355    }
356}