use std::ffi::CString;
use std::os::raw::c_char;
use std::slice;
use crate::djvu_document::DjVuDocument;
use crate::foreign::{self, ForeignError};
use crate::pixmap::Pixmap;
pub struct DjvuDoc {
inner: DjVuDocument,
}
pub struct DjvuPixmap {
inner: Pixmap,
}
#[repr(C)]
pub struct DjvuError {
pub code: i32,
pub message: *mut c_char,
}
fn set_error(err: *mut DjvuError, code: i32, msg: &str) {
if err.is_null() {
return;
}
let c_msg = CString::new(msg).unwrap_or_else(|_| CString::new("unknown error").unwrap());
unsafe {
(*err).code = code;
(*err).message = c_msg.into_raw();
}
}
fn clear_error(err: *mut DjvuError) {
if err.is_null() {
return;
}
unsafe {
(*err).code = 0;
(*err).message = std::ptr::null_mut();
}
}
fn guard<T>(
err: *mut DjvuError,
fallback: T,
panic_code: i32,
panic_msg: &str,
f: impl FnOnce() -> Result<T, ForeignError>,
) -> T {
clear_error(err);
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
Ok(Ok(value)) => value,
Ok(Err(e)) => {
set_error(err, e.code(), &e.to_string());
fallback
}
Err(_) => {
set_error(err, panic_code, panic_msg);
fallback
}
}
}
unsafe fn doc_ref<'a>(doc: *const DjvuDoc) -> Result<&'a DjVuDocument, ForeignError> {
if doc.is_null() {
return Err(ForeignError::OutOfRange("null document".to_string()));
}
Ok(unsafe { &(*doc).inner })
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_error_free(err: *mut DjvuError) {
if err.is_null() {
return;
}
unsafe {
if !(*err).message.is_null() {
drop(CString::from_raw((*err).message));
(*err).message = std::ptr::null_mut();
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_doc_open(
data: *const u8,
len: usize,
err: *mut DjvuError,
) -> *mut DjvuDoc {
guard(
err,
std::ptr::null_mut(),
1,
"panic during document open",
|| {
if data.is_null() || len == 0 {
return Err(ForeignError::Parse("null or empty input".to_string()));
}
let bytes = unsafe { slice::from_raw_parts(data, len) };
let doc = foreign::open(bytes)?;
Ok(Box::into_raw(Box::new(DjvuDoc { inner: doc })))
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_doc_free(doc: *mut DjvuDoc) {
if !doc.is_null() {
unsafe { drop(Box::from_raw(doc)) };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_doc_page_count(doc: *const DjvuDoc) -> usize {
if doc.is_null() {
return 0;
}
unsafe { (*doc).inner.page_count() }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_page_width(
doc: *const DjvuDoc,
page: usize,
err: *mut DjvuError,
) -> u32 {
guard(err, 0, 3, "panic", || {
let doc = unsafe { doc_ref(doc)? };
foreign::page_width(doc, page)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_page_height(
doc: *const DjvuDoc,
page: usize,
err: *mut DjvuError,
) -> u32 {
guard(err, 0, 3, "panic", || {
let doc = unsafe { doc_ref(doc)? };
foreign::page_height(doc, page)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_page_dpi(
doc: *const DjvuDoc,
page: usize,
err: *mut DjvuError,
) -> u32 {
guard(err, 0, 3, "panic", || {
let doc = unsafe { doc_ref(doc)? };
foreign::page_dpi(doc, page)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_page_render(
doc: *const DjvuDoc,
page: usize,
dpi: f32,
err: *mut DjvuError,
) -> *mut DjvuPixmap {
guard(err, std::ptr::null_mut(), 2, "panic during render", || {
let doc = unsafe { doc_ref(doc)? };
let pixmap = foreign::render_at_dpi(doc, page, dpi)?;
Ok(Box::into_raw(Box::new(DjvuPixmap { inner: pixmap })))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_pixmap_free(pm: *mut DjvuPixmap) {
if !pm.is_null() {
unsafe { drop(Box::from_raw(pm)) };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_pixmap_width(pm: *const DjvuPixmap) -> u32 {
if pm.is_null() {
return 0;
}
unsafe { (*pm).inner.width }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_pixmap_height(pm: *const DjvuPixmap) -> u32 {
if pm.is_null() {
return 0;
}
unsafe { (*pm).inner.height }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_pixmap_data(pm: *const DjvuPixmap) -> *const u8 {
if pm.is_null() {
return std::ptr::null();
}
unsafe { (*pm).inner.data.as_ptr() }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_pixmap_data_len(pm: *const DjvuPixmap) -> usize {
if pm.is_null() {
return 0;
}
unsafe { (*pm).inner.data.len() }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_page_text(
doc: *const DjvuDoc,
page: usize,
err: *mut DjvuError,
) -> *mut c_char {
guard(
err,
std::ptr::null_mut(),
2,
"panic during text extraction",
|| {
let doc = unsafe { doc_ref(doc)? };
match foreign::text(doc, page)? {
Some(text) => {
let c = CString::new(text).map_err(|e| ForeignError::Decode(e.to_string()))?;
Ok(c.into_raw())
}
None => Ok(std::ptr::null_mut()),
}
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn djvu_text_free(text: *mut c_char) {
if !text.is_null() {
unsafe { drop(CString::from_raw(text)) };
}
}