use super::{clear_error, set_error};
use crate::cpdf::document::{CpdfDocument, merge, parse_page_range, split_by_range};
use crate::ffi::{Handle, HandleStore};
use std::ffi::{CStr, CString, c_char, c_int};
use std::sync::LazyLock;
pub static CPDF_DOCS: LazyLock<HandleStore<CpdfDocument>> = LazyLock::new(HandleStore::new);
pub static CPDF_RANGES: LazyLock<HandleStore<Vec<usize>>> = LazyLock::new(HandleStore::new);
macro_rules! get_doc {
($handle:expr) => {
match CPDF_DOCS.get($handle) {
Some(arc) => arc,
None => {
set_error(1, &format!("invalid document handle: {}", $handle));
return 0;
}
}
};
($handle:expr, $ret:expr) => {
match CPDF_DOCS.get($handle) {
Some(arc) => arc,
None => {
set_error(1, &format!("invalid document handle: {}", $handle));
return $ret;
}
}
};
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_fromFile(path: *const c_char, _password: *const c_char) -> Handle {
clear_error();
if path.is_null() {
set_error(1, "cpdf_fromFile: null path");
return 0;
}
let path = unsafe { CStr::from_ptr(path) }.to_string_lossy();
match CpdfDocument::from_file(&path) {
Ok(doc) => CPDF_DOCS.insert(doc),
Err(e) => {
set_error(1, &e.to_string());
0
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_fromMemory(data: *const u8, len: c_int) -> Handle {
clear_error();
if data.is_null() || len <= 0 {
set_error(1, "cpdf_fromMemory: null/empty data");
return 0;
}
let bytes = unsafe { std::slice::from_raw_parts(data, len as usize) }.to_vec();
match CpdfDocument::from_bytes(bytes) {
Ok(doc) => CPDF_DOCS.insert(doc),
Err(e) => {
set_error(1, &e.to_string());
0
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_toFile(
handle: Handle,
path: *const c_char,
_linearize: c_int,
_make_id: c_int,
) -> c_int {
clear_error();
if path.is_null() {
set_error(1, "cpdf_toFile: null path");
return 1;
}
let path = unsafe { CStr::from_ptr(path) }.to_string_lossy();
let arc = get_doc!(handle, 1);
match arc.lock().unwrap().to_file(&path) {
Ok(()) => 0,
Err(e) => {
set_error(1, &e.to_string());
1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_toMemory(
handle: Handle,
_linearize: c_int,
_make_id: c_int,
len_out: *mut c_int,
) -> *mut u8 {
clear_error();
if len_out.is_null() {
set_error(1, "cpdf_toMemory: null len_out");
return std::ptr::null_mut();
}
let arc = get_doc!(handle, std::ptr::null_mut());
let bytes = arc.lock().unwrap().to_bytes();
let byte_len = bytes.len();
if byte_len > i32::MAX as usize {
set_error(1, "cpdf_toMemory: PDF too large for c_int length (>2GB)");
return std::ptr::null_mut();
}
unsafe { *len_out = byte_len as c_int };
let mut boxed = bytes.into_boxed_slice();
let ptr = boxed.as_mut_ptr();
std::mem::forget(boxed);
ptr
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cpdf_free(ptr: *mut u8, len: c_int) {
if !ptr.is_null() && len > 0 {
drop(unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len as usize)) });
}
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_pages(handle: Handle) -> c_int {
clear_error();
let arc = get_doc!(handle, -1);
arc.lock().unwrap().page_count() as c_int
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_blankDocument(width: f64, height: f64, num_pages: c_int) -> Handle {
clear_error();
if num_pages <= 0 {
set_error(1, "cpdf_blankDocument: num_pages must be > 0");
return 0;
}
match CpdfDocument::blank(width, height, num_pages as usize) {
Ok(doc) => CPDF_DOCS.insert(doc),
Err(e) => {
set_error(1, &e.to_string());
0
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cpdf_mergeSimple(handles: *const Handle, num: c_int) -> Handle {
clear_error();
if handles.is_null() || num <= 0 {
set_error(1, "cpdf_mergeSimple: invalid args");
return 0;
}
let hs = unsafe { std::slice::from_raw_parts(handles, num as usize) };
let mut docs = Vec::with_capacity(num as usize);
for &h in hs {
match CPDF_DOCS.get(h) {
Some(arc) => docs.push(arc.lock().unwrap().clone()),
None => {
set_error(1, &format!("invalid handle {h}"));
return 0;
}
}
}
match merge(docs) {
Ok(doc) => CPDF_DOCS.insert(doc),
Err(e) => {
set_error(1, &e.to_string());
0
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_deletePdf(handle: Handle) {
CPDF_DOCS.remove(handle);
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_version(handle: Handle) -> *mut c_char {
clear_error();
let arc = get_doc!(handle, std::ptr::null_mut());
let v = arc.lock().unwrap().version();
CString::new(v)
.map(|cs| cs.into_raw())
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cpdf_freeString(ptr: *mut c_char) {
if !ptr.is_null() {
drop(unsafe { CString::from_raw(ptr) });
}
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_range(start: c_int, end: c_int) -> Handle {
clear_error();
if start <= 0 || end <= 0 {
set_error(1, "cpdf_range: page numbers must be >= 1");
return 0;
}
let pages: Vec<usize> = if start <= end {
(start as usize..=end as usize).collect()
} else {
(end as usize..=start as usize).rev().collect()
};
CPDF_RANGES.insert(pages)
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_all(doc_handle: Handle) -> Handle {
clear_error();
let arc = get_doc!(doc_handle);
let n = arc.lock().unwrap().page_count();
if n == 0 {
set_error(
1,
"cpdf_all: document has no pages or page count could not be determined",
);
return 0;
}
CPDF_RANGES.insert((1..=n).collect())
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_odd(doc_handle: Handle) -> Handle {
clear_error();
let arc = get_doc!(doc_handle);
let n = arc.lock().unwrap().page_count();
if n == 0 {
set_error(
1,
"cpdf_odd: document has no pages or page count could not be determined",
);
return 0;
}
CPDF_RANGES.insert((1..=n).filter(|p| p % 2 == 1).collect())
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_even(doc_handle: Handle) -> Handle {
clear_error();
let arc = get_doc!(doc_handle);
let n = arc.lock().unwrap().page_count();
if n == 0 {
set_error(
1,
"cpdf_even: document has no pages or page count could not be determined",
);
return 0;
}
CPDF_RANGES.insert((1..=n).filter(|p| p % 2 == 0).collect())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cpdf_parsePagespec(doc_handle: Handle, spec: *const c_char) -> Handle {
clear_error();
if spec.is_null() {
set_error(1, "cpdf_parsePagespec: null spec");
return 0;
}
let spec_str = unsafe { CStr::from_ptr(spec) }.to_string_lossy();
let arc = get_doc!(doc_handle);
let n = arc.lock().unwrap().page_count();
if n == 0 {
set_error(
1,
&format!(
"cpdf_parsePagespec: document has no pages or page count could not be \
determined (spec=\"{spec_str}\")"
),
);
return 0;
}
match parse_page_range(&spec_str, n) {
Ok(pages) => CPDF_RANGES.insert(pages),
Err(e) => {
set_error(1, &e.to_string());
0
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_deleteRange(handle: Handle) {
CPDF_RANGES.remove(handle);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cpdf_splitByRange(
doc_handle: Handle,
range_handles: *const Handle,
num_ranges: c_int,
out_count: *mut c_int,
) -> *mut Handle {
clear_error();
if range_handles.is_null() || num_ranges <= 0 || out_count.is_null() {
set_error(1, "cpdf_splitByRange: invalid args");
return std::ptr::null_mut();
}
let rhs = unsafe { std::slice::from_raw_parts(range_handles, num_ranges as usize) };
let doc_arc = get_doc!(doc_handle, std::ptr::null_mut());
let doc = doc_arc.lock().unwrap().clone();
let mut ranges: Vec<Vec<usize>> = Vec::with_capacity(num_ranges as usize);
for &rh in rhs {
match CPDF_RANGES.get(rh) {
Some(arc) => ranges.push(arc.lock().unwrap().clone()),
None => {
set_error(1, &format!("invalid range handle {rh}"));
return std::ptr::null_mut();
}
}
}
match split_by_range(&doc, &ranges) {
Ok(parts) => {
unsafe { *out_count = parts.len() as c_int };
let handles: Vec<Handle> = parts.into_iter().map(|d| CPDF_DOCS.insert(d)).collect();
let mut boxed = handles.into_boxed_slice();
let ptr = boxed.as_mut_ptr();
std::mem::forget(boxed);
ptr
}
Err(e) => {
set_error(1, &e.to_string());
std::ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cpdf_freeHandleArray(ptr: *mut Handle, count: c_int) {
if !ptr.is_null() && count > 0 {
drop(unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, count as usize)) });
}
}