micropdf 0.15.15

A pure Rust PDF library - A pure Rust PDF library with fz_/pdf_ API compatibility
//! FFI bindings for pdf::write::PdfWriter
//!
//! Provides C-compatible exports for the native PDF writer module.
//! Uses `mp_writer_*` prefix to distinguish from MuPDF document writers.
//!
//! # Safety
//!
//! All functions are `unsafe extern "C"` and require the caller to ensure:
//! - Pointers are valid and non-null where documented
//! - Buffers have sufficient capacity for output
//! - Handles are not used after free

use super::pdf_native_conversion;
use crate::ffi::pdf_object::types::PdfObjHandle;
use crate::pdf::write::PdfWriter;
use std::sync::LazyLock;

use super::{Handle, HandleStore};

/// Global store for PdfWriter instances
pub static PDF_WRITERS: LazyLock<HandleStore<PdfWriter>> = LazyLock::new(HandleStore::default);

/// Create a new PDF writer.
///
/// # Returns
/// Opaque handle (u64) to the writer, or 0 on failure.
///
/// # Safety
/// Caller must call mp_writer_free when done.
#[unsafe(no_mangle)]
pub extern "C" fn mp_writer_new() -> super::Handle {
    let writer = PdfWriter::new();
    PDF_WRITERS.insert(writer)
}

/// Add an object to the writer.
///
/// # Arguments
/// * `writer` - Handle from mp_writer_new
/// * `obj_num` - PDF object number (must be > 0)
/// * `gen` - Generation number (typically 0)
/// * `object` - Handle to PDF object (from pdf_object module)
///
/// # Returns
/// 0 on success, negative on error (-1 invalid params, -2 add_object failed)
///
/// # Safety
/// writer and object must be valid handles.
#[unsafe(no_mangle)]
pub extern "C" fn mp_writer_add_object(
    writer: super::Handle,
    obj_num: i32,
    generation: u16,
    object: PdfObjHandle,
) -> i32 {
    if writer == 0 || object == 0 {
        return -1;
    }
    let obj = match pdf_native_conversion::pdf_obj_to_object(object) {
        Some(o) => o,
        None => return -1,
    };
    let result = PDF_WRITERS.get(writer).and_then(|arc| {
        arc.lock()
            .ok()
            .map(|mut w| w.add_object(obj_num, generation, obj))
    });
    match result {
        Some(Ok(())) => 0,
        Some(Err(_)) => -2,
        None => -1,
    }
}

/// Write the PDF to a buffer.
///
/// # Arguments
/// * `writer` - Handle from mp_writer_new
/// * `buf` - Output buffer (may be null if buf_len is 0)
/// * `buf_len` - Capacity of buf in bytes
///
/// # Returns
/// Number of bytes written on success, negative on error.
/// If buf is null or too small, returns -1.
///
/// # Safety
/// buf must point to writable memory of at least buf_len bytes, or be null.
#[unsafe(no_mangle)]
pub extern "C" fn mp_writer_write(writer: super::Handle, buf: *mut u8, buf_len: usize) -> i64 {
    if writer == 0 {
        return -1;
    }
    if buf.is_null() && buf_len > 0 {
        return -1;
    }
    let pdf_bytes = match PDF_WRITERS.get(writer) {
        Some(arc) => match arc.lock() {
            Ok(w) => match w.write_to_vec() {
                Ok(b) => b,
                Err(_) => return -2,
            },
            Err(_) => return -1,
        },
        None => return -1,
    };
    let len = pdf_bytes.len();
    if len > buf_len {
        return -1;
    }
    if !buf.is_null() && len > 0 {
        unsafe { std::ptr::copy_nonoverlapping(pdf_bytes.as_ptr(), buf, len) };
    }
    len as i64
}

/// Free a PDF writer and release resources.
///
/// # Safety
/// writer must be a valid handle from mp_writer_new.
/// Must not be called twice with the same handle.
#[unsafe(no_mangle)]
pub extern "C" fn mp_writer_free(writer: super::Handle) {
    if writer == 0 {
        return;
    }
    PDF_WRITERS.remove(writer);
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ffi::pdf_object::pdf_new_int;

    #[test]
    fn test_mp_writer_new_free() {
        let w = mp_writer_new();
        assert_ne!(w, 0);
        mp_writer_free(w);
    }

    #[test]
    fn test_mp_writer_add_object_and_write() {
        let w = mp_writer_new();
        assert_ne!(w, 0);

        let obj = pdf_new_int(0, 42);
        let result = mp_writer_add_object(w, 1, 0, obj);
        assert_eq!(result, 0);

        let mut buf = vec![0u8; 4096];
        let written = mp_writer_write(w, buf.as_mut_ptr(), buf.len());
        assert!(written > 0);
        assert!(buf[..written as usize].starts_with(b"%PDF-"));

        mp_writer_free(w);
    }

    #[test]
    fn test_mp_writer_null_checks() {
        assert_eq!(mp_writer_add_object(0, 1, 0, 1), -1);
        let mut buf = [0u8; 10];
        assert!(mp_writer_write(0, buf.as_mut_ptr(), 10) < 0);
    }
}