use super::{CONTEXTS, Handle};
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::{CStr, CString, c_char, c_int, c_void};
use std::sync::{Arc, LazyLock, Mutex};
thread_local! {
static CACHED_ERROR_MSG: RefCell<Option<CString>> = const { RefCell::new(None) };
}
#[repr(C)]
pub enum FzErrorType {
None = 0,
Generic = 1,
System = 2, Library = 3, Argument = 4, Limit = 5, Unsupported = 6, Format = 7, Syntax = 8, TryLater = 9, Abort = 10, Repaired = 11, }
#[derive(Clone)]
struct ErrorState {
code: c_int,
message: String,
}
impl Default for ErrorState {
fn default() -> Self {
Self {
code: FzErrorType::None as c_int,
message: String::new(),
}
}
}
struct ContextSettings {
icc_enabled: bool,
aa_level: c_int,
}
impl Default for ContextSettings {
fn default() -> Self {
Self {
icc_enabled: true, aa_level: 8, }
}
}
static CONTEXT_SETTINGS: LazyLock<Mutex<HashMap<Handle, ContextSettings>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
struct StoreState {
allocated_bytes: usize,
max_bytes: usize,
}
impl Default for StoreState {
fn default() -> Self {
Self {
allocated_bytes: 0,
max_bytes: 256 * 1024 * 1024, }
}
}
static STORE_STATE: LazyLock<Mutex<StoreState>> =
LazyLock::new(|| Mutex::new(StoreState::default()));
pub struct Context {
max_store: usize,
error: Arc<Mutex<ErrorState>>,
user_data: *mut c_void,
warn_callback: Option<unsafe extern "C" fn(*mut c_void, *const c_char)>,
error_callback: Option<unsafe extern "C" fn(*mut c_void, c_int, *const c_char)>,
}
unsafe impl Send for Context {}
unsafe impl Sync for Context {}
impl Context {
pub fn new(max_store: usize) -> Self {
Self {
max_store,
error: Arc::new(Mutex::new(ErrorState::default())),
user_data: std::ptr::null_mut(),
warn_callback: None,
error_callback: None,
}
}
pub fn set_error(&self, code: c_int, message: String) {
if let Ok(mut err) = self.error.lock() {
err.code = code;
err.message = message;
}
}
pub fn get_error(&self) -> (c_int, String) {
if let Ok(err) = self.error.lock() {
(err.code, err.message.clone())
} else {
(
FzErrorType::Generic as c_int,
"Failed to lock error state".into(),
)
}
}
pub fn clear_error(&self) {
if let Ok(mut err) = self.error.lock() {
err.code = FzErrorType::None as c_int;
err.message.clear();
}
}
pub fn max_store(&self) -> usize {
self.max_store
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn fz_new_context(
_alloc: *const c_void, _locks: *const c_void, max_store: usize,
) -> Handle {
CONTEXTS.insert(Context::new(max_store))
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_clone_context(ctx: Handle) -> Handle {
CONTEXTS.keep(ctx)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_context(ctx: Handle) {
let _ = CONTEXTS.remove(ctx);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn fz_set_user_context(ctx: Handle, user: *mut c_void) {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(mut guard) = context.lock() {
guard.user_data = user;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_user_context(ctx: Handle) -> *mut c_void {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
return guard.user_data;
}
}
std::ptr::null_mut()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn fz_throw(ctx: Handle, errcode: c_int, fmt: *const c_char) {
if fmt.is_null() {
return;
}
let message = unsafe {
if let Ok(c_str) = CStr::from_ptr(fmt).to_str() {
c_str.to_string()
} else {
"Invalid error message".to_string()
}
};
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
guard.set_error(errcode, message.clone());
if let Some(callback) = guard.error_callback {
let msg_cstr = std::ffi::CString::new(message).unwrap_or_default();
unsafe {
callback(guard.user_data, errcode, msg_cstr.as_ptr());
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_rethrow(ctx: Handle) {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
let (code, message) = guard.get_error();
if code != FzErrorType::None as c_int {
if let Some(callback) = guard.error_callback {
let msg_cstr = std::ffi::CString::new(message).unwrap_or_default();
unsafe {
callback(guard.user_data, code, msg_cstr.as_ptr());
}
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_caught(ctx: Handle) -> c_int {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
let (code, _) = guard.get_error();
return code;
}
}
FzErrorType::Generic as c_int
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_caught_message(ctx: Handle) -> *const c_char {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
let (_, message) = guard.get_error();
if message.is_empty() {
return c"No error".as_ptr();
}
if let Ok(c_str) = CString::new(message) {
CACHED_ERROR_MSG.with(|cell| {
*cell.borrow_mut() = Some(c_str);
});
return CACHED_ERROR_MSG.with(|cell| {
cell.borrow()
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(c"Unknown error".as_ptr())
});
}
}
}
c"Unknown error".as_ptr()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_ignore_error(ctx: Handle) {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
guard.clear_error();
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn fz_warn(ctx: Handle, fmt: *const c_char) {
if fmt.is_null() {
return;
}
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
if let Some(callback) = guard.warn_callback {
unsafe {
callback(guard.user_data, fmt);
}
} else {
unsafe {
if let Ok(c_str) = CStr::from_ptr(fmt).to_str() {
eprintln!("warning: {}", c_str);
}
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_flush_warnings(_ctx: Handle) {
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn fz_set_error_callback(
ctx: Handle,
callback: Option<unsafe extern "C" fn(*mut c_void, c_int, *const c_char)>,
user: *mut c_void,
) {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(mut guard) = context.lock() {
guard.error_callback = callback;
guard.user_data = user;
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn fz_set_warning_callback(
ctx: Handle,
callback: Option<unsafe extern "C" fn(*mut c_void, *const c_char)>,
user: *mut c_void,
) {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(mut guard) = context.lock() {
guard.warn_callback = callback;
guard.user_data = user;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_size(ctx: Handle) -> usize {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
return guard.max_store();
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_convert_error(ctx: Handle, code: *mut c_int) -> *const c_char {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
let (err_code, message) = guard.get_error();
if !code.is_null() {
unsafe {
*code = err_code;
}
}
if message.is_empty() {
return c"No error".as_ptr();
}
if let Ok(c_str) = CString::new(message) {
CACHED_ERROR_MSG.with(|cell| {
*cell.borrow_mut() = Some(c_str);
});
return CACHED_ERROR_MSG.with(|cell| {
cell.borrow()
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(c"Unknown error".as_ptr())
});
}
}
}
if !code.is_null() {
unsafe {
*code = FzErrorType::Generic as c_int;
}
}
c"Unknown error".as_ptr()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_report_error(ctx: Handle) {
if let Some(context) = CONTEXTS.get(ctx) {
if let Ok(guard) = context.lock() {
let (code, message) = guard.get_error();
if code != FzErrorType::None as c_int {
if let Some(callback) = guard.error_callback {
let msg_cstr = std::ffi::CString::new(message).unwrap_or_default();
unsafe {
callback(guard.user_data, code, msg_cstr.as_ptr());
}
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_default_context() -> Handle {
unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 256 * 1024 * 1024) } }
#[unsafe(no_mangle)]
pub extern "C" fn fz_keep_context(ctx: Handle) -> Handle {
fz_clone_context(ctx)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_has_error(ctx: Handle) -> c_int {
let code = fz_caught(ctx);
if code == FzErrorType::None as c_int {
0
} else {
1
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_context_is_valid(ctx: Handle) -> c_int {
if CONTEXTS.get(ctx).is_some() { 1 } else { 0 }
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_shrink_store(_ctx: Handle, percent: c_int) {
if let Ok(mut store) = STORE_STATE.lock() {
let target = (store.max_bytes as f32 * (percent as f32 / 100.0)) as usize;
if target < store.allocated_bytes {
store.allocated_bytes = target;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_empty_store(_ctx: Handle) {
if let Ok(mut store) = STORE_STATE.lock() {
store.allocated_bytes = 0;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_scavenge(_ctx: Handle, size: usize, phase: *mut c_int) -> c_int {
if let Ok(mut store) = STORE_STATE.lock() {
let available = store.allocated_bytes;
let to_free = size.min(available);
if !phase.is_null() {
unsafe {
*phase = 0; }
}
store.allocated_bytes = store.allocated_bytes.saturating_sub(to_free);
return to_free as c_int;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_enable_icc(ctx: Handle) {
if CONTEXTS.get(ctx).is_some() {
if let Ok(mut settings) = CONTEXT_SETTINGS.lock() {
settings
.entry(ctx)
.or_insert_with(ContextSettings::default)
.icc_enabled = true;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_disable_icc(ctx: Handle) {
if CONTEXTS.get(ctx).is_some() {
if let Ok(mut settings) = CONTEXT_SETTINGS.lock() {
settings
.entry(ctx)
.or_insert_with(ContextSettings::default)
.icc_enabled = false;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_aa_level(ctx: Handle, bits: c_int) {
if CONTEXTS.get(ctx).is_some() {
if let Ok(mut settings) = CONTEXT_SETTINGS.lock() {
settings
.entry(ctx)
.or_insert_with(ContextSettings::default)
.aa_level = bits.clamp(0, 8); }
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_aa_level(ctx: Handle) -> c_int {
if CONTEXTS.get(ctx).is_some() {
if let Ok(settings) = CONTEXT_SETTINGS.lock() {
return settings.get(&ctx).map(|s| s.aa_level).unwrap_or(8); }
}
8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_create_drop() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
assert_ne!(ctx, 0);
fz_drop_context(ctx);
}
#[test]
fn test_context_clone() {
let ctx1 = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
let ctx2 = fz_clone_context(ctx1);
assert_eq!(ctx1, ctx2);
fz_drop_context(ctx1);
fz_drop_context(ctx2);
}
#[test]
fn test_error_handling() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
let msg = std::ffi::CString::new("Test error").unwrap();
unsafe {
fz_throw(ctx, FzErrorType::Argument as c_int, msg.as_ptr());
}
let code = fz_caught(ctx);
assert_eq!(code, FzErrorType::Argument as c_int);
fz_ignore_error(ctx);
let code = fz_caught(ctx);
assert_eq!(code, FzErrorType::None as c_int);
fz_drop_context(ctx);
}
#[test]
fn test_user_context() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
let user_data = 42i32;
let user_ptr = &user_data as *const i32 as *mut c_void;
unsafe {
fz_set_user_context(ctx, user_ptr);
}
let retrieved = fz_user_context(ctx);
assert_eq!(retrieved, user_ptr);
fz_drop_context(ctx);
}
#[test]
fn test_store_size() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
let size = fz_store_size(ctx);
assert_eq!(size, 1024 * 1024);
fz_drop_context(ctx);
}
#[test]
fn test_warning() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
let msg = std::ffi::CString::new("Test warning").unwrap();
unsafe {
fz_warn(ctx, msg.as_ptr());
}
fz_drop_context(ctx);
}
#[test]
fn test_default_context() {
let ctx = fz_new_default_context();
assert_ne!(ctx, 0);
let size = fz_store_size(ctx);
assert_eq!(size, 256 * 1024 * 1024);
fz_drop_context(ctx);
}
#[test]
fn test_rethrow() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
let msg = std::ffi::CString::new("Original error").unwrap();
unsafe {
fz_throw(ctx, FzErrorType::Format as c_int, msg.as_ptr());
}
fz_rethrow(ctx);
let code = fz_caught(ctx);
assert_eq!(code, FzErrorType::Format as c_int);
fz_drop_context(ctx);
}
#[test]
fn test_convert_error() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
let msg = std::ffi::CString::new("Conversion test").unwrap();
unsafe {
fz_throw(ctx, FzErrorType::Limit as c_int, msg.as_ptr());
}
let mut code: c_int = 0;
let _msg_ptr = fz_convert_error(ctx, &mut code);
assert_eq!(code, FzErrorType::Limit as c_int);
fz_drop_context(ctx);
}
#[test]
fn test_caught_message() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
let msg = std::ffi::CString::new("Detailed error").unwrap();
unsafe {
fz_throw(ctx, FzErrorType::Generic as c_int, msg.as_ptr());
}
let msg_ptr = fz_caught_message(ctx);
assert!(!msg_ptr.is_null());
fz_drop_context(ctx);
}
#[test]
fn test_flush_warnings() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
fz_flush_warnings(ctx); fz_drop_context(ctx);
}
#[test]
fn test_report_error() {
let ctx = unsafe { fz_new_context(std::ptr::null(), std::ptr::null(), 1024 * 1024) };
let msg = std::ffi::CString::new("Report test").unwrap();
unsafe {
fz_throw(ctx, FzErrorType::System as c_int, msg.as_ptr());
}
fz_report_error(ctx);
fz_drop_context(ctx);
}
}