use super::{Handle, HandleStore};
use std::sync::LazyLock;
#[derive(Debug, Clone, Copy)]
pub struct SendPtr(pub usize);
impl SendPtr {
pub fn new(ptr: *mut std::ffi::c_void) -> Self {
Self(ptr as usize)
}
pub fn as_ptr(&self) -> *mut std::ffi::c_void {
self.0 as *mut std::ffi::c_void
}
pub fn is_null(&self) -> bool {
self.0 == 0
}
}
unsafe impl Send for SendPtr {}
unsafe impl Sync for SendPtr {}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BandFormat {
PNG = 0,
JPEG = 1,
PNM = 2,
PAM = 3,
TIFF = 4,
PWG = 5,
PCL = 6,
PS = 7,
Raw = 8,
}
pub type ProgressCallback =
extern "C" fn(current: i32, total: i32, user_data: *mut std::ffi::c_void);
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BandWriterState {
Ready = 0,
HeaderWritten = 1,
WritingBands = 2,
BandsComplete = 3,
Complete = 4,
Error = 5,
}
#[derive(Debug, Clone)]
pub struct BandWriterConfig {
pub format: BandFormat,
pub width: i32,
pub height: i32,
pub n: i32,
pub alpha: bool,
pub x_res: i32,
pub y_res: i32,
pub jpeg_quality: i32,
pub compression: i32,
pub page_num: i32,
pub total_pages: i32,
}
impl Default for BandWriterConfig {
fn default() -> Self {
Self {
format: BandFormat::PNG,
width: 0,
height: 0,
n: 3,
alpha: false,
x_res: 72,
y_res: 72,
jpeg_quality: 90,
compression: 6,
page_num: 1,
total_pages: 1,
}
}
}
#[derive(Debug)]
pub struct BandWriter {
pub config: BandWriterConfig,
pub output: Handle,
pub state: BandWriterState,
pub current_band: i32,
pub rows_per_band: i32,
pub total_bands: i32,
pub bytes_written: usize,
pub progress_fn: Option<ProgressCallback>,
pub progress_data: SendPtr,
pub output_buffer: Vec<u8>,
}
impl Default for BandWriter {
fn default() -> Self {
Self {
config: BandWriterConfig::default(),
output: 0,
state: BandWriterState::Ready,
current_band: 0,
rows_per_band: 16,
total_bands: 0,
bytes_written: 0,
progress_fn: None,
progress_data: SendPtr::new(std::ptr::null_mut()),
output_buffer: Vec::new(),
}
}
}
pub static BAND_WRITERS: LazyLock<HandleStore<BandWriter>> = LazyLock::new(HandleStore::new);
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_band_writer(_ctx: Handle, output: Handle, format: i32) -> Handle {
let fmt = match format {
1 => BandFormat::JPEG,
2 => BandFormat::PNM,
3 => BandFormat::PAM,
4 => BandFormat::TIFF,
5 => BandFormat::PWG,
6 => BandFormat::PCL,
7 => BandFormat::PS,
8 => BandFormat::Raw,
_ => BandFormat::PNG,
};
let writer = BandWriter {
output,
config: BandWriterConfig {
format: fmt,
..Default::default()
},
..Default::default()
};
BAND_WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_band_writer_with_config(
_ctx: Handle,
output: Handle,
format: i32,
width: i32,
height: i32,
n: i32,
alpha: i32,
) -> Handle {
let fmt = match format {
1 => BandFormat::JPEG,
2 => BandFormat::PNM,
3 => BandFormat::PAM,
4 => BandFormat::TIFF,
5 => BandFormat::PWG,
6 => BandFormat::PCL,
7 => BandFormat::PS,
8 => BandFormat::Raw,
_ => BandFormat::PNG,
};
let rows_per_band = 16;
let total_bands = (height + rows_per_band - 1) / rows_per_band;
let writer = BandWriter {
output,
config: BandWriterConfig {
format: fmt,
width,
height,
n,
alpha: alpha != 0,
..Default::default()
},
rows_per_band,
total_bands,
..Default::default()
};
BAND_WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_set_dimensions(
_ctx: Handle,
writer: Handle,
width: i32,
height: i32,
) {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
guard.config.width = width;
guard.config.height = height;
guard.total_bands = (height + guard.rows_per_band - 1) / guard.rows_per_band;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_set_components(_ctx: Handle, writer: Handle, n: i32, alpha: i32) {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
guard.config.n = n;
guard.config.alpha = alpha != 0;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_set_res(_ctx: Handle, writer: Handle, x_res: i32, y_res: i32) {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
guard.config.x_res = x_res.max(1);
guard.config.y_res = y_res.max(1);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_set_jpeg_quality(_ctx: Handle, writer: Handle, quality: i32) {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
guard.config.jpeg_quality = quality.clamp(1, 100);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_set_compression(_ctx: Handle, writer: Handle, level: i32) {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
guard.config.compression = level.clamp(0, 9);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_set_rows_per_band(_ctx: Handle, writer: Handle, rows: i32) {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
guard.rows_per_band = rows.max(1);
if guard.config.height > 0 {
guard.total_bands =
(guard.config.height + guard.rows_per_band - 1) / guard.rows_per_band;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_set_page_info(
_ctx: Handle,
writer: Handle,
page_num: i32,
total_pages: i32,
) {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
guard.config.page_num = page_num.max(1);
guard.config.total_pages = total_pages.max(1);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_set_progress(
_ctx: Handle,
writer: Handle,
callback: ProgressCallback,
user_data: *mut std::ffi::c_void,
) {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
guard.progress_fn = Some(callback);
guard.progress_data = SendPtr::new(user_data);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_write_header(_ctx: Handle, writer: Handle) -> i32 {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
if guard.state != BandWriterState::Ready {
return 0;
}
let header = match guard.config.format {
BandFormat::PNG => generate_png_header(&guard.config),
BandFormat::PNM => generate_pnm_header(&guard.config),
BandFormat::PAM => generate_pam_header(&guard.config),
_ => Vec::new(),
};
guard.output_buffer.extend_from_slice(&header);
guard.bytes_written += header.len();
guard.state = BandWriterState::HeaderWritten;
return 1;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_write_band(
_ctx: Handle,
writer: Handle,
band_rows: i32,
data: *const u8,
) -> i32 {
if data.is_null() || band_rows <= 0 {
return 0;
}
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
if guard.state != BandWriterState::HeaderWritten
&& guard.state != BandWriterState::WritingBands
{
return 0;
}
guard.state = BandWriterState::WritingBands;
let components = guard.config.n + if guard.config.alpha { 1 } else { 0 };
let row_size = (guard.config.width * components) as usize;
let band_size = row_size * band_rows as usize;
let band_data = unsafe { std::slice::from_raw_parts(data, band_size) };
guard.output_buffer.extend_from_slice(band_data);
guard.bytes_written += band_size;
guard.current_band += 1;
if let Some(callback) = guard.progress_fn {
callback(
guard.current_band,
guard.total_bands,
guard.progress_data.as_ptr(),
);
}
if guard.current_band >= guard.total_bands {
guard.state = BandWriterState::BandsComplete;
}
return 1;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_write_trailer(_ctx: Handle, writer: Handle) -> i32 {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(mut guard) = w.lock() {
if guard.state != BandWriterState::BandsComplete {
return 0;
}
let trailer = match guard.config.format {
BandFormat::PNG => generate_png_trailer(),
_ => Vec::new(),
};
guard.output_buffer.extend_from_slice(&trailer);
guard.bytes_written += trailer.len();
guard.state = BandWriterState::Complete;
return 1;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_state(_ctx: Handle, writer: Handle) -> i32 {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(guard) = w.lock() {
return guard.state as i32;
}
}
BandWriterState::Error as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_current_band(_ctx: Handle, writer: Handle) -> i32 {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(guard) = w.lock() {
return guard.current_band;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_total_bands(_ctx: Handle, writer: Handle) -> i32 {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(guard) = w.lock() {
return guard.total_bands;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_bytes_written(_ctx: Handle, writer: Handle) -> usize {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(guard) = w.lock() {
return guard.bytes_written;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_progress(_ctx: Handle, writer: Handle) -> f32 {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(guard) = w.lock() {
if guard.total_bands == 0 {
return 0.0;
}
return guard.current_band as f32 / guard.total_bands as f32;
}
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_band_writer_get_output(
_ctx: Handle,
writer: Handle,
size: *mut usize,
) -> *const u8 {
if let Some(w) = BAND_WRITERS.get(writer) {
if let Ok(guard) = w.lock() {
if !size.is_null() {
unsafe { *size = guard.output_buffer.len() };
}
return guard.output_buffer.as_ptr();
}
}
if !size.is_null() {
unsafe { *size = 0 };
}
std::ptr::null()
}
fn generate_png_header(config: &BandWriterConfig) -> Vec<u8> {
let mut header = Vec::new();
header.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
let mut ihdr = Vec::new();
ihdr.extend_from_slice(&(config.width as u32).to_be_bytes());
ihdr.extend_from_slice(&(config.height as u32).to_be_bytes());
ihdr.push(8);
let color_type = match (config.n, config.alpha) {
(1, false) => 0,
(1, true) => 4,
(3, false) => 2,
(3, true) => 6,
_ => 2,
};
ihdr.push(color_type);
ihdr.push(0); ihdr.push(0); ihdr.push(0);
write_png_chunk(&mut header, b"IHDR", &ihdr);
header
}
fn generate_png_trailer() -> Vec<u8> {
let mut trailer = Vec::new();
write_png_chunk(&mut trailer, b"IEND", &[]);
trailer
}
fn write_png_chunk(output: &mut Vec<u8>, chunk_type: &[u8; 4], data: &[u8]) {
output.extend_from_slice(&(data.len() as u32).to_be_bytes());
output.extend_from_slice(chunk_type);
output.extend_from_slice(data);
let crc = png_crc32(chunk_type, data);
output.extend_from_slice(&crc.to_be_bytes());
}
fn png_crc32(chunk_type: &[u8; 4], data: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFFFFFF;
for &byte in chunk_type.iter().chain(data.iter()) {
crc ^= byte as u32;
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
}
}
!crc
}
fn generate_pnm_header(config: &BandWriterConfig) -> Vec<u8> {
let magic = if config.n == 1 { "P5" } else { "P6" };
format!("{}\n{} {}\n255\n", magic, config.width, config.height).into_bytes()
}
fn generate_pam_header(config: &BandWriterConfig) -> Vec<u8> {
let tupltype = match (config.n, config.alpha) {
(1, false) => "GRAYSCALE",
(1, true) => "GRAYSCALE_ALPHA",
(3, false) => "RGB",
(3, true) => "RGB_ALPHA",
_ => "RGB",
};
let depth = config.n + if config.alpha { 1 } else { 0 };
format!(
"P7\nWIDTH {}\nHEIGHT {}\nDEPTH {}\nMAXVAL 255\nTUPLTYPE {}\nENDHDR\n",
config.width, config.height, depth, tupltype
)
.into_bytes()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_keep_band_writer(_ctx: Handle, writer: Handle) -> Handle {
BAND_WRITERS.keep(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_band_writer(_ctx: Handle, writer: Handle) {
BAND_WRITERS.remove(writer);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_band_writer() {
let writer = fz_new_band_writer(0, 1, 0); assert!(writer > 0);
assert_eq!(
fz_band_writer_state(0, writer),
BandWriterState::Ready as i32
);
fz_drop_band_writer(0, writer);
}
#[test]
fn test_band_writer_config() {
let writer = fz_new_band_writer_with_config(0, 1, 0, 100, 200, 3, 0);
fz_band_writer_set_res(0, writer, 300, 300);
fz_band_writer_set_rows_per_band(0, writer, 32);
assert_eq!(fz_band_writer_total_bands(0, writer), 7);
fz_drop_band_writer(0, writer);
}
#[test]
fn test_write_sequence() {
let writer = fz_new_band_writer_with_config(0, 1, 8, 10, 20, 3, 0); fz_band_writer_set_rows_per_band(0, writer, 10);
assert_eq!(fz_band_writer_write_header(0, writer), 1);
assert_eq!(
fz_band_writer_state(0, writer),
BandWriterState::HeaderWritten as i32
);
let band_data = vec![128u8; 10 * 10 * 3];
assert_eq!(
fz_band_writer_write_band(0, writer, 10, band_data.as_ptr()),
1
);
assert_eq!(
fz_band_writer_write_band(0, writer, 10, band_data.as_ptr()),
1
);
assert_eq!(
fz_band_writer_state(0, writer),
BandWriterState::BandsComplete as i32
);
assert_eq!(fz_band_writer_write_trailer(0, writer), 1);
assert_eq!(
fz_band_writer_state(0, writer),
BandWriterState::Complete as i32
);
assert!(fz_band_writer_bytes_written(0, writer) > 0);
fz_drop_band_writer(0, writer);
}
#[test]
fn test_progress() {
let writer = fz_new_band_writer_with_config(0, 1, 8, 10, 40, 3, 0);
fz_band_writer_set_rows_per_band(0, writer, 10);
assert_eq!(fz_band_writer_progress(0, writer), 0.0);
fz_band_writer_write_header(0, writer);
let band_data = vec![0u8; 10 * 10 * 3];
fz_band_writer_write_band(0, writer, 10, band_data.as_ptr());
assert_eq!(fz_band_writer_progress(0, writer), 0.25);
fz_band_writer_write_band(0, writer, 10, band_data.as_ptr());
assert_eq!(fz_band_writer_progress(0, writer), 0.5);
fz_drop_band_writer(0, writer);
}
}