use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use libloading::Library;
use crate::*;
static BINDINGS: OnceLock<PdfiumBindings> = OnceLock::new();
const PDFIUM_LIB_DIR: &str = env!("PDFIUM_LIB_DIR");
macro_rules! load_fn {
($lib:expr, $name:literal) => {{
let sym = unsafe { $lib.get::<*const ()>($name.as_bytes())? };
unsafe { std::mem::transmute(*sym) }
}};
}
pub struct PdfiumBindings {
_lib: Library,
pub FPDF_InitLibrary: unsafe extern "C" fn(),
pub FPDF_GetLastError: unsafe extern "C" fn() -> std::os::raw::c_ulong,
pub FPDF_LoadDocument: unsafe extern "C" fn(FPDF_STRING, FPDF_BYTESTRING) -> FPDF_DOCUMENT,
pub FPDF_LoadMemDocument: unsafe extern "C" fn(
*const std::os::raw::c_void,
std::os::raw::c_int,
FPDF_BYTESTRING,
) -> FPDF_DOCUMENT,
pub FPDF_CloseDocument: unsafe extern "C" fn(FPDF_DOCUMENT),
pub FPDF_GetPageCount: unsafe extern "C" fn(FPDF_DOCUMENT) -> std::os::raw::c_int,
pub FPDF_LoadPage: unsafe extern "C" fn(FPDF_DOCUMENT, std::os::raw::c_int) -> FPDF_PAGE,
pub FPDF_ClosePage: unsafe extern "C" fn(FPDF_PAGE),
pub FPDF_GetPageWidthF: unsafe extern "C" fn(FPDF_PAGE) -> f32,
pub FPDF_GetPageHeightF: unsafe extern "C" fn(FPDF_PAGE) -> f32,
pub FPDF_GetPageBoundingBox: unsafe extern "C" fn(FPDF_PAGE, *mut FS_RECTF) -> FPDF_BOOL,
pub FPDFPage_GetRotation: unsafe extern "C" fn(FPDF_PAGE) -> std::os::raw::c_int,
pub FPDF_PageToDevice: unsafe extern "C" fn(
FPDF_PAGE,
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
f64,
f64,
*mut std::os::raw::c_int,
*mut std::os::raw::c_int,
) -> FPDF_BOOL,
pub FPDFPage_CountObjects: unsafe extern "C" fn(FPDF_PAGE) -> std::os::raw::c_int,
pub FPDFPage_GetObject: unsafe extern "C" fn(FPDF_PAGE, std::os::raw::c_int) -> FPDF_PAGEOBJECT,
pub FPDFPageObj_GetType: unsafe extern "C" fn(FPDF_PAGEOBJECT) -> std::os::raw::c_int,
pub FPDFPageObj_GetBounds:
unsafe extern "C" fn(FPDF_PAGEOBJECT, *mut f32, *mut f32, *mut f32, *mut f32) -> FPDF_BOOL,
pub FPDFPageObj_GetMarkedContentID:
unsafe extern "C" fn(FPDF_PAGEOBJECT) -> std::os::raw::c_int,
pub FPDFImageObj_GetRenderedBitmap:
unsafe extern "C" fn(FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT) -> FPDF_BITMAP,
pub FPDFText_LoadPage: unsafe extern "C" fn(FPDF_PAGE) -> FPDF_TEXTPAGE,
pub FPDFText_ClosePage: unsafe extern "C" fn(FPDF_TEXTPAGE),
pub FPDFText_CountChars: unsafe extern "C" fn(FPDF_TEXTPAGE) -> std::os::raw::c_int,
pub FPDFText_GetUnicode:
unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int) -> std::os::raw::c_uint,
pub FPDFText_GetCharCode:
unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int) -> std::os::raw::c_uint,
pub FPDFText_GetFontSize: unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int) -> f64,
pub FPDFText_GetFontWeight:
unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int) -> std::os::raw::c_int,
pub FPDFText_GetFontInfo: unsafe extern "C" fn(
FPDF_TEXTPAGE,
std::os::raw::c_int,
*mut std::os::raw::c_void,
std::os::raw::c_ulong,
*mut std::os::raw::c_int,
) -> std::os::raw::c_ulong,
pub FPDFText_GetCharAngle: unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int) -> f32,
pub FPDFText_GetCharBox: unsafe extern "C" fn(
FPDF_TEXTPAGE,
std::os::raw::c_int,
*mut f64,
*mut f64,
*mut f64,
*mut f64,
) -> FPDF_BOOL,
pub FPDFText_GetLooseCharBox:
unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int, *mut FS_RECTF) -> FPDF_BOOL,
pub FPDFText_GetMatrix:
unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int, *mut FS_MATRIX) -> FPDF_BOOL,
pub FPDFText_IsGenerated:
unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int) -> std::os::raw::c_int,
pub FPDFText_HasUnicodeMapError:
unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int) -> std::os::raw::c_int,
pub FPDFText_GetTextObject:
unsafe extern "C" fn(FPDF_TEXTPAGE, std::os::raw::c_int) -> FPDF_PAGEOBJECT,
pub FPDFText_GetStrokeColor: unsafe extern "C" fn(
FPDF_TEXTPAGE,
std::os::raw::c_int,
*mut std::os::raw::c_uint,
*mut std::os::raw::c_uint,
*mut std::os::raw::c_uint,
*mut std::os::raw::c_uint,
) -> FPDF_BOOL,
pub FPDFText_GetFillColor: unsafe extern "C" fn(
FPDF_TEXTPAGE,
std::os::raw::c_int,
*mut std::os::raw::c_uint,
*mut std::os::raw::c_uint,
*mut std::os::raw::c_uint,
*mut std::os::raw::c_uint,
) -> FPDF_BOOL,
pub FPDFText_CountRects: unsafe extern "C" fn(
FPDF_TEXTPAGE,
std::os::raw::c_int,
std::os::raw::c_int,
) -> std::os::raw::c_int,
pub FPDFText_GetRect: unsafe extern "C" fn(
FPDF_TEXTPAGE,
std::os::raw::c_int,
*mut f64,
*mut f64,
*mut f64,
*mut f64,
) -> FPDF_BOOL,
pub FPDFText_GetBoundedText: unsafe extern "C" fn(
FPDF_TEXTPAGE,
f64,
f64,
f64,
f64,
*mut std::os::raw::c_ushort,
std::os::raw::c_int,
) -> std::os::raw::c_int,
pub FPDFText_GetText: unsafe extern "C" fn(
FPDF_TEXTPAGE,
std::os::raw::c_int,
std::os::raw::c_int,
*mut std::os::raw::c_ushort,
) -> std::os::raw::c_int,
pub FPDFTextObj_GetTextRenderMode:
unsafe extern "C" fn(FPDF_PAGEOBJECT) -> FPDF_TEXT_RENDERMODE,
pub FPDFBitmap_CreateEx: unsafe extern "C" fn(
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
*mut std::os::raw::c_void,
std::os::raw::c_int,
) -> FPDF_BITMAP,
pub FPDFBitmap_Destroy: unsafe extern "C" fn(FPDF_BITMAP),
pub FPDFBitmap_GetWidth: unsafe extern "C" fn(FPDF_BITMAP) -> std::os::raw::c_int,
pub FPDFBitmap_GetHeight: unsafe extern "C" fn(FPDF_BITMAP) -> std::os::raw::c_int,
pub FPDFBitmap_GetStride: unsafe extern "C" fn(FPDF_BITMAP) -> std::os::raw::c_int,
pub FPDFBitmap_GetBuffer: unsafe extern "C" fn(FPDF_BITMAP) -> *mut std::os::raw::c_void,
pub FPDFBitmap_FillRect: unsafe extern "C" fn(
FPDF_BITMAP,
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
FPDF_DWORD,
) -> FPDF_BOOL,
pub FPDF_RenderPageBitmap: unsafe extern "C" fn(
FPDF_BITMAP,
FPDF_PAGE,
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
std::os::raw::c_int,
),
pub FPDFTextObj_GetFont: unsafe extern "C" fn(FPDF_PAGEOBJECT) -> FPDF_FONT,
pub FPDFFont_GetBaseFontName:
unsafe extern "C" fn(FPDF_FONT, *mut std::os::raw::c_char, usize) -> usize,
pub FPDFFont_GetType: unsafe extern "C" fn(FPDF_FONT) -> FPDF_FONT_TYPE,
pub FPDFFont_GetIsEmbedded: unsafe extern "C" fn(FPDF_FONT) -> std::os::raw::c_int,
pub FPDFFont_GetAscent: unsafe extern "C" fn(FPDF_FONT, f32, *mut f32) -> FPDF_BOOL,
pub FPDFFont_GetDescent: unsafe extern "C" fn(FPDF_FONT, f32, *mut f32) -> FPDF_BOOL,
pub FPDFFont_GetGlyphWidth: unsafe extern "C" fn(FPDF_FONT, u32, f32, *mut f32) -> FPDF_BOOL,
pub FPDFFont_GetGlyphWidthFromCharCode:
unsafe extern "C" fn(FPDF_FONT, u32, f32, *mut f32) -> FPDF_BOOL,
}
unsafe impl Send for PdfiumBindings {}
unsafe impl Sync for PdfiumBindings {}
impl PdfiumBindings {
fn load(lib: Library) -> Result<Self, libloading::Error> {
Ok(Self {
FPDF_InitLibrary: load_fn!(lib, "FPDF_InitLibrary"),
FPDF_GetLastError: load_fn!(lib, "FPDF_GetLastError"),
FPDF_LoadDocument: load_fn!(lib, "FPDF_LoadDocument"),
FPDF_LoadMemDocument: load_fn!(lib, "FPDF_LoadMemDocument"),
FPDF_CloseDocument: load_fn!(lib, "FPDF_CloseDocument"),
FPDF_GetPageCount: load_fn!(lib, "FPDF_GetPageCount"),
FPDF_LoadPage: load_fn!(lib, "FPDF_LoadPage"),
FPDF_ClosePage: load_fn!(lib, "FPDF_ClosePage"),
FPDF_GetPageWidthF: load_fn!(lib, "FPDF_GetPageWidthF"),
FPDF_GetPageHeightF: load_fn!(lib, "FPDF_GetPageHeightF"),
FPDF_GetPageBoundingBox: load_fn!(lib, "FPDF_GetPageBoundingBox"),
FPDFPage_GetRotation: load_fn!(lib, "FPDFPage_GetRotation"),
FPDF_PageToDevice: load_fn!(lib, "FPDF_PageToDevice"),
FPDFPage_CountObjects: load_fn!(lib, "FPDFPage_CountObjects"),
FPDFPage_GetObject: load_fn!(lib, "FPDFPage_GetObject"),
FPDFPageObj_GetType: load_fn!(lib, "FPDFPageObj_GetType"),
FPDFPageObj_GetBounds: load_fn!(lib, "FPDFPageObj_GetBounds"),
FPDFPageObj_GetMarkedContentID: load_fn!(lib, "FPDFPageObj_GetMarkedContentID"),
FPDFImageObj_GetRenderedBitmap: load_fn!(lib, "FPDFImageObj_GetRenderedBitmap"),
FPDFText_LoadPage: load_fn!(lib, "FPDFText_LoadPage"),
FPDFText_ClosePage: load_fn!(lib, "FPDFText_ClosePage"),
FPDFText_CountChars: load_fn!(lib, "FPDFText_CountChars"),
FPDFText_GetUnicode: load_fn!(lib, "FPDFText_GetUnicode"),
FPDFText_GetCharCode: load_fn!(lib, "FPDFText_GetCharCode"),
FPDFText_GetFontSize: load_fn!(lib, "FPDFText_GetFontSize"),
FPDFText_GetFontWeight: load_fn!(lib, "FPDFText_GetFontWeight"),
FPDFText_GetFontInfo: load_fn!(lib, "FPDFText_GetFontInfo"),
FPDFText_GetCharAngle: load_fn!(lib, "FPDFText_GetCharAngle"),
FPDFText_GetCharBox: load_fn!(lib, "FPDFText_GetCharBox"),
FPDFText_GetLooseCharBox: load_fn!(lib, "FPDFText_GetLooseCharBox"),
FPDFText_GetMatrix: load_fn!(lib, "FPDFText_GetMatrix"),
FPDFText_IsGenerated: load_fn!(lib, "FPDFText_IsGenerated"),
FPDFText_HasUnicodeMapError: load_fn!(lib, "FPDFText_HasUnicodeMapError"),
FPDFText_GetTextObject: load_fn!(lib, "FPDFText_GetTextObject"),
FPDFText_GetStrokeColor: load_fn!(lib, "FPDFText_GetStrokeColor"),
FPDFText_GetFillColor: load_fn!(lib, "FPDFText_GetFillColor"),
FPDFText_CountRects: load_fn!(lib, "FPDFText_CountRects"),
FPDFText_GetRect: load_fn!(lib, "FPDFText_GetRect"),
FPDFText_GetBoundedText: load_fn!(lib, "FPDFText_GetBoundedText"),
FPDFText_GetText: load_fn!(lib, "FPDFText_GetText"),
FPDFTextObj_GetTextRenderMode: load_fn!(lib, "FPDFTextObj_GetTextRenderMode"),
FPDFBitmap_CreateEx: load_fn!(lib, "FPDFBitmap_CreateEx"),
FPDFBitmap_Destroy: load_fn!(lib, "FPDFBitmap_Destroy"),
FPDFBitmap_GetWidth: load_fn!(lib, "FPDFBitmap_GetWidth"),
FPDFBitmap_GetHeight: load_fn!(lib, "FPDFBitmap_GetHeight"),
FPDFBitmap_GetStride: load_fn!(lib, "FPDFBitmap_GetStride"),
FPDFBitmap_GetBuffer: load_fn!(lib, "FPDFBitmap_GetBuffer"),
FPDFBitmap_FillRect: load_fn!(lib, "FPDFBitmap_FillRect"),
FPDF_RenderPageBitmap: load_fn!(lib, "FPDF_RenderPageBitmap"),
FPDFTextObj_GetFont: load_fn!(lib, "FPDFTextObj_GetFont"),
FPDFFont_GetBaseFontName: load_fn!(lib, "FPDFFont_GetBaseFontName"),
FPDFFont_GetType: load_fn!(lib, "FPDFFont_GetType"),
FPDFFont_GetIsEmbedded: load_fn!(lib, "FPDFFont_GetIsEmbedded"),
FPDFFont_GetAscent: load_fn!(lib, "FPDFFont_GetAscent"),
FPDFFont_GetDescent: load_fn!(lib, "FPDFFont_GetDescent"),
FPDFFont_GetGlyphWidth: load_fn!(lib, "FPDFFont_GetGlyphWidth"),
FPDFFont_GetGlyphWidthFromCharCode: load_fn!(lib, "FPDFFont_GetGlyphWidthFromCharCode"),
_lib: lib,
})
}
}
fn dylib_name() -> &'static str {
#[cfg(target_os = "macos")]
{
"libpdfium.dylib"
}
#[cfg(target_os = "windows")]
{
"pdfium.dll"
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
"libpdfium.so"
}
}
fn self_dir() -> Option<PathBuf> {
let addr = self_dir as *const ();
#[cfg(target_os = "windows")]
{
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetModuleHandleExW(
dwFlags: u32,
lpModuleName: *const u8,
phModule: *mut *mut std::ffi::c_void,
) -> i32;
fn GetModuleFileNameW(
hModule: *mut std::ffi::c_void,
lpFilename: *mut u16,
nSize: u32,
) -> u32;
}
const FROM_ADDRESS: u32 = 0x00000004;
const UNCHANGED_REFCOUNT: u32 = 0x00000002;
unsafe {
let mut module = std::ptr::null_mut();
if GetModuleHandleExW(
FROM_ADDRESS | UNCHANGED_REFCOUNT,
addr as *const u8,
&mut module,
) == 0
{
return None;
}
let mut buf = vec![0u16; 1024];
let len = GetModuleFileNameW(module, buf.as_mut_ptr(), buf.len() as u32);
if len == 0 || len >= buf.len() as u32 {
return None;
}
let path = PathBuf::from(OsString::from_wide(&buf[..len as usize]));
path.parent().map(|p| p.to_path_buf())
}
}
#[cfg(not(target_os = "windows"))]
{
#[repr(C)]
struct DlInfo {
dli_fname: *const std::os::raw::c_char,
dli_fbase: *mut std::ffi::c_void,
dli_sname: *const std::os::raw::c_char,
dli_saddr: *mut std::ffi::c_void,
}
unsafe extern "C" {
fn dladdr(addr: *const std::ffi::c_void, info: *mut DlInfo) -> i32;
}
unsafe {
let mut info: DlInfo = std::mem::zeroed();
if dladdr(addr as *const std::ffi::c_void, &mut info) != 0 && !info.dli_fname.is_null()
{
let cstr = std::ffi::CStr::from_ptr(info.dli_fname);
let path = PathBuf::from(cstr.to_string_lossy().as_ref());
return path.parent().map(|p| p.to_path_buf());
}
None
}
}
}
fn search_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
let name = dylib_name();
if let Ok(dir) = std::env::var("PDFIUM_LIB_PATH") {
paths.push(PathBuf::from(&dir).join(name));
}
if !PDFIUM_LIB_DIR.is_empty() {
let lib_dir = PathBuf::from(PDFIUM_LIB_DIR);
paths.push(lib_dir.join(name));
#[cfg(target_os = "windows")]
if let Some(parent) = lib_dir.parent() {
paths.push(parent.join("bin").join(name));
}
}
if let Some(dir) = self_dir() {
paths.push(dir.join(name));
}
if let Ok(exe) = std::env::current_exe() {
if let Some(exe_dir) = exe.parent() {
paths.push(exe_dir.join(name));
}
}
paths.push(PathBuf::from(name));
paths
}
pub fn load(lib_path: &Path) -> Result<(), String> {
if BINDINGS.get().is_some() {
return Ok(());
}
let lib = unsafe { Library::new(lib_path) }
.map_err(|e| format!("failed to load pdfium from {}: {e}", lib_path.display()))?;
let bindings =
PdfiumBindings::load(lib).map_err(|e| format!("failed to resolve pdfium symbols: {e}"))?;
let _ = BINDINGS.set(bindings);
Ok(())
}
pub fn load_default() -> Result<(), String> {
if BINDINGS.get().is_some() {
return Ok(());
}
let paths = search_paths();
let mut last_err = String::from("no search paths configured");
for path in &paths {
match unsafe { Library::new(path) } {
Ok(lib) => match PdfiumBindings::load(lib) {
Ok(bindings) => {
let _ = BINDINGS.set(bindings);
return Ok(());
}
Err(e) => {
last_err = format!(
"failed to resolve pdfium symbols from {}: {e}",
path.display()
);
}
},
Err(e) => {
last_err = format!("{}: {e}", path.display());
}
}
}
Err(format!(
"could not find pdfium shared library. Last error: {last_err}. \
Set PDFIUM_LIB_PATH to the directory containing {}",
dylib_name()
))
}
pub fn pdfium() -> &'static PdfiumBindings {
BINDINGS
.get()
.expect("pdfium not loaded — call pdfium_sys::dynamic::load_default() first")
}