1use crate::bindings::PdfiumLibraryBindings;
4use crate::error::{PdfiumError, PdfiumInternalError};
5use crate::font_provider::FontDescriptor;
6use crate::pdf::document::{PdfDocument, PdfDocumentVersion};
7use once_cell::sync::OnceCell;
8use std::ffi::CString;
9use std::fmt::{Debug, Formatter};
10
11#[cfg(all(not(target_arch = "wasm32"), not(pdfium_use_static)))]
12use {
13 crate::bindings::dynamic_bindings::DynamicPdfiumBindings, libloading::Library, std::ffi::OsString,
14 std::path::PathBuf,
15};
16
17#[cfg(all(not(target_arch = "wasm32"), pdfium_use_static))]
18use crate::bindings::static_bindings::StaticPdfiumBindings;
19
20#[cfg(not(target_arch = "wasm32"))]
21use {
22 crate::utils::files::get_pdfium_file_accessor_from_reader,
23 std::fs::File,
24 std::io::{Read, Seek},
25 std::path::Path,
26};
27
28#[cfg(target_arch = "wasm32")]
29use {
30 js_sys::{ArrayBuffer, Uint8Array},
31 wasm_bindgen::JsCast,
32 wasm_bindgen_futures::JsFuture,
33 web_sys::{Blob, Response, window},
34};
35
36#[cfg(doc)]
37struct Blob;
38
39static BINDINGS: OnceCell<Box<dyn PdfiumLibraryBindings>> = OnceCell::new();
40
41#[cfg(feature = "thread_safe")]
42pub(crate) trait PdfiumLibraryBindingsAccessor: Send + Sync {
43 fn bindings(&self) -> &dyn PdfiumLibraryBindings {
44 BINDINGS.wait().as_ref()
45 }
46}
47
48#[cfg(not(feature = "thread_safe"))]
49pub(crate) trait PdfiumLibraryBindingsAccessor {
50 fn bindings(&self) -> &dyn PdfiumLibraryBindings {
51 BINDINGS.get().unwrap().as_ref()
52 }
53}
54
55#[derive(Debug, Default, Clone)]
57pub struct PdfiumConfig {
58 user_font_paths: Option<Vec<String>>,
59 font_provider: Option<Vec<FontDescriptor>>,
60}
61
62impl PdfiumConfig {
63 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn set_user_font_paths(mut self, paths: Vec<String>) -> Self {
72 self.user_font_paths = Some(paths);
73 self
74 }
75
76 pub fn set_font_provider(mut self, fonts: Vec<FontDescriptor>) -> Self {
101 self.font_provider = Some(fonts);
102 self
103 }
104}
105
106#[derive(Clone)]
109pub struct Pdfium;
110
111impl Pdfium {
112 #[cfg(not(target_arch = "wasm32"))]
119 #[cfg(any(doc, pdfium_use_static))]
120 #[inline]
121 pub fn bind_to_statically_linked_library() -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError> {
122 if BINDINGS.get().is_none() {
123 let bindings = StaticPdfiumBindings::new();
124
125 Ok(Box::new(bindings))
126 } else {
127 Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized)
128 }
129 }
130
131 #[cfg(not(target_arch = "wasm32"))]
135 #[cfg(not(pdfium_use_static))]
136 #[inline]
137 pub fn bind_to_system_library() -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError> {
138 if BINDINGS.get().is_none() {
139 let bindings = DynamicPdfiumBindings::new(
140 unsafe { Library::new(Self::pdfium_platform_library_name()) }.map_err(PdfiumError::LoadLibraryError)?,
141 )?;
142
143 Ok(Box::new(bindings))
144 } else {
145 Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized)
146 }
147 }
148
149 #[cfg(not(target_arch = "wasm32"))]
153 #[cfg(not(pdfium_use_static))]
154 #[inline]
155 pub fn bind_to_library(path: impl AsRef<Path>) -> Result<Box<dyn PdfiumLibraryBindings>, PdfiumError> {
156 if BINDINGS.get().is_none() {
157 let bindings = DynamicPdfiumBindings::new(
158 unsafe { Library::new(path.as_ref().as_os_str()) }.map_err(PdfiumError::LoadLibraryError)?,
159 )?;
160
161 Ok(Box::new(bindings))
162 } else {
163 Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized)
164 }
165 }
166
167 #[cfg(not(target_arch = "wasm32"))]
171 #[cfg(not(pdfium_use_static))]
172 #[inline]
173 pub fn pdfium_platform_library_name() -> OsString {
174 libloading::library_filename("pdfium")
175 }
176
177 #[cfg(not(target_arch = "wasm32"))]
180 #[cfg(not(pdfium_use_static))]
181 #[inline]
182 pub fn pdfium_platform_library_name_at_path(path: &(impl AsRef<Path> + ?Sized)) -> PathBuf {
183 path.as_ref().join(Pdfium::pdfium_platform_library_name())
184 }
185
186 #[inline]
188 pub fn new(bindings: Box<dyn PdfiumLibraryBindings>) -> Self {
189 Pdfium::new_with_config(bindings, &PdfiumConfig::default())
190 }
191
192 #[inline]
204 pub fn new_with_config(bindings: Box<dyn PdfiumLibraryBindings>, config: &PdfiumConfig) -> Self {
205 assert!(BINDINGS.get().is_none());
206
207 let has_user_font_paths =
208 config.user_font_paths.is_some() && !config.user_font_paths.as_ref().unwrap().is_empty();
209 let has_font_provider = config.font_provider.is_some() && !config.font_provider.as_ref().unwrap().is_empty();
210
211 if !has_user_font_paths && !has_font_provider {
212 bindings.FPDF_InitLibrary();
213 } else {
214 let mut c_strings = Vec::new();
215 let mut c_ptrs = Vec::new();
216
217 if let Some(paths) = &config.user_font_paths {
218 for path in paths {
219 if let Ok(c_str) = CString::new(path.as_str()) {
220 c_ptrs.push(c_str.as_ptr());
221 c_strings.push(c_str);
222 }
223 }
224 c_ptrs.push(std::ptr::null());
225 }
226
227 let font_paths_ptr = if c_ptrs.is_empty() {
228 std::ptr::null_mut()
229 } else {
230 Box::leak(c_strings.into_boxed_slice());
231
232 let leaked_ptrs = Box::leak(c_ptrs.into_boxed_slice());
233 leaked_ptrs.as_mut_ptr()
234 };
235
236 let library_config = crate::bindgen::FPDF_LIBRARY_CONFIG_ {
237 version: 2,
238 m_pUserFontPaths: font_paths_ptr,
239 m_pIsolate: std::ptr::null_mut(),
240 m_v8EmbedderSlot: 0,
241 m_pPlatform: std::ptr::null_mut(),
242 m_RendererType: 0,
243 };
244
245 bindings
246 .FPDF_InitLibraryWithConfig(&library_config as *const _ as *const crate::bindgen::FPDF_LIBRARY_CONFIG);
247
248 if let Some(font_descriptors) = &config.font_provider
249 && !font_descriptors.is_empty()
250 {
251 use crate::font_provider::MemoryFontProvider;
252
253 let provider = MemoryFontProvider::new(font_descriptors.clone());
254 let mut boxed_provider = Box::new(provider);
255
256 let provider_ptr = boxed_provider.as_mut_ptr();
257
258 let _leaked_provider = Box::leak(boxed_provider);
259
260 bindings.FPDF_SetSystemFontInfo(provider_ptr);
261 }
262 }
263
264 assert!(BINDINGS.set(bindings).is_ok());
265
266 Self {}
267 }
268
269 pub fn load_pdf_from_byte_slice<'a>(
273 &'a self,
274 bytes: &'a [u8],
275 password: Option<&str>,
276 ) -> Result<PdfDocument<'a>, PdfiumError> {
277 Self::pdfium_document_handle_to_result(self.bindings().FPDF_LoadMemDocument64(bytes, password), self.bindings())
278 }
279
280 pub fn load_pdf_from_byte_vec(
287 &self,
288 bytes: Vec<u8>,
289 password: Option<&str>,
290 ) -> Result<PdfDocument<'_>, PdfiumError> {
291 Self::pdfium_document_handle_to_result(
292 self.bindings().FPDF_LoadMemDocument64(bytes.as_slice(), password),
293 self.bindings(),
294 )
295 .map(|mut document| {
296 document.set_source_byte_buffer(bytes);
297
298 document
299 })
300 }
301
302 #[cfg(not(target_arch = "wasm32"))]
321 pub fn load_pdf_from_file<'a>(
322 &'a self,
323 path: &(impl AsRef<Path> + ?Sized),
324 password: Option<&'a str>,
325 ) -> Result<PdfDocument<'a>, PdfiumError> {
326 self.load_pdf_from_reader(File::open(path).map_err(PdfiumError::IoError)?, password)
327 }
328
329 #[cfg(not(target_arch = "wasm32"))]
357 pub fn load_pdf_from_reader<'a, R: Read + Seek + 'a>(
358 &'a self,
359 reader: R,
360 password: Option<&'a str>,
361 ) -> Result<PdfDocument<'a>, PdfiumError> {
362 let mut reader = get_pdfium_file_accessor_from_reader(reader);
363
364 Pdfium::pdfium_document_handle_to_result(
365 self.bindings()
366 .FPDF_LoadCustomDocument(reader.as_fpdf_file_access_mut_ptr(), password),
367 self.bindings(),
368 )
369 .map(|mut document| {
370 document.set_file_access_reader(reader);
371
372 document
373 })
374 }
375
376 #[cfg(any(doc, target_arch = "wasm32"))]
383 pub async fn load_pdf_from_fetch<'a>(
384 &'a self,
385 url: impl ToString,
386 password: Option<&str>,
387 ) -> Result<PdfDocument<'a>, PdfiumError> {
388 if let Some(window) = window() {
389 let fetch_result = JsFuture::from(window.fetch_with_str(url.to_string().as_str()))
390 .await
391 .map_err(PdfiumError::WebSysFetchError)?;
392
393 debug_assert!(fetch_result.is_instance_of::<Response>());
394
395 let response: Response = fetch_result
396 .dyn_into()
397 .map_err(|_| PdfiumError::WebSysInvalidResponseError)?;
398
399 let blob: Blob = JsFuture::from(response.blob().map_err(PdfiumError::WebSysFetchError)?)
400 .await
401 .map_err(PdfiumError::WebSysFetchError)?
402 .into();
403
404 self.load_pdf_from_blob(blob, password).await
405 } else {
406 Err(PdfiumError::WebSysWindowObjectNotAvailable)
407 }
408 }
409
410 #[cfg(any(doc, target_arch = "wasm32"))]
423 pub async fn load_pdf_from_blob<'a>(
424 &'a self,
425 blob: Blob,
426 password: Option<&str>,
427 ) -> Result<PdfDocument<'a>, PdfiumError> {
428 let array_buffer: ArrayBuffer = JsFuture::from(blob.array_buffer())
429 .await
430 .map_err(PdfiumError::WebSysFetchError)?
431 .into();
432
433 let u8_array: Uint8Array = Uint8Array::new(&array_buffer);
434
435 let bytes: Vec<u8> = u8_array.to_vec();
436
437 self.load_pdf_from_byte_vec(bytes, password)
438 }
439
440 pub fn create_new_pdf(&self) -> Result<PdfDocument<'_>, PdfiumError> {
442 Self::pdfium_document_handle_to_result(self.bindings().FPDF_CreateNewDocument(), self.bindings()).map(
443 |mut document| {
444 document.set_version(PdfDocumentVersion::DEFAULT_VERSION);
445
446 document
447 },
448 )
449 }
450
451 pub(crate) fn pdfium_document_handle_to_result(
453 handle: crate::bindgen::FPDF_DOCUMENT,
454 bindings: &dyn PdfiumLibraryBindings,
455 ) -> Result<PdfDocument<'_>, PdfiumError> {
456 if handle.is_null() {
457 #[allow(clippy::unnecessary_cast)]
458 if let Some(error) = match bindings.FPDF_GetLastError() as u32 {
459 crate::bindgen::FPDF_ERR_SUCCESS => None,
460 crate::bindgen::FPDF_ERR_UNKNOWN => Some(PdfiumInternalError::Unknown),
461 crate::bindgen::FPDF_ERR_FILE => Some(PdfiumInternalError::FileError),
462 crate::bindgen::FPDF_ERR_FORMAT => Some(PdfiumInternalError::FormatError),
463 crate::bindgen::FPDF_ERR_PASSWORD => Some(PdfiumInternalError::PasswordError),
464 crate::bindgen::FPDF_ERR_SECURITY => Some(PdfiumInternalError::SecurityError),
465 crate::bindgen::FPDF_ERR_PAGE => Some(PdfiumInternalError::PageError),
466 _ => None,
467 } {
468 Err(PdfiumError::PdfiumLibraryInternalError(error))
469 } else {
470 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
471 }
472 } else {
473 Ok(PdfDocument::from_pdfium(handle, bindings))
474 }
475 }
476}
477
478impl PdfiumLibraryBindingsAccessor for Pdfium {}
479
480impl Default for Pdfium {
481 #[cfg(pdfium_use_static)]
485 #[inline]
486 fn default() -> Self {
487 Pdfium::new(Pdfium::bind_to_statically_linked_library().unwrap())
488 }
489
490 #[cfg(not(pdfium_use_static))]
496 #[cfg(not(target_arch = "wasm32"))]
497 #[inline]
498 fn default() -> Self {
499 match Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./")) {
500 Ok(bindings) => Pdfium::new(bindings),
501 Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized) => Pdfium {},
502 Err(PdfiumError::LoadLibraryError(err)) => match err {
503 libloading::Error::DlOpen { .. } => Pdfium::new(Pdfium::bind_to_system_library().unwrap()),
504 _ => panic!("Failed to load Pdfium library: {:?}", err),
505 },
506 Err(err) => panic!("Failed to initialize Pdfium: {:?}", err),
507 }
508 }
509}
510
511impl Debug for Pdfium {
512 #[inline]
513 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
514 f.debug_struct("Pdfium").finish()
515 }
516}
517
518#[cfg(feature = "thread_safe")]
519unsafe impl Sync for Pdfium {}
520
521#[cfg(feature = "thread_safe")]
522unsafe impl Send for Pdfium {}