Skip to main content

pdfium_render/
pdfium.rs

1//! Defines the [Pdfium] struct, a high-level idiomatic Rust wrapper around Pdfium.
2
3use crate::bindgen::{
4    FPDF_DOCUMENT, FPDF_ERR_FILE, FPDF_ERR_FORMAT, FPDF_ERR_PAGE, FPDF_ERR_PASSWORD,
5    FPDF_ERR_SECURITY, FPDF_ERR_SUCCESS, FPDF_ERR_UNKNOWN,
6};
7use crate::bindings::PdfiumLibraryBindings;
8use crate::config::PdfiumLibraryConfig;
9use crate::error::{PdfiumError, PdfiumInternalError};
10use crate::pdf::document::{PdfDocument, PdfDocumentVersion};
11use crate::pdf::font::provider::{PdfiumCustomFontProvider, PdfiumCustomFontProviderExt};
12use once_cell::sync::OnceCell;
13use std::fmt::{Debug, Formatter};
14use std::pin::Pin;
15
16#[cfg(all(not(target_arch = "wasm32"), not(feature = "static")))]
17use {
18    crate::bindings::dynamic_bindings::DynamicPdfiumBindings, libloading::Library,
19    std::ffi::OsString, std::path::PathBuf,
20};
21
22#[cfg(all(not(target_arch = "wasm32"), feature = "static"))]
23use crate::bindings::static_bindings::StaticPdfiumBindings;
24
25#[cfg(not(target_arch = "wasm32"))]
26use {
27    crate::bindgen::FPDF_SYSFONTINFO,
28    crate::utils::files::get_pdfium_file_accessor_from_reader,
29    std::fs::File,
30    std::io::{Read, Seek},
31    std::path::Path,
32};
33
34#[cfg(target_arch = "wasm32")]
35use {
36    crate::bindings::wasm_bindings::{PdfiumRenderWasmState, WasmPdfiumBindings},
37    js_sys::{ArrayBuffer, Uint8Array},
38    wasm_bindgen::JsCast,
39    wasm_bindgen_futures::JsFuture,
40    web_sys::{window, Blob, Response},
41};
42
43#[cfg(feature = "thread_safe")]
44use crate::bindings::thread_safe::ThreadSafePdfiumBindings;
45
46// The following dummy declaration is used only when running cargo doc.
47// It allows documentation of WASM-specific functionality to be included
48// in documentation generated on non-WASM targets.
49#[cfg(doc)]
50struct Blob;
51
52#[cfg(all(not(target_arch = "wasm32"), feature = "thread_safe"))]
53/// The trait bound for a thread-safe reader passed to [Pdfium::load_pdf_from_reader].
54pub trait PdfiumReader: Read + Seek + Send {}
55
56#[cfg(all(not(target_arch = "wasm32"), feature = "thread_safe"))]
57impl<R: Read + Seek + Send> PdfiumReader for R {}
58
59#[cfg(all(not(target_arch = "wasm32"), not(feature = "thread_safe")))]
60/// The trait bound for a non-thread-safe reader passed to [Pdfium::load_pdf_from_reader].
61pub trait PdfiumReader: Read + Seek {}
62
63#[cfg(all(not(target_arch = "wasm32"), not(feature = "thread_safe")))]
64impl<R: Read + Seek> PdfiumReader for R {}
65
66// The first instantiation of a Pdfium object will promote a concrete PdfiumLibraryBindings
67// trait implementation into a global static OnceCell. This allows for thread-safe,
68// lifetime-free access to that PdfiumLibraryBindings instance from any object that
69// implements the PdfiumLibraryBindingsAccessor trait.
70static BINDINGS: OnceCell<Box<dyn PdfiumLibraryBindings>> = OnceCell::new();
71
72#[cfg(feature = "thread_safe")]
73pub(crate) trait PdfiumLibraryBindingsAccessor<'a>: Send + Sync {
74    fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
75        BINDINGS.wait().as_ref()
76    }
77}
78
79#[cfg(not(feature = "thread_safe"))]
80pub(crate) trait PdfiumLibraryBindingsAccessor<'a> {
81    fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
82        BINDINGS.get().unwrap().as_ref()
83    }
84}
85
86/// A high-level idiomatic Rust wrapper around Pdfium, the C++ PDF library used by
87/// the Google Chromium project.
88pub struct Pdfium {
89    pub(crate) custom_font_provider: Option<Pin<Box<PdfiumCustomFontProviderExt>>>,
90
91    #[cfg(not(target_arch = "wasm32"))]
92    pub(crate) platform_default_font_provider: Option<*mut FPDF_SYSFONTINFO>,
93}
94
95impl Pdfium {
96    #[cfg(not(target_arch = "wasm32"))]
97    #[cfg(any(doc, feature = "static"))]
98    /// Binds to a Pdfium library that was statically linked into the currently running
99    /// executable, returning a new [PdfiumLibraryBindings] object that contains bindings to the
100    /// functions exposed by the library. The application will immediately crash if Pdfium
101    /// was not correctly statically linked into the executable at compile time.
102    ///
103    /// This function is only available when this crate's `static` feature is enabled.
104    #[inline]
105    pub fn bind_to_statically_linked_library() -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError>
106    {
107        if BINDINGS.get().is_none() {
108            let bindings = StaticPdfiumBindings::new();
109
110            #[cfg(feature = "thread_safe")]
111            let bindings = ThreadSafePdfiumBindings::new(bindings);
112
113            Ok(Box::new(bindings))
114        } else {
115            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized)
116        }
117    }
118
119    #[cfg(not(target_arch = "wasm32"))]
120    #[cfg(not(feature = "static"))]
121    /// Initializes the external Pdfium library, loading it from the system libraries.
122    /// Returns a new [PdfiumLibraryBindings] object that contains bindings to the functions exposed
123    /// by the library, or an error if the library could not be loaded.
124    #[inline]
125    pub fn bind_to_system_library() -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError> {
126        if BINDINGS.get().is_none() {
127            let bindings = DynamicPdfiumBindings::new(
128                unsafe { Library::new(Self::pdfium_platform_library_name()) }
129                    .map_err(PdfiumError::LoadLibraryError)?,
130            )?;
131
132            #[cfg(feature = "thread_safe")]
133            let bindings = ThreadSafePdfiumBindings::new(bindings);
134
135            Ok(Box::new(bindings))
136        } else {
137            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized)
138        }
139    }
140
141    #[cfg(target_arch = "wasm32")]
142    /// Initializes the external Pdfium library, binding to an external WASM module.
143    /// Returns a new [PdfiumLibraryBindings] object that contains bindings to the functions exposed
144    /// by the library, or an error if the library is not available.
145    ///
146    /// It is essential that the exported `initialize_pdfium_render()` function be called
147    /// from Javascript _before_ calling this function from within your Rust code. For an example, see:
148    /// <https://github.com/ajrcarey/pdfium-render/blob/master/examples/index.html>
149    #[inline]
150    pub fn bind_to_system_library() -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError> {
151        if BINDINGS.get().is_none() {
152            if PdfiumRenderWasmState::lock().is_ready() {
153                let bindings = WasmPdfiumBindings::new();
154
155                #[cfg(feature = "thread_safe")]
156                let bindings = ThreadSafePdfiumBindings::new(bindings);
157
158                Ok(Box::new(bindings))
159            } else {
160                Err(PdfiumError::PdfiumWasmModuleNotInitialized)
161            }
162        } else {
163            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized)
164        }
165    }
166
167    #[cfg(not(target_arch = "wasm32"))]
168    #[cfg(not(feature = "static"))]
169    /// Initializes the external pdfium library, loading it from the given path.
170    /// Returns a new [PdfiumLibraryBindings] object that contains bindings to the functions
171    /// exposed by the library, or an error if the library could not be loaded.
172    #[inline]
173    pub fn bind_to_library(
174        path: impl AsRef<Path>,
175    ) -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError> {
176        if BINDINGS.get().is_none() {
177            let bindings = DynamicPdfiumBindings::new(
178                unsafe { Library::new(path.as_ref().as_os_str()) }
179                    .map_err(PdfiumError::LoadLibraryError)?,
180            )?;
181
182            #[cfg(feature = "thread_safe")]
183            let bindings = ThreadSafePdfiumBindings::new(bindings);
184
185            Ok(Box::new(bindings))
186        } else {
187            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized)
188        }
189    }
190
191    #[cfg(not(target_arch = "wasm32"))]
192    #[cfg(not(feature = "static"))]
193    /// Returns the name of the external Pdfium library on the currently running platform.
194    /// On Linux and Android, this will be `libpdfium.so` or similar; on Windows, this will
195    /// be `pdfium.dll` or similar; on MacOS, this will be `libpdfium.dylib` or similar.
196    #[inline]
197    pub fn pdfium_platform_library_name() -> OsString {
198        libloading::library_filename("pdfium")
199    }
200
201    #[cfg(not(target_arch = "wasm32"))]
202    #[cfg(not(feature = "static"))]
203    /// Returns the name of the external Pdfium library on the currently running platform,
204    /// prefixed with the given path string.
205    #[inline]
206    pub fn pdfium_platform_library_name_at_path(path: &(impl AsRef<Path> + ?Sized)) -> PathBuf {
207        path.as_ref().join(Pdfium::pdfium_platform_library_name())
208    }
209
210    /// Creates a new [Pdfium] instance from the given external Pdfium library bindings.
211    #[inline]
212    pub fn new(bindings: Box<dyn PdfiumLibraryBindings>) -> Self {
213        BINDINGS.get_or_init(move || {
214            unsafe {
215                bindings.FPDF_InitLibrary();
216            }
217
218            bindings
219        });
220
221        Self {
222            custom_font_provider: None,
223
224            #[cfg(not(target_arch = "wasm32"))]
225            platform_default_font_provider: None,
226        }
227    }
228
229    /// Creates a new [Pdfium] instance from the given external Pdfium library bindings,
230    /// using the custom library configuration in the given [PdfiumLibraryConfig].
231    #[inline]
232    pub fn new_with_config(
233        bindings: Box<dyn PdfiumLibraryBindings>,
234        mut config: PdfiumLibraryConfig,
235    ) -> Self {
236        BINDINGS.get_or_init(move || {
237            unsafe {
238                bindings.FPDF_InitLibraryWithConfig(&config.as_pdfium());
239            }
240
241            bindings
242        });
243
244        Self {
245            custom_font_provider: None,
246
247            #[cfg(not(target_arch = "wasm32"))]
248            platform_default_font_provider: None,
249        }
250    }
251
252    /// Applies the given custom font provider to this [Pdfium] instance.
253    ///
254    /// If the given custom font provider implementation itself calls Pdfium functions,
255    /// then it will block when used in conjunction with this crate's `thread_safe` feature.
256    pub fn set_custom_font_provider(&mut self, provider: Box<dyn PdfiumCustomFontProvider>) {
257        let mut wrapper = Box::pin(PdfiumCustomFontProviderExt::new(provider));
258
259        unsafe {
260            self.bindings()
261                .FPDF_SetSystemFontInfo(wrapper.as_fpdf_sys_font_info_mut_ptr());
262        }
263
264        self.custom_font_provider = Some(wrapper);
265    }
266
267    /// Clears the currently set font provider, including Pdfium's platform default font provider.
268    pub fn clear_custom_font_provider(&mut self) {
269        unsafe {
270            self.bindings().FPDF_SetSystemFontInfo(std::ptr::null_mut());
271        }
272
273        self.custom_font_provider = None;
274    }
275
276    #[cfg(not(target_arch = "wasm32"))]
277    /// Applies Pdfium's included default font provider for the current platform, if any,
278    /// to this [Pdfium] instance.
279    pub fn use_platform_default_font_provider(&mut self) -> Result<(), PdfiumError> {
280        self.clear_custom_font_provider();
281
282        let platform_default_font_provider =
283            unsafe { self.bindings().FPDF_GetDefaultSystemFontInfo() };
284
285        if !platform_default_font_provider.is_null() {
286            unsafe {
287                self.bindings()
288                    .FPDF_SetSystemFontInfo(platform_default_font_provider);
289            }
290
291            self.platform_default_font_provider = Some(platform_default_font_provider);
292
293            Ok(())
294        } else {
295            Err(PdfiumError::NoPlatformDefaultFontProvider)
296        }
297    }
298
299    #[cfg(target_arch = "wasm32")]
300    /// Applies Pdfium's included default font provider for the current platform, if any,
301    /// to this [Pdfium] instance.
302    ///
303    /// This function will always return a `PdfiumError::NoPlatformDefaultFontProvider` error
304    /// when compiling to WASM, because Pdfium does not include a default platform provider
305    /// implementation for WASM.
306    pub fn use_platform_default_font_provider(&mut self) -> Result<(), PdfiumError> {
307        Err(PdfiumError::NoPlatformDefaultFontProvider)
308    }
309
310    /// Attempts to open a [PdfDocument] from the given static byte buffer.
311    ///
312    /// If the document is password protected, the given password will be used to unlock it.
313    pub fn load_pdf_from_byte_slice<'a>(
314        &'a self,
315        bytes: &'a [u8],
316        password: Option<&str>,
317    ) -> Result<PdfDocument<'a>, PdfiumError> {
318        Self::pdfium_document_handle_to_result(
319            unsafe { self.bindings().FPDF_LoadMemDocument64(bytes, password) },
320            self.bindings(),
321        )
322    }
323
324    /// Attempts to open a [PdfDocument] from the given owned byte buffer.
325    ///
326    /// If the document is password protected, the given password will be used to unlock it.
327    ///
328    /// `pdfium-render` will take ownership of the given byte buffer, ensuring its lifetime lasts
329    /// as long as the [PdfDocument] opened from it.
330    pub fn load_pdf_from_byte_vec(
331        &self,
332        bytes: Vec<u8>,
333        password: Option<&str>,
334    ) -> Result<PdfDocument<'_>, PdfiumError> {
335        Self::pdfium_document_handle_to_result(
336            unsafe {
337                self.bindings()
338                    .FPDF_LoadMemDocument64(bytes.as_slice(), password)
339            },
340            self.bindings(),
341        )
342        .map(|mut document| {
343            // Give the newly-created document ownership of the byte buffer, so that Pdfium can continue
344            // to read from it on an as-needed basis throughout the lifetime of the document.
345
346            document.set_source_byte_buffer(bytes);
347
348            document
349        })
350    }
351
352    #[cfg(not(target_arch = "wasm32"))]
353    /// Attempts to open a [PdfDocument] from the given file path.
354    ///
355    /// If the document is password protected, the given password will be used
356    /// to unlock it.
357    ///
358    /// This function is not available when compiling to WASM. You have several options for
359    /// loading your PDF document data in WASM:
360    /// * Use the [Pdfium::load_pdf_from_fetch()] function to download document data from a
361    ///   URL using the browser's built-in `fetch` API. This function is only available when
362    ///   compiling to WASM.
363    /// * Use the [Pdfium::load_pdf_from_blob()] function to load document data from a
364    ///   Javascript `File` or `Blob` object (such as a `File` object returned from an HTML
365    ///   `<input type="file">` element). This function is only available when compiling to WASM.
366    /// * Use another method to retrieve the bytes of the target document over the network,
367    ///   then load those bytes into Pdfium using either the [Pdfium::load_pdf_from_byte_slice()]
368    ///   function or the [Pdfium::load_pdf_from_byte_vec()] function.
369    /// * Embed the bytes of the target document directly into the compiled WASM module
370    ///   using the `include_bytes!` macro.
371    pub fn load_pdf_from_file<'a>(
372        &'a self,
373        path: &(impl AsRef<Path> + ?Sized),
374        password: Option<&str>,
375    ) -> Result<PdfDocument<'a>, PdfiumError> {
376        self.load_pdf_from_reader(File::open(path).map_err(PdfiumError::IoError)?, password)
377    }
378
379    #[cfg(not(target_arch = "wasm32"))]
380    /// Attempts to open a [PdfDocument] from the given reader.
381    ///
382    /// Pdfium will only load the portions of the document it actually needs into memory.
383    /// This is more efficient than loading the entire document into memory, especially when
384    /// working with large documents, and allows for working with documents larger than the
385    /// amount of available memory.
386    ///
387    /// Because Pdfium must know the total content length in advance prior to loading
388    /// any portion of it, the given reader must implement the [Seek] trait as well as
389    /// the [Read] trait.
390    ///
391    /// If the given reader implementation itself calls Pdfium functions, then it will block
392    /// when used in conjunction with this crate's `thread_safe` feature.
393    ///
394    /// If the document is password protected, the given password will be used
395    /// to unlock it.
396    ///
397    /// This function is not available when compiling to WASM. You have several options for
398    /// loading your PDF document data in WASM:
399    /// * Use the [Pdfium::load_pdf_from_fetch()] function to download document data from a
400    ///   URL using the browser's built-in `fetch` API. This function is only available when
401    ///   compiling to WASM.
402    /// * Use the [Pdfium::load_pdf_from_blob()] function to load document data from a
403    ///   Javascript `File` or `Blob` object (such as a `File` object returned from an HTML
404    ///   `<input type="file">` element). This function is only available when compiling to WASM.
405    /// * Use another method to retrieve the bytes of the target document over the network,
406    ///   then load those bytes into Pdfium using either the [Pdfium::load_pdf_from_byte_slice()]
407    ///   function or the [Pdfium::load_pdf_from_byte_vec()] function.
408    /// * Embed the bytes of the target document directly into the compiled WASM module
409    ///   using the `include_bytes!` macro.
410    pub fn load_pdf_from_reader<'a, R: PdfiumReader + 'a>(
411        &'a self,
412        reader: R,
413        password: Option<&str>,
414    ) -> Result<PdfDocument<'a>, PdfiumError> {
415        let mut reader = get_pdfium_file_accessor_from_reader(reader);
416
417        Pdfium::pdfium_document_handle_to_result(
418            unsafe {
419                self.bindings()
420                    .FPDF_LoadCustomDocument(reader.as_fpdf_file_access_mut_ptr(), password)
421            },
422            self.bindings(),
423        )
424        .map(|mut document| {
425            // Give the newly-created document ownership of the reader, so that Pdfium can continue
426            // to read from it on an as-needed basis throughout the lifetime of the document.
427
428            document.set_file_access_reader(reader);
429
430            document
431        })
432    }
433
434    #[cfg(any(doc, target_arch = "wasm32"))]
435    /// Attempts to open a [PdfDocument] by loading document data from the given URL.
436    /// The Javascript `fetch` API is used to download data over the network.
437    ///
438    /// If the document is password protected, the given password will be used to unlock it.
439    ///
440    /// This function is only available when compiling to WASM.
441    pub async fn load_pdf_from_fetch<'a>(
442        &'a self,
443        url: impl ToString,
444        password: Option<&str>,
445    ) -> Result<PdfDocument<'a>, PdfiumError> {
446        if let Some(window) = window() {
447            let fetch_result = JsFuture::from(window.fetch_with_str(url.to_string().as_str()))
448                .await
449                .map_err(PdfiumError::WebSysFetchError)?;
450
451            debug_assert!(fetch_result.is_instance_of::<Response>());
452
453            let response: Response = fetch_result
454                .dyn_into()
455                .map_err(|_| PdfiumError::WebSysInvalidResponseError)?;
456
457            let blob: Blob =
458                JsFuture::from(response.blob().map_err(PdfiumError::WebSysFetchError)?)
459                    .await
460                    .map_err(PdfiumError::WebSysFetchError)?
461                    .into();
462
463            self.load_pdf_from_blob(blob, password).await
464        } else {
465            Err(PdfiumError::WebSysWindowObjectNotAvailable)
466        }
467    }
468
469    #[cfg(any(doc, target_arch = "wasm32"))]
470    /// Attempts to open a [PdfDocument] by loading document data from the given `Blob`.
471    /// A `File` object returned from a `FileList` is a suitable `Blob`:
472    ///
473    /// ```text
474    /// <input id="filePicker" type="file">
475    ///
476    /// const file = document.getElementById('filePicker').files[0];
477    /// ```
478    ///
479    /// If the document is password protected, the given password will be used to unlock it.
480    ///
481    /// This function is only available when compiling to WASM.
482    pub async fn load_pdf_from_blob<'a>(
483        &'a self,
484        blob: Blob,
485        password: Option<&str>,
486    ) -> Result<PdfDocument<'a>, PdfiumError> {
487        let array_buffer: ArrayBuffer = JsFuture::from(blob.array_buffer())
488            .await
489            .map_err(PdfiumError::WebSysFetchError)?
490            .into();
491
492        let u8_array: Uint8Array = Uint8Array::new(&array_buffer);
493
494        let bytes: Vec<u8> = u8_array.to_vec();
495
496        self.load_pdf_from_byte_vec(bytes, password)
497    }
498
499    /// Creates a new, empty [PdfDocument] in memory.
500    pub fn create_new_pdf<'a>(&'a self) -> Result<PdfDocument<'a>, PdfiumError> {
501        Self::pdfium_document_handle_to_result(
502            unsafe { self.bindings().FPDF_CreateNewDocument() },
503            self.bindings(),
504        )
505        .map(|mut document| {
506            document.set_version(PdfDocumentVersion::DEFAULT_VERSION);
507
508            document
509        })
510    }
511
512    /// Returns a [PdfDocument] from the given `FPDF_DOCUMENT` handle, if possible.
513    pub(crate) fn pdfium_document_handle_to_result(
514        handle: FPDF_DOCUMENT,
515        bindings: &dyn PdfiumLibraryBindings,
516    ) -> Result<PdfDocument<'_>, PdfiumError> {
517        if handle.is_null() {
518            // Retrieve the error code of the last error recorded by Pdfium.
519
520            if let Some(error) = match unsafe { bindings.FPDF_GetLastError() } as u32 {
521                FPDF_ERR_SUCCESS => None,
522                FPDF_ERR_UNKNOWN => Some(PdfiumInternalError::Unknown),
523                FPDF_ERR_FILE => Some(PdfiumInternalError::FileError),
524                FPDF_ERR_FORMAT => Some(PdfiumInternalError::FormatError),
525                FPDF_ERR_PASSWORD => Some(PdfiumInternalError::PasswordError),
526                FPDF_ERR_SECURITY => Some(PdfiumInternalError::SecurityError),
527                FPDF_ERR_PAGE => Some(PdfiumInternalError::PageError),
528                // The Pdfium documentation says "... if the previous SDK call succeeded, [then] the
529                // return value of this function is not defined". On Linux, at least, a return value
530                // of FPDF_ERR_SUCCESS seems to be consistently returned; on Windows, however, the
531                // return values are indeed unpredictable. See https://github.com/ajrcarey/pdfium-render/issues/24.
532                // Therefore, if the return value does not match one of the FPDF_ERR_* constants, we must
533                // assume success.
534                _ => None,
535            } {
536                Err(PdfiumError::PdfiumLibraryInternalError(error))
537            } else {
538                // This would be an unusual situation; a null handle indicating failure,
539                // yet Pdfium's error code indicates success.
540
541                Err(PdfiumError::PdfiumLibraryInternalError(
542                    PdfiumInternalError::Unknown,
543                ))
544            }
545        } else {
546            Ok(PdfDocument::from_pdfium(handle))
547        }
548    }
549}
550
551impl Default for Pdfium {
552    #[cfg(feature = "static")]
553    /// Binds to a Pdfium library that was statically linked into the currently running
554    /// executable by calling [Pdfium::bind_to_statically_linked_library]. This function
555    /// will panic if no statically linked Pdfium functions can be located.
556    #[inline]
557    fn default() -> Self {
558        Pdfium::new(Pdfium::bind_to_statically_linked_library().expect("No Pdfium library found"))
559    }
560
561    #[cfg(not(feature = "static"))]
562    #[cfg(not(target_arch = "wasm32"))]
563    /// Binds to an external Pdfium library by first attempting to bind to a Pdfium library
564    /// in the current working directory; if that fails, then a system-provided library
565    /// will be used as a fall back.
566    ///
567    /// This function will panic if no suitable Pdfium library can be loaded.
568    #[inline]
569    fn default() -> Self {
570        // Attempt to bind to a Pdfium library in the current working directory.
571
572        match Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./")) {
573            Ok(bindings) => Pdfium::new(bindings), // Create new bindings
574            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized) => Pdfium {
575                custom_font_provider: None,
576                platform_default_font_provider: None,
577            }, // Re-use the existing bindings
578            Err(PdfiumError::LoadLibraryError(err)) => {
579                match err {
580                    libloading::Error::DlOpen { .. } => {
581                        // For DlOpen errors specifically, indicating the Pdfium library in the
582                        // current working directory does not exist or is corrupted, we attempt
583                        // to fall back to a system-provided library.
584
585                        Pdfium::new(
586                            Pdfium::bind_to_system_library().expect("No Pdfium library found"),
587                        )
588                    }
589                    _ => Err(PdfiumError::LoadLibraryError(err)).expect("No Pdfium library found"), // Explicitly re-throw the error
590                }
591            }
592            Err(err) => Err(err).expect("No Pdfium library found"), // Explicitly re-throw the error
593        }
594    }
595
596    #[cfg(target_arch = "wasm32")]
597    /// Binds to an external Pdfium library by attempting to a system-provided library.
598    ///
599    /// This function will panic if no suitable Pdfium library can be loaded.
600    fn default() -> Self {
601        Pdfium::new(Pdfium::bind_to_system_library().expect("No Pdfium library found"))
602    }
603}
604
605impl Debug for Pdfium {
606    #[inline]
607    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
608        f.debug_struct("Pdfium").finish()
609    }
610}
611
612#[cfg(not(target_arch = "wasm32"))]
613impl Drop for Pdfium {
614    fn drop(&mut self) {
615        if let Some(ptr) = self.platform_default_font_provider {
616            unsafe {
617                self.bindings().FPDF_FreeDefaultSystemFontInfo(ptr);
618            }
619        }
620    }
621}
622
623impl PdfiumLibraryBindingsAccessor<'_> for Pdfium {}
624
625#[cfg(feature = "thread_safe")]
626unsafe impl Sync for Pdfium {}
627
628#[cfg(feature = "thread_safe")]
629unsafe impl Send for Pdfium {}