use std::path::{Path, PathBuf};
use std::sync::{Mutex, MutexGuard, OnceLock};
use crate::error::{Error, LoadError, Result};
use crate::sys;
pub fn platform_library_name() -> &'static str {
if cfg!(target_os = "macos") {
"libpdfium.dylib"
} else if cfg!(target_os = "windows") {
"pdfium.dll"
} else {
"libpdfium.so"
}
}
pub fn platform_slug() -> &'static str {
match (std::env::consts::OS, std::env::consts::ARCH) {
("macos", "aarch64") => "mac-arm64",
("macos", "x86_64") => "mac-x64",
("linux", "aarch64") => "linux-arm64",
("linux", "x86_64") => "linux-x64",
("windows", "aarch64") => "win-arm64",
("windows", "x86_64") => "win-x64",
_ => "unknown",
}
}
pub const PDFIUM_LIB_PATH_ENV: &str = "PDFIUM_LIB_PATH";
struct LibraryInner {
bindings: Box<sys::Bindings>,
ffi_lock: Mutex<()>,
loaded_from: Option<PathBuf>,
}
static INSTANCE: OnceLock<LibraryInner> = OnceLock::new();
static INIT_LOCK: Mutex<()> = Mutex::new(());
#[derive(Clone, Copy)]
pub struct Pdfium {
inner: &'static LibraryInner,
}
impl std::fmt::Debug for Pdfium {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Pdfium")
.field("loaded_from", &self.inner.loaded_from)
.finish_non_exhaustive()
}
}
impl Pdfium {
pub fn load() -> Result<Pdfium> {
let _init = lock_ignore_poison(&INIT_LOCK);
if let Some(inner) = INSTANCE.get() {
return Ok(Pdfium { inner });
}
let mut searched: Vec<String> = Vec::new();
if let Some(raw) = std::env::var_os(PDFIUM_LIB_PATH_ENV) {
let p = PathBuf::from(&raw);
let file = if p.is_dir() {
p.join(platform_library_name())
} else {
p
};
return init_from_file(&file);
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let candidate = dir.join(platform_library_name());
searched.push(candidate.display().to_string());
if candidate.is_file() {
return init_from_file(&candidate);
}
}
}
let slug = platform_slug();
for sub in ["lib", "bin"] {
let candidate = PathBuf::from("target")
.join("pdfium")
.join(slug)
.join(sub)
.join(platform_library_name());
searched.push(candidate.display().to_string());
if candidate.is_file() {
return init_from_file(&candidate);
}
}
searched.push(format!("<system loader: {}>", platform_library_name()));
match unsafe { libloading::Library::new(platform_library_name()) } {
Ok(lib) => init_from_library(lib, None),
Err(_) => Err(Error::Load(LoadError::LibraryNotFound { searched })),
}
}
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Pdfium> {
let path = path.as_ref();
let _init = lock_ignore_poison(&INIT_LOCK);
if let Some(inner) = INSTANCE.get() {
let requested = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
return if inner.loaded_from.as_deref() == Some(requested.as_path()) {
Ok(Pdfium { inner })
} else {
Err(Error::AlreadyLoaded {
loaded_from: inner.loaded_from.clone(),
requested,
})
};
}
init_from_file(path)
}
pub fn load_from_directory(dir: impl AsRef<Path>) -> Result<Pdfium> {
Self::load_from_path(dir.as_ref().join(platform_library_name()))
}
pub fn instance() -> Option<Pdfium> {
INSTANCE.get().map(|inner| Pdfium { inner })
}
pub fn loaded_from(&self) -> Option<&Path> {
self.inner.loaded_from.as_deref()
}
pub fn ffi_lock(&self) -> MutexGuard<'static, ()> {
lock_ignore_poison(&self.inner.ffi_lock)
}
pub unsafe fn raw(&self) -> &sys::Bindings {
&self.inner.bindings
}
pub(crate) fn ffi<R>(&self, f: impl FnOnce(&sys::Bindings) -> R) -> R {
let _guard = lock_ignore_poison(&self.inner.ffi_lock);
f(&self.inner.bindings)
}
}
fn lock_ignore_poison<'a, T>(m: &'a Mutex<T>) -> MutexGuard<'a, T> {
m.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn init_from_file(path: &Path) -> Result<Pdfium> {
if !path.is_file() {
return Err(Error::Load(LoadError::LibraryNotFound {
searched: vec![path.display().to_string()],
}));
}
let lib = unsafe { libloading::Library::new(path) }.map_err(|source| {
Error::Load(LoadError::OpenFailed {
path: path.to_path_buf(),
source,
})
})?;
let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
init_from_library(lib, Some(canonical))
}
fn init_from_library(lib: libloading::Library, loaded_from: Option<PathBuf>) -> Result<Pdfium> {
let bindings = unsafe { sys::Bindings::load_from_library(lib) }
.map_err(|e| Error::Load(LoadError::MissingSymbol(e)))?;
let config = sys::FPDF_LIBRARY_CONFIG {
version: 2,
m_pUserFontPaths: std::ptr::null(),
m_pIsolate: std::ptr::null_mut(),
m_v8EmbedderSlot: 0,
m_pPlatform: std::ptr::null_mut(),
m_RendererType: 0,
m_FontLibraryType: 0,
m_BrotliEnabled: 0,
};
unsafe { bindings.FPDF_InitLibraryWithConfig(&config) };
let inner = LibraryInner {
bindings,
ffi_lock: Mutex::new(()),
loaded_from,
};
let _ = INSTANCE.set(inner);
Ok(Pdfium {
inner: INSTANCE.get().expect("instance just set"),
})
}