micropdf 0.15.15

A pure Rust PDF library - A pure Rust PDF library with fz_/pdf_ API compatibility
//! C FFI for cpdf — functionally equivalent to libcpdf.

use std::cell::RefCell;
use std::ffi::{CString, c_char, c_int};

thread_local! {
    static LAST_ERROR: RefCell<(i32, String)> = const { RefCell::new((0, String::new())) };
    static LAST_ERROR_CSTRING: RefCell<CString> = RefCell::new(CString::new("").unwrap());
}

pub(crate) fn set_error(code: i32, msg: &str) {
    LAST_ERROR.with(|e| *e.borrow_mut() = (code, msg.to_owned()));
}

pub(crate) fn clear_error() {
    LAST_ERROR.with(|e| *e.borrow_mut() = (0, String::new()));
}

/// Returns the error code from the most recent cpdf operation.
/// Returns 0 on success, non-zero on error.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_lastError() -> c_int {
    LAST_ERROR.with(|e| e.borrow().0)
}

/// Returns a pointer to the error message from the most recent cpdf operation.
///
/// # Safety
///
/// The returned pointer is valid until the next call to any cpdf function
/// that modifies error state. Do not free this pointer. Copy the string
/// immediately if the value is needed beyond the next cpdf call.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_lastErrorString() -> *const c_char {
    LAST_ERROR.with(|e| {
        let msg = e.borrow().1.clone();
        let cs = CString::new(msg).unwrap_or_else(|_| CString::new("").unwrap());
        LAST_ERROR_CSTRING.with(|slot| {
            *slot.borrow_mut() = cs;
            slot.borrow().as_ptr()
        })
    })
}

/// Clears the current error state, resetting the error code to 0.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_clearError() {
    clear_error();
}

pub mod annotations;
pub mod attachments;
pub mod bookmarks;
pub mod compression;
pub mod document;
pub mod encryption;
pub mod fonts;
pub mod images;
pub mod metadata;
pub mod ocg;
pub mod page_labels;
pub mod pages;
pub mod stamps;
pub mod text;
pub use document::CPDF_DOCS;

#[cfg(test)]
mod tests {
    use super::*;
    use std::ffi::CStr;

    #[test]
    fn test_cpdf_last_error_default() {
        cpdf_clearError();
        assert_eq!(cpdf_lastError(), 0);
    }

    #[test]
    fn test_cpdf_last_error_string_default() {
        cpdf_clearError();
        let ptr = cpdf_lastErrorString();
        assert!(!ptr.is_null());
        let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
        assert!(s.is_empty());
    }

    #[test]
    fn test_cpdf_error_from_invalid_handle() {
        cpdf_clearError();
        let _ = super::bookmarks::cpdf_getBookmarksJSON(0, std::ptr::null_mut());
        assert_ne!(cpdf_lastError(), 0);
        let ptr = cpdf_lastErrorString();
        assert!(!ptr.is_null());
        let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
        assert!(!s.is_empty());
        cpdf_clearError();
        assert_eq!(cpdf_lastError(), 0);
    }
}