1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! Defines the [Pdfium] struct, a high-level idiomatic Rust wrapper around Pdfium.

use crate::bindings::PdfiumLibraryBindings;
use crate::document::PdfDocument;
use crate::error::{PdfiumError, PdfiumInternalError};

#[cfg(not(target_arch = "wasm32"))]
use std::ffi::OsString;

#[cfg(not(target_arch = "wasm32"))]
use libloading::Library;

#[cfg(not(target_arch = "wasm32"))]
use crate::native::NativePdfiumBindings;

#[cfg(target_arch = "wasm32")]
use crate::wasm::WasmPdfiumBindings;

/// A high-level idiomatic Rust wrapper around Pdfium, the C++ PDF library used by
/// the Google Chromium project.
pub struct Pdfium {
    bindings: Box<dyn PdfiumLibraryBindings>,
}

impl Pdfium {
    /// Initializes the external pdfium library, loading it from the system libraries.
    /// Returns a new PdfiumLibraryBindings object that contains bindings to the functions exposed
    /// by the library, or an error if the library could not be loaded.
    #[cfg(not(target_arch = "wasm32"))]
    #[inline]
    pub fn bind_to_system_library() -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError> {
        let bindings = NativePdfiumBindings::new(
            unsafe { Library::new(Self::pdfium_platform_library_name()) }
                .map_err(PdfiumError::LoadLibraryError)?,
        )
        .map_err(PdfiumError::LoadLibraryError)?;

        Ok(Box::new(bindings))
    }

    /// Binds to the external Pdfium WASM module. The Pdfium module must already be
    /// loaded and present in the browser context for binding to be successful.
    /// Returns a new PdfiumLibraryBindings object that contains bindings to the
    /// functions exposed by the Pdfium module. This function will never return an
    /// error; the return type is for compatibility with library binding in native code.
    #[cfg(target_arch = "wasm32")]
    #[inline]
    pub fn bind_to_system_library() -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError> {
        Ok(Box::new(WasmPdfiumBindings::new()))
    }

    /// Initializes the external pdfium library, loading it from the given path.
    /// Returns a new PdfiumLibraryBindings object that contains bindings to the functions
    /// exposed by the library, or an error if the library could not be loaded.
    #[cfg(not(target_arch = "wasm32"))]
    #[inline]
    pub fn bind_to_library(
        path: impl ToString,
    ) -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError> {
        let bindings = NativePdfiumBindings::new(
            unsafe { Library::new(OsString::from(path.to_string())) }
                .map_err(PdfiumError::LoadLibraryError)?,
        )
        .map_err(PdfiumError::LoadLibraryError)?;

        Ok(Box::new(bindings))
    }

    /// Returns the name of the external Pdfium library on the currently running platform.
    /// On Linux and Android, this will be libpdfium.so or similar; on Windows, this will
    /// be pdfium.dll or similar; on MacOS, this will be libpdfium.dylib or similar.
    #[cfg(not(target_arch = "wasm32"))]
    #[inline]
    pub fn pdfium_platform_library_name() -> OsString {
        libloading::library_filename("pdfium")
    }

    /// Returns the name of the external Pdfium library on the currently running platform,
    /// prefixed with the given path string.
    #[cfg(not(target_arch = "wasm32"))]
    #[inline]
    pub fn pdfium_platform_library_name_at_path(path: impl ToString) -> String {
        let mut path = path.to_string();

        path.push_str(Pdfium::pdfium_platform_library_name().to_str().unwrap());

        path
    }

    /// Creates a new Pdfium object from the given external pdfium library bindings.
    #[inline]
    pub fn new(bindings: Box<dyn PdfiumLibraryBindings>) -> Self {
        bindings.FPDF_InitLibrary();

        Self { bindings }
    }

    /// Attempts to open a PdfDocument from the given file path.
    ///
    /// If the document is password protected, the given password will be used
    /// to unlock it.
    ///
    /// This function is not available when compiling to WASM. Either embed the bytes of
    /// the target PDF document directly into the compiled WASM module using the
    /// `include_bytes!()` macro, or use Javascript's `fetch()` API to retrieve the bytes
    /// of the target document over the network, then load those bytes into Pdfium using
    /// the `load_pdf_from_bytes()` function.
    #[cfg(not(target_arch = "wasm32"))]
    pub fn load_pdf_from_file(
        &self,
        path: &str,
        password: Option<&str>,
    ) -> Result<PdfDocument, PdfiumError> {
        self.pdfium_load_document_to_result(self.bindings.FPDF_LoadDocument(path, password))
    }

    /// Attempts to open a PdfDocument from the given byte buffer.
    ///
    /// If the document is password protected, the given password will be used
    /// to unlock it.
    pub fn load_pdf_from_bytes(
        &self,
        bytes: &[u8],
        password: Option<&str>,
    ) -> Result<PdfDocument, PdfiumError> {
        self.pdfium_load_document_to_result(self.bindings.FPDF_LoadMemDocument(bytes, password))
    }

    /// Returns a PdfDocument from the given FPDF_DOCUMENT handle, if possible.
    fn pdfium_load_document_to_result(
        &self,
        handle: crate::bindgen::FPDF_DOCUMENT,
    ) -> Result<PdfDocument, PdfiumError> {
        if handle.is_null() {
            if let Some(error) = self.bindings.get_pdfium_last_error() {
                Err(PdfiumError::PdfiumLibraryInternalError(error))
            } else {
                // This would be an unusual situation; a null handle indicating failure,
                // yet pdfium's error code indicates success.

                Err(PdfiumError::PdfiumLibraryInternalError(
                    PdfiumInternalError::Unknown,
                ))
            }
        } else {
            Ok(PdfDocument::from_pdfium(handle, self.bindings.as_ref()))
        }
    }
}

impl Drop for Pdfium {
    /// Closes the external pdfium library, releasing held memory.
    #[inline]
    fn drop(&mut self) {
        self.bindings.FPDF_DestroyLibrary();
    }
}

impl Default for Pdfium {
    #[inline]
    fn default() -> Self {
        Pdfium::new(Pdfium::bind_to_system_library().unwrap())
    }
}