1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! 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);
}
}