#![allow(missing_docs)]
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_mut)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
#![allow(clippy::collapsible_match)]
#![allow(clippy::collapsible_if)]
#![allow(clippy::too_many_arguments)]
use crate::converters::ConversionOptions;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
use crate::annotations::Annotation as RustAnnotation;
use crate::api::Pdf;
use crate::document::PdfDocument;
use crate::editor::DocumentEditor;
use crate::editor::EditableDocument;
use crate::search::{SearchOptions, SearchResult as RustSearchResult, TextSearcher};
#[allow(unsafe_code)]
#[inline(always)]
fn handle_ref<'a, T>(ptr: *const T) -> &'a T {
let nn = std::ptr::NonNull::new(ptr as *mut T).expect("FFI: handle pointer must be non-null");
unsafe { nn.as_ref() }
}
#[allow(unsafe_code)]
#[inline(always)]
fn handle_mut<'a, T>(ptr: *mut T) -> &'a mut T {
let mut nn = std::ptr::NonNull::new(ptr).expect("FFI: handle pointer must be non-null");
unsafe { nn.as_mut() }
}
const ERR_SUCCESS: i32 = 0;
const ERR_INVALID_ARG: i32 = 1;
const ERR_IO: i32 = 2;
const ERR_PARSE: i32 = 3;
const ERR_EXTRACTION: i32 = 4;
const ERR_INTERNAL: i32 = 5;
const ERR_INVALID_PAGE: i32 = 6;
const ERR_SEARCH: i32 = 7;
const _ERR_UNSUPPORTED: i32 = 8;
fn set_error(ptr: *mut i32, code: i32) {
if !ptr.is_null() {
unsafe {
*ptr = code;
}
}
}
fn classify_error(e: &crate::error::Error) -> i32 {
use crate::error::Error;
match e {
Error::Io(_) => ERR_IO,
Error::InvalidHeader(_)
| Error::UnsupportedVersion(_)
| Error::ParseError { .. }
| Error::ParseWarning { .. }
| Error::InvalidXref
| Error::ObjectNotFound(_, _)
| Error::InvalidObjectType { .. }
| Error::UnexpectedEof
| Error::InvalidPdf(_)
| Error::Decode(_) => ERR_PARSE,
Error::LayoutAnalysis(_) => ERR_EXTRACTION,
Error::InvalidOperation(_) => ERR_INVALID_ARG,
Error::Unsupported(_) | Error::UnsupportedFilter(_) | Error::Barcode(_) => _ERR_UNSUPPORTED,
_ => ERR_INTERNAL,
}
}
fn vec_to_ffi_bytes(bytes: Vec<u8>) -> *mut u8 {
let len = bytes.len();
if len == 0 {
return ptr::null_mut();
}
let ptr = unsafe { libc::malloc(len) as *mut u8 };
if !ptr.is_null() {
unsafe { std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, len) };
}
ptr
}
fn to_c_string(s: &str) -> *mut c_char {
match CString::new(s) {
Ok(cs) => cs.into_raw(),
Err(_) => {
let cleaned: String = s.replace('\0', "");
CString::new(cleaned)
.map(|c| c.into_raw())
.unwrap_or(ptr::null_mut())
},
}
}
fn to_c_string_opt(s: Option<String>) -> *mut c_char {
match s {
Some(s) => to_c_string(&s),
None => ptr::null_mut(),
}
}
#[allow(unsafe_code)]
fn c_str<'a>(ptr: *const c_char) -> Result<&'a str, ()> {
if ptr.is_null() {
return Err(());
}
unsafe { CStr::from_ptr(ptr) }.to_str().map_err(|_| ())
}
#[allow(unsafe_code)]
fn c_str_lossy(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
Some(
unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned(),
)
}
#[allow(unsafe_code)]
fn raw_slice<'a, T>(ptr: *const T, len: usize) -> &'a [T] {
if ptr.is_null() || len == 0 {
return &[];
}
unsafe { std::slice::from_raw_parts(ptr, len) }
}
#[allow(unsafe_code)]
#[inline]
fn write_out<T: Copy>(ptr: *mut T, val: T) {
if !ptr.is_null() {
unsafe { *ptr = val }
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_set_log_level(level: i32) {
let filter = match level {
0 => log::LevelFilter::Off,
1 => log::LevelFilter::Error,
2 => log::LevelFilter::Warn,
3 => log::LevelFilter::Info,
4 => log::LevelFilter::Debug,
_ => log::LevelFilter::Trace,
};
log::set_max_level(filter);
}
#[no_mangle]
pub extern "C" fn pdf_oxide_get_log_level() -> i32 {
match log::max_level() {
log::LevelFilter::Off => 0,
log::LevelFilter::Error => 1,
log::LevelFilter::Warn => 2,
log::LevelFilter::Info => 3,
log::LevelFilter::Debug => 4,
log::LevelFilter::Trace => 5,
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_set_max_ops_per_stream(limit: i64) -> i64 {
let new_limit = if limit < 0 {
None
} else {
Some(limit as usize)
};
let prev = crate::content::parser::set_max_ops_per_stream(new_limit);
prev.map(|v| v as i64).unwrap_or(-1)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_set_preserve_unmapped_glyphs(preserve: i32) -> i32 {
let prev = crate::extractors::text::set_preserve_unmapped_glyphs(preserve != 0);
if prev {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_crypto_active_provider() -> *mut c_char {
use crate::crypto::CryptoProvider;
let name = if crate::crypto::is_set() {
crate::crypto::active().name().to_string()
} else {
format!("{} (default, lazy)", crate::crypto::RustCryptoProvider.name())
};
std::ffi::CString::new(name)
.map(std::ffi::CString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn pdf_oxide_crypto_fips_available() -> i32 {
if cfg!(feature = "fips") {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_crypto_use_fips() -> i32 {
#[cfg(feature = "fips")]
{
use std::sync::Arc;
match crate::crypto::set_provider(Arc::new(crate::crypto::AwsLcProvider::new())) {
Ok(()) => 0,
Err(_) => 2,
}
}
#[cfg(not(feature = "fips"))]
{
1
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_crypto_set_policy(spec: *const c_char) -> i32 {
let s = match c_str(spec) {
Ok(s) => s,
Err(_) => return 1,
};
let policy: crate::crypto::SecurityPolicy = match s.parse() {
Ok(p) => p,
Err(_) => return 2,
};
match crate::crypto::set_policy(policy) {
Ok(()) => 0,
Err(_) => 3,
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_crypto_policy() -> *mut c_char {
to_c_string(&crate::crypto::active_policy().to_string())
}
#[no_mangle]
pub extern "C" fn pdf_oxide_crypto_inventory() -> *mut c_char {
let joined = crate::crypto::inventory()
.into_iter()
.map(|a| a.token())
.collect::<Vec<_>>()
.join(",");
to_c_string(&joined)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_crypto_cbom() -> *mut c_char {
to_c_string(&crate::crypto::cbom_json())
}
#[no_mangle]
pub extern "C" fn free_string(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
drop(CString::from_raw(ptr));
}
}
}
#[no_mangle]
pub extern "C" fn free_bytes(ptr: *mut u8) {
if !ptr.is_null() {
unsafe {
libc::free(ptr as *mut libc::c_void);
}
}
}
#[no_mangle]
pub extern "C" fn pdf_document_open(path: *const c_char, error_code: *mut i32) -> *mut PdfDocument {
if path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let path_str = match c_str(path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
match PdfDocument::open(path_str) {
Ok(doc) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(doc))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_free(handle: *mut PdfDocument) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
#[no_mangle]
pub extern "C" fn pdf_document_get_page_count(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_ref(handle);
match doc.page_count() {
Ok(count) => {
set_error(error_code, ERR_SUCCESS);
count as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_get_version(
handle: *const PdfDocument,
major: *mut u8,
minor: *mut u8,
) {
if handle.is_null() || major.is_null() || minor.is_null() {
return;
}
let doc = handle_ref(handle);
let (maj, min) = doc.version();
write_out(major, maj);
write_out(minor, min);
}
#[no_mangle]
pub extern "C" fn pdf_document_has_structure_tree(handle: *mut PdfDocument) -> bool {
if handle.is_null() {
return false;
}
let doc = handle_ref(handle);
doc.structure_tree().ok().flatten().is_some()
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_text(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_text(page_index as usize) {
Ok(text) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&text)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_structured_to_json(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_structured(page_index as usize) {
Ok(structured) => match serde_json::to_string(&structured) {
Ok(json) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&json)
},
Err(_) => {
set_error(error_code, ERR_INTERNAL);
ptr::null_mut()
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_to_markdown(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
let opts = ConversionOptions::default();
match doc.to_markdown(page_index as usize, &opts) {
Ok(text) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&text)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_to_html(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
let opts = ConversionOptions::default();
match doc.to_html(page_index as usize, &opts) {
Ok(text) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&text)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_to_plain_text(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
let opts = ConversionOptions::default();
match doc.to_plain_text(page_index as usize, &opts) {
Ok(text) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&text)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_to_markdown_all(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
let opts = ConversionOptions::default();
match doc.to_markdown_all(&opts) {
Ok(text) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&text)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_to_docx(
handle: *mut PdfDocument,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if handle.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.to_docx_bytes() {
Ok(bytes) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, bytes.len());
vec_to_ffi_bytes(bytes)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_to_pptx(
handle: *mut PdfDocument,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if handle.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.to_pptx_bytes() {
Ok(bytes) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, bytes.len());
vec_to_ffi_bytes(bytes)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_to_xlsx(
handle: *mut PdfDocument,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if handle.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.to_xlsx_bytes() {
Ok(bytes) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, bytes.len());
vec_to_ffi_bytes(bytes)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_open_from_docx_bytes(
data: *const u8,
len: usize,
error_code: *mut i32,
) -> *mut PdfDocument {
if data.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let bytes = raw_slice(data, len);
match crate::converters::office::OfficeConverter::new().convert_docx_bytes(bytes) {
Ok(pdf_bytes) => match PdfDocument::from_bytes(pdf_bytes) {
Ok(doc) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(doc))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_open_from_pptx_bytes(
data: *const u8,
len: usize,
error_code: *mut i32,
) -> *mut PdfDocument {
if data.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let bytes = raw_slice(data, len);
match crate::converters::office::OfficeConverter::new().convert_pptx_bytes(bytes) {
Ok(pdf_bytes) => match PdfDocument::from_bytes(pdf_bytes) {
Ok(doc) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(doc))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_open_from_xlsx_bytes(
data: *const u8,
len: usize,
error_code: *mut i32,
) -> *mut PdfDocument {
if data.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let bytes = raw_slice(data, len);
match crate::converters::office::OfficeConverter::new().convert_xlsx_bytes(bytes) {
Ok(pdf_bytes) => match PdfDocument::from_bytes(pdf_bytes) {
Ok(doc) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(doc))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_open(
path: *const c_char,
error_code: *mut i32,
) -> *mut DocumentEditor {
if path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let path_str = match c_str(path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
match DocumentEditor::open(path_str) {
Ok(editor) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(editor))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_free(handle: *mut DocumentEditor) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
#[no_mangle]
pub extern "C" fn document_editor_is_modified(handle: *const DocumentEditor) -> bool {
if handle.is_null() {
return false;
}
let editor = handle_ref(handle);
editor.is_modified()
}
#[no_mangle]
pub extern "C" fn document_editor_get_source_path(
handle: *const DocumentEditor,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let editor = handle_ref(handle);
set_error(error_code, ERR_SUCCESS);
to_c_string(editor.source_path())
}
#[no_mangle]
pub extern "C" fn document_editor_get_version(
handle: *const DocumentEditor,
major: *mut u8,
minor: *mut u8,
) {
if handle.is_null() || major.is_null() || minor.is_null() {
return;
}
let editor = handle_ref(handle);
let (maj, min) = editor.version();
write_out(major, maj);
write_out(minor, min);
}
#[no_mangle]
pub extern "C" fn document_editor_get_page_count(
handle: *mut DocumentEditor,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_ref(handle);
set_error(error_code, ERR_SUCCESS);
editor.current_page_count() as i32
}
macro_rules! editor_get_string_field {
($fn_name:ident, $method:ident) => {
#[no_mangle]
pub extern "C" fn $fn_name(
handle: *const DocumentEditor,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let editor = handle_mut(handle as *mut DocumentEditor);
match editor.$method() {
Ok(Some(val)) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&val)
},
Ok(None) => {
set_error(error_code, ERR_SUCCESS);
ptr::null_mut()
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
};
}
macro_rules! editor_set_string_field {
($fn_name:ident, $method:ident) => {
#[no_mangle]
pub extern "C" fn $fn_name(
handle: *mut DocumentEditor,
value: *const c_char,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || value.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let val = match c_str(value) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
editor.$method(val);
set_error(error_code, ERR_SUCCESS);
0
}
};
}
editor_get_string_field!(document_editor_get_title, title);
editor_set_string_field!(document_editor_set_title, set_title);
editor_get_string_field!(document_editor_get_author, author);
editor_set_string_field!(document_editor_set_author, set_author);
editor_get_string_field!(document_editor_get_subject, subject);
editor_set_string_field!(document_editor_set_subject, set_subject);
#[no_mangle]
pub extern "C" fn document_editor_get_producer(
handle: *mut DocumentEditor,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let editor = handle_mut(handle);
match editor.producer() {
Ok(Some(s)) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&s)
},
Ok(None) => {
set_error(error_code, ERR_SUCCESS);
ptr::null_mut()
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_set_producer(
handle: *mut DocumentEditor,
value: *const c_char,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || value.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let s = match c_str(value) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
handle_mut(handle).set_producer(s);
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn document_editor_get_creation_date(
handle: *mut DocumentEditor,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let editor = handle_mut(handle);
match editor.creation_date() {
Ok(Some(s)) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&s)
},
Ok(None) => {
set_error(error_code, ERR_SUCCESS);
ptr::null_mut()
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_set_creation_date(
handle: *mut DocumentEditor,
date_str: *const c_char,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || date_str.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let s = match c_str(date_str) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
handle_mut(handle).set_creation_date(s);
set_error(error_code, ERR_SUCCESS);
0
}
editor_get_string_field!(document_editor_get_keywords, keywords);
editor_set_string_field!(document_editor_set_keywords, set_keywords);
#[no_mangle]
pub extern "C" fn document_editor_save(
handle: *mut DocumentEditor,
path: *const c_char,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let path_str = match c_str(path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
match editor.save(path_str) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_open_from_bytes(
data: *const u8,
len: usize,
error_code: *mut i32,
) -> *mut DocumentEditor {
if data.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let bytes = raw_slice(data, len).to_vec();
match DocumentEditor::from_bytes(bytes) {
Ok(editor) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(editor))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_save_to_bytes(
handle: *mut DocumentEditor,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if handle.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let editor = handle_mut(handle);
match editor.save_to_bytes() {
Ok(bytes) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, bytes.len());
vec_to_ffi_bytes(bytes)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_save_to_bytes_with_options(
handle: *mut DocumentEditor,
compress: bool,
garbage_collect: bool,
linearize: bool,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if handle.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let editor = handle_mut(handle);
let mut opts = crate::editor::SaveOptions::full_rewrite();
opts.compress = compress;
opts.garbage_collect = garbage_collect;
opts.linearize = linearize;
match editor.save_to_bytes_with_options(opts) {
Ok(bytes) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, bytes.len());
vec_to_ffi_bytes(bytes)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_extract_pages_to_bytes(
handle: *mut DocumentEditor,
pages: *const i32,
count: usize,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if handle.is_null() || pages.is_null() || out_len.is_null() || count == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let page_indices: Vec<usize> = unsafe { std::slice::from_raw_parts(pages, count) }
.iter()
.map(|&i| i as usize)
.collect();
let editor = handle_mut(handle);
match editor.extract_pages_to_bytes(&page_indices) {
Ok(bytes) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, bytes.len());
vec_to_ffi_bytes(bytes)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_convert_to_pdf_a(
handle: *mut DocumentEditor,
level: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
use crate::compliance::convert_to_pdf_a;
use crate::compliance::types::PdfALevel;
let pdf_level = match level {
0 => PdfALevel::A1b,
1 => PdfALevel::A1a,
2 => PdfALevel::A2b,
3 => PdfALevel::A2a,
4 => PdfALevel::A2u,
5 => PdfALevel::A3b,
6 => PdfALevel::A3a,
7 => PdfALevel::A3u,
_ => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
let editor = handle_mut(handle);
match convert_to_pdf_a(editor.source_mut(), pdf_level) {
Ok(_) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_save_encrypted_to_bytes(
handle: *mut DocumentEditor,
user_password: *const c_char,
owner_password: *const c_char,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if handle.is_null() || user_password.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let user_pwd = match c_str(user_password) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let owner_pwd = if owner_password.is_null() {
user_pwd
} else {
match c_str(owner_password) {
Ok(s) => s,
Err(_) => user_pwd,
}
};
let enc_config = crate::editor::EncryptionConfig::new(user_pwd, owner_pwd)
.with_algorithm(crate::editor::EncryptionAlgorithm::Aes256);
let save_opts = crate::editor::SaveOptions::with_encryption(enc_config);
let editor = handle_mut(handle);
match editor.save_to_bytes_with_options(save_opts) {
Ok(bytes) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, bytes.len());
vec_to_ffi_bytes(bytes)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_merge_from_bytes(
handle: *mut DocumentEditor,
data: *const u8,
len: usize,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || data.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let bytes = raw_slice(data, len);
match editor.merge_from_bytes(bytes) {
Ok(n) => {
set_error(error_code, ERR_SUCCESS);
n as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_embed_file(
handle: *mut DocumentEditor,
name: *const std::os::raw::c_char,
data: *const u8,
len: usize,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || name.is_null() || data.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let name_str = match c_str(name) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
let bytes = raw_slice(data, len).to_vec();
match editor.embed_file(name_str, bytes) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_apply_page_redactions(
handle: *mut DocumentEditor,
page: usize,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.apply_page_redactions(page) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_apply_all_redactions(
handle: *mut DocumentEditor,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.apply_all_redactions() {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn pdf_redaction_add(
handle: *mut DocumentEditor,
page: usize,
x1: f64,
y1: f64,
x2: f64,
y2: f64,
r: f64,
g: f64,
b: f64,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let rect = [x1 as f32, y1 as f32, x2 as f32, y2 as f32];
let fill = Some([r as f32, g as f32, b as f32]);
match editor.add_redaction(page, rect, fill) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_redaction_count(
handle: *mut DocumentEditor,
page: usize,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.redaction_count(page) {
Ok(n) => {
set_error(error_code, ERR_SUCCESS);
n as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_redaction_apply(
handle: *mut DocumentEditor,
scrub_metadata: bool,
r: f64,
g: f64,
b: f64,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let opts = crate::redaction::RedactionOptions {
scrub_metadata,
default_fill: [r as f32, g as f32, b as f32],
..crate::redaction::RedactionOptions::default()
};
match editor.apply_redactions_destructive(opts) {
Ok(report) => {
set_error(error_code, ERR_SUCCESS);
report.glyphs_removed.min(i32::MAX as usize) as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_redaction_scrub_metadata(
handle: *mut DocumentEditor,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.sanitize_document(crate::redaction::RedactionOptions::default()) {
Ok(report) => {
set_error(error_code, ERR_SUCCESS);
report.annotations_removed.min(i32::MAX as usize) as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_rotate_all_pages(
handle: *mut DocumentEditor,
degrees: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.rotate_all_pages(degrees) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_rotate_page_by(
handle: *mut DocumentEditor,
page: usize,
degrees: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.rotate_page_by(page, degrees) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_get_page_media_box(
handle: *mut DocumentEditor,
page: usize,
x: *mut f64,
y: *mut f64,
w: *mut f64,
h: *mut f64,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || x.is_null() || y.is_null() || w.is_null() || h.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.get_page_media_box(page) {
Ok(b) => {
set_error(error_code, ERR_SUCCESS);
write_out(x, b[0] as f64);
write_out(y, b[1] as f64);
write_out(w, b[2] as f64);
write_out(h, b[3] as f64);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_set_page_media_box(
handle: *mut DocumentEditor,
page: usize,
x: f64,
y: f64,
w: f64,
h: f64,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.set_page_media_box(page, [x as f32, y as f32, w as f32, h as f32]) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_get_page_crop_box(
handle: *mut DocumentEditor,
page: usize,
x: *mut f64,
y: *mut f64,
w: *mut f64,
h: *mut f64,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || x.is_null() || y.is_null() || w.is_null() || h.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.get_page_crop_box(page) {
Ok(Some(b)) => {
set_error(error_code, ERR_SUCCESS);
write_out(x, b[0] as f64);
write_out(y, b[1] as f64);
write_out(w, b[2] as f64);
write_out(h, b[3] as f64);
0
},
Ok(None) => {
set_error(error_code, ERR_SUCCESS);
write_out(x, 0.0f64);
write_out(y, 0.0f64);
write_out(w, 0.0f64);
write_out(h, 0.0f64);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_set_page_crop_box(
handle: *mut DocumentEditor,
page: usize,
x: f64,
y: f64,
w: f64,
h: f64,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.set_page_crop_box(page, [x as f32, y as f32, w as f32, h as f32]) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_erase_regions(
handle: *mut DocumentEditor,
page: usize,
rects: *const f64,
rects_count: usize,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || rects.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let flat = raw_slice(rects, rects_count * 4);
let boxes: Vec<[f32; 4]> = flat
.chunks_exact(4)
.map(|c| [c[0] as f32, c[1] as f32, c[2] as f32, c[3] as f32])
.collect();
match editor.erase_regions(page, &boxes) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_clear_erase_regions(
handle: *mut DocumentEditor,
page: usize,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
editor.clear_erase_regions(page);
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn document_editor_is_page_marked_for_flatten(
handle: *const DocumentEditor,
page: usize,
) -> i32 {
if handle.is_null() {
return -1;
}
let editor = handle_ref(handle);
if editor.is_page_marked_for_flatten(page) {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn document_editor_unmark_page_for_flatten(
handle: *mut DocumentEditor,
page: usize,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
editor.unmark_page_for_flatten(page);
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn document_editor_is_page_marked_for_redaction(
handle: *const DocumentEditor,
page: usize,
) -> i32 {
if handle.is_null() {
return -1;
}
let editor = handle_ref(handle);
if editor.is_page_marked_for_redaction(page) {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn document_editor_unmark_page_for_redaction(
handle: *mut DocumentEditor,
page: usize,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
editor.unmark_page_for_redaction(page);
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn pdf_from_markdown(markdown: *const c_char, error_code: *mut i32) -> *mut Pdf {
if markdown.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let md = match c_str(markdown) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
match Pdf::from_markdown(md) {
Ok(pdf) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(pdf))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_from_html(html: *const c_char, error_code: *mut i32) -> *mut Pdf {
if html.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let html_str = match c_str(html) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
match Pdf::from_html(html_str) {
Ok(pdf) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(pdf))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_from_text(text: *const c_char, error_code: *mut i32) -> *mut Pdf {
if text.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let text_str = match c_str(text) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
match Pdf::from_text(text_str) {
Ok(pdf) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(pdf))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_save(handle: *mut Pdf, path: *const c_char, error_code: *mut i32) -> i32 {
if handle.is_null() || path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let path_str = match c_str(path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
let pdf = handle_mut(handle);
match pdf.save(path_str) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_save_to_bytes(
handle: *mut Pdf,
data_len: *mut i32,
error_code: *mut i32,
) -> *mut u8 {
if handle.is_null() || data_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let pdf = handle_mut(handle);
match pdf.save_to_bytes() {
Ok(bytes) => {
set_error(error_code, ERR_SUCCESS);
write_out(data_len, bytes.len() as i32);
vec_to_ffi_bytes(bytes)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_get_page_count(handle: *mut Pdf, error_code: *mut i32) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let pdf = handle_mut(handle);
match pdf.page_count() {
Ok(count) => {
set_error(error_code, ERR_SUCCESS);
count as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_free(handle: *mut Pdf) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
pub struct FfiSearchResults {
results: Vec<RustSearchResult>,
}
#[no_mangle]
pub extern "C" fn pdf_document_search_page(
handle: *mut PdfDocument,
page_index: i32,
search_term: *const c_char,
case_sensitive: bool,
error_code: *mut i32,
) -> *mut FfiSearchResults {
if handle.is_null() || search_term.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
let term = match c_str(search_term) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let opts = SearchOptions::new()
.with_case_insensitive(!case_sensitive)
.with_page_range(page_index as usize, page_index as usize + 1);
match TextSearcher::search(doc, term, &opts) {
Ok(results) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiSearchResults { results }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_search_all(
handle: *mut PdfDocument,
search_term: *const c_char,
case_sensitive: bool,
error_code: *mut i32,
) -> *mut FfiSearchResults {
if handle.is_null() || search_term.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
let term = match c_str(search_term) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let opts = SearchOptions::new().with_case_insensitive(!case_sensitive);
match TextSearcher::search(doc, term, &opts) {
Ok(results) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiSearchResults { results }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_search_result_count(results: *const FfiSearchResults) -> i32 {
if results.is_null() {
return 0;
}
let r = handle_ref(results);
r.results.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_search_result_get_text(
results: *const FfiSearchResults,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if results.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let r = handle_ref(results);
if index < 0 || (index as usize) >= r.results.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&r.results[index as usize].text)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_search_result_get_page(
results: *const FfiSearchResults,
index: i32,
error_code: *mut i32,
) -> i32 {
if results.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let r = handle_ref(results);
if index < 0 || (index as usize) >= r.results.len() {
set_error(error_code, ERR_INVALID_PAGE);
return -1;
}
set_error(error_code, ERR_SUCCESS);
r.results[index as usize].page as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_search_result_get_bbox(
results: *const FfiSearchResults,
index: i32,
x: *mut f32,
y: *mut f32,
width: *mut f32,
height: *mut f32,
error_code: *mut i32,
) {
if results.is_null() || x.is_null() || y.is_null() || width.is_null() || height.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return;
}
let r = handle_ref(results);
if index < 0 || (index as usize) >= r.results.len() {
set_error(error_code, ERR_INVALID_PAGE);
return;
}
let bbox = &r.results[index as usize].bbox;
write_out(x, bbox.x);
write_out(y, bbox.y);
write_out(width, bbox.width);
write_out(height, bbox.height);
set_error(error_code, ERR_SUCCESS);
}
#[no_mangle]
pub extern "C" fn pdf_oxide_search_result_free(handle: *mut FfiSearchResults) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
pub struct FfiFont {
name: String,
subtype: String,
encoding: String,
is_embedded: bool,
is_subset: bool,
}
pub struct FfiFontList {
fonts: Vec<FfiFont>,
}
#[no_mangle]
pub extern "C" fn pdf_document_get_embedded_fonts(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut FfiFontList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
let fonts = match doc.extract_spans(page_index as usize) {
Ok(spans) => {
let mut seen = std::collections::HashSet::new();
let mut result = Vec::new();
for span in &spans {
let name = &span.font_name;
if !name.is_empty() && seen.insert(name.clone()) {
let is_subset = name.len() > 7 && name.as_bytes().get(6) == Some(&b'+');
let is_embedded = span.font_name.contains('+') || !span.font_name.is_empty();
result.push(FfiFont {
name: name.clone(),
subtype: String::from("Unknown"),
encoding: String::from("Unknown"),
is_embedded,
is_subset,
});
}
}
result
},
Err(_) => Vec::new(),
};
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiFontList { fonts }))
}
#[no_mangle]
pub extern "C" fn pdf_oxide_font_count(fonts: *const FfiFontList) -> i32 {
if fonts.is_null() {
return 0;
}
handle_ref(fonts).fonts.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_font_get_name(
fonts: *const FfiFontList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if fonts.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(fonts);
if (index as usize) >= list.fonts.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.fonts[index as usize].name)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_font_get_type(
fonts: *const FfiFontList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if fonts.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(fonts);
if (index as usize) >= list.fonts.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.fonts[index as usize].subtype)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_font_get_encoding(
fonts: *const FfiFontList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if fonts.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(fonts);
if (index as usize) >= list.fonts.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.fonts[index as usize].encoding)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_font_is_embedded(
fonts: *const FfiFontList,
index: i32,
error_code: *mut i32,
) -> i32 {
if fonts.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(fonts);
if (index as usize) >= list.fonts.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
if list.fonts[index as usize].is_embedded {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_font_is_subset(
fonts: *const FfiFontList,
index: i32,
error_code: *mut i32,
) -> i32 {
if fonts.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(fonts);
if (index as usize) >= list.fonts.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
if list.fonts[index as usize].is_subset {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_font_get_size(
_fonts: *const FfiFontList,
_index: i32,
error_code: *mut i32,
) -> f32 {
set_error(error_code, ERR_SUCCESS);
0.0
}
#[no_mangle]
pub extern "C" fn pdf_oxide_font_list_free(handle: *mut FfiFontList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
use crate::extractors::PdfImage;
pub struct FfiImageList {
images: Vec<PdfImage>,
}
#[no_mangle]
pub extern "C" fn pdf_document_get_embedded_images(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut FfiImageList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_images(page_index as usize) {
Ok(images) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiImageList { images }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_image_count(images: *const FfiImageList) -> i32 {
if images.is_null() {
return 0;
}
handle_ref(images).images.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_image_get_width(
images: *const FfiImageList,
index: i32,
error_code: *mut i32,
) -> i32 {
if images.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(images);
if (index as usize) >= list.images.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
list.images[index as usize].width() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_image_get_height(
images: *const FfiImageList,
index: i32,
error_code: *mut i32,
) -> i32 {
if images.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(images);
if (index as usize) >= list.images.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
list.images[index as usize].height() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_image_get_format(
images: *const FfiImageList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if images.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(images);
if (index as usize) >= list.images.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&format!("{:?}", list.images[index as usize].color_space()))
}
#[no_mangle]
pub extern "C" fn pdf_oxide_image_get_colorspace(
images: *const FfiImageList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if images.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(images);
if (index as usize) >= list.images.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&format!("{:?}", list.images[index as usize].color_space()))
}
#[no_mangle]
pub extern "C" fn pdf_oxide_image_get_bits_per_component(
images: *const FfiImageList,
index: i32,
error_code: *mut i32,
) -> i32 {
if images.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(images);
if (index as usize) >= list.images.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
list.images[index as usize].bits_per_component() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_image_get_data(
images: *const FfiImageList,
index: i32,
data_len: *mut i32,
error_code: *mut i32,
) -> *mut u8 {
if images.is_null() || index < 0 || data_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(images);
if (index as usize) >= list.images.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
let img = &list.images[index as usize];
let data = match img.data() {
crate::extractors::ImageData::Jpeg(bytes) => bytes.clone(),
crate::extractors::ImageData::Raw { pixels, .. } => pixels.clone(),
};
write_out(data_len, data.len() as i32);
set_error(error_code, ERR_SUCCESS);
vec_to_ffi_bytes(data)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_image_list_free(handle: *mut FfiImageList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
pub struct FfiAnnotationList {
annotations: Vec<RustAnnotation>,
}
#[no_mangle]
pub extern "C" fn pdf_document_get_page_annotations(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut FfiAnnotationList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.get_annotations(page_index as usize) {
Ok(annotations) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiAnnotationList { annotations }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_count(annotations: *const FfiAnnotationList) -> i32 {
if annotations.is_null() {
return 0;
}
handle_ref(annotations).annotations.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_get_type(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.annotations[index as usize].annotation_type)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_get_content(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string_opt(list.annotations[index as usize].contents.clone())
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_get_rect(
annotations: *const FfiAnnotationList,
index: i32,
x: *mut f32,
y: *mut f32,
width: *mut f32,
height: *mut f32,
error_code: *mut i32,
) {
if annotations.is_null() || x.is_null() || y.is_null() || width.is_null() || height.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return;
}
let list = handle_ref(annotations);
if index < 0 || (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return;
}
if let Some(rect) = &list.annotations[index as usize].rect {
write_out(x, rect[0] as f32);
write_out(y, rect[1] as f32);
write_out(width, (rect[2] - rect[0]) as f32);
write_out(height, (rect[3] - rect[1]) as f32);
} else {
write_out(x, 0.0f32);
write_out(y, 0.0f32);
write_out(width, 0.0f32);
write_out(height, 0.0f32);
}
set_error(error_code, ERR_SUCCESS);
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_list_free(handle: *mut FfiAnnotationList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_get_subtype(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&format!("{:?}", list.annotations[index as usize].subtype_enum))
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_is_marked_deleted(
_annotations: *const FfiAnnotationList,
_index: i32,
error_code: *mut i32,
) -> bool {
set_error(error_code, ERR_SUCCESS);
false
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_get_creation_date(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> i64 {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_get_modification_date(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> i64 {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_get_author(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string_opt(list.annotations[index as usize].author.clone())
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_get_border_width(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> f32 {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0.0;
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0.0;
}
set_error(error_code, ERR_SUCCESS);
list.annotations[index as usize]
.border
.map(|b| b[2] as f32)
.unwrap_or(0.0)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_get_color(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> u32 {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
if let Some(color) = &list.annotations[index as usize].color {
if color.len() >= 3 {
let r = (color[0] * 255.0) as u32;
let g = (color[1] * 255.0) as u32;
let b = (color[2] * 255.0) as u32;
(r << 16) | (g << 8) | b
} else {
0
}
} else {
0
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_is_hidden(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> bool {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return false;
}
set_error(error_code, ERR_SUCCESS);
list.annotations[index as usize].flags.is_hidden()
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_is_printable(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> bool {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return false;
}
set_error(error_code, ERR_SUCCESS);
list.annotations[index as usize].flags.is_printable()
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotation_is_read_only(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> bool {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return false;
}
set_error(error_code, ERR_SUCCESS);
list.annotations[index as usize].flags.is_read_only()
}
#[no_mangle]
pub extern "C" fn pdf_oxide_link_annotation_get_uri(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
if let Some(LinkAction::Uri(uri)) = &list.annotations[index as usize].action {
to_c_string(uri)
} else {
ptr::null_mut()
}
}
use crate::annotations::LinkAction;
#[no_mangle]
pub extern "C" fn pdf_oxide_text_annotation_get_icon_name(
_annotations: *const FfiAnnotationList,
_index: i32,
error_code: *mut i32,
) -> *mut c_char {
set_error(error_code, ERR_SUCCESS);
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn pdf_oxide_highlight_annotation_get_quad_points_count(
annotations: *const FfiAnnotationList,
index: i32,
error_code: *mut i32,
) -> i32 {
if annotations.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
list.annotations[index as usize]
.quad_points
.as_ref()
.map(|q| q.len() as i32)
.unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_highlight_annotation_get_quad_point(
annotations: *const FfiAnnotationList,
index: i32,
quad_index: i32,
x1: *mut f32,
y1: *mut f32,
x2: *mut f32,
y2: *mut f32,
x3: *mut f32,
y3: *mut f32,
x4: *mut f32,
y4: *mut f32,
error_code: *mut i32,
) {
if annotations.is_null() || index < 0 || quad_index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return;
}
let list = handle_ref(annotations);
if (index as usize) >= list.annotations.len() {
set_error(error_code, ERR_INVALID_PAGE);
return;
}
if let Some(quads) = &list.annotations[index as usize].quad_points {
if (quad_index as usize) < quads.len() {
let q = &quads[quad_index as usize];
write_out(x1, q[0] as f32);
write_out(y1, q[1] as f32);
write_out(x2, q[2] as f32);
write_out(y2, q[3] as f32);
write_out(x3, q[4] as f32);
write_out(y3, q[5] as f32);
write_out(x4, q[6] as f32);
write_out(y4, q[7] as f32);
set_error(error_code, ERR_SUCCESS);
return;
}
}
set_error(error_code, ERR_INVALID_PAGE);
}
#[no_mangle]
pub extern "C" fn pdf_page_get_width(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> f32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return 0.0;
}
let doc = handle_ref(handle);
match doc.get_page_media_box(page_index as usize) {
Ok((_, _, w, _)) => {
set_error(error_code, ERR_SUCCESS);
w
},
Err(e) => {
set_error(error_code, classify_error(&e));
0.0
},
}
}
#[no_mangle]
pub extern "C" fn pdf_page_get_height(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> f32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return 0.0;
}
let doc = handle_ref(handle);
match doc.get_page_media_box(page_index as usize) {
Ok((_, _, _, h)) => {
set_error(error_code, ERR_SUCCESS);
h
},
Err(e) => {
set_error(error_code, classify_error(&e));
0.0
},
}
}
#[no_mangle]
pub extern "C" fn pdf_page_get_rotation(
_handle: *mut PdfDocument,
_page_index: i32,
error_code: *mut i32,
) -> i32 {
set_error(error_code, ERR_SUCCESS);
0 }
macro_rules! page_box_fn {
($fn_name:ident) => {
#[no_mangle]
pub extern "C" fn $fn_name(
handle: *mut PdfDocument,
page_index: i32,
x: *mut f32,
y: *mut f32,
width: *mut f32,
height: *mut f32,
error_code: *mut i32,
) {
if handle.is_null() || x.is_null() || y.is_null() || width.is_null() || height.is_null()
{
set_error(error_code, ERR_INVALID_ARG);
return;
}
let doc = handle_ref(handle);
match doc.get_page_media_box(page_index as usize) {
Ok((bx, by, bw, bh)) => {
write_out(x, bx);
write_out(y, by);
write_out(width, bw);
write_out(height, bh);
set_error(error_code, ERR_SUCCESS);
},
Err(e) => {
set_error(error_code, classify_error(&e));
},
}
}
};
}
page_box_fn!(pdf_page_get_media_box);
page_box_fn!(pdf_page_get_crop_box);
page_box_fn!(pdf_page_get_art_box);
page_box_fn!(pdf_page_get_bleed_box);
page_box_fn!(pdf_page_get_trim_box);
use crate::layout::TextSpan;
pub struct FfiElementList {
spans: Vec<TextSpan>,
}
#[no_mangle]
pub extern "C" fn pdf_page_get_elements(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut FfiElementList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_spans(page_index as usize) {
Ok(spans) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiElementList { spans }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_element_count(elements: *const FfiElementList) -> i32 {
if elements.is_null() {
return 0;
}
handle_ref(elements).spans.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_element_get_type(
_elements: *const FfiElementList,
_index: i32,
error_code: *mut i32,
) -> *mut c_char {
set_error(error_code, ERR_SUCCESS);
to_c_string("text")
}
#[no_mangle]
pub extern "C" fn pdf_oxide_element_get_text(
elements: *const FfiElementList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if elements.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(elements);
if (index as usize) >= list.spans.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.spans[index as usize].text)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_element_get_rect(
elements: *const FfiElementList,
index: i32,
x: *mut f32,
y: *mut f32,
width: *mut f32,
height: *mut f32,
error_code: *mut i32,
) {
if elements.is_null() || x.is_null() || y.is_null() || width.is_null() || height.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return;
}
let list = handle_ref(elements);
if index < 0 || (index as usize) >= list.spans.len() {
set_error(error_code, ERR_INVALID_PAGE);
return;
}
let span = &list.spans[index as usize];
write_out(x, span.bbox.x);
write_out(y, span.bbox.y);
write_out(width, span.bbox.width);
write_out(height, span.bbox.height);
set_error(error_code, ERR_SUCCESS);
}
#[no_mangle]
pub extern "C" fn pdf_oxide_elements_free(handle: *mut FfiElementList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
use crate::writer::barcode::{
BarcodeGenerator, BarcodeOptions, BarcodeType, QrCodeOptions, QrErrorCorrection,
};
pub struct FfiBarcodeImage {
data: Vec<u8>,
format: i32, source_data: String,
}
#[no_mangle]
pub extern "C" fn pdf_generate_qr_code(
data: *const c_char,
error_correction: i32,
size_px: i32,
error_code: *mut i32,
) -> *mut FfiBarcodeImage {
if data.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let data_str = match c_str(data) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let ec = match error_correction {
0 => QrErrorCorrection::Low,
1 => QrErrorCorrection::Medium,
2 => QrErrorCorrection::Quartile,
3 => QrErrorCorrection::High,
_ => QrErrorCorrection::Medium,
};
let opts = QrCodeOptions::new()
.size(size_px.max(1) as u32)
.error_correction(ec);
match BarcodeGenerator::generate_qr(data_str, &opts) {
Ok(png_bytes) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiBarcodeImage {
data: png_bytes,
format: 100, source_data: data_str.to_string(),
}))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_generate_barcode(
data: *const c_char,
format: i32,
size_px: i32,
error_code: *mut i32,
) -> *mut FfiBarcodeImage {
if data.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let data_str = match c_str(data) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let barcode_type = match format {
0 => BarcodeType::Code128,
1 => BarcodeType::Code39,
2 => BarcodeType::Ean13,
3 => BarcodeType::Ean8,
4 => BarcodeType::UpcA,
5 => BarcodeType::Itf,
_ => BarcodeType::Code128,
};
let opts = BarcodeOptions::new()
.width(size_px.max(1) as u32)
.height((size_px.max(1) / 3).max(30) as u32);
match BarcodeGenerator::generate_1d(barcode_type, data_str, &opts) {
Ok(png_bytes) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiBarcodeImage {
data: png_bytes,
format,
source_data: data_str.to_string(),
}))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_barcode_get_image_png(
barcode_handle: *const FfiBarcodeImage,
_size_px: i32,
out_len: *mut i32,
error_code: *mut i32,
) -> *mut u8 {
if barcode_handle.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
write_out(out_len, 0i32);
return ptr::null_mut();
}
let bc = handle_ref(barcode_handle);
let data = bc.data.clone();
let len = data.len() as i32;
write_out(out_len, len);
set_error(error_code, ERR_SUCCESS);
vec_to_ffi_bytes(data)
}
#[no_mangle]
pub extern "C" fn pdf_barcode_get_svg(
barcode_handle: *const FfiBarcodeImage,
_size_px: i32,
error_code: *mut i32,
) -> *mut c_char {
if barcode_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
#[cfg(feature = "barcodes")]
{
let bc = handle_ref(barcode_handle);
let result = if bc.format == 100 {
BarcodeGenerator::generate_qr_svg(&bc.source_data, &QrCodeOptions::default())
} else {
let barcode_type = match bc.format {
0 => BarcodeType::Code128,
1 => BarcodeType::Code39,
2 => BarcodeType::Ean13,
3 => BarcodeType::Ean8,
4 => BarcodeType::UpcA,
5 => BarcodeType::Itf,
6 => BarcodeType::Code93,
7 => BarcodeType::Codabar,
_ => BarcodeType::Code128,
};
BarcodeGenerator::generate_1d_svg(
barcode_type,
&bc.source_data,
&BarcodeOptions::default(),
)
};
match result {
Ok(svg) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&svg)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "barcodes"))]
{
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_add_barcode_to_page(
document_handle: *mut DocumentEditor,
page_index: i32,
barcode_handle: *const FfiBarcodeImage,
x: f32,
y: f32,
width: f32,
height: f32,
error_code: *mut i32,
) -> i32 {
if document_handle.is_null() || barcode_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
if page_index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(document_handle);
let barcode = handle_ref(barcode_handle);
match editor.add_image_bytes_to_page(page_index as usize, &barcode.data, x, y, width, height) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(_) => {
set_error(error_code, ERR_INTERNAL);
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_barcode_get_format(
barcode_handle: *const FfiBarcodeImage,
error_code: *mut i32,
) -> i32 {
if barcode_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
set_error(error_code, ERR_SUCCESS);
handle_ref(barcode_handle).format
}
#[no_mangle]
pub extern "C" fn pdf_barcode_get_data(
barcode_handle: *const FfiBarcodeImage,
error_code: *mut i32,
) -> *mut c_char {
if barcode_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&handle_ref(barcode_handle).source_data)
}
#[no_mangle]
pub extern "C" fn pdf_barcode_get_confidence(
_barcode_handle: *const FfiBarcodeImage,
error_code: *mut i32,
) -> f32 {
set_error(error_code, ERR_SUCCESS);
1.0 }
#[no_mangle]
pub extern "C" fn pdf_barcode_free(handle: *mut FfiBarcodeImage) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
#[cfg(feature = "signatures")]
pub struct FfiSignatureInfo {
info: crate::signatures::SignatureInfo,
}
#[cfg(not(feature = "signatures"))]
pub struct FfiSignatureInfo {
_dummy: (),
}
#[no_mangle]
pub extern "C" fn pdf_certificate_load_from_bytes(
cert_bytes: *const u8,
cert_len: i32,
password: *const c_char,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "signatures")]
{
if cert_bytes.is_null() || cert_len <= 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let data = raw_slice(cert_bytes, cert_len as usize);
let pwd = if password.is_null() {
""
} else {
c_str(password).unwrap_or("")
};
if let Ok(creds) = crate::signatures::SigningCredentials::from_pkcs12(data, pwd) {
set_error(error_code, ERR_SUCCESS);
return Box::into_raw(Box::new(creds)) as *mut std::ffi::c_void;
}
match crate::signatures::SigningCredentials::from_der(data.to_vec()) {
Ok(creds) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(creds)) as *mut std::ffi::c_void
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = (cert_bytes, cert_len, password);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn pdf_certificate_load_from_pem(
cert_pem: *const c_char,
key_pem: *const c_char,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "signatures")]
{
let Some(cert_str) = read_cstr_or_fail(cert_pem, error_code) else {
return ptr::null_mut();
};
let Some(key_str) = read_cstr_or_fail(key_pem, error_code) else {
return ptr::null_mut();
};
match crate::signatures::SigningCredentials::from_pem(&cert_str, &key_str) {
Ok(creds) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(creds)) as *mut std::ffi::c_void
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = (cert_pem, key_pem);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_document_sign(
document_handle: *mut std::ffi::c_void,
certificate_handle: *const std::ffi::c_void,
reason: *const c_char,
location: *const c_char,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "signatures")]
{
use crate::signatures::{sign_pdf_bytes, SignOptions};
if document_handle.is_null() || certificate_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_mut(document_handle as *mut PdfDocument);
let creds = handle_ref(certificate_handle as *const crate::signatures::SigningCredentials);
if doc.source_bytes.is_empty() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let reason_str = c_str_lossy(reason);
let location_str = c_str_lossy(location);
let opts = SignOptions {
reason: reason_str,
location: location_str,
..Default::default()
};
match sign_pdf_bytes(&doc.source_bytes.clone(), creds, opts) {
Ok(signed) => {
doc.source_bytes = signed;
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = (document_handle, certificate_handle, reason, location);
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
}
#[no_mangle]
pub unsafe extern "C" fn pdf_sign_bytes(
pdf_data: *const u8,
pdf_len: usize,
certificate_handle: *const std::ffi::c_void,
reason: *const c_char,
location: *const c_char,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
#[cfg(feature = "signatures")]
{
use crate::signatures::{sign_pdf_bytes, SignOptions};
if pdf_data.is_null() || certificate_handle.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let data = raw_slice(pdf_data, pdf_len);
let creds = handle_ref(certificate_handle as *const crate::signatures::SigningCredentials);
let reason_str = c_str_lossy(reason);
let location_str = c_str_lossy(location);
let opts = SignOptions {
reason: reason_str,
location: location_str,
..Default::default()
};
match sign_pdf_bytes(data, creds, opts) {
Ok(signed) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, signed.len());
vec_to_ffi_bytes(signed)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = (pdf_data, pdf_len, certificate_handle, reason, location, out_len);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_document_get_signature_count(
document_handle: *const std::ffi::c_void,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "signatures")]
{
if document_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_mut(document_handle as *mut PdfDocument);
match crate::signatures::count_signatures(doc) {
Ok(n) => {
set_error(error_code, ERR_SUCCESS);
n as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = document_handle;
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
}
#[no_mangle]
pub extern "C" fn pdf_document_get_signature(
document_handle: *const std::ffi::c_void,
index: i32,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "signatures")]
{
if document_handle.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_mut(document_handle as *mut PdfDocument);
match crate::signatures::enumerate_signatures(doc) {
Ok(list) => match list.into_iter().nth(index as usize) {
Some(info) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiSignatureInfo { info })) as *mut std::ffi::c_void
},
None => {
set_error(error_code, ERR_INVALID_ARG);
ptr::null_mut()
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = (document_handle, index);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_signature_verify(
signature_handle: *const std::ffi::c_void,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "signatures")]
{
if signature_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let ffi = handle_ref(signature_handle as *const FfiSignatureInfo);
let Some(contents) = ffi.info.contents() else {
set_error(error_code, _ERR_UNSUPPORTED);
return -1;
};
match crate::signatures::verify_signer(contents) {
Ok(crate::signatures::SignerVerify::Valid) => {
set_error(error_code, ERR_SUCCESS);
1
},
Ok(crate::signatures::SignerVerify::Invalid) => {
set_error(error_code, ERR_SUCCESS);
0
},
Ok(crate::signatures::SignerVerify::Unknown) => {
set_error(error_code, _ERR_UNSUPPORTED);
-1
},
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
-1
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = signature_handle;
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
}
#[no_mangle]
pub extern "C" fn pdf_signature_verify_detached(
signature_handle: *const std::ffi::c_void,
pdf_data: *const u8,
pdf_len: usize,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "signatures")]
{
if signature_handle.is_null() || pdf_data.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let ffi = handle_ref(signature_handle as *const FfiSignatureInfo);
let Some(contents) = ffi.info.contents() else {
set_error(error_code, _ERR_UNSUPPORTED);
return -1;
};
let br = ffi.info.byte_range();
if br.len() != 4 {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let byte_range: [i64; 4] = [br[0], br[1], br[2], br[3]];
let pdf_slice = raw_slice(pdf_data, pdf_len);
let signed_bytes = match crate::signatures::ByteRangeCalculator::extract_signed_bytes(
pdf_slice,
&byte_range,
) {
Ok(b) => b,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
match crate::signatures::verify_signer_detached(contents, &signed_bytes) {
Ok(crate::signatures::SignerVerify::Valid) => {
set_error(error_code, ERR_SUCCESS);
1
},
Ok(crate::signatures::SignerVerify::Invalid) => {
set_error(error_code, ERR_SUCCESS);
0
},
Ok(crate::signatures::SignerVerify::Unknown) => {
set_error(error_code, _ERR_UNSUPPORTED);
-1
},
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
-1
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = (signature_handle, pdf_data, pdf_len);
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
}
#[no_mangle]
pub extern "C" fn pdf_document_verify_all_signatures(
_document_handle: *const std::ffi::c_void,
error_code: *mut i32,
) -> i32 {
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
#[no_mangle]
pub extern "C" fn pdf_signature_get_signer_name(
sig: *const FfiSignatureInfo,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "signatures")]
{
if sig.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let info = handle_ref(sig);
set_error(error_code, ERR_SUCCESS);
to_c_string_opt(info.info.signer_name.clone())
}
#[cfg(not(feature = "signatures"))]
{
let _ = sig;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_signature_get_signing_time(
sig: *const FfiSignatureInfo,
error_code: *mut i32,
) -> i64 {
#[cfg(feature = "signatures")]
{
if sig.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let info = handle_ref(sig);
set_error(error_code, ERR_SUCCESS);
info.info
.signing_time
.as_deref()
.and_then(crate::signatures::parse_pdf_date_to_epoch)
.unwrap_or(0)
}
#[cfg(not(feature = "signatures"))]
{
let _ = sig;
set_error(error_code, _ERR_UNSUPPORTED);
0
}
}
#[no_mangle]
pub extern "C" fn pdf_signature_get_signing_reason(
sig: *const FfiSignatureInfo,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "signatures")]
{
if sig.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let info = handle_ref(sig);
set_error(error_code, ERR_SUCCESS);
to_c_string_opt(info.info.reason.clone())
}
#[cfg(not(feature = "signatures"))]
{
let _ = sig;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_signature_get_signing_location(
sig: *const FfiSignatureInfo,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "signatures")]
{
if sig.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let info = handle_ref(sig);
set_error(error_code, ERR_SUCCESS);
to_c_string_opt(info.info.location.clone())
}
#[cfg(not(feature = "signatures"))]
{
let _ = sig;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_signature_get_certificate(
sig: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "signatures")]
{
if sig.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let info = handle_ref(sig as *const FfiSignatureInfo);
let contents = match info.info.contents.as_ref() {
Some(c) => c,
None => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let cert_der = match crate::signatures::extract_signer_certificate_der(contents) {
Ok(d) => d,
Err(e) => {
set_error(error_code, classify_error(&e));
return ptr::null_mut();
},
};
match crate::signatures::SigningCredentials::from_der(cert_der) {
Ok(creds) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(creds)) as *mut std::ffi::c_void
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = sig;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_certificate_get_subject(
cert: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "signatures")]
{
if cert.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let creds = handle_ref(cert as *const crate::signatures::SigningCredentials);
match creds.subject() {
Ok(s) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&s)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = cert;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_certificate_get_issuer(
cert: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "signatures")]
{
if cert.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let creds = handle_ref(cert as *const crate::signatures::SigningCredentials);
match creds.issuer() {
Ok(s) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&s)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = cert;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_certificate_get_serial(
cert: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "signatures")]
{
if cert.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let creds = handle_ref(cert as *const crate::signatures::SigningCredentials);
match creds.serial() {
Ok(s) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&s)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = cert;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_certificate_get_validity(
cert: *const std::ffi::c_void,
not_before: *mut i64,
not_after: *mut i64,
error_code: *mut i32,
) {
#[cfg(feature = "signatures")]
{
if cert.is_null() || not_before.is_null() || not_after.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return;
}
let creds = handle_ref(cert as *const crate::signatures::SigningCredentials);
match creds.validity() {
Ok((nb, na)) => {
write_out(not_before, nb);
write_out(not_after, na);
set_error(error_code, ERR_SUCCESS);
},
Err(e) => set_error(error_code, classify_error(&e)),
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = (cert, not_before, not_after);
set_error(error_code, _ERR_UNSUPPORTED);
}
}
#[no_mangle]
pub extern "C" fn pdf_certificate_is_valid(
cert: *const std::ffi::c_void,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "signatures")]
{
if cert.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let creds = handle_ref(cert as *const crate::signatures::SigningCredentials);
match creds.is_valid() {
Ok(v) => {
set_error(error_code, ERR_SUCCESS);
if v {
1
} else {
0
}
},
Err(e) => {
set_error(error_code, classify_error(&e));
0
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = cert;
set_error(error_code, _ERR_UNSUPPORTED);
0
}
}
#[no_mangle]
pub extern "C" fn pdf_signature_free(handle: *mut FfiSignatureInfo) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
#[cfg(feature = "signatures")]
pub struct FfiDss {
dss: crate::signatures::DocumentSecurityStore,
}
#[cfg(not(feature = "signatures"))]
pub struct FfiDss {
_private: [u8; 0],
}
#[cfg(feature = "signatures")]
#[allow(unsafe_code)]
fn gather_der_blobs(ptrs: *const *const u8, lens: *const usize, n: usize) -> Vec<Vec<u8>> {
if ptrs.is_null() || lens.is_null() || n == 0 {
return Vec::new();
}
let p = unsafe { std::slice::from_raw_parts(ptrs, n) };
let l = unsafe { std::slice::from_raw_parts(lens, n) };
p.iter()
.zip(l)
.filter(|(pp, _)| !pp.is_null())
.map(|(pp, &ll)| raw_slice(*pp, ll).to_vec())
.collect()
}
#[no_mangle]
pub unsafe extern "C" fn pdf_sign_bytes_pades(
pdf_data: *const u8,
pdf_len: usize,
certificate_handle: *const std::ffi::c_void,
level: i32,
tsa_url: *const c_char,
reason: *const c_char,
location: *const c_char,
certs: *const *const u8,
cert_lens: *const usize,
n_certs: usize,
crls: *const *const u8,
crl_lens: *const usize,
n_crls: usize,
ocsps: *const *const u8,
ocsp_lens: *const usize,
n_ocsps: usize,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
#[cfg(feature = "signatures")]
{
use crate::signatures::{sign_pdf_bytes_pades, RevocationMaterial, SignOptions};
if pdf_data.is_null() || certificate_handle.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let Some(level_enum) = crate::signatures::PadesLevel::from_code(level) else {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
};
let data = raw_slice(pdf_data, pdf_len);
let creds = handle_ref(certificate_handle as *const crate::signatures::SigningCredentials);
let opts = SignOptions {
reason: c_str_lossy(reason),
location: c_str_lossy(location),
..Default::default()
};
let material = RevocationMaterial {
certificates: gather_der_blobs(certs, cert_lens, n_certs),
crls: gather_der_blobs(crls, crl_lens, n_crls),
ocsp_responses: gather_der_blobs(ocsps, ocsp_lens, n_ocsps),
..Default::default()
};
#[cfg(feature = "tsa-client")]
let ts_closure: Option<Box<dyn Fn(&[u8]) -> crate::error::Result<Vec<u8>>>> =
c_str_lossy(tsa_url).map(|u| {
let client =
crate::signatures::TsaClient::new(crate::signatures::TsaClientConfig::new(u));
Box::new(move |sig: &[u8]| {
client
.request_timestamp(sig)
.map(|t| t.token_bytes().to_vec())
}) as Box<dyn Fn(&[u8]) -> crate::error::Result<Vec<u8>>>
});
#[cfg(not(feature = "tsa-client"))]
let ts_closure: Option<Box<dyn Fn(&[u8]) -> crate::error::Result<Vec<u8>>>> = {
let _ = tsa_url;
None
};
match sign_pdf_bytes_pades(data, creds, opts, level_enum, ts_closure.as_deref(), &material)
{
Ok(signed) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, signed.len());
vec_to_ffi_bytes(signed)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = (
pdf_data,
pdf_len,
certificate_handle,
level,
tsa_url,
reason,
location,
certs,
cert_lens,
n_certs,
crls,
crl_lens,
n_crls,
ocsps,
ocsp_lens,
n_ocsps,
out_len,
);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[repr(C)]
pub struct PadesSignOptionsC {
pub certificate_handle: *const std::ffi::c_void,
pub certs: *const *const u8,
pub cert_lens: *const usize,
pub n_certs: usize,
pub crls: *const *const u8,
pub crl_lens: *const usize,
pub n_crls: usize,
pub ocsps: *const *const u8,
pub ocsp_lens: *const usize,
pub n_ocsps: usize,
pub tsa_url: *const c_char,
pub reason: *const c_char,
pub location: *const c_char,
pub level: i32,
}
#[no_mangle]
pub unsafe extern "C" fn pdf_sign_bytes_pades_opts(
pdf_data: *const u8,
pdf_len: usize,
options: *const PadesSignOptionsC,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if options.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let o = &*options;
pdf_sign_bytes_pades(
pdf_data,
pdf_len,
o.certificate_handle,
o.level,
o.tsa_url,
o.reason,
o.location,
o.certs,
o.cert_lens,
o.n_certs,
o.crls,
o.crl_lens,
o.n_crls,
o.ocsps,
o.ocsp_lens,
o.n_ocsps,
out_len,
error_code,
)
}
#[no_mangle]
pub extern "C" fn pdf_signature_get_pades_level(
signature_handle: *const std::ffi::c_void,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "signatures")]
{
if signature_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let ffi = handle_ref(signature_handle as *const FfiSignatureInfo);
set_error(error_code, ERR_SUCCESS);
crate::signatures::classify_pades_level(&ffi.info, None).code()
}
#[cfg(not(feature = "signatures"))]
{
let _ = signature_handle;
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
}
#[no_mangle]
pub extern "C" fn pdf_document_get_dss(
document_handle: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "signatures")]
{
if document_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(document_handle as *const PdfDocument);
match crate::signatures::read_dss(doc) {
Ok(Some(dss)) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiDss { dss })) as *mut std::ffi::c_void
},
Ok(None) => {
set_error(error_code, ERR_SUCCESS);
ptr::null_mut()
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = document_handle;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_document_has_timestamp(
document_handle: *const std::ffi::c_void,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "signatures")]
{
if document_handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_ref(document_handle as *const PdfDocument);
set_error(error_code, ERR_SUCCESS);
i32::from(crate::signatures::has_document_timestamp(&doc.source_bytes))
}
#[cfg(not(feature = "signatures"))]
{
let _ = document_handle;
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
}
#[cfg(feature = "signatures")]
macro_rules! dss_count_fn {
($name:ident, $field:ident) => {
#[no_mangle]
pub extern "C" fn $name(dss: *const std::ffi::c_void) -> i32 {
if dss.is_null() {
return -1;
}
handle_ref(dss as *const FfiDss).dss.$field.len() as i32
}
};
}
#[cfg(feature = "signatures")]
dss_count_fn!(pdf_dss_cert_count, certificates);
#[cfg(feature = "signatures")]
dss_count_fn!(pdf_dss_crl_count, crls);
#[cfg(feature = "signatures")]
dss_count_fn!(pdf_dss_ocsp_count, ocsp_responses);
#[cfg(feature = "signatures")]
dss_count_fn!(pdf_dss_vri_count, vri);
#[cfg(not(feature = "signatures"))]
#[no_mangle]
pub extern "C" fn pdf_dss_cert_count(_dss: *const std::ffi::c_void) -> i32 {
-1
}
#[cfg(not(feature = "signatures"))]
#[no_mangle]
pub extern "C" fn pdf_dss_crl_count(_dss: *const std::ffi::c_void) -> i32 {
-1
}
#[cfg(not(feature = "signatures"))]
#[no_mangle]
pub extern "C" fn pdf_dss_ocsp_count(_dss: *const std::ffi::c_void) -> i32 {
-1
}
#[cfg(not(feature = "signatures"))]
#[no_mangle]
pub extern "C" fn pdf_dss_vri_count(_dss: *const std::ffi::c_void) -> i32 {
-1
}
#[cfg(feature = "signatures")]
macro_rules! dss_get_fn {
($name:ident, $field:ident) => {
#[no_mangle]
pub extern "C" fn $name(
dss: *const std::ffi::c_void,
index: i32,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if dss.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let d = handle_ref(dss as *const FfiDss);
match d.dss.$field.get(index as usize) {
Some(der) => {
set_error(error_code, ERR_SUCCESS);
write_out(out_len, der.len());
vec_to_ffi_bytes(der.clone())
},
None => {
set_error(error_code, ERR_INVALID_ARG);
ptr::null_mut()
},
}
}
};
}
#[cfg(feature = "signatures")]
dss_get_fn!(pdf_dss_get_cert, certificates);
#[cfg(feature = "signatures")]
dss_get_fn!(pdf_dss_get_crl, crls);
#[cfg(feature = "signatures")]
dss_get_fn!(pdf_dss_get_ocsp, ocsp_responses);
#[cfg(not(feature = "signatures"))]
#[no_mangle]
pub extern "C" fn pdf_dss_get_cert(
_dss: *const std::ffi::c_void,
_index: i32,
_out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
#[cfg(not(feature = "signatures"))]
#[no_mangle]
pub extern "C" fn pdf_dss_get_crl(
_dss: *const std::ffi::c_void,
_index: i32,
_out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
#[cfg(not(feature = "signatures"))]
#[no_mangle]
pub extern "C" fn pdf_dss_get_ocsp(
_dss: *const std::ffi::c_void,
_index: i32,
_out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn pdf_dss_free(dss: *mut std::ffi::c_void) {
if !dss.is_null() {
unsafe {
drop(Box::from_raw(dss as *mut FfiDss));
}
}
}
#[no_mangle]
pub extern "C" fn pdf_certificate_free(handle: *mut std::ffi::c_void) {
#[cfg(feature = "signatures")]
{
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle as *mut crate::signatures::SigningCredentials));
}
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = handle;
}
}
#[cfg(feature = "rendering")]
use crate::rendering::{
self, ImageFormat as RenderImageFormat, RenderOptions as RustRenderOptions,
RenderedImage as RustRenderedImage,
};
#[cfg(feature = "rendering")]
pub struct FfiRenderedImage {
inner: RustRenderedImage,
}
#[cfg(not(feature = "rendering"))]
pub struct FfiRenderedImage {
_dummy: (),
}
#[no_mangle]
pub extern "C" fn pdf_estimate_render_time(
_doc: *const std::ffi::c_void,
_page_index: i32,
error_code: *mut i32,
) -> i32 {
set_error(error_code, ERR_SUCCESS);
100 }
#[no_mangle]
pub extern "C" fn pdf_create_renderer(
_dpi: i32,
_format: i32,
_quality: i32,
_anti_alias: bool,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn pdf_render_page(
doc: *mut PdfDocument,
page_index: i32,
format: i32,
error_code: *mut i32,
) -> *mut FfiRenderedImage {
#[cfg(feature = "rendering")]
{
if doc.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let d = handle_ref(doc as *const _);
let fmt = if format == 1 {
RenderImageFormat::Jpeg
} else {
RenderImageFormat::Png
};
let opts = RustRenderOptions {
dpi: 150,
format: fmt,
..Default::default()
};
match rendering::render_page(d, page_index as usize, &opts) {
Ok(img) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiRenderedImage { inner: img }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "rendering"))]
{
let _ = (doc, page_index, format);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn pdf_render_page_with_options(
doc: *mut PdfDocument,
page_index: i32,
dpi: i32,
format: i32,
bg_r: f32,
bg_g: f32,
bg_b: f32,
bg_a: f32,
transparent_background: i32,
render_annotations: i32,
jpeg_quality: i32,
error_code: *mut i32,
) -> *mut FfiRenderedImage {
#[cfg(feature = "rendering")]
{
if doc.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
if dpi <= 0 || !(1..=100).contains(&jpeg_quality) {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let d = handle_ref(doc as *const _);
let fmt = if format == 1 {
RenderImageFormat::Jpeg
} else {
RenderImageFormat::Png
};
let background = if transparent_background != 0 {
None
} else {
Some([bg_r, bg_g, bg_b, bg_a])
};
let opts = RustRenderOptions {
dpi: dpi as u32,
format: fmt,
background,
render_annotations: render_annotations != 0,
jpeg_quality: jpeg_quality as u8,
excluded_layers: std::collections::HashSet::new(),
scale_override: None,
};
match rendering::render_page(d, page_index as usize, &opts) {
Ok(img) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiRenderedImage { inner: img }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "rendering"))]
{
let _ = (
doc,
page_index,
dpi,
format,
bg_r,
bg_g,
bg_b,
bg_a,
transparent_background,
render_annotations,
jpeg_quality,
);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn pdf_render_page_with_options_ex(
doc: *mut PdfDocument,
page_index: i32,
dpi: i32,
format: i32,
bg_r: f32,
bg_g: f32,
bg_b: f32,
bg_a: f32,
transparent_background: i32,
render_annotations: i32,
jpeg_quality: i32,
excluded_layers: *const *const c_char,
excluded_layers_count: usize,
error_code: *mut i32,
) -> *mut FfiRenderedImage {
#[cfg(feature = "rendering")]
{
if doc.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
if dpi <= 0 || !(1..=100).contains(&jpeg_quality) {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let d = handle_ref(doc as *const _);
let fmt = if format == 1 {
RenderImageFormat::Jpeg
} else {
RenderImageFormat::Png
};
let background = if transparent_background != 0 {
None
} else {
Some([bg_r, bg_g, bg_b, bg_a])
};
let mut layers: std::collections::HashSet<String> = std::collections::HashSet::new();
if !excluded_layers.is_null() && excluded_layers_count > 0 {
for i in 0..excluded_layers_count {
let entry: *const c_char = unsafe { *excluded_layers.add(i) };
if entry.is_null() {
continue;
}
if let Some(s) = c_str_lossy(entry) {
layers.insert(s);
}
}
}
let opts = RustRenderOptions {
dpi: dpi as u32,
format: fmt,
background,
render_annotations: render_annotations != 0,
jpeg_quality: jpeg_quality as u8,
excluded_layers: layers,
scale_override: None,
};
match rendering::render_page(d, page_index as usize, &opts) {
Ok(img) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiRenderedImage { inner: img }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "rendering"))]
{
let _ = (
doc,
page_index,
dpi,
format,
bg_r,
bg_g,
bg_b,
bg_a,
transparent_background,
render_annotations,
jpeg_quality,
excluded_layers,
excluded_layers_count,
);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_render_page_region(
doc: *mut PdfDocument,
page_index: i32,
crop_x: f32,
crop_y: f32,
crop_width: f32,
crop_height: f32,
format: i32,
error_code: *mut i32,
) -> *mut FfiRenderedImage {
#[cfg(feature = "rendering")]
{
if doc.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let d = handle_ref(doc as *const _);
let fmt = if format == 1 {
RenderImageFormat::Jpeg
} else {
RenderImageFormat::Png
};
let opts = RustRenderOptions {
dpi: 150,
format: fmt,
..Default::default()
};
match rendering::render_page_region(
d,
page_index as usize,
(crop_x, crop_y, crop_width, crop_height),
&opts,
) {
Ok(img) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiRenderedImage { inner: img }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "rendering"))]
{
let _ = (doc, page_index, crop_x, crop_y, crop_width, crop_height, format);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_render_page_zoom(
doc: *mut PdfDocument,
page_index: i32,
zoom: f32,
format: i32,
error_code: *mut i32,
) -> *mut FfiRenderedImage {
#[cfg(feature = "rendering")]
{
if doc.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let d = handle_ref(doc as *const _);
let dpi = (72.0 * zoom) as u32;
let fmt = if format == 1 {
RenderImageFormat::Jpeg
} else {
RenderImageFormat::Png
};
let opts = RustRenderOptions {
dpi,
format: fmt,
..Default::default()
};
match rendering::render_page(d, page_index as usize, &opts) {
Ok(img) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiRenderedImage { inner: img }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "rendering"))]
{
let _ = (doc, page_index, zoom, format);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_render_page_fit(
doc: *mut PdfDocument,
page_index: i32,
w: i32,
h: i32,
format: i32,
error_code: *mut i32,
) -> *mut FfiRenderedImage {
#[cfg(feature = "rendering")]
{
if doc.is_null() || w <= 0 || h <= 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let d = handle_ref(doc as *const _);
let fmt = if format == 1 {
RenderImageFormat::Jpeg
} else {
RenderImageFormat::Png
};
let opts = RustRenderOptions {
dpi: 150,
format: fmt,
..Default::default()
};
match rendering::render_page_fit(d, page_index as usize, w as u32, h as u32, &opts) {
Ok(img) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiRenderedImage { inner: img }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "rendering"))]
{
let _ = (doc, page_index, w, h, format);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_render_page_thumbnail(
doc: *mut PdfDocument,
page_index: i32,
_size: i32,
format: i32,
error_code: *mut i32,
) -> *mut FfiRenderedImage {
#[cfg(feature = "rendering")]
{
if doc.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let d = handle_ref(doc as *const _);
let fmt = if format == 1 {
RenderImageFormat::Jpeg
} else {
RenderImageFormat::Png
};
let opts = RustRenderOptions {
dpi: 72,
format: fmt,
..Default::default()
};
match rendering::render_page(d, page_index as usize, &opts) {
Ok(img) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiRenderedImage { inner: img }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "rendering"))]
{
let _ = (doc, page_index, _size, format);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_render_page_raw(
doc: *mut PdfDocument,
page_index: i32,
dpi: i32,
out_width: *mut i32,
out_height: *mut i32,
error_code: *mut i32,
) -> *mut FfiRenderedImage {
#[cfg(feature = "rendering")]
{
if doc.is_null() || out_width.is_null() || out_height.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let effective_dpi = if dpi <= 0 { 150 } else { dpi as u32 };
let d = handle_ref(doc as *const _);
let opts = RustRenderOptions {
dpi: effective_dpi,
format: RenderImageFormat::RawRgba8,
..Default::default()
};
match rendering::render_page(d, page_index as usize, &opts) {
Ok(img) => {
write_out(out_width, img.width as i32);
write_out(out_height, img.height as i32);
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiRenderedImage { inner: img }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "rendering"))]
{
let _ = (doc, page_index, dpi, out_width, out_height);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_get_rendered_image_width(
img: *const FfiRenderedImage,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "rendering")]
{
if img.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
set_error(error_code, ERR_SUCCESS);
handle_ref(img).inner.width as i32
}
#[cfg(not(feature = "rendering"))]
{
let _ = img;
set_error(error_code, _ERR_UNSUPPORTED);
0
}
}
#[no_mangle]
pub extern "C" fn pdf_get_rendered_image_height(
img: *const FfiRenderedImage,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "rendering")]
{
if img.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
set_error(error_code, ERR_SUCCESS);
handle_ref(img).inner.height as i32
}
#[cfg(not(feature = "rendering"))]
{
let _ = img;
set_error(error_code, _ERR_UNSUPPORTED);
0
}
}
#[no_mangle]
pub extern "C" fn pdf_get_rendered_image_data(
img: *const FfiRenderedImage,
data_len: *mut i32,
error_code: *mut i32,
) -> *mut u8 {
#[cfg(feature = "rendering")]
{
if img.is_null() || data_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let i = handle_ref(img);
let bytes = i.inner.as_bytes().to_vec();
write_out(data_len, bytes.len() as i32);
set_error(error_code, ERR_SUCCESS);
vec_to_ffi_bytes(bytes)
}
#[cfg(not(feature = "rendering"))]
{
let _ = (img, data_len);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_save_rendered_image(
img: *const FfiRenderedImage,
file_path: *const c_char,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "rendering")]
{
if img.is_null() || file_path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let path = match c_str(file_path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
let i = handle_ref(img);
match i.inner.save(path) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[cfg(not(feature = "rendering"))]
{
let _ = (img, file_path);
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
}
#[no_mangle]
pub extern "C" fn pdf_rendered_image_free(handle: *mut FfiRenderedImage) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
#[no_mangle]
pub extern "C" fn pdf_renderer_free(_handle: *mut std::ffi::c_void) {}
#[no_mangle]
pub extern "C" fn pdf_tsa_client_create(
url: *const c_char,
username: *const c_char,
password: *const c_char,
timeout: i32,
hash_algo: i32,
use_nonce: bool,
cert_req: bool,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "tsa-client")]
{
if url.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let url_str = c_str_lossy(url).unwrap_or_default();
let user_opt = c_str_lossy(username);
let pw_opt = c_str_lossy(password);
let algo = hash_algo_from_i32(hash_algo);
let cfg = crate::signatures::TsaClientConfig {
url: url_str,
username: user_opt,
password: pw_opt,
timeout: if timeout > 0 {
std::time::Duration::from_secs(timeout as u64)
} else {
std::time::Duration::from_secs(30)
},
hash_algorithm: algo,
use_nonce,
cert_req,
};
let client = crate::signatures::TsaClient::new(cfg);
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(client)) as *mut std::ffi::c_void
}
#[cfg(not(feature = "tsa-client"))]
{
let _ = (url, username, password, timeout, hash_algo, use_nonce, cert_req);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_tsa_client_free(client: *mut std::ffi::c_void) {
#[cfg(feature = "tsa-client")]
{
if !client.is_null() {
unsafe {
drop(Box::from_raw(client as *mut crate::signatures::TsaClient));
}
}
}
#[cfg(not(feature = "tsa-client"))]
{
let _ = client;
}
}
#[no_mangle]
pub extern "C" fn pdf_tsa_request_timestamp(
client: *const std::ffi::c_void,
data: *const u8,
data_len: usize,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "tsa-client")]
{
if client.is_null() || data.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let c = handle_ref(client as *const crate::signatures::TsaClient);
let slice = raw_slice(data, data_len);
match c.request_timestamp(slice) {
Ok(ts) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(ts)) as *mut std::ffi::c_void
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "tsa-client"))]
{
let _ = (client, data, data_len);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_tsa_request_timestamp_hash(
client: *const std::ffi::c_void,
hash: *const u8,
hash_len: usize,
hash_algo: i32,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "tsa-client")]
{
if client.is_null() || hash.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let c = handle_ref(client as *const crate::signatures::TsaClient);
let slice = raw_slice(hash, hash_len);
let algo = hash_algo_from_i32(hash_algo);
match c.request_timestamp_hash(slice, algo) {
Ok(ts) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(ts)) as *mut std::ffi::c_void
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "tsa-client"))]
{
let _ = (client, hash, hash_len, hash_algo);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[cfg(feature = "tsa-client")]
fn hash_algo_from_i32(code: i32) -> crate::signatures::HashAlgorithm {
match code {
1 => crate::signatures::HashAlgorithm::Sha1,
2 => crate::signatures::HashAlgorithm::Sha256,
3 => crate::signatures::HashAlgorithm::Sha384,
4 => crate::signatures::HashAlgorithm::Sha512,
_ => crate::signatures::HashAlgorithm::Sha256,
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_parse(
bytes: *const u8,
len: usize,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "signatures")]
{
if bytes.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let slice = raw_slice(bytes, len);
match crate::signatures::Timestamp::from_der(slice) {
Ok(ts) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(ts)) as *mut std::ffi::c_void
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = (bytes, len);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_get_token(
ts: *const std::ffi::c_void,
out_len: *mut usize,
error_code: *mut i32,
) -> *const u8 {
#[cfg(feature = "signatures")]
{
if ts.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null();
}
let t = handle_ref(ts as *const crate::signatures::Timestamp);
let bytes = t.token_bytes();
write_out(out_len, bytes.len());
set_error(error_code, ERR_SUCCESS);
bytes.as_ptr()
}
#[cfg(not(feature = "signatures"))]
{
let _ = (ts, out_len);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null()
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_get_time(ts: *const std::ffi::c_void, error_code: *mut i32) -> i64 {
#[cfg(feature = "signatures")]
{
if ts.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let t = handle_ref(ts as *const crate::signatures::Timestamp);
set_error(error_code, ERR_SUCCESS);
t.time()
}
#[cfg(not(feature = "signatures"))]
{
let _ = ts;
set_error(error_code, _ERR_UNSUPPORTED);
0
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_get_serial(
ts: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "signatures")]
{
if ts.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let t = handle_ref(ts as *const crate::signatures::Timestamp);
set_error(error_code, ERR_SUCCESS);
to_c_string(&t.serial())
}
#[cfg(not(feature = "signatures"))]
{
let _ = ts;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_get_tsa_name(
ts: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "signatures")]
{
if ts.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let t = handle_ref(ts as *const crate::signatures::Timestamp);
set_error(error_code, ERR_SUCCESS);
to_c_string(&t.tsa_name())
}
#[cfg(not(feature = "signatures"))]
{
let _ = ts;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_get_policy_oid(
ts: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "signatures")]
{
if ts.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let t = handle_ref(ts as *const crate::signatures::Timestamp);
set_error(error_code, ERR_SUCCESS);
to_c_string(&t.policy_oid())
}
#[cfg(not(feature = "signatures"))]
{
let _ = ts;
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_get_hash_algorithm(
ts: *const std::ffi::c_void,
error_code: *mut i32,
) -> i32 {
#[cfg(feature = "signatures")]
{
if ts.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let t = handle_ref(ts as *const crate::signatures::Timestamp);
set_error(error_code, ERR_SUCCESS);
t.hash_algorithm() as i32
}
#[cfg(not(feature = "signatures"))]
{
let _ = ts;
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_get_message_imprint(
ts: *const std::ffi::c_void,
out_len: *mut usize,
error_code: *mut i32,
) -> *const u8 {
#[cfg(feature = "signatures")]
{
if ts.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null();
}
let t = handle_ref(ts as *const crate::signatures::Timestamp);
let imprint = t.message_imprint_ref();
write_out(out_len, imprint.len());
set_error(error_code, ERR_SUCCESS);
imprint.as_ptr()
}
#[cfg(not(feature = "signatures"))]
{
let _ = (ts, out_len);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null()
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_verify(ts: *const std::ffi::c_void, error_code: *mut i32) -> bool {
#[cfg(feature = "signatures")]
{
if ts.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let t = handle_ref(ts as *const crate::signatures::Timestamp);
match t.verify() {
Ok(valid) => {
set_error(error_code, ERR_SUCCESS);
valid
},
Err(_) => {
set_error(error_code, _ERR_UNSUPPORTED);
false
},
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = ts;
set_error(error_code, _ERR_UNSUPPORTED);
false
}
}
#[no_mangle]
pub extern "C" fn pdf_timestamp_free(ts: *mut std::ffi::c_void) {
#[cfg(feature = "signatures")]
{
if !ts.is_null() {
unsafe {
drop(Box::from_raw(ts as *mut crate::signatures::Timestamp));
}
}
}
#[cfg(not(feature = "signatures"))]
{
let _ = ts;
}
}
#[no_mangle]
pub extern "C" fn pdf_signature_add_timestamp(
_sig: *const std::ffi::c_void,
_ts: *const std::ffi::c_void,
error_code: *mut i32,
) -> bool {
set_error(error_code, _ERR_UNSUPPORTED);
false
}
#[no_mangle]
pub extern "C" fn pdf_signature_has_timestamp(
_sig: *const std::ffi::c_void,
error_code: *mut i32,
) -> bool {
set_error(error_code, _ERR_UNSUPPORTED);
false
}
#[no_mangle]
pub extern "C" fn pdf_signature_get_timestamp(
_sig: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn pdf_add_timestamp(
_pdf_data: *const u8,
_pdf_len: usize,
_sig_index: i32,
_tsa_url: *const c_char,
_out_data: *mut *mut u8,
_out_len: *mut usize,
error_code: *mut i32,
) -> bool {
set_error(error_code, _ERR_UNSUPPORTED);
false
}
use crate::compliance::pdf_ua::{PdfUaLevel, PdfUaValidator, UaValidationResult};
pub struct FfiUaResults {
result: UaValidationResult,
}
#[no_mangle]
pub extern "C" fn pdf_validate_pdf_ua(
document: *mut PdfDocument,
level: i32,
error_code: *mut i32,
) -> *mut FfiUaResults {
if document.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_mut(document);
let ua_level = if level == 2 {
PdfUaLevel::Ua2
} else {
PdfUaLevel::Ua1
};
match PdfUaValidator::new().validate(doc, ua_level) {
Ok(result) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiUaResults { result }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_pdf_ua_is_accessible(
results: *const FfiUaResults,
error_code: *mut i32,
) -> bool {
if results.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
set_error(error_code, ERR_SUCCESS);
handle_ref(results).result.is_compliant
}
#[no_mangle]
pub extern "C" fn pdf_pdf_ua_error_count(results: *const FfiUaResults) -> i32 {
if results.is_null() {
return 0;
}
handle_ref(results).result.errors.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_pdf_ua_get_error(
results: *const FfiUaResults,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if results.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let r = handle_ref(results);
if (index as usize) >= r.result.errors.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&r.result.errors[index as usize].message)
}
#[no_mangle]
pub extern "C" fn pdf_pdf_ua_warning_count(results: *const FfiUaResults) -> i32 {
if results.is_null() {
return 0;
}
handle_ref(results).result.warnings.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_pdf_ua_get_warning(
results: *const FfiUaResults,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if results.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let r = handle_ref(results);
if (index as usize) >= r.result.warnings.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&r.result.warnings[index as usize].message)
}
#[no_mangle]
pub extern "C" fn pdf_pdf_ua_get_stats(
results: *const FfiUaResults,
out_struct: *mut i32,
out_images: *mut i32,
out_tables: *mut i32,
out_forms: *mut i32,
out_annotations: *mut i32,
out_pages: *mut i32,
error_code: *mut i32,
) -> bool {
if results.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let r = handle_ref(results);
let s = &r.result.stats;
write_out(out_struct, s.structure_elements_checked as i32);
write_out(out_images, s.images_checked as i32);
write_out(out_tables, s.tables_checked as i32);
write_out(out_forms, s.form_fields_checked as i32);
write_out(out_annotations, s.annotations_checked as i32);
write_out(out_pages, s.pages_checked as i32);
set_error(error_code, ERR_SUCCESS);
true
}
#[no_mangle]
pub extern "C" fn pdf_pdf_ua_results_free(results: *mut FfiUaResults) {
if !results.is_null() {
unsafe {
drop(Box::from_raw(results));
}
}
}
use crate::extractors::FormExtractor;
use crate::fdf::{FdfWriter, XfdfWriter};
#[no_mangle]
pub extern "C" fn pdf_form_import_from_file(
_document: *const std::ffi::c_void,
_filename: *const c_char,
error_code: *mut i32,
) -> bool {
set_error(error_code, _ERR_UNSUPPORTED);
false
}
#[no_mangle]
pub extern "C" fn pdf_document_import_form_data(
_document: *const std::ffi::c_void,
_data_path: *const c_char,
error_code: *mut i32,
) -> i32 {
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
#[no_mangle]
pub extern "C" fn pdf_editor_import_fdf_bytes(
_document: *const std::ffi::c_void,
_data: *const u8,
_data_len: usize,
error_code: *mut i32,
) -> i32 {
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
#[no_mangle]
pub extern "C" fn pdf_editor_import_xfdf_bytes(
_document: *const std::ffi::c_void,
_data: *const u8,
_data_len: usize,
error_code: *mut i32,
) -> i32 {
set_error(error_code, _ERR_UNSUPPORTED);
-1
}
#[no_mangle]
pub extern "C" fn pdf_document_export_form_data_to_bytes(
document: *mut PdfDocument,
format_type: i32,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if document.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_mut(document);
let fields = match FormExtractor::extract_fields(doc) {
Ok(f) => f,
Err(e) => {
set_error(error_code, classify_error(&e));
return ptr::null_mut();
},
};
let bytes = if format_type == 1 {
let writer = XfdfWriter::from_fields(fields);
writer.to_bytes()
} else {
let writer = FdfWriter::from_fields(fields);
match writer.to_bytes() {
Ok(b) => b,
Err(e) => {
set_error(error_code, classify_error(&e));
return ptr::null_mut();
},
}
};
write_out(out_len, bytes.len());
set_error(error_code, ERR_SUCCESS);
vec_to_ffi_bytes(bytes)
}
#[no_mangle]
pub extern "C" fn pdf_document_open_from_bytes(
data: *const u8,
len: usize,
error_code: *mut i32,
) -> *mut PdfDocument {
if data.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let bytes = raw_slice(data, len).to_vec();
match PdfDocument::from_bytes(bytes) {
Ok(doc) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(doc))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_open_with_password(
path: *const c_char,
password: *const c_char,
error_code: *mut i32,
) -> *mut PdfDocument {
if path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let path_str = match c_str(path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
match PdfDocument::open(path_str) {
Ok(mut doc) => {
if !password.is_null() {
if let Ok(pwd) = c_str(password) {
let _ = doc.authenticate(pwd.as_bytes());
}
}
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(doc))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_is_encrypted(handle: *const PdfDocument) -> bool {
if handle.is_null() {
return false;
}
handle_ref(handle).is_encrypted()
}
#[no_mangle]
pub extern "C" fn pdf_document_authenticate(
handle: *mut PdfDocument,
password: *const c_char,
error_code: *mut i32,
) -> bool {
if handle.is_null() || password.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let doc = handle_ref(handle);
let pwd = match c_str(password) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return false;
},
};
match doc.authenticate(pwd.as_bytes()) {
Ok(ok) => {
set_error(error_code, ERR_SUCCESS);
ok
},
Err(e) => {
set_error(error_code, classify_error(&e));
false
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_all_text(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_all_text() {
Ok(t) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&t)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_to_html_all(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
let opts = ConversionOptions::default();
match doc.to_html_all(&opts) {
Ok(t) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&t)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_to_plain_text_all(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
let opts = ConversionOptions::default();
match doc.to_plain_text_all(&opts) {
Ok(t) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&t)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
use crate::layout::{TextChar, TextLine as RustTextLine, Word};
pub struct FfiCharList {
chars: Vec<TextChar>,
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_chars(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut FfiCharList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_chars(page_index as usize) {
Ok(chars) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiCharList { chars }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_char_count(chars: *const FfiCharList) -> i32 {
if chars.is_null() {
return 0;
}
handle_ref(chars).chars.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_char_get_char(
chars: *const FfiCharList,
index: i32,
error_code: *mut i32,
) -> u32 {
if chars.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(chars);
if (index as usize) >= list.chars.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
list.chars[index as usize].char as u32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_char_get_bbox(
chars: *const FfiCharList,
index: i32,
x: *mut f32,
y: *mut f32,
w: *mut f32,
h: *mut f32,
error_code: *mut i32,
) {
if chars.is_null() || index < 0 || x.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return;
}
let list = handle_ref(chars);
if (index as usize) >= list.chars.len() {
set_error(error_code, ERR_INVALID_PAGE);
return;
}
let b = &list.chars[index as usize].bbox;
write_out(x, b.x);
write_out(y, b.y);
write_out(w, b.width);
write_out(h, b.height);
set_error(error_code, ERR_SUCCESS);
}
#[no_mangle]
pub extern "C" fn pdf_oxide_char_get_font_name(
chars: *const FfiCharList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if chars.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(chars);
if (index as usize) >= list.chars.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.chars[index as usize].font_name)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_char_get_font_size(
chars: *const FfiCharList,
index: i32,
error_code: *mut i32,
) -> f32 {
if chars.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0.0;
}
let list = handle_ref(chars);
if (index as usize) >= list.chars.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0.0;
}
set_error(error_code, ERR_SUCCESS);
list.chars[index as usize].font_size
}
#[no_mangle]
pub extern "C" fn pdf_oxide_char_list_free(handle: *mut FfiCharList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
pub struct FfiWordList {
words: Vec<Word>,
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_words(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut FfiWordList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_words(page_index as usize) {
Ok(words) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiWordList { words }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_word_count(words: *const FfiWordList) -> i32 {
if words.is_null() {
return 0;
}
handle_ref(words).words.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_word_get_text(
words: *const FfiWordList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if words.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(words);
if (index as usize) >= list.words.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.words[index as usize].text)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_word_get_bbox(
words: *const FfiWordList,
index: i32,
x: *mut f32,
y: *mut f32,
w: *mut f32,
h: *mut f32,
error_code: *mut i32,
) {
if words.is_null() || index < 0 || x.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return;
}
let list = handle_ref(words);
if (index as usize) >= list.words.len() {
set_error(error_code, ERR_INVALID_PAGE);
return;
}
let b = &list.words[index as usize].bbox;
write_out(x, b.x);
write_out(y, b.y);
write_out(w, b.width);
write_out(h, b.height);
set_error(error_code, ERR_SUCCESS);
}
#[no_mangle]
pub extern "C" fn pdf_oxide_word_get_font_name(
words: *const FfiWordList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if words.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(words);
if (index as usize) >= list.words.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.words[index as usize].dominant_font)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_word_get_font_size(
words: *const FfiWordList,
index: i32,
error_code: *mut i32,
) -> f32 {
if words.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0.0;
}
let list = handle_ref(words);
if (index as usize) >= list.words.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0.0;
}
set_error(error_code, ERR_SUCCESS);
list.words[index as usize].avg_font_size
}
#[no_mangle]
pub extern "C" fn pdf_oxide_word_is_bold(
words: *const FfiWordList,
index: i32,
error_code: *mut i32,
) -> bool {
if words.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let list = handle_ref(words);
if (index as usize) >= list.words.len() {
set_error(error_code, ERR_INVALID_PAGE);
return false;
}
set_error(error_code, ERR_SUCCESS);
list.words[index as usize].is_bold
}
#[no_mangle]
pub extern "C" fn pdf_oxide_word_list_free(handle: *mut FfiWordList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
pub struct FfiTextLineList {
lines: Vec<RustTextLine>,
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_text_lines(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut FfiTextLineList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_text_lines(page_index as usize) {
Ok(lines) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiTextLineList { lines }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_line_count(lines: *const FfiTextLineList) -> i32 {
if lines.is_null() {
return 0;
}
handle_ref(lines).lines.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_line_get_text(
lines: *const FfiTextLineList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if lines.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(lines);
if (index as usize) >= list.lines.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.lines[index as usize].text)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_line_get_bbox(
lines: *const FfiTextLineList,
index: i32,
x: *mut f32,
y: *mut f32,
w: *mut f32,
h: *mut f32,
error_code: *mut i32,
) {
if lines.is_null() || index < 0 || x.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return;
}
let list = handle_ref(lines);
if (index as usize) >= list.lines.len() {
set_error(error_code, ERR_INVALID_PAGE);
return;
}
let b = &list.lines[index as usize].bbox;
write_out(x, b.x);
write_out(y, b.y);
write_out(w, b.width);
write_out(h, b.height);
set_error(error_code, ERR_SUCCESS);
}
#[no_mangle]
pub extern "C" fn pdf_oxide_line_get_word_count(
lines: *const FfiTextLineList,
index: i32,
error_code: *mut i32,
) -> i32 {
if lines.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(lines);
if (index as usize) >= list.lines.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
list.lines[index as usize].words.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_line_list_free(handle: *mut FfiTextLineList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
use crate::structure::table_extractor::Table;
pub struct FfiTableList {
tables: Vec<Table>,
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_tables(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut FfiTableList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_tables(page_index as usize) {
Ok(tables) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiTableList { tables }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_table_count(tables: *const FfiTableList) -> i32 {
if tables.is_null() {
return 0;
}
handle_ref(tables).tables.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_table_get_row_count(
tables: *const FfiTableList,
index: i32,
error_code: *mut i32,
) -> i32 {
if tables.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(tables);
if (index as usize) >= list.tables.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
list.tables[index as usize].rows.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_table_get_col_count(
tables: *const FfiTableList,
index: i32,
error_code: *mut i32,
) -> i32 {
if tables.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(tables);
if (index as usize) >= list.tables.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
list.tables[index as usize].col_count as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_table_get_cell_text(
tables: *const FfiTableList,
table_index: i32,
row: i32,
col: i32,
error_code: *mut i32,
) -> *mut c_char {
if tables.is_null() || table_index < 0 || row < 0 || col < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(tables);
if (table_index as usize) >= list.tables.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
let table = &list.tables[table_index as usize];
if (row as usize) >= table.rows.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
let row_data = &table.rows[row as usize];
if (col as usize) >= row_data.cells.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&row_data.cells[col as usize].text)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_table_has_header(
tables: *const FfiTableList,
index: i32,
error_code: *mut i32,
) -> bool {
if tables.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let list = handle_ref(tables);
if (index as usize) >= list.tables.len() {
set_error(error_code, ERR_INVALID_PAGE);
return false;
}
set_error(error_code, ERR_SUCCESS);
list.tables[index as usize].has_header
}
#[no_mangle]
pub extern "C" fn pdf_oxide_table_list_free(handle: *mut FfiTableList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
use crate::geometry::Rect;
use crate::layout::RectFilterMode;
fn make_rect(x: f32, y: f32, w: f32, h: f32) -> Rect {
Rect::new(x, y, w, h)
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_text_in_rect(
handle: *mut PdfDocument,
page_index: i32,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_text_in_rect(
page_index as usize,
make_rect(x, y, w, h),
RectFilterMode::Intersects,
) {
Ok(t) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&t)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_words_in_rect(
handle: *mut PdfDocument,
page_index: i32,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> *mut FfiWordList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_words_in_rect(
page_index as usize,
make_rect(x, y, w, h),
RectFilterMode::Intersects,
) {
Ok(words) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiWordList { words }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_lines_in_rect(
handle: *mut PdfDocument,
page_index: i32,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> *mut FfiTextLineList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_text_lines_in_rect(
page_index as usize,
make_rect(x, y, w, h),
RectFilterMode::Intersects,
) {
Ok(lines) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiTextLineList { lines }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_tables_in_rect(
handle: *mut PdfDocument,
page_index: i32,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> *mut FfiTableList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_tables_in_rect(page_index as usize, make_rect(x, y, w, h)) {
Ok(tables) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiTableList { tables }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_images_in_rect(
handle: *mut PdfDocument,
page_index: i32,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> *mut FfiImageList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_images_in_rect(page_index as usize, make_rect(x, y, w, h)) {
Ok(images) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiImageList { images }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
use crate::extractors::FormField as RustFormField;
pub struct FfiFormFieldList {
fields: Vec<RustFormField>,
}
#[no_mangle]
pub extern "C" fn pdf_document_get_form_fields(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> *mut FfiFormFieldList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match FormExtractor::extract_fields(doc) {
Ok(fields) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiFormFieldList { fields }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_form_field_count(fields: *const FfiFormFieldList) -> i32 {
if fields.is_null() {
return 0;
}
handle_ref(fields).fields.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_form_field_get_name(
fields: *const FfiFormFieldList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if fields.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(fields);
if (index as usize) >= list.fields.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&list.fields[index as usize].full_name)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_form_field_get_type(
fields: *const FfiFormFieldList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if fields.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(fields);
if (index as usize) >= list.fields.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&format!("{:?}", list.fields[index as usize].field_type))
}
#[no_mangle]
pub extern "C" fn pdf_oxide_form_field_get_value(
fields: *const FfiFormFieldList,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if fields.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(fields);
if (index as usize) >= list.fields.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&format!("{:?}", list.fields[index as usize].value))
}
#[no_mangle]
pub extern "C" fn pdf_oxide_form_field_is_readonly(
fields: *const FfiFormFieldList,
index: i32,
error_code: *mut i32,
) -> bool {
if fields.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let list = handle_ref(fields);
if (index as usize) >= list.fields.len() {
set_error(error_code, ERR_INVALID_PAGE);
return false;
}
set_error(error_code, ERR_SUCCESS);
list.fields[index as usize]
.flags
.map(|f| (f & 0x1) != 0)
.unwrap_or(false)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_form_field_is_required(
fields: *const FfiFormFieldList,
index: i32,
error_code: *mut i32,
) -> bool {
if fields.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let list = handle_ref(fields);
if (index as usize) >= list.fields.len() {
set_error(error_code, ERR_INVALID_PAGE);
return false;
}
set_error(error_code, ERR_SUCCESS);
list.fields[index as usize]
.flags
.map(|f| (f & 0x2) != 0)
.unwrap_or(false)
}
#[no_mangle]
pub extern "C" fn pdf_oxide_form_field_list_free(handle: *mut FfiFormFieldList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
#[no_mangle]
pub extern "C" fn pdf_document_has_xfa(handle: *mut PdfDocument) -> bool {
if handle.is_null() {
return false;
}
let doc = handle_ref(handle);
if let Ok(catalog) = doc.catalog() {
if let crate::object::Object::Dictionary(dict) = &catalog {
if let Some(acroform) = dict.get("AcroForm") {
if let crate::object::Object::Dictionary(form_dict) = acroform {
return form_dict.contains_key("XFA");
}
}
}
}
false
}
#[no_mangle]
pub extern "C" fn pdf_document_remove_headers(
handle: *mut PdfDocument,
threshold: f32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_ref(handle);
match doc.remove_headers(threshold) {
Ok(n) => {
set_error(error_code, ERR_SUCCESS);
n as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_remove_footers(
handle: *mut PdfDocument,
threshold: f32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_ref(handle);
match doc.remove_footers(threshold) {
Ok(n) => {
set_error(error_code, ERR_SUCCESS);
n as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_remove_artifacts(
handle: *mut PdfDocument,
threshold: f32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_ref(handle);
match doc.remove_artifacts(threshold) {
Ok(n) => {
set_error(error_code, ERR_SUCCESS);
n as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_erase_header(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_ref(handle);
match doc.erase_header(page_index as usize) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_erase_footer(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_ref(handle);
match doc.erase_footer(page_index as usize) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_erase_artifacts(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let doc = handle_ref(handle);
match doc.erase_artifacts(page_index as usize) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_delete_page(
handle: *mut DocumentEditor,
page_index: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.remove_page(page_index as usize) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_move_page(
handle: *mut DocumentEditor,
from: i32,
to: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.move_page(from as usize, to as usize) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_get_page_rotation(
handle: *mut DocumentEditor,
page: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let editor = handle_mut(handle);
match editor.get_page_rotation(page as usize) {
Ok(r) => {
set_error(error_code, ERR_SUCCESS);
r
},
Err(e) => {
set_error(error_code, classify_error(&e));
0
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_set_page_rotation(
handle: *mut DocumentEditor,
page: i32,
degrees: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.set_page_rotation(page as usize, degrees) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_erase_region(
handle: *mut DocumentEditor,
page: i32,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.erase_region(page as usize, [x, y, x + w, y + h]) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_flatten_annotations(
handle: *mut DocumentEditor,
page: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.flatten_page_annotations(page as usize) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_flatten_all_annotations(
handle: *mut DocumentEditor,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.flatten_all_annotations() {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_crop_margins(
handle: *mut DocumentEditor,
left: f32,
right: f32,
top: f32,
bottom: f32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.crop_margins(left, right, top, bottom) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_merge_from(
handle: *mut DocumentEditor,
source_path: *const c_char,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || source_path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let path = match c_str(source_path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
match editor.merge_from(path) {
Ok(n) => {
set_error(error_code, ERR_SUCCESS);
n as i32
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_from_image(path: *const c_char, error_code: *mut i32) -> *mut Pdf {
if path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let p = match c_str(path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
match Pdf::from_image(p) {
Ok(pdf) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(pdf))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_from_image_bytes(
data: *const u8,
data_len: i32,
error_code: *mut i32,
) -> *mut Pdf {
if data.is_null() || data_len <= 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let bytes = raw_slice(data, data_len as usize);
match Pdf::from_image_bytes(bytes) {
Ok(pdf) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(pdf))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_merge(
paths: *const *const c_char,
path_count: i32,
data_len: *mut i32,
error_code: *mut i32,
) -> *mut u8 {
if paths.is_null() || path_count <= 0 || data_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let mut path_strs: Vec<String> = Vec::new();
for &p in raw_slice(paths, path_count as usize) {
if p.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
match c_str(p) {
Ok(s) => path_strs.push(s.to_string()),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
}
}
let path_refs: Vec<&str> = path_strs.iter().map(|s| s.as_str()).collect();
match crate::api::merge_pdfs(&path_refs) {
Ok(bytes) => {
set_error(error_code, ERR_SUCCESS);
write_out(data_len, bytes.len() as i32);
vec_to_ffi_bytes(bytes)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
use crate::compliance::{
convert_to_pdf_a, validate_pdf_a, PdfALevel, ValidationResult as PdfAValidationResult,
};
pub struct FfiPdfAResults {
result: PdfAValidationResult,
}
#[no_mangle]
pub extern "C" fn pdf_validate_pdf_a_level(
document: *mut PdfDocument,
level: i32,
error_code: *mut i32,
) -> *mut FfiPdfAResults {
if document.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_mut(document);
let pdf_a_level = match level {
0 => PdfALevel::A1b,
1 => PdfALevel::A1a,
2 => PdfALevel::A2b,
3 => PdfALevel::A2a,
4 => PdfALevel::A2u,
5 => PdfALevel::A3b,
6 => PdfALevel::A3a,
7 => PdfALevel::A3u,
_ => PdfALevel::A2b,
};
match validate_pdf_a(doc, pdf_a_level) {
Ok(result) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiPdfAResults { result }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_convert_to_pdf_a(
document: *mut PdfDocument,
level: i32,
error_code: *mut i32,
) -> bool {
if document.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let pdf_level = match level {
0 => PdfALevel::A1b,
1 => PdfALevel::A1a,
2 => PdfALevel::A2b,
3 => PdfALevel::A2a,
4 => PdfALevel::A2u,
5 => PdfALevel::A3b,
6 => PdfALevel::A3a,
7 => PdfALevel::A3u,
_ => {
set_error(error_code, ERR_INVALID_ARG);
return false;
},
};
let doc = handle_mut(document);
match convert_to_pdf_a(doc, pdf_level) {
Ok(result) => {
set_error(error_code, ERR_SUCCESS);
result.success
},
Err(e) => {
set_error(error_code, classify_error(&e));
false
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_get_source_bytes(
document: *mut PdfDocument,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if document.is_null() || out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_mut(document);
let bytes = doc.source_bytes.clone();
set_error(error_code, ERR_SUCCESS);
write_out(out_len, bytes.len());
vec_to_ffi_bytes(bytes)
}
#[no_mangle]
pub extern "C" fn pdf_pdf_a_is_compliant(
results: *const FfiPdfAResults,
error_code: *mut i32,
) -> bool {
if results.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
set_error(error_code, ERR_SUCCESS);
handle_ref(results).result.is_compliant
}
#[no_mangle]
pub extern "C" fn pdf_pdf_a_error_count(results: *const FfiPdfAResults) -> i32 {
if results.is_null() {
return 0;
}
handle_ref(results).result.errors.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_pdf_a_get_error(
results: *const FfiPdfAResults,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if results.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let r = handle_ref(results);
if (index as usize) >= r.result.errors.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&r.result.errors[index as usize].message)
}
#[no_mangle]
pub extern "C" fn pdf_pdf_a_warning_count(results: *const FfiPdfAResults) -> i32 {
if results.is_null() {
return 0;
}
handle_ref(results).result.warnings.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_pdf_a_results_free(results: *mut FfiPdfAResults) {
if !results.is_null() {
unsafe {
drop(Box::from_raw(results));
}
}
}
use crate::compliance::pdf_x::{validate_pdf_x, PdfXLevel, XValidationResult};
pub struct FfiPdfXResults {
result: XValidationResult,
}
#[no_mangle]
pub extern "C" fn pdf_validate_pdf_x_level(
document: *mut PdfDocument,
level: i32,
error_code: *mut i32,
) -> *mut FfiPdfXResults {
if document.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_mut(document);
let x_level = match level {
0 => PdfXLevel::X1a2001,
1 => PdfXLevel::X32002,
2 => PdfXLevel::X4,
_ => PdfXLevel::X4,
};
match validate_pdf_x(doc, x_level) {
Ok(result) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiPdfXResults { result }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_pdf_x_is_compliant(
results: *const FfiPdfXResults,
error_code: *mut i32,
) -> bool {
if results.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
set_error(error_code, ERR_SUCCESS);
handle_ref(results).result.is_compliant
}
#[no_mangle]
pub extern "C" fn pdf_pdf_x_error_count(results: *const FfiPdfXResults) -> i32 {
if results.is_null() {
return 0;
}
handle_ref(results).result.errors.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_pdf_x_get_error(
results: *const FfiPdfXResults,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if results.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let r = handle_ref(results);
if (index as usize) >= r.result.errors.len() {
set_error(error_code, ERR_INVALID_PAGE);
return ptr::null_mut();
}
set_error(error_code, ERR_SUCCESS);
to_c_string(&r.result.errors[index as usize].message)
}
#[no_mangle]
pub extern "C" fn pdf_pdf_x_results_free(results: *mut FfiPdfXResults) {
if !results.is_null() {
unsafe {
drop(Box::from_raw(results));
}
}
}
#[no_mangle]
pub extern "C" fn document_editor_save_encrypted(
handle: *mut DocumentEditor,
path: *const c_char,
user_password: *const c_char,
owner_password: *const c_char,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || path.is_null() || user_password.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let path_str = match c_str(path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
let user_pwd = match c_str(user_password) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
let owner_pwd = if owner_password.is_null() {
user_pwd
} else {
match c_str(owner_password) {
Ok(s) => s,
Err(_) => user_pwd,
}
};
let enc_config = crate::editor::EncryptionConfig::new(user_pwd, owner_pwd)
.with_algorithm(crate::editor::EncryptionAlgorithm::Aes256);
let save_opts = crate::editor::SaveOptions::with_encryption(enc_config);
match editor.save_to_bytes_with_options(save_opts) {
Ok(bytes) => match std::fs::write(path_str, &bytes) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(_) => {
set_error(error_code, ERR_IO);
-1
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
use crate::elements::PathContent;
pub struct FfiPathList {
paths: Vec<PathContent>,
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_paths(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut FfiPathList {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_paths(page_index as usize) {
Ok(paths) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiPathList { paths }))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_path_count(paths: *const FfiPathList) -> i32 {
if paths.is_null() {
return 0;
}
handle_ref(paths).paths.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_path_get_bbox(
paths: *const FfiPathList,
index: i32,
x: *mut f32,
y: *mut f32,
w: *mut f32,
h: *mut f32,
error_code: *mut i32,
) {
if paths.is_null() || index < 0 || x.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return;
}
let list = handle_ref(paths);
if (index as usize) >= list.paths.len() {
set_error(error_code, ERR_INVALID_PAGE);
return;
}
let b = &list.paths[index as usize].bbox;
write_out(x, b.x);
write_out(y, b.y);
write_out(w, b.width);
write_out(h, b.height);
set_error(error_code, ERR_SUCCESS);
}
#[no_mangle]
pub extern "C" fn pdf_oxide_path_get_stroke_width(
paths: *const FfiPathList,
index: i32,
error_code: *mut i32,
) -> f32 {
if paths.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0.0;
}
let list = handle_ref(paths);
if (index as usize) >= list.paths.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0.0;
}
set_error(error_code, ERR_SUCCESS);
list.paths[index as usize].stroke_width
}
#[no_mangle]
pub extern "C" fn pdf_oxide_path_has_stroke(
paths: *const FfiPathList,
index: i32,
error_code: *mut i32,
) -> bool {
if paths.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let list = handle_ref(paths);
if (index as usize) >= list.paths.len() {
set_error(error_code, ERR_INVALID_PAGE);
return false;
}
set_error(error_code, ERR_SUCCESS);
list.paths[index as usize].stroke_color.is_some()
}
#[no_mangle]
pub extern "C" fn pdf_oxide_path_has_fill(
paths: *const FfiPathList,
index: i32,
error_code: *mut i32,
) -> bool {
if paths.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let list = handle_ref(paths);
if (index as usize) >= list.paths.len() {
set_error(error_code, ERR_INVALID_PAGE);
return false;
}
set_error(error_code, ERR_SUCCESS);
list.paths[index as usize].fill_color.is_some()
}
#[no_mangle]
pub extern "C" fn pdf_oxide_path_get_operation_count(
paths: *const FfiPathList,
index: i32,
error_code: *mut i32,
) -> i32 {
if paths.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return 0;
}
let list = handle_ref(paths);
if (index as usize) >= list.paths.len() {
set_error(error_code, ERR_INVALID_PAGE);
return 0;
}
set_error(error_code, ERR_SUCCESS);
list.paths[index as usize].operations.len() as i32
}
#[no_mangle]
pub extern "C" fn pdf_oxide_path_list_free(handle: *mut FfiPathList) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
use crate::extractors::PageLabelExtractor;
#[no_mangle]
pub extern "C" fn pdf_document_get_page_labels(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match PageLabelExtractor::extract(doc) {
Ok(ranges) => {
let page_count = doc.page_count().unwrap_or(0);
let labels = PageLabelExtractor::get_all_labels(&ranges, page_count);
let json: Vec<String> = labels
.iter()
.enumerate()
.map(|(i, l)| format!(r#"{{"page":{},"label":"{}"}}"#, i, l.replace('"', "\\\"")))
.collect();
set_error(error_code, ERR_SUCCESS);
to_c_string(&format!("[{}]", json.join(",")))
},
Err(_) => {
set_error(error_code, ERR_SUCCESS);
to_c_string("[]")
},
}
}
use crate::extractors::XmpExtractor;
#[no_mangle]
pub extern "C" fn pdf_document_get_xmp_metadata(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match XmpExtractor::extract(doc) {
Ok(Some(xmp)) => {
let title = xmp.dc_title.as_deref().unwrap_or("").replace('"', "\\\"");
let desc = xmp
.dc_description
.as_deref()
.unwrap_or("")
.replace('"', "\\\"");
let creator_tool = xmp
.xmp_creator_tool
.as_deref()
.unwrap_or("")
.replace('"', "\\\"");
let create_date = xmp
.xmp_create_date
.as_deref()
.unwrap_or("")
.replace('"', "\\\"");
let modify_date = xmp
.xmp_modify_date
.as_deref()
.unwrap_or("")
.replace('"', "\\\"");
let producer = xmp
.pdf_producer
.as_deref()
.unwrap_or("")
.replace('"', "\\\"");
let creators: Vec<String> = xmp
.dc_creator
.iter()
.map(|c| format!(r#""{}""#, c.replace('"', "\\\"")))
.collect();
let subjects: Vec<String> = xmp
.dc_subject
.iter()
.map(|s| format!(r#""{}""#, s.replace('"', "\\\"")))
.collect();
let json = format!(
r#"{{"title":"{}","description":"{}","creators":[{}],"subjects":[{}],"creatorTool":"{}","createDate":"{}","modifyDate":"{}","producer":"{}"}}"#,
title,
desc,
creators.join(","),
subjects.join(","),
creator_tool,
create_date,
modify_date,
producer
);
set_error(error_code, ERR_SUCCESS);
to_c_string(&json)
},
Ok(None) => {
set_error(error_code, ERR_SUCCESS);
to_c_string("{}")
},
Err(_) => {
set_error(error_code, ERR_SUCCESS);
to_c_string("{}")
},
}
}
fn outline_to_json(items: &[crate::outline::OutlineItem]) -> String {
let json: Vec<String> = items
.iter()
.map(|item| {
let dest = match &item.dest {
Some(crate::outline::Destination::PageIndex(p)) => format!("{}", p),
Some(crate::outline::Destination::Named(n)) => {
format!(r#""{}""#, n.replace('"', "\\\""))
},
None => "null".to_string(),
};
let children_json = if item.children.is_empty() {
"[]".to_string()
} else {
outline_to_json(&item.children)
};
format!(
r#"{{"title":"{}","dest":{},"children":{}}}"#,
item.title.replace('"', "\\\""),
dest,
children_json
)
})
.collect();
format!("[{}]", json.join(","))
}
#[no_mangle]
pub extern "C" fn pdf_document_get_outline(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.get_outline() {
Ok(Some(items)) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&outline_to_json(&items))
},
Ok(None) => {
set_error(error_code, ERR_SUCCESS);
to_c_string("[]")
},
Err(_) => {
set_error(error_code, ERR_SUCCESS);
to_c_string("[]")
},
}
}
#[derive(serde::Deserialize)]
struct FfiSplitOpts {
#[serde(default)]
title_prefix: Option<String>,
#[serde(default)]
ignore_case: bool,
#[serde(default = "ffi_split_level_default")]
level: u32,
#[serde(default = "ffi_split_true")]
include_front_matter: bool,
}
fn ffi_split_level_default() -> u32 {
1
}
fn ffi_split_true() -> bool {
true
}
impl Default for FfiSplitOpts {
fn default() -> Self {
Self {
title_prefix: None,
ignore_case: false,
level: 1,
include_front_matter: true,
}
}
}
#[no_mangle]
pub extern "C" fn pdf_document_plan_split_by_bookmarks(
handle: *mut PdfDocument,
options_json: *const c_char,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let opts_in: FfiSplitOpts = match c_str(options_json) {
Ok(s) if !s.trim().is_empty() => match serde_json::from_str(s) {
Ok(o) => o,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
},
Ok(_) => FfiSplitOpts::default(),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let opts = crate::split_bookmarks::SplitByBookmarksOptions {
title_prefix: opts_in.title_prefix,
ignore_case: opts_in.ignore_case,
level: crate::split_bookmarks::BookmarkLevel::from_u32(opts_in.level),
include_front_matter: opts_in.include_front_matter,
..Default::default()
};
let doc = handle_ref(handle);
match crate::split_bookmarks::plan_split_by_bookmarks(doc, &opts) {
Ok(segs) => match serde_json::to_string(&segs) {
Ok(j) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&j)
},
Err(e) => {
set_error(
error_code,
classify_error(&crate::error::Error::InvalidOperation(e.to_string())),
);
ptr::null_mut()
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_classify_page(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.classify_page(page_index as usize) {
Ok(c) => match serde_json::to_string(&c) {
Ok(j) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&j)
},
Err(e) => {
set_error(
error_code,
classify_error(&crate::error::Error::InvalidOperation(e.to_string())),
);
ptr::null_mut()
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_classify_document(
handle: *mut PdfDocument,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.classify_document() {
Ok(c) => match serde_json::to_string(&c) {
Ok(j) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&j)
},
Err(e) => {
set_error(
error_code,
classify_error(&crate::error::Error::InvalidOperation(e.to_string())),
);
ptr::null_mut()
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_text_auto(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let doc = handle_ref(handle);
match doc.extract_text_auto(page_index as usize) {
Ok(text) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&text)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_extract_page_auto(
handle: *mut PdfDocument,
page_index: i32,
options_json: *const c_char,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let opts: crate::extractors::auto::AutoExtractOptions = if options_json.is_null() {
crate::extractors::auto::AutoExtractOptions::default()
} else {
match c_str(options_json) {
Ok(s) if !s.trim().is_empty() => match serde_json::from_str(s) {
Ok(o) => o,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
},
Ok(_) => crate::extractors::auto::AutoExtractOptions::default(),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
}
};
let doc = handle_ref(handle);
let ae = crate::extractors::auto::AutoExtractor::with(opts);
match ae.extract_page(doc, page_index as usize) {
Ok(pe) => match serde_json::to_string(&pe) {
Ok(j) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&j)
},
Err(e) => {
set_error(
error_code,
classify_error(&crate::error::Error::InvalidOperation(e.to_string())),
);
ptr::null_mut()
},
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_prefetch_models(
languages_csv: *const c_char,
error_code: *mut i32,
) -> *mut c_char {
use crate::extractors::auto::{AutoExtractor, OcrLanguage};
let langs: Vec<OcrLanguage> = match c_str(languages_csv) {
Ok(s) if !s.trim().is_empty() => s
.split(',')
.filter_map(|t| OcrLanguage::from_code(t.trim()))
.collect(),
_ => Vec::new(),
};
let want = if langs.is_empty() {
vec![OcrLanguage::English]
} else {
langs
};
match AutoExtractor::prefetch_models(&want) {
Ok(dir) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&dir.to_string_lossy())
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_model_manifest() -> *mut c_char {
to_c_string(&crate::extractors::auto::AutoExtractor::model_manifest())
}
#[no_mangle]
pub extern "C" fn pdf_oxide_prefetch_available() -> i32 {
i32::from(crate::extractors::auto::AutoExtractor::prefetch_available())
}
#[no_mangle]
pub extern "C" fn document_editor_set_form_field_value(
handle: *mut DocumentEditor,
name: *const c_char,
value: *const c_char,
error_code: *mut i32,
) -> i32 {
if handle.is_null() || name.is_null() || value.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
let field_name = match c_str(name) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
let field_value = match c_str(value) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
match editor.set_form_field_value(
field_name,
crate::editor::form_fields::FormFieldValue::Text(field_value.to_string()),
) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_flatten_forms(
handle: *mut DocumentEditor,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.flatten_forms() {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_flatten_forms_on_page(
handle: *mut DocumentEditor,
page_index: i32,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let editor = handle_mut(handle);
match editor.flatten_forms_on_page(page_index as usize) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn document_editor_flatten_warnings_count(handle: *const DocumentEditor) -> i32 {
if handle.is_null() {
return -1;
}
let editor = handle_ref(handle);
editor.flatten_warnings().len() as i32
}
#[no_mangle]
pub extern "C" fn document_editor_flatten_warning(
handle: *const DocumentEditor,
index: i32,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() || index < 0 {
set_error(error_code, ERR_INVALID_ARG);
return std::ptr::null_mut();
}
let editor = handle_ref(handle);
let warnings = editor.flatten_warnings();
match warnings.get(index as usize) {
Some(w) => match CString::new(w.as_str()) {
Ok(cs) => {
set_error(error_code, ERR_SUCCESS);
cs.into_raw()
},
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
std::ptr::null_mut()
},
},
None => {
set_error(error_code, ERR_INVALID_ARG);
std::ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn PdfDocumentOpen(path: *const c_char, error_code: *mut i32) -> *mut PdfDocument {
pdf_document_open(path, error_code)
}
#[no_mangle]
pub extern "C" fn PdfDocumentFree(handle: *mut PdfDocument) {
pdf_document_free(handle)
}
#[no_mangle]
pub extern "C" fn PdfDocumentGetPageCount(handle: *mut PdfDocument, error_code: *mut i32) -> i32 {
pdf_document_get_page_count(handle, error_code)
}
#[no_mangle]
pub extern "C" fn PdfDocumentExtractText(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
pdf_document_extract_text(handle, page_index, error_code)
}
#[no_mangle]
pub extern "C" fn PdfDocumentToMarkdown(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
pdf_document_to_markdown(handle, page_index, error_code)
}
#[no_mangle]
pub extern "C" fn PdfDocumentToHtml(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
pdf_document_to_html(handle, page_index, error_code)
}
#[no_mangle]
pub extern "C" fn PdfDocumentToPlainText(
handle: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> *mut c_char {
pdf_document_to_plain_text(handle, page_index, error_code)
}
#[no_mangle]
pub extern "C" fn PdfFromMarkdown(markdown: *const c_char, error_code: *mut i32) -> *mut Pdf {
pdf_from_markdown(markdown, error_code)
}
#[no_mangle]
pub extern "C" fn PdfFromHtml(html: *const c_char, error_code: *mut i32) -> *mut Pdf {
pdf_from_html(html, error_code)
}
#[no_mangle]
pub extern "C" fn PdfFromText(text: *const c_char, error_code: *mut i32) -> *mut Pdf {
pdf_from_text(text, error_code)
}
#[no_mangle]
pub extern "C" fn PdfSave(handle: *mut Pdf, path: *const c_char, error_code: *mut i32) -> i32 {
pdf_save(handle, path, error_code)
}
#[no_mangle]
pub extern "C" fn PdfSaveToBytes(
handle: *mut Pdf,
data_len: *mut i32,
error_code: *mut i32,
) -> *mut u8 {
pdf_save_to_bytes(handle, data_len, error_code)
}
#[no_mangle]
pub extern "C" fn PdfFree(handle: *mut Pdf) {
pdf_free(handle)
}
#[no_mangle]
pub extern "C" fn DocumentEditorOpen(
path: *const c_char,
error_code: *mut i32,
) -> *mut DocumentEditor {
document_editor_open(path, error_code)
}
#[no_mangle]
pub extern "C" fn DocumentEditorFree(handle: *mut DocumentEditor) {
document_editor_free(handle)
}
#[no_mangle]
pub extern "C" fn DocumentEditorSave(
handle: *mut DocumentEditor,
path: *const c_char,
error_code: *mut i32,
) -> i32 {
document_editor_save(handle, path, error_code)
}
#[no_mangle]
pub extern "C" fn DocumentEditorSetTitle(
handle: *mut DocumentEditor,
value: *const c_char,
error_code: *mut i32,
) -> i32 {
document_editor_set_title(handle, value, error_code)
}
#[no_mangle]
pub extern "C" fn DocumentEditorSetAuthor(
handle: *mut DocumentEditor,
value: *const c_char,
error_code: *mut i32,
) -> i32 {
document_editor_set_author(handle, value, error_code)
}
#[no_mangle]
pub extern "C" fn FreeString(ptr: *mut c_char) {
free_string(ptr)
}
#[no_mangle]
pub extern "C" fn FreeBytes(ptr: *mut u8) {
free_bytes(ptr)
}
#[no_mangle]
pub extern "C" fn AllocString(s: *const c_char) -> *mut c_char {
if s.is_null() {
return ptr::null_mut();
}
match c_str(s).ok().and_then(|s| CString::new(s).ok()) {
Some(cs) => cs.into_raw(),
None => ptr::null_mut(),
}
}
use serde::Serialize;
#[derive(Serialize)]
struct JsonFont<'a> {
name: &'a str,
r#type: &'a str,
encoding: &'a str,
isEmbedded: bool,
isSubset: bool,
size: f32,
}
#[derive(Serialize)]
struct JsonAnnotation<'a> {
r#type: &'a str,
subtype: &'a str,
content: &'a str,
x: f32,
y: f32,
width: f32,
height: f32,
author: &'a str,
borderWidth: f32,
color: u32,
creationDate: i64,
modificationDate: i64,
linkURI: &'a str,
textIconName: &'a str,
isHidden: bool,
isPrintable: bool,
isReadOnly: bool,
isMarkedDeleted: bool,
}
#[derive(Serialize)]
struct JsonElement<'a> {
r#type: &'a str,
text: &'a str,
x: f32,
y: f32,
width: f32,
height: f32,
}
#[derive(Serialize)]
struct JsonSearchResult<'a> {
text: &'a str,
page: i32,
x: f32,
y: f32,
width: f32,
height: f32,
}
fn string_to_c(s: String) -> *mut c_char {
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn pdf_oxide_fonts_to_json(
fonts: *const FfiFontList,
error_code: *mut i32,
) -> *mut c_char {
if fonts.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(fonts);
let items: Vec<JsonFont> = list
.fonts
.iter()
.map(|f| JsonFont {
name: &f.name,
r#type: &f.subtype,
encoding: &f.encoding,
isEmbedded: f.is_embedded,
isSubset: f.is_subset,
size: 0.0,
})
.collect();
match serde_json::to_string(&items) {
Ok(s) => {
set_error(error_code, ERR_SUCCESS);
string_to_c(s)
},
Err(_) => {
set_error(error_code, ERR_INTERNAL);
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_annotations_to_json(
annotations: *const FfiAnnotationList,
error_code: *mut i32,
) -> *mut c_char {
if annotations.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(annotations);
let rows: Vec<(
String,
String,
String,
f32,
f32,
f32,
f32,
String,
f32,
u32,
i64,
i64,
String,
String,
bool,
bool,
bool,
bool,
)> = list
.annotations
.iter()
.map(|a| {
let rect = a.rect.unwrap_or([0.0, 0.0, 0.0, 0.0]);
let (x, y, w, h) = (
rect[0] as f32,
rect[1] as f32,
(rect[2] - rect[0]) as f32,
(rect[3] - rect[1]) as f32,
);
(
a.annotation_type.clone(),
a.subtype.clone().unwrap_or_default(),
a.contents.clone().unwrap_or_default(),
x,
y,
w,
h,
a.author.clone().unwrap_or_default(),
a.border.map(|b| b[2] as f32).unwrap_or(0.0),
0, 0, 0, String::new(), String::new(), false,
false,
false,
false, )
})
.collect();
let items: Vec<JsonAnnotation> = rows
.iter()
.map(|r| JsonAnnotation {
r#type: &r.0,
subtype: &r.1,
content: &r.2,
x: r.3,
y: r.4,
width: r.5,
height: r.6,
author: &r.7,
borderWidth: r.8,
color: r.9,
creationDate: r.10,
modificationDate: r.11,
linkURI: &r.12,
textIconName: &r.13,
isHidden: r.14,
isPrintable: r.15,
isReadOnly: r.16,
isMarkedDeleted: r.17,
})
.collect();
match serde_json::to_string(&items) {
Ok(s) => {
set_error(error_code, ERR_SUCCESS);
string_to_c(s)
},
Err(_) => {
set_error(error_code, ERR_INTERNAL);
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_elements_to_json(
elements: *const FfiElementList,
error_code: *mut i32,
) -> *mut c_char {
if elements.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(elements);
let items: Vec<JsonElement> = list
.spans
.iter()
.map(|s| JsonElement {
r#type: "text",
text: &s.text,
x: s.bbox.x,
y: s.bbox.y,
width: s.bbox.width,
height: s.bbox.height,
})
.collect();
match serde_json::to_string(&items) {
Ok(s) => {
set_error(error_code, ERR_SUCCESS);
string_to_c(s)
},
Err(_) => {
set_error(error_code, ERR_INTERNAL);
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_oxide_search_results_to_json(
results: *const FfiSearchResults,
error_code: *mut i32,
) -> *mut c_char {
if results.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let list = handle_ref(results);
let items: Vec<JsonSearchResult> = list
.results
.iter()
.map(|r| JsonSearchResult {
text: &r.text,
page: r.page as i32,
x: r.bbox.x,
y: r.bbox.y,
width: r.bbox.width,
height: r.bbox.height,
})
.collect();
match serde_json::to_string(&items) {
Ok(s) => {
set_error(error_code, ERR_SUCCESS);
string_to_c(s)
},
Err(_) => {
set_error(error_code, ERR_INTERNAL);
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_ocr_engine_create(
det_model_path: *const c_char,
rec_model_path: *const c_char,
dict_path: *const c_char,
error_code: *mut i32,
) -> *mut std::ffi::c_void {
#[cfg(feature = "ocr")]
{
use crate::ocr::{OcrConfig, OcrEngine};
if det_model_path.is_null() || rec_model_path.is_null() || dict_path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let det = match c_str(det_model_path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let rec = match c_str(rec_model_path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let dict = match c_str(dict_path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
match OcrEngine::new(det, rec, dict, OcrConfig::default()) {
Ok(engine) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(engine)) as *mut std::ffi::c_void
},
Err(_e) => {
set_error(error_code, ERR_INTERNAL);
ptr::null_mut()
},
}
}
#[cfg(not(feature = "ocr"))]
{
let _ = (det_model_path, rec_model_path, dict_path);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
#[no_mangle]
pub extern "C" fn pdf_ocr_engine_free(engine: *mut std::ffi::c_void) {
#[cfg(feature = "ocr")]
{
use crate::ocr::OcrEngine;
if !engine.is_null() {
unsafe {
drop(Box::from_raw(engine as *mut OcrEngine));
}
}
}
#[cfg(not(feature = "ocr"))]
{
let _ = engine;
}
}
#[no_mangle]
pub extern "C" fn pdf_ocr_page_needs_ocr(
doc: *mut PdfDocument,
page_index: i32,
error_code: *mut i32,
) -> bool {
#[cfg(feature = "ocr")]
{
if doc.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return false;
}
let d = handle_mut(doc);
match crate::ocr::needs_ocr(d, page_index as usize) {
Ok(v) => {
set_error(error_code, ERR_SUCCESS);
v
},
Err(e) => {
set_error(error_code, classify_error(&e));
false
},
}
}
#[cfg(not(feature = "ocr"))]
{
let _ = (doc, page_index);
set_error(error_code, _ERR_UNSUPPORTED);
false
}
}
#[no_mangle]
pub extern "C" fn pdf_ocr_extract_text(
doc: *mut PdfDocument,
page_index: i32,
engine: *const std::ffi::c_void,
error_code: *mut i32,
) -> *mut c_char {
#[cfg(feature = "ocr")]
{
use crate::ocr::{OcrEngine, OcrExtractOptions};
if doc.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let d = handle_mut(doc);
let ocr_engine: Option<&OcrEngine> = if engine.is_null() {
None
} else {
Some(handle_ref(engine as *const OcrEngine))
};
match crate::ocr::extract_text_with_ocr(
d,
page_index as usize,
ocr_engine,
OcrExtractOptions::default(),
) {
Ok(text) => {
set_error(error_code, ERR_SUCCESS);
to_c_string(&text)
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(not(feature = "ocr"))]
{
let _ = (doc, page_index, engine);
set_error(error_code, _ERR_UNSUPPORTED);
ptr::null_mut()
}
}
const _ERR_FONT: i32 = 9;
pub struct FfiDocumentBuilder {
inner: Option<crate::writer::DocumentBuilder>,
open_page: bool,
}
enum FfiPageOp {
Font(String, f32),
At(f32, f32),
Text(String),
Heading(u8, String),
Paragraph(String),
Space(f32),
HorizontalRule,
LinkUrl(String),
LinkPage(usize),
LinkNamed(String),
Highlight(f32, f32, f32),
Underline(f32, f32, f32),
Strikeout(f32, f32, f32),
Squiggly(f32, f32, f32),
StickyNote(String),
StickyNoteAt(f32, f32, String),
Watermark(String),
WatermarkConfidential,
WatermarkDraft,
Stamp(String),
FreeText {
x: f32,
y: f32,
w: f32,
h: f32,
text: String,
},
TextField {
name: String,
x: f32,
y: f32,
w: f32,
h: f32,
default_value: Option<String>,
},
Checkbox {
name: String,
x: f32,
y: f32,
w: f32,
h: f32,
checked: bool,
},
ComboBox {
name: String,
x: f32,
y: f32,
w: f32,
h: f32,
options: Vec<String>,
selected: Option<String>,
},
RadioGroup {
name: String,
buttons: Vec<(String, f32, f32, f32, f32)>,
selected: Option<String>,
},
PushButton {
name: String,
x: f32,
y: f32,
w: f32,
h: f32,
caption: String,
},
SignatureField {
name: String,
x: f32,
y: f32,
w: f32,
h: f32,
},
Footnote {
ref_mark: String,
note_text: String,
},
Columns {
count: u32,
gap_pt: f32,
text: String,
},
Inline(String),
InlineBold(String),
InlineItalic(String),
InlineColor {
r: f32,
g: f32,
b: f32,
text: String,
},
Newline,
Rect(f32, f32, f32, f32),
FilledRect(f32, f32, f32, f32, f32, f32, f32),
Line(f32, f32, f32, f32),
StrokeRect {
x: f32,
y: f32,
w: f32,
h: f32,
width: f32,
r: f32,
g: f32,
b: f32,
},
StrokeRectDashed {
x: f32,
y: f32,
w: f32,
h: f32,
width: f32,
r: f32,
g: f32,
b: f32,
dash: Vec<f32>,
phase: f32,
},
StrokeLine {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
width: f32,
r: f32,
g: f32,
b: f32,
},
StrokeLineDashed {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
width: f32,
r: f32,
g: f32,
b: f32,
dash: Vec<f32>,
phase: f32,
},
TextInRect {
x: f32,
y: f32,
w: f32,
h: f32,
text: String,
align: i32, },
NewPageSameSize,
Table {
widths: Vec<f32>,
aligns: Vec<i32>, rows: Vec<Vec<String>>,
has_header: bool,
},
StreamingTableOpen {
columns: Vec<(String, f32, i32)>,
repeat_header: bool,
mode: i32,
sample_rows: usize,
min_col_width_pt: f32,
max_col_width_pt: f32,
max_rowspan: usize,
},
StreamingTableRow(Vec<(String, usize)>),
StreamingTableFinish,
BarcodeImage {
bytes: Vec<u8>,
x: f32,
y: f32,
w: f32,
h: f32,
},
Image {
bytes: Vec<u8>,
x: f32,
y: f32,
w: f32,
h: f32,
},
ImageWithAlt {
bytes: Vec<u8>,
x: f32,
y: f32,
w: f32,
h: f32,
alt_text: String,
},
ImageArtifact {
bytes: Vec<u8>,
x: f32,
y: f32,
w: f32,
h: f32,
},
LinkJavaScript(String),
OnOpen(String),
OnClose(String),
FieldKeystroke(String),
FieldFormat(String),
FieldValidate(String),
FieldCalculate(String),
}
fn ffi_parse_stamp_type(name: &str) -> crate::writer::StampType {
use crate::writer::StampType;
match name {
"Approved" => StampType::Approved,
"Experimental" => StampType::Experimental,
"NotApproved" => StampType::NotApproved,
"AsIs" => StampType::AsIs,
"Expired" => StampType::Expired,
"NotForPublicRelease" => StampType::NotForPublicRelease,
"Confidential" => StampType::Confidential,
"Final" => StampType::Final,
"Sold" => StampType::Sold,
"Departmental" => StampType::Departmental,
"ForComment" => StampType::ForComment,
"TopSecret" => StampType::TopSecret,
"Draft" => StampType::Draft,
"ForPublicRelease" => StampType::ForPublicRelease,
other => StampType::Custom(other.to_string()),
}
}
pub struct FfiPageBuilder {
parent: *mut FfiDocumentBuilder,
page_size: Option<crate::writer::PageSize>,
custom_width: f32,
custom_height: f32,
ops: Vec<FfiPageOp>,
done_called: bool,
st_batch_size: usize,
st_batch_count: usize,
st_pending_count: usize,
}
#[no_mangle]
pub extern "C" fn pdf_embedded_font_from_file(
path: *const c_char,
error_code: *mut i32,
) -> *mut crate::writer::EmbeddedFont {
if path.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let path_str = match c_str(path) {
Ok(s) => s,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
match crate::writer::EmbeddedFont::from_file(path_str) {
Ok(font) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(font))
},
Err(_) => {
set_error(error_code, ERR_IO);
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_embedded_font_from_bytes(
data: *const u8,
len: usize,
name: *const c_char,
error_code: *mut i32,
) -> *mut crate::writer::EmbeddedFont {
if data.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let bytes = raw_slice(data, len).to_vec();
let name_opt = if name.is_null() {
None
} else {
match c_str(name) {
Ok(s) => Some(s.to_string()),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
}
};
match crate::writer::EmbeddedFont::from_data(name_opt, bytes) {
Ok(font) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(font))
},
Err(_) => {
set_error(error_code, ERR_PARSE);
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_embedded_font_free(handle: *mut crate::writer::EmbeddedFont) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_create(error_code: *mut i32) -> *mut FfiDocumentBuilder {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiDocumentBuilder {
inner: Some(crate::writer::DocumentBuilder::new()),
open_page: false,
}))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_free(handle: *mut FfiDocumentBuilder) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle));
}
}
}
fn ffi_builder_mut<'a>(
handle: *mut FfiDocumentBuilder,
error_code: *mut i32,
) -> Option<&'a mut FfiDocumentBuilder> {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return None;
}
let wrapper = handle_mut(handle);
if wrapper.inner.is_none() {
set_error(error_code, ERR_INVALID_ARG);
return None;
}
Some(wrapper)
}
fn ffi_builder_apply<F>(handle: *mut FfiDocumentBuilder, error_code: *mut i32, f: F) -> i32
where
F: FnOnce(crate::writer::DocumentBuilder) -> crate::writer::DocumentBuilder,
{
let Some(wrapper) = ffi_builder_mut(handle, error_code) else {
return -1;
};
if wrapper.open_page {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let taken = wrapper.inner.take().unwrap();
wrapper.inner = Some(f(taken));
set_error(error_code, ERR_SUCCESS);
0
}
fn read_cstr_or_fail(ptr: *const c_char, error_code: *mut i32) -> Option<String> {
if ptr.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return None;
}
match c_str(ptr) {
Ok(s) => Some(s.to_string()),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
None
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_set_title(
handle: *mut FfiDocumentBuilder,
title: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(title) = read_cstr_or_fail(title, error_code) else {
return -1;
};
ffi_builder_apply(handle, error_code, |b| b.title(title))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_set_author(
handle: *mut FfiDocumentBuilder,
author: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(author) = read_cstr_or_fail(author, error_code) else {
return -1;
};
ffi_builder_apply(handle, error_code, |b| b.author(author))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_set_subject(
handle: *mut FfiDocumentBuilder,
subject: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(subject) = read_cstr_or_fail(subject, error_code) else {
return -1;
};
ffi_builder_apply(handle, error_code, |b| b.subject(subject))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_set_keywords(
handle: *mut FfiDocumentBuilder,
keywords: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(keywords) = read_cstr_or_fail(keywords, error_code) else {
return -1;
};
ffi_builder_apply(handle, error_code, |b| b.keywords(keywords))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_set_creator(
handle: *mut FfiDocumentBuilder,
creator: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(creator) = read_cstr_or_fail(creator, error_code) else {
return -1;
};
ffi_builder_apply(handle, error_code, |b| b.creator(creator))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_on_open(
handle: *mut FfiDocumentBuilder,
script: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(script, error_code) else {
return -1;
};
ffi_builder_apply(handle, error_code, |b| b.on_open(s))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_tagged_pdf_ua1(
handle: *mut FfiDocumentBuilder,
error_code: *mut i32,
) -> i32 {
ffi_builder_apply(handle, error_code, |b| b.tagged_pdf_ua1())
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_language(
handle: *mut FfiDocumentBuilder,
lang: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(lang) = read_cstr_or_fail(lang, error_code) else {
return -1;
};
ffi_builder_apply(handle, error_code, |b| b.language(lang))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_role_map(
handle: *mut FfiDocumentBuilder,
custom: *const c_char,
standard: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(custom) = read_cstr_or_fail(custom, error_code) else {
return -1;
};
let Some(standard) = read_cstr_or_fail(standard, error_code) else {
return -1;
};
ffi_builder_apply(handle, error_code, |b| b.role_map(custom, standard))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_register_embedded_font(
handle: *mut FfiDocumentBuilder,
name: *const c_char,
font: *mut crate::writer::EmbeddedFont,
error_code: *mut i32,
) -> i32 {
let Some(name_s) = read_cstr_or_fail(name, error_code) else {
return -1;
};
if font.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let Some(wrapper) = ffi_builder_mut(handle, error_code) else {
return -1;
};
if wrapper.open_page {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let font_owned = unsafe { Box::from_raw(font) };
let taken = wrapper.inner.take().unwrap();
wrapper.inner = Some(taken.register_embedded_font(name_s, *font_owned));
set_error(error_code, ERR_SUCCESS);
0
}
fn open_page(
handle: *mut FfiDocumentBuilder,
page_size: Option<crate::writer::PageSize>,
width: f32,
height: f32,
error_code: *mut i32,
) -> *mut FfiPageBuilder {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let Some(wrapper) = ffi_builder_mut(handle, error_code) else {
return ptr::null_mut();
};
if wrapper.open_page {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
wrapper.open_page = true;
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(FfiPageBuilder {
parent: handle,
page_size,
custom_width: width,
custom_height: height,
ops: Vec::new(),
done_called: false,
st_batch_size: 0,
st_batch_count: 0,
st_pending_count: 0,
}))
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_a4_page(
handle: *mut FfiDocumentBuilder,
error_code: *mut i32,
) -> *mut FfiPageBuilder {
open_page(handle, Some(crate::writer::PageSize::A4), 0.0, 0.0, error_code)
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_letter_page(
handle: *mut FfiDocumentBuilder,
error_code: *mut i32,
) -> *mut FfiPageBuilder {
open_page(handle, Some(crate::writer::PageSize::Letter), 0.0, 0.0, error_code)
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_page(
handle: *mut FfiDocumentBuilder,
width: f32,
height: f32,
error_code: *mut i32,
) -> *mut FfiPageBuilder {
open_page(handle, None, width, height, error_code)
}
fn push_page_op(handle: *mut FfiPageBuilder, error_code: *mut i32, op: FfiPageOp) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let page = handle_mut(handle);
if page.done_called {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
page.ops.push(op);
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_font(
handle: *mut FfiPageBuilder,
name: *const c_char,
size: f32,
error_code: *mut i32,
) -> i32 {
let Some(name_s) = read_cstr_or_fail(name, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::Font(name_s, size))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_at(
handle: *mut FfiPageBuilder,
x: f32,
y: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::At(x, y))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_text(
handle: *mut FfiPageBuilder,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(text_s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::Text(text_s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_heading(
handle: *mut FfiPageBuilder,
level: u8,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(text_s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::Heading(level, text_s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_paragraph(
handle: *mut FfiPageBuilder,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(text_s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::Paragraph(text_s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_space(
handle: *mut FfiPageBuilder,
points: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::Space(points))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_horizontal_rule(
handle: *mut FfiPageBuilder,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::HorizontalRule)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_link_url(
handle: *mut FfiPageBuilder,
url: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(url_s) = read_cstr_or_fail(url, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::LinkUrl(url_s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_link_page(
handle: *mut FfiPageBuilder,
page: usize,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::LinkPage(page))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_link_named(
handle: *mut FfiPageBuilder,
destination: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(dest_s) = read_cstr_or_fail(destination, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::LinkNamed(dest_s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_link_javascript(
handle: *mut FfiPageBuilder,
script: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(script, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::LinkJavaScript(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_on_open(
handle: *mut FfiPageBuilder,
script: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(script, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::OnOpen(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_on_close(
handle: *mut FfiPageBuilder,
script: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(script, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::OnClose(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_field_keystroke(
handle: *mut FfiPageBuilder,
script: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(script, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::FieldKeystroke(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_field_format(
handle: *mut FfiPageBuilder,
script: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(script, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::FieldFormat(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_field_validate(
handle: *mut FfiPageBuilder,
script: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(script, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::FieldValidate(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_field_calculate(
handle: *mut FfiPageBuilder,
script: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(script, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::FieldCalculate(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_highlight(
handle: *mut FfiPageBuilder,
r: f32,
g: f32,
b: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::Highlight(r, g, b))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_underline(
handle: *mut FfiPageBuilder,
r: f32,
g: f32,
b: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::Underline(r, g, b))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_strikeout(
handle: *mut FfiPageBuilder,
r: f32,
g: f32,
b: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::Strikeout(r, g, b))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_squiggly(
handle: *mut FfiPageBuilder,
r: f32,
g: f32,
b: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::Squiggly(r, g, b))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_sticky_note(
handle: *mut FfiPageBuilder,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(text_s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::StickyNote(text_s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_sticky_note_at(
handle: *mut FfiPageBuilder,
x: f32,
y: f32,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(text_s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::StickyNoteAt(x, y, text_s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_watermark(
handle: *mut FfiPageBuilder,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(text_s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::Watermark(text_s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_watermark_confidential(
handle: *mut FfiPageBuilder,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::WatermarkConfidential)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_watermark_draft(
handle: *mut FfiPageBuilder,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::WatermarkDraft)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_stamp(
handle: *mut FfiPageBuilder,
type_name: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(name) = read_cstr_or_fail(type_name, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::Stamp(name))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_freetext(
handle: *mut FfiPageBuilder,
x: f32,
y: f32,
w: f32,
h: f32,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(text_s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(
handle,
error_code,
FfiPageOp::FreeText {
x,
y,
w,
h,
text: text_s,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_text_field(
handle: *mut FfiPageBuilder,
name: *const c_char,
x: f32,
y: f32,
w: f32,
h: f32,
default_value: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(name_s) = read_cstr_or_fail(name, error_code) else {
return -1;
};
let default_s = if default_value.is_null() {
None
} else {
match c_str(default_value) {
Ok(s) => Some(s.to_string()),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
}
};
push_page_op(
handle,
error_code,
FfiPageOp::TextField {
name: name_s,
x,
y,
w,
h,
default_value: default_s,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_checkbox(
handle: *mut FfiPageBuilder,
name: *const c_char,
x: f32,
y: f32,
w: f32,
h: f32,
checked: i32,
error_code: *mut i32,
) -> i32 {
let Some(name_s) = read_cstr_or_fail(name, error_code) else {
return -1;
};
push_page_op(
handle,
error_code,
FfiPageOp::Checkbox {
name: name_s,
x,
y,
w,
h,
checked: checked != 0,
},
)
}
fn read_cstring_array(
array: *const *const c_char,
count: usize,
error_code: *mut i32,
) -> Option<Vec<String>> {
if array.is_null() || count == 0 {
set_error(error_code, ERR_INVALID_ARG);
return None;
}
let mut out = Vec::with_capacity(count);
for &ptr in raw_slice(array, count) {
if ptr.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return None;
}
match c_str(ptr) {
Ok(s) => out.push(s.to_string()),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return None;
},
}
}
Some(out)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_combo_box(
handle: *mut FfiPageBuilder,
name: *const c_char,
x: f32,
y: f32,
w: f32,
h: f32,
options: *const *const c_char,
options_count: usize,
selected: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(name_s) = read_cstr_or_fail(name, error_code) else {
return -1;
};
let Some(opts) = read_cstring_array(options, options_count, error_code) else {
return -1;
};
let selected_s = if selected.is_null() {
None
} else {
match c_str(selected) {
Ok(s) => Some(s.to_string()),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
}
};
push_page_op(
handle,
error_code,
FfiPageOp::ComboBox {
name: name_s,
x,
y,
w,
h,
options: opts,
selected: selected_s,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_radio_group(
handle: *mut FfiPageBuilder,
name: *const c_char,
values: *const *const c_char,
xs: *const f32,
ys: *const f32,
ws: *const f32,
hs: *const f32,
count: usize,
selected: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(name_s) = read_cstr_or_fail(name, error_code) else {
return -1;
};
if count == 0 || xs.is_null() || ys.is_null() || ws.is_null() || hs.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let Some(vs) = read_cstring_array(values, count, error_code) else {
return -1;
};
let xs_slice = raw_slice(xs, count);
let ys_slice = raw_slice(ys, count);
let ws_slice = raw_slice(ws, count);
let hs_slice = raw_slice(hs, count);
let buttons: Vec<(String, f32, f32, f32, f32)> = vs
.into_iter()
.zip(xs_slice.iter().copied())
.zip(ys_slice.iter().copied())
.zip(ws_slice.iter().copied())
.zip(hs_slice.iter().copied())
.map(|((((v, x), y), w), h)| (v, x, y, w, h))
.collect();
let selected_s = if selected.is_null() {
None
} else {
match c_str(selected) {
Ok(s) => Some(s.to_string()),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
}
};
push_page_op(
handle,
error_code,
FfiPageOp::RadioGroup {
name: name_s,
buttons,
selected: selected_s,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_push_button(
handle: *mut FfiPageBuilder,
name: *const c_char,
x: f32,
y: f32,
w: f32,
h: f32,
caption: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(name_s) = read_cstr_or_fail(name, error_code) else {
return -1;
};
let Some(caption_s) = read_cstr_or_fail(caption, error_code) else {
return -1;
};
push_page_op(
handle,
error_code,
FfiPageOp::PushButton {
name: name_s,
x,
y,
w,
h,
caption: caption_s,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_signature_field(
handle: *mut FfiPageBuilder,
name: *const c_char,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> i32 {
let Some(name_s) = read_cstr_or_fail(name, error_code) else {
return -1;
};
push_page_op(
handle,
error_code,
FfiPageOp::SignatureField {
name: name_s,
x,
y,
w,
h,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_footnote(
handle: *mut FfiPageBuilder,
ref_mark: *const c_char,
note_text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(ref_mark_s) = read_cstr_or_fail(ref_mark, error_code) else {
return -1;
};
let Some(note_text_s) = read_cstr_or_fail(note_text, error_code) else {
return -1;
};
push_page_op(
handle,
error_code,
FfiPageOp::Footnote {
ref_mark: ref_mark_s,
note_text: note_text_s,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_columns(
handle: *mut FfiPageBuilder,
column_count: u32,
gap_pt: f32,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(text_s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(
handle,
error_code,
FfiPageOp::Columns {
count: column_count,
gap_pt,
text: text_s,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_inline(
handle: *mut FfiPageBuilder,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::Inline(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_inline_bold(
handle: *mut FfiPageBuilder,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::InlineBold(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_inline_italic(
handle: *mut FfiPageBuilder,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::InlineItalic(s))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_inline_color(
handle: *mut FfiPageBuilder,
r: f32,
g: f32,
b: f32,
text: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(s) = read_cstr_or_fail(text, error_code) else {
return -1;
};
push_page_op(handle, error_code, FfiPageOp::InlineColor { r, g, b, text: s })
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_newline(
handle: *mut FfiPageBuilder,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::Newline)
}
fn ffi_barcode_type(n: i32) -> Option<crate::writer::BarcodeType> {
use crate::writer::BarcodeType;
match n {
0 => Some(BarcodeType::Code128),
1 => Some(BarcodeType::Code39),
2 => Some(BarcodeType::Ean13),
3 => Some(BarcodeType::Ean8),
4 => Some(BarcodeType::UpcA),
5 => Some(BarcodeType::Itf),
6 => Some(BarcodeType::Code93),
7 => Some(BarcodeType::Codabar),
_ => None,
}
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_barcode_1d(
handle: *mut FfiPageBuilder,
barcode_type: i32,
data: *const c_char,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> i32 {
let Some(bt) = ffi_barcode_type(barcode_type) else {
set_error(error_code, ERR_INVALID_ARG);
return -1;
};
let Some(data_s) = read_cstr_or_fail(data, error_code) else {
return -1;
};
let opts = crate::writer::BarcodeOptions::new()
.width(w as u32)
.height(h as u32);
let bytes = match crate::writer::BarcodeGenerator::generate_1d(bt, &data_s, &opts) {
Ok(b) => b,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
push_page_op(handle, error_code, FfiPageOp::BarcodeImage { bytes, x, y, w, h })
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_barcode_qr(
handle: *mut FfiPageBuilder,
data: *const c_char,
x: f32,
y: f32,
size: f32,
error_code: *mut i32,
) -> i32 {
let Some(data_s) = read_cstr_or_fail(data, error_code) else {
return -1;
};
let opts = crate::writer::QrCodeOptions::new().size(size as u32);
let bytes = match crate::writer::BarcodeGenerator::generate_qr(&data_s, &opts) {
Ok(b) => b,
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
push_page_op(
handle,
error_code,
FfiPageOp::BarcodeImage {
bytes,
x,
y,
w: size,
h: size,
},
)
}
#[no_mangle]
pub unsafe extern "C" fn pdf_page_builder_image(
handle: *mut FfiPageBuilder,
bytes: *const u8,
len: usize,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> i32 {
if bytes.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let data = raw_slice(bytes, len).to_vec();
push_page_op(
handle,
error_code,
FfiPageOp::Image {
bytes: data,
x,
y,
w,
h,
},
)
}
#[no_mangle]
pub unsafe extern "C" fn pdf_page_builder_image_with_alt(
handle: *mut FfiPageBuilder,
bytes: *const u8,
len: usize,
x: f32,
y: f32,
w: f32,
h: f32,
alt_text: *const c_char,
error_code: *mut i32,
) -> i32 {
if bytes.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let data = raw_slice(bytes, len).to_vec();
let Some(alt) = read_cstr_or_fail(alt_text, error_code) else {
return -1;
};
push_page_op(
handle,
error_code,
FfiPageOp::ImageWithAlt {
bytes: data,
x,
y,
w,
h,
alt_text: alt,
},
)
}
#[no_mangle]
pub unsafe extern "C" fn pdf_page_builder_image_artifact(
handle: *mut FfiPageBuilder,
bytes: *const u8,
len: usize,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> i32 {
if bytes.is_null() || len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let data = raw_slice(bytes, len).to_vec();
push_page_op(
handle,
error_code,
FfiPageOp::ImageArtifact {
bytes: data,
x,
y,
w,
h,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_rect(
handle: *mut FfiPageBuilder,
x: f32,
y: f32,
w: f32,
h: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::Rect(x, y, w, h))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_filled_rect(
handle: *mut FfiPageBuilder,
x: f32,
y: f32,
w: f32,
h: f32,
r: f32,
g: f32,
b: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::FilledRect(x, y, w, h, r, g, b))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_line(
handle: *mut FfiPageBuilder,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::Line(x1, y1, x2, y2))
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_stroke_rect(
handle: *mut FfiPageBuilder,
x: f32,
y: f32,
w: f32,
h: f32,
width: f32,
r: f32,
g: f32,
b: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(
handle,
error_code,
FfiPageOp::StrokeRect {
x,
y,
w,
h,
width,
r,
g,
b,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_stroke_line(
handle: *mut FfiPageBuilder,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
width: f32,
r: f32,
g: f32,
b: f32,
error_code: *mut i32,
) -> i32 {
push_page_op(
handle,
error_code,
FfiPageOp::StrokeLine {
x1,
y1,
x2,
y2,
width,
r,
g,
b,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_stroke_rect_dashed(
handle: *mut FfiPageBuilder,
x: f32,
y: f32,
w: f32,
h: f32,
width: f32,
r: f32,
g: f32,
b: f32,
dash_array: *const f32,
n_dash: usize,
phase: f32,
error_code: *mut i32,
) -> i32 {
let dash = if n_dash == 0 || dash_array.is_null() {
vec![]
} else {
raw_slice(dash_array, n_dash).to_vec()
};
push_page_op(
handle,
error_code,
FfiPageOp::StrokeRectDashed {
x,
y,
w,
h,
width,
r,
g,
b,
dash,
phase,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_stroke_line_dashed(
handle: *mut FfiPageBuilder,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
width: f32,
r: f32,
g: f32,
b: f32,
dash_array: *const f32,
n_dash: usize,
phase: f32,
error_code: *mut i32,
) -> i32 {
let dash = if n_dash == 0 || dash_array.is_null() {
vec![]
} else {
raw_slice(dash_array, n_dash).to_vec()
};
push_page_op(
handle,
error_code,
FfiPageOp::StrokeLineDashed {
x1,
y1,
x2,
y2,
width,
r,
g,
b,
dash,
phase,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_text_in_rect(
handle: *mut FfiPageBuilder,
x: f32,
y: f32,
w: f32,
h: f32,
text: *const c_char,
align: i32,
error_code: *mut i32,
) -> i32 {
if text.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let text_s = match c_str(text) {
Ok(s) => s.to_string(),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
push_page_op(
handle,
error_code,
FfiPageOp::TextInRect {
x,
y,
w,
h,
text: text_s,
align,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_new_page_same_size(
handle: *mut FfiPageBuilder,
error_code: *mut i32,
) -> i32 {
push_page_op(handle, error_code, FfiPageOp::NewPageSameSize)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_table(
handle: *mut FfiPageBuilder,
n_columns: usize,
widths: *const f32,
aligns: *const i32,
n_rows: usize,
cell_strings: *const *const c_char,
has_header: i32,
error_code: *mut i32,
) -> i32 {
if n_columns == 0 {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
if (aligns.is_null() || widths.is_null()) && n_columns > 0
|| (cell_strings.is_null() && n_rows > 0)
{
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let widths_vec: Vec<f32> = raw_slice(widths, n_columns).to_vec();
let aligns_vec: Vec<i32> = raw_slice(aligns, n_columns).to_vec();
let total = n_rows.saturating_mul(n_columns);
let mut rows: Vec<Vec<String>> = Vec::with_capacity(n_rows);
if total > 0 {
let strings = raw_slice(cell_strings, total);
for row_idx in 0..n_rows {
let mut row = Vec::with_capacity(n_columns);
for col_idx in 0..n_columns {
let ptr = strings[row_idx * n_columns + col_idx];
if ptr.is_null() {
row.push(String::new());
continue;
}
match c_str(ptr) {
Ok(s) => row.push(s.to_string()),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
}
}
rows.push(row);
}
}
push_page_op(
handle,
error_code,
FfiPageOp::Table {
widths: widths_vec,
aligns: aligns_vec,
rows,
has_header: has_header != 0,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_streaming_table_begin(
handle: *mut FfiPageBuilder,
n_columns: usize,
headers: *const *const c_char,
widths: *const f32,
aligns: *const i32,
repeat_header: i32,
error_code: *mut i32,
) -> i32 {
if n_columns == 0 || headers.is_null() || widths.is_null() || aligns.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let widths_slice = raw_slice(widths, n_columns);
let aligns_slice = raw_slice(aligns, n_columns);
let headers_slice = raw_slice(headers, n_columns);
let mut cols: Vec<(String, f32, i32)> = Vec::with_capacity(n_columns);
for i in 0..n_columns {
let ptr = headers_slice[i];
if ptr.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let header = match c_str(ptr) {
Ok(s) => s.to_string(),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
cols.push((header, widths_slice[i], aligns_slice[i]));
}
let pb = handle_mut(handle);
pb.st_batch_size = 256;
pb.st_batch_count = 0;
pb.st_pending_count = 0;
push_page_op(
handle,
error_code,
FfiPageOp::StreamingTableOpen {
columns: cols,
repeat_header: repeat_header != 0,
mode: 0,
sample_rows: 50,
min_col_width_pt: 20.0,
max_col_width_pt: 400.0,
max_rowspan: 1,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_streaming_table_begin_v2(
handle: *mut FfiPageBuilder,
n_columns: usize,
headers: *const *const c_char,
widths: *const f32,
aligns: *const i32,
repeat_header: i32,
mode: i32,
sample_rows: usize,
min_col_width_pt: f32,
max_col_width_pt: f32,
max_rowspan: usize,
error_code: *mut i32,
) -> i32 {
if n_columns == 0 || headers.is_null() || widths.is_null() || aligns.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let widths_slice = raw_slice(widths, n_columns);
let aligns_slice = raw_slice(aligns, n_columns);
let headers_slice = raw_slice(headers, n_columns);
let mut cols: Vec<(String, f32, i32)> = Vec::with_capacity(n_columns);
for i in 0..n_columns {
let ptr = headers_slice[i];
if ptr.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let header = match c_str(ptr) {
Ok(s) => s.to_string(),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
cols.push((header, widths_slice[i], aligns_slice[i]));
}
let pb = handle_mut(handle);
pb.st_batch_size = 256;
pb.st_batch_count = 0;
pb.st_pending_count = 0;
push_page_op(
handle,
error_code,
FfiPageOp::StreamingTableOpen {
columns: cols,
repeat_header: repeat_header != 0,
mode,
sample_rows,
min_col_width_pt,
max_col_width_pt,
max_rowspan,
},
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_streaming_table_set_batch_size(
handle: *mut FfiPageBuilder,
batch_size: usize,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let pb = handle_mut(handle);
pb.st_batch_size = if batch_size == 0 { 256 } else { batch_size };
pb.st_batch_count = 0;
pb.st_pending_count = 0;
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_streaming_table_pending_row_count(
handle: *mut FfiPageBuilder,
) -> usize {
if handle.is_null() {
return 0;
}
handle_ref(handle).st_pending_count
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_streaming_table_batch_count(
handle: *mut FfiPageBuilder,
) -> usize {
if handle.is_null() {
return 0;
}
handle_ref(handle).st_batch_count
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_streaming_table_flush(
handle: *mut FfiPageBuilder,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let pb = handle_mut(handle);
if pb.st_pending_count > 0 {
pb.st_batch_count += 1;
pb.st_pending_count = 0;
}
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_streaming_table_push_row(
handle: *mut FfiPageBuilder,
n_cells: usize,
cells: *const *const c_char,
error_code: *mut i32,
) -> i32 {
pdf_page_builder_streaming_table_push_row_v2(
handle,
n_cells,
cells,
std::ptr::null(), error_code,
)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_streaming_table_push_row_v2(
handle: *mut FfiPageBuilder,
n_cells: usize,
cells: *const *const c_char,
rowspans: *const usize,
error_code: *mut i32,
) -> i32 {
if n_cells == 0 || cells.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let cells_slice = raw_slice(cells, n_cells);
let rowspans_slice = if rowspans.is_null() {
None
} else {
Some(raw_slice(rowspans, n_cells))
};
let mut row: Vec<(String, usize)> = Vec::with_capacity(n_cells);
for i in 0..n_cells {
let ptr = cells_slice[i];
let text = if ptr.is_null() {
String::new()
} else {
match c_str(ptr) {
Ok(s) => s.to_string(),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
}
};
let span = rowspans_slice.map_or(1, |rs| rs[i].max(1));
row.push((text, span));
}
let rc = push_page_op(handle, error_code, FfiPageOp::StreamingTableRow(row));
if rc == 0 {
let pb = handle_mut(handle);
pb.st_pending_count += 1;
if pb.st_batch_size > 0 && pb.st_pending_count >= pb.st_batch_size {
pb.st_batch_count += 1;
pb.st_pending_count = 0;
}
}
rc
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_streaming_table_finish(
handle: *mut FfiPageBuilder,
error_code: *mut i32,
) -> i32 {
if !handle.is_null() {
let pb = handle_mut(handle);
if pb.st_pending_count > 0 {
pb.st_batch_count += 1;
pb.st_pending_count = 0;
}
pb.st_batch_size = 0;
pb.st_batch_count = 0;
}
push_page_op(handle, error_code, FfiPageOp::StreamingTableFinish)
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_done(handle: *mut FfiPageBuilder, error_code: *mut i32) -> i32 {
if handle.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let mut page = unsafe { Box::from_raw(handle) };
if page.done_called {
set_error(error_code, ERR_INVALID_ARG);
Box::leak(page);
return -1;
}
page.done_called = true;
if page.parent.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
let parent = handle_mut(page.parent);
let Some(builder) = parent.inner.as_mut() else {
set_error(error_code, ERR_INVALID_ARG);
parent.open_page = false;
return -1;
};
let page_size = page
.page_size
.unwrap_or(crate::writer::PageSize::Custom(page.custom_width, page.custom_height));
let mut rust_page_opt: Option<crate::writer::FluentPageBuilder<'_>> =
Some(builder.page(page_size));
let mut streaming_opt: Option<crate::writer::StreamingTable<'_>> = None;
for op in page.ops.drain(..) {
match op {
FfiPageOp::StreamingTableOpen {
columns,
repeat_header,
mode,
sample_rows,
min_col_width_pt,
max_col_width_pt,
max_rowspan,
} => {
let Some(rp) = rust_page_opt.take() else {
set_error(error_code, ERR_INVALID_ARG);
return -1;
};
let mut config = crate::writer::StreamingTableConfig::new()
.repeat_header(repeat_header)
.max_rowspan(max_rowspan);
config = match mode {
1 => config.mode_sample(sample_rows, min_col_width_pt, max_col_width_pt),
2 => config.mode_auto_all(),
_ => config.mode_fixed(),
};
for (header, width, align) in columns {
let cell_align = match align {
1 => crate::writer::CellAlign::Center,
2 => crate::writer::CellAlign::Right,
_ => crate::writer::CellAlign::Left,
};
config = config.column(
crate::writer::StreamingColumn::new(header)
.width_pt(width)
.align(cell_align),
);
}
streaming_opt = Some(rp.streaming_table(config));
continue;
},
FfiPageOp::StreamingTableRow(cells) => {
if let Some(st) = streaming_opt.as_mut() {
let _ = st.push_row(|r| {
for (text, span) in cells {
if span > 1 {
r.span_cell(text, span);
} else {
r.cell(text);
}
}
});
} else {
set_error(error_code, ERR_INVALID_ARG);
return -1;
}
continue;
},
FfiPageOp::StreamingTableFinish => {
let Some(st) = streaming_opt.take() else {
set_error(error_code, ERR_INVALID_ARG);
return -1;
};
rust_page_opt = Some(st.finish());
continue;
},
FfiPageOp::BarcodeImage { bytes, x, y, w, h } => {
let rp = match rust_page_opt.take() {
Some(p) => p,
None => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
match rp.image_from_bytes(&bytes, crate::geometry::Rect::new(x, y, w, h)) {
Ok(p) => rust_page_opt = Some(p),
Err(_) => {
set_error(error_code, ERR_INTERNAL);
return -1;
},
}
continue;
},
FfiPageOp::Image { bytes, x, y, w, h } => {
let rp = match rust_page_opt.take() {
Some(p) => p,
None => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
match rp.image_from_bytes(&bytes, crate::geometry::Rect::new(x, y, w, h)) {
Ok(p) => rust_page_opt = Some(p),
Err(_) => {
set_error(error_code, ERR_INTERNAL);
return -1;
},
}
continue;
},
FfiPageOp::ImageWithAlt {
bytes,
x,
y,
w,
h,
alt_text,
} => {
let rp = match rust_page_opt.take() {
Some(p) => p,
None => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
match rp.image_from_bytes_with_alt(
&bytes,
crate::geometry::Rect::new(x, y, w, h),
&alt_text,
) {
Ok(p) => rust_page_opt = Some(p),
Err(_) => {
set_error(error_code, ERR_INTERNAL);
return -1;
},
}
continue;
},
FfiPageOp::ImageArtifact { bytes, x, y, w, h } => {
let rp = match rust_page_opt.take() {
Some(p) => p,
None => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
match rp
.image_from_bytes_as_artifact(&bytes, crate::geometry::Rect::new(x, y, w, h))
{
Ok(p) => rust_page_opt = Some(p),
Err(_) => {
set_error(error_code, ERR_INTERNAL);
return -1;
},
}
continue;
},
_ => {},
}
let rust_page = match rust_page_opt.take() {
Some(rp) => rp,
None => {
set_error(error_code, ERR_INVALID_ARG);
return -1;
},
};
rust_page_opt = Some(match op {
FfiPageOp::Font(name, size) => rust_page.font(&name, size),
FfiPageOp::At(x, y) => rust_page.at(x, y),
FfiPageOp::Text(text) => rust_page.text(&text),
FfiPageOp::Heading(level, text) => rust_page.heading(level, &text),
FfiPageOp::Paragraph(text) => rust_page.paragraph(&text),
FfiPageOp::Space(points) => rust_page.space(points),
FfiPageOp::HorizontalRule => rust_page.horizontal_rule(),
FfiPageOp::LinkUrl(url) => rust_page.link_url(&url),
FfiPageOp::LinkPage(p) => rust_page.link_page(p),
FfiPageOp::LinkNamed(dest) => rust_page.link_named(&dest),
FfiPageOp::LinkJavaScript(script) => rust_page.link_javascript(&script),
FfiPageOp::OnOpen(script) => rust_page.on_open(&script),
FfiPageOp::OnClose(script) => rust_page.on_close(&script),
FfiPageOp::FieldKeystroke(s) => rust_page.field_keystroke(&s),
FfiPageOp::FieldFormat(s) => rust_page.field_format(&s),
FfiPageOp::FieldValidate(s) => rust_page.field_validate(&s),
FfiPageOp::FieldCalculate(s) => rust_page.field_calculate(&s),
FfiPageOp::Highlight(r, g, b) => rust_page.highlight((r, g, b)),
FfiPageOp::Underline(r, g, b) => rust_page.underline((r, g, b)),
FfiPageOp::Strikeout(r, g, b) => rust_page.strikeout((r, g, b)),
FfiPageOp::Squiggly(r, g, b) => rust_page.squiggly((r, g, b)),
FfiPageOp::StickyNote(text) => rust_page.sticky_note(&text),
FfiPageOp::StickyNoteAt(x, y, text) => rust_page.sticky_note_at(x, y, &text),
FfiPageOp::Watermark(text) => rust_page.watermark(&text),
FfiPageOp::WatermarkConfidential => rust_page.watermark_confidential(),
FfiPageOp::WatermarkDraft => rust_page.watermark_draft(),
FfiPageOp::Stamp(name) => rust_page.stamp(ffi_parse_stamp_type(&name)),
FfiPageOp::FreeText { x, y, w, h, text } => {
rust_page.freetext(crate::geometry::Rect::new(x, y, w, h), &text)
},
FfiPageOp::TextField {
name,
x,
y,
w,
h,
default_value,
} => rust_page.text_field(name, x, y, w, h, default_value),
FfiPageOp::Checkbox {
name,
x,
y,
w,
h,
checked,
} => rust_page.checkbox(name, x, y, w, h, checked),
FfiPageOp::ComboBox {
name,
x,
y,
w,
h,
options,
selected,
} => rust_page.combo_box(name, x, y, w, h, options, selected),
FfiPageOp::RadioGroup {
name,
buttons,
selected,
} => rust_page.radio_group(name, buttons, selected),
FfiPageOp::PushButton {
name,
x,
y,
w,
h,
caption,
} => rust_page.push_button(name, x, y, w, h, caption),
FfiPageOp::SignatureField { name, x, y, w, h } => {
rust_page.signature_field(name, x, y, w, h)
},
FfiPageOp::Footnote {
ref_mark,
note_text,
} => rust_page.footnote(&ref_mark, ¬e_text),
FfiPageOp::Columns {
count,
gap_pt,
text,
} => rust_page.columns(count, gap_pt, &text),
FfiPageOp::Inline(text) => rust_page.inline(&text),
FfiPageOp::InlineBold(text) => rust_page.inline_bold(&text),
FfiPageOp::InlineItalic(text) => rust_page.inline_italic(&text),
FfiPageOp::InlineColor { r, g, b, text } => rust_page.inline_color(r, g, b, &text),
FfiPageOp::Newline => rust_page.newline(),
FfiPageOp::Rect(x, y, w, h) => rust_page.rect(x, y, w, h),
FfiPageOp::FilledRect(x, y, w, h, r, g, b) => {
rust_page.filled_rect(x, y, w, h, r, g, b)
},
FfiPageOp::Line(x1, y1, x2, y2) => rust_page.line(x1, y1, x2, y2),
FfiPageOp::StrokeRect {
x,
y,
w,
h,
width,
r,
g,
b,
} => rust_page.stroke_rect(x, y, w, h, crate::writer::LineStyle::new(width, r, g, b)),
FfiPageOp::StrokeRectDashed {
x,
y,
w,
h,
width,
r,
g,
b,
dash,
phase,
} => {
let style = if dash.is_empty() {
crate::writer::LineStyle::new(width, r, g, b)
} else {
crate::writer::LineStyle::new(width, r, g, b).with_dash(&dash, phase)
};
rust_page.stroke_rect(x, y, w, h, style)
},
FfiPageOp::StrokeLine {
x1,
y1,
x2,
y2,
width,
r,
g,
b,
} => {
rust_page.stroke_line(x1, y1, x2, y2, crate::writer::LineStyle::new(width, r, g, b))
},
FfiPageOp::StrokeLineDashed {
x1,
y1,
x2,
y2,
width,
r,
g,
b,
dash,
phase,
} => {
let style = if dash.is_empty() {
crate::writer::LineStyle::new(width, r, g, b)
} else {
crate::writer::LineStyle::new(width, r, g, b).with_dash(&dash, phase)
};
rust_page.stroke_line(x1, y1, x2, y2, style)
},
FfiPageOp::TextInRect {
x,
y,
w,
h,
text,
align,
} => {
let align_e = match align {
1 => crate::writer::TextAlign::Center,
2 => crate::writer::TextAlign::Right,
_ => crate::writer::TextAlign::Left,
};
rust_page.text_in_rect(crate::geometry::Rect::new(x, y, w, h), &text, align_e)
},
FfiPageOp::NewPageSameSize => rust_page.new_page_same_size(),
FfiPageOp::Table {
widths,
aligns,
rows,
has_header,
} => {
let cells: Vec<Vec<crate::writer::TableCell>> = rows
.into_iter()
.map(|row| {
row.into_iter()
.map(crate::writer::TableCell::text)
.collect()
})
.collect();
let mut table = crate::writer::Table::new(cells);
let col_widths: Vec<crate::writer::ColumnWidth> = widths
.iter()
.map(|&w| crate::writer::ColumnWidth::Fixed(w))
.collect();
table = table.with_column_widths(col_widths);
let col_aligns: Vec<crate::writer::CellAlign> = aligns
.iter()
.map(|&a| match a {
1 => crate::writer::CellAlign::Center,
2 => crate::writer::CellAlign::Right,
_ => crate::writer::CellAlign::Left,
})
.collect();
table.column_aligns = col_aligns;
if has_header {
table = table.with_header_row();
}
rust_page.table(table)
},
FfiPageOp::StreamingTableOpen { .. }
| FfiPageOp::StreamingTableRow(_)
| FfiPageOp::StreamingTableFinish
| FfiPageOp::BarcodeImage { .. }
| FfiPageOp::Image { .. }
| FfiPageOp::ImageWithAlt { .. }
| FfiPageOp::ImageArtifact { .. } => {
unreachable!("streaming ops handled above; reaching here is a replay-loop bug")
},
});
}
if let Some(st) = streaming_opt.take() {
rust_page_opt = Some(st.finish());
}
let Some(rust_page) = rust_page_opt else {
set_error(error_code, ERR_INVALID_ARG);
return -1;
};
rust_page.done();
parent.open_page = false;
set_error(error_code, ERR_SUCCESS);
0
}
#[no_mangle]
pub extern "C" fn pdf_page_builder_free(handle: *mut FfiPageBuilder) {
if !handle.is_null() {
unsafe {
let page = Box::from_raw(handle);
if !page.parent.is_null() && !page.done_called {
(*page.parent).open_page = false;
}
}
}
}
fn consume_builder(
handle: *mut FfiDocumentBuilder,
error_code: *mut i32,
) -> Option<crate::writer::DocumentBuilder> {
let wrapper = ffi_builder_mut(handle, error_code)?;
if wrapper.open_page {
set_error(error_code, ERR_INVALID_ARG);
return None;
}
wrapper.inner.take()
}
fn bytes_to_ffi(bytes: Vec<u8>, out_len: *mut usize, error_code: *mut i32) -> *mut u8 {
if out_len.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
write_out(out_len, bytes.len());
set_error(error_code, ERR_SUCCESS);
vec_to_ffi_bytes(bytes)
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_build(
handle: *mut FfiDocumentBuilder,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
let Some(builder) = consume_builder(handle, error_code) else {
return ptr::null_mut();
};
match builder.build() {
Ok(bytes) => bytes_to_ffi(bytes, out_len, error_code),
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_save(
handle: *mut FfiDocumentBuilder,
path: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(path_s) = read_cstr_or_fail(path, error_code) else {
return -1;
};
let Some(builder) = consume_builder(handle, error_code) else {
return -1;
};
match builder.save(&path_s) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_save_encrypted(
handle: *mut FfiDocumentBuilder,
path: *const c_char,
user_password: *const c_char,
owner_password: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(path_s) = read_cstr_or_fail(path, error_code) else {
return -1;
};
let Some(user_pw) = read_cstr_or_fail(user_password, error_code) else {
return -1;
};
let Some(owner_pw) = read_cstr_or_fail(owner_password, error_code) else {
return -1;
};
let Some(builder) = consume_builder(handle, error_code) else {
return -1;
};
match builder.save_encrypted(&path_s, &user_pw, &owner_pw) {
Ok(()) => {
set_error(error_code, ERR_SUCCESS);
0
},
Err(e) => {
set_error(error_code, classify_error(&e));
-1
},
}
}
#[no_mangle]
pub extern "C" fn pdf_document_builder_to_bytes_encrypted(
handle: *mut FfiDocumentBuilder,
user_password: *const c_char,
owner_password: *const c_char,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
let Some(user_pw) = read_cstr_or_fail(user_password, error_code) else {
return ptr::null_mut();
};
let Some(owner_pw) = read_cstr_or_fail(owner_password, error_code) else {
return ptr::null_mut();
};
let Some(builder) = consume_builder(handle, error_code) else {
return ptr::null_mut();
};
match builder.to_bytes_encrypted(&user_pw, &owner_pw) {
Ok(bytes) => bytes_to_ffi(bytes, out_len, error_code),
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_from_html_css(
html: *const c_char,
css: *const c_char,
font_bytes: *const u8,
font_len: usize,
error_code: *mut i32,
) -> *mut Pdf {
let Some(html_s) = read_cstr_or_fail(html, error_code) else {
return ptr::null_mut();
};
let Some(css_s) = read_cstr_or_fail(css, error_code) else {
return ptr::null_mut();
};
if font_bytes.is_null() || font_len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let font_vec = raw_slice(font_bytes, font_len).to_vec();
match Pdf::from_html_css(&html_s, &css_s, font_vec) {
Ok(pdf) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(pdf))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[no_mangle]
pub extern "C" fn pdf_from_html_css_with_fonts(
html: *const c_char,
css: *const c_char,
families: *const *const c_char,
font_bytes: *const *const u8,
font_lens: *const usize,
count: usize,
error_code: *mut i32,
) -> *mut Pdf {
let Some(html_s) = read_cstr_or_fail(html, error_code) else {
return ptr::null_mut();
};
let Some(css_s) = read_cstr_or_fail(css, error_code) else {
return ptr::null_mut();
};
if count == 0 || families.is_null() || font_bytes.is_null() || font_lens.is_null() {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let mut fonts: Vec<(String, Vec<u8>)> = Vec::with_capacity(count);
let names_slice = raw_slice(families, count);
let bytes_ptrs_slice = raw_slice(font_bytes, count);
let bytes_lens_slice = raw_slice(font_lens, count);
for i in 0..count {
let name_ptr = names_slice[i];
let bytes_ptr = bytes_ptrs_slice[i];
let bytes_len = bytes_lens_slice[i];
if name_ptr.is_null() || bytes_ptr.is_null() || bytes_len == 0 {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
}
let name = match c_str(name_ptr) {
Ok(s) => s.to_string(),
Err(_) => {
set_error(error_code, ERR_INVALID_ARG);
return ptr::null_mut();
},
};
let bytes = raw_slice(bytes_ptr, bytes_len).to_vec();
fonts.push((name, bytes));
}
match Pdf::from_html_css_with_fonts(&html_s, &css_s, fonts) {
Ok(pdf) => {
set_error(error_code, ERR_SUCCESS);
Box::into_raw(Box::new(pdf))
},
Err(e) => {
set_error(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
#[test]
fn classify_barcode_error_returns_unsupported() {
let e = Error::Barcode("requires barcodes feature".into());
assert_eq!(classify_error(&e), _ERR_UNSUPPORTED);
}
#[test]
fn classify_unsupported_error_returns_unsupported() {
let e = Error::Unsupported("not supported".into());
assert_eq!(classify_error(&e), _ERR_UNSUPPORTED);
}
#[test]
fn classify_unsupported_filter_returns_unsupported() {
let e = Error::UnsupportedFilter("JBIG2".into());
assert_eq!(classify_error(&e), _ERR_UNSUPPORTED);
}
#[test]
fn classify_io_error_returns_io() {
let e = Error::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "missing"));
assert_eq!(classify_error(&e), ERR_IO);
}
#[test]
fn classify_parse_error_returns_parse() {
let e = Error::ParseError {
offset: 0,
reason: "bad token".into(),
};
assert_eq!(classify_error(&e), ERR_PARSE);
}
#[test]
fn classify_invalid_pdf_returns_parse() {
let e = Error::InvalidPdf("corrupt".into());
assert_eq!(classify_error(&e), ERR_PARSE);
}
#[test]
fn classify_invalid_operation_returns_invalid_arg() {
let e = Error::InvalidOperation("called on closed doc".into());
assert_eq!(classify_error(&e), ERR_INVALID_ARG);
}
#[test]
fn classify_layout_analysis_returns_extraction() {
let e = Error::LayoutAnalysis("layout failed".into());
assert_eq!(classify_error(&e), ERR_EXTRACTION);
}
unsafe fn new_test_builder() -> *mut FfiDocumentBuilder {
let mut ec = 0i32;
let b = pdf_document_builder_create(&mut ec);
assert!(!b.is_null(), "pdf_document_builder_create ec={ec}");
b
}
unsafe fn new_test_page(b: *mut FfiDocumentBuilder) -> *mut FfiPageBuilder {
let mut ec = 0i32;
let p = pdf_document_builder_a4_page(b, &mut ec);
assert!(!p.is_null(), "pdf_document_builder_a4_page ec={ec}");
p
}
unsafe fn free_test_pair(b: *mut FfiDocumentBuilder, p: *mut FfiPageBuilder) {
pdf_page_builder_free(p);
pdf_document_builder_free(b);
}
#[test]
fn stroke_rect_dashed_returns_success() {
unsafe {
let b = new_test_builder();
let p = new_test_page(b);
let dash = [5.0f32, 3.0f32];
let mut ec = 0i32;
let rc = pdf_page_builder_stroke_rect_dashed(
p,
10.0,
10.0,
200.0,
100.0,
2.0,
0.0,
0.0,
1.0,
dash.as_ptr(),
dash.len(),
0.0,
&mut ec,
);
assert_eq!(rc, 0, "expected 0, ec={ec}");
assert_eq!(ec, ERR_SUCCESS);
free_test_pair(b, p);
}
}
#[test]
fn stroke_line_dashed_returns_success() {
unsafe {
let b = new_test_builder();
let p = new_test_page(b);
let dash = [4.0f32, 2.0f32];
let mut ec = 0i32;
let rc = pdf_page_builder_stroke_line_dashed(
p,
10.0,
10.0,
200.0,
10.0,
1.5,
0.0,
0.5,
0.5,
dash.as_ptr(),
dash.len(),
1.0,
&mut ec,
);
assert_eq!(rc, 0, "expected 0, ec={ec}");
assert_eq!(ec, ERR_SUCCESS);
free_test_pair(b, p);
}
}
#[test]
fn stroke_rect_dashed_null_dash_array_is_solid() {
unsafe {
let b = new_test_builder();
let p = new_test_page(b);
let mut ec = 0i32;
let rc = pdf_page_builder_stroke_rect_dashed(
p,
0.0,
0.0,
100.0,
50.0,
1.0,
0.0,
0.0,
0.0,
std::ptr::null(),
0,
0.0,
&mut ec,
);
assert_eq!(rc, 0, "ec={ec}");
free_test_pair(b, p);
}
}
unsafe fn begin_single_col_table(p: *mut FfiPageBuilder) {
let hdr = b"Col\0";
let hdrs = [hdr.as_ptr() as *const c_char];
let widths = [100.0f32];
let aligns = [0i32];
let mut ec = 0i32;
pdf_page_builder_streaming_table_begin_v2(
p,
1,
hdrs.as_ptr(),
widths.as_ptr(),
aligns.as_ptr(),
1,
0,
20,
0.0,
9999.0,
1,
&mut ec,
);
assert_eq!(ec, ERR_SUCCESS, "streaming_table_begin_v2 failed");
}
unsafe fn push_str_row(p: *mut FfiPageBuilder, val: &str) {
let s = format!("{val}\0");
let ptr = s.as_ptr() as *const c_char;
let cells = [ptr];
let mut ec = 0i32;
pdf_page_builder_streaming_table_push_row(p, 1, cells.as_ptr(), &mut ec);
assert_eq!(ec, ERR_SUCCESS, "push_row failed for {val:?}");
}
#[test]
fn streaming_table_set_batch_size_resets_counts() {
unsafe {
let b = new_test_builder();
let p = new_test_page(b);
begin_single_col_table(p);
let mut ec = 0i32;
pdf_page_builder_streaming_table_set_batch_size(p, 5, &mut ec);
assert_eq!(ec, ERR_SUCCESS);
assert_eq!(pdf_page_builder_streaming_table_pending_row_count(p), 0);
assert_eq!(pdf_page_builder_streaming_table_batch_count(p), 0);
pdf_page_builder_streaming_table_finish(p, &mut ec);
free_test_pair(b, p);
}
}
#[test]
fn streaming_table_auto_flush_at_batch_boundary() {
unsafe {
let b = new_test_builder();
let p = new_test_page(b);
begin_single_col_table(p);
let mut ec = 0i32;
pdf_page_builder_streaming_table_set_batch_size(p, 3, &mut ec);
assert_eq!(ec, ERR_SUCCESS);
for i in 0..7 {
push_str_row(p, &format!("r{i}"));
}
assert_eq!(pdf_page_builder_streaming_table_batch_count(p), 2);
assert_eq!(pdf_page_builder_streaming_table_pending_row_count(p), 1);
pdf_page_builder_streaming_table_finish(p, &mut ec);
free_test_pair(b, p);
}
}
#[test]
fn streaming_table_explicit_flush_marks_boundary() {
unsafe {
let b = new_test_builder();
let p = new_test_page(b);
begin_single_col_table(p);
let mut ec = 0i32;
pdf_page_builder_streaming_table_set_batch_size(p, 100, &mut ec);
push_str_row(p, "a");
push_str_row(p, "b");
assert_eq!(pdf_page_builder_streaming_table_batch_count(p), 0);
assert_eq!(pdf_page_builder_streaming_table_pending_row_count(p), 2);
pdf_page_builder_streaming_table_flush(p, &mut ec);
assert_eq!(ec, ERR_SUCCESS);
assert_eq!(pdf_page_builder_streaming_table_batch_count(p), 1);
assert_eq!(pdf_page_builder_streaming_table_pending_row_count(p), 0);
pdf_page_builder_streaming_table_finish(p, &mut ec);
free_test_pair(b, p);
}
}
#[test]
fn streaming_table_finish_records_final_batch() {
unsafe {
let b = new_test_builder();
let p = new_test_page(b);
begin_single_col_table(p);
let mut ec = 0i32;
pdf_page_builder_streaming_table_set_batch_size(p, 100, &mut ec);
push_str_row(p, "only");
assert_eq!(pdf_page_builder_streaming_table_pending_row_count(p), 1);
pdf_page_builder_streaming_table_finish(p, &mut ec);
assert_eq!(ec, ERR_SUCCESS);
assert_eq!(pdf_page_builder_streaming_table_pending_row_count(p), 0);
free_test_pair(b, p);
}
}
}