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