use std::io::{Read, Write};
use std::sync::LazyLock;
use crate::ffi::buffer::Buffer;
use crate::ffi::{BUFFERS, Handle, HandleStore};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeflateLevel {
None = 0,
BestSpeed = 1,
Best = 9,
Default = -1,
}
impl DeflateLevel {
pub fn to_flate2_level(self) -> flate2::Compression {
match self {
DeflateLevel::None => flate2::Compression::none(),
DeflateLevel::BestSpeed => flate2::Compression::fast(),
DeflateLevel::Best => flate2::Compression::best(),
DeflateLevel::Default => flate2::Compression::default(),
}
}
pub fn from_i32(value: i32) -> Self {
match value {
0 => DeflateLevel::None,
1 => DeflateLevel::BestSpeed,
9 => DeflateLevel::Best,
_ => DeflateLevel::Default,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrotliLevel {
None = 0,
BestSpeed = 1,
Best = 11,
Default = 6,
}
impl BrotliLevel {
pub fn to_u32(self) -> u32 {
match self {
BrotliLevel::None => 0,
BrotliLevel::BestSpeed => 1,
BrotliLevel::Best => 11,
BrotliLevel::Default => 6,
}
}
pub fn from_i32(value: i32) -> Self {
match value {
0 => BrotliLevel::None,
1 => BrotliLevel::BestSpeed,
11 => BrotliLevel::Best,
_ => BrotliLevel::Default,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ImageType {
#[default]
Unknown = 0,
Raw = 1,
Fax = 2,
Flate = 3,
Lzw = 4,
Rld = 5,
Brotli = 6,
Bmp = 7,
Gif = 8,
Jbig2 = 9,
Jpeg = 10,
Jpx = 11,
Jxr = 12,
Png = 13,
Pnm = 14,
Tiff = 15,
Psd = 16,
}
impl ImageType {
pub fn name(&self) -> &'static str {
match self {
ImageType::Unknown => "unknown",
ImageType::Raw => "raw",
ImageType::Fax => "fax",
ImageType::Flate => "flate",
ImageType::Lzw => "lzw",
ImageType::Rld => "rld",
ImageType::Brotli => "brotli",
ImageType::Bmp => "bmp",
ImageType::Gif => "gif",
ImageType::Jbig2 => "jbig2",
ImageType::Jpeg => "jpeg",
ImageType::Jpx => "jpx",
ImageType::Jxr => "jxr",
ImageType::Png => "png",
ImageType::Pnm => "pnm",
ImageType::Tiff => "tiff",
ImageType::Psd => "psd",
}
}
pub fn from_name(name: &str) -> Self {
match name.to_lowercase().as_str() {
"raw" => ImageType::Raw,
"fax" | "ccitt" => ImageType::Fax,
"flate" | "deflate" | "zlib" => ImageType::Flate,
"lzw" => ImageType::Lzw,
"rld" | "runlength" => ImageType::Rld,
"brotli" => ImageType::Brotli,
"bmp" => ImageType::Bmp,
"gif" => ImageType::Gif,
"jbig2" => ImageType::Jbig2,
"jpeg" | "jpg" => ImageType::Jpeg,
"jpx" | "jp2" | "jpeg2000" => ImageType::Jpx,
"jxr" => ImageType::Jxr,
"png" => ImageType::Png,
"pnm" | "pbm" | "pgm" | "ppm" => ImageType::Pnm,
"tiff" | "tif" => ImageType::Tiff,
"psd" => ImageType::Psd,
_ => ImageType::Unknown,
}
}
pub fn from_i32(value: i32) -> Self {
match value {
1 => ImageType::Raw,
2 => ImageType::Fax,
3 => ImageType::Flate,
4 => ImageType::Lzw,
5 => ImageType::Rld,
6 => ImageType::Brotli,
7 => ImageType::Bmp,
8 => ImageType::Gif,
9 => ImageType::Jbig2,
10 => ImageType::Jpeg,
11 => ImageType::Jpx,
12 => ImageType::Jxr,
13 => ImageType::Png,
14 => ImageType::Pnm,
15 => ImageType::Tiff,
16 => ImageType::Psd,
_ => ImageType::Unknown,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Default)]
pub struct CompressionParams {
pub image_type: ImageType,
pub jpeg_color_transform: i32,
pub jpeg_invert_cmyk: i32,
pub jpx_smask_in_data: i32,
pub fax_columns: i32,
pub fax_rows: i32,
pub fax_k: i32,
pub fax_end_of_line: i32,
pub fax_encoded_byte_align: i32,
pub fax_end_of_block: i32,
pub fax_black_is_1: i32,
pub predictor_columns: i32,
pub predictor_colors: i32,
pub predictor: i32,
pub bpc: i32,
pub lzw_early_change: i32,
}
#[derive(Debug, Clone, Default)]
pub struct CompressedBuffer {
pub refs: i32,
pub params: CompressionParams,
pub buffer: Handle,
}
pub static COMPRESSED_BUFFERS: LazyLock<HandleStore<CompressedBuffer>> =
LazyLock::new(HandleStore::new);
#[unsafe(no_mangle)]
pub extern "C" fn fz_deflate_bound(_ctx: Handle, size: usize) -> usize {
size + (size / 1000) + 12 + 6
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_deflate(
_ctx: Handle,
dest: *mut u8,
compressed_length: *mut usize,
source: *const u8,
source_length: usize,
level: i32,
) {
if dest.is_null() || compressed_length.is_null() || source.is_null() {
return;
}
let deflate_level = DeflateLevel::from_i32(level);
let source_slice = unsafe { std::slice::from_raw_parts(source, source_length) };
let dest_capacity = unsafe { *compressed_length };
let mut encoder = flate2::write::ZlibEncoder::new(
Vec::with_capacity(dest_capacity),
deflate_level.to_flate2_level(),
);
if encoder.write_all(source_slice).is_ok() {
if let Ok(compressed) = encoder.finish() {
let len = compressed.len().min(dest_capacity);
unsafe {
std::ptr::copy_nonoverlapping(compressed.as_ptr(), dest, len);
*compressed_length = len;
}
return;
}
}
unsafe {
*compressed_length = 0;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_deflated_data(
_ctx: Handle,
compressed_length: *mut usize,
source: *const u8,
source_length: usize,
level: i32,
) -> *mut u8 {
if compressed_length.is_null() || source.is_null() {
return std::ptr::null_mut();
}
let deflate_level = DeflateLevel::from_i32(level);
let source_slice = unsafe { std::slice::from_raw_parts(source, source_length) };
let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), deflate_level.to_flate2_level());
if encoder.write_all(source_slice).is_ok() {
if let Ok(compressed) = encoder.finish() {
let len = compressed.len();
let ptr = compressed.as_ptr() as *mut u8;
std::mem::forget(compressed);
unsafe {
*compressed_length = len;
}
return ptr;
}
}
unsafe {
*compressed_length = 0;
}
std::ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_deflated_data_from_buffer(
_ctx: Handle,
compressed_length: *mut usize,
buffer: Handle,
level: i32,
) -> *mut u8 {
if compressed_length.is_null() {
return std::ptr::null_mut();
}
let buf_arc = match BUFFERS.get(buffer) {
Some(b) => b,
None => {
unsafe {
*compressed_length = 0;
}
return std::ptr::null_mut();
}
};
let buf_guard = buf_arc.lock().unwrap();
let data = buf_guard.data();
fz_new_deflated_data(_ctx, compressed_length, data.as_ptr(), data.len(), level)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_deflate_to_buffer(
_ctx: Handle,
source: *const u8,
source_length: usize,
level: i32,
) -> Handle {
if source.is_null() {
return 0;
}
let deflate_level = DeflateLevel::from_i32(level);
let source_slice = unsafe { std::slice::from_raw_parts(source, source_length) };
let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), deflate_level.to_flate2_level());
if encoder.write_all(source_slice).is_ok() {
if let Ok(compressed) = encoder.finish() {
let buffer = Buffer::from_data(&compressed);
return BUFFERS.insert(buffer);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_brotli_bound(_ctx: Handle, size: usize) -> usize {
size + (size / 100) + 503
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_compress_brotli(
_ctx: Handle,
dest: *mut u8,
compressed_length: *mut usize,
source: *const u8,
source_length: usize,
level: i32,
) {
if dest.is_null() || compressed_length.is_null() || source.is_null() {
return;
}
let brotli_level = BrotliLevel::from_i32(level);
let source_slice = unsafe { std::slice::from_raw_parts(source, source_length) };
let dest_capacity = unsafe { *compressed_length };
let mut compressed = Vec::with_capacity(dest_capacity);
let params = brotli::enc::BrotliEncoderParams {
quality: brotli_level.to_u32() as i32,
..Default::default()
};
let mut encoder = brotli::CompressorWriter::with_params(&mut compressed, 4096, ¶ms);
if encoder.write_all(source_slice).is_ok() {
drop(encoder);
let len = compressed.len().min(dest_capacity);
unsafe {
std::ptr::copy_nonoverlapping(compressed.as_ptr(), dest, len);
*compressed_length = len;
}
return;
}
unsafe {
*compressed_length = 0;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_brotli_data(
_ctx: Handle,
compressed_length: *mut usize,
source: *const u8,
source_length: usize,
level: i32,
) -> *mut u8 {
if compressed_length.is_null() || source.is_null() {
return std::ptr::null_mut();
}
let brotli_level = BrotliLevel::from_i32(level);
let source_slice = unsafe { std::slice::from_raw_parts(source, source_length) };
let mut compressed = Vec::new();
let params = brotli::enc::BrotliEncoderParams {
quality: brotli_level.to_u32() as i32,
..Default::default()
};
let mut encoder = brotli::CompressorWriter::with_params(&mut compressed, 4096, ¶ms);
if encoder.write_all(source_slice).is_ok() {
drop(encoder);
let len = compressed.len();
let ptr = compressed.as_ptr() as *mut u8;
std::mem::forget(compressed);
unsafe {
*compressed_length = len;
}
return ptr;
}
unsafe {
*compressed_length = 0;
}
std::ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_brotli_data_from_buffer(
_ctx: Handle,
compressed_length: *mut usize,
buffer: Handle,
level: i32,
) -> *mut u8 {
if compressed_length.is_null() {
return std::ptr::null_mut();
}
let buf_arc = match BUFFERS.get(buffer) {
Some(b) => b,
None => {
unsafe {
*compressed_length = 0;
}
return std::ptr::null_mut();
}
};
let buf_guard = buf_arc.lock().unwrap();
let data = buf_guard.data();
fz_new_brotli_data(_ctx, compressed_length, data.as_ptr(), data.len(), level)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_brotli_to_buffer(
_ctx: Handle,
source: *const u8,
source_length: usize,
level: i32,
) -> Handle {
if source.is_null() {
return 0;
}
let brotli_level = BrotliLevel::from_i32(level);
let source_slice = unsafe { std::slice::from_raw_parts(source, source_length) };
let mut compressed = Vec::new();
let params = brotli::enc::BrotliEncoderParams {
quality: brotli_level.to_u32() as i32,
..Default::default()
};
let mut encoder = brotli::CompressorWriter::with_params(&mut compressed, 4096, ¶ms);
if encoder.write_all(source_slice).is_ok() {
drop(encoder);
let buffer = Buffer::from_data(&compressed);
return BUFFERS.insert(buffer);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_compress_ccitt_fax_g3(
_ctx: Handle,
data: *const u8,
columns: i32,
rows: i32,
stride: isize,
) -> Handle {
if data.is_null() || columns <= 0 || rows <= 0 {
return 0;
}
let stride = if stride == 0 {
(columns + 7) / 8
} else {
stride.unsigned_abs() as i32
};
let total_bytes = (stride * rows) as usize;
let input_data = unsafe { std::slice::from_raw_parts(data, total_bytes) };
let mut writer = BitWriter::new();
for row in 0..rows as usize {
let row_start = row * stride as usize;
let row_end = row_start + stride as usize;
let row_data = &input_data[row_start..row_end];
encode_g3_row(&mut writer, row_data, columns as usize);
}
for _ in 0..6 {
writer.write_bits(0x001, 12);
}
let buffer = Buffer::from_data(&writer.flush());
BUFFERS.insert(buffer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_compress_ccitt_fax_g4(
_ctx: Handle,
data: *const u8,
columns: i32,
rows: i32,
stride: isize,
) -> Handle {
if data.is_null() || columns <= 0 || rows <= 0 {
return 0;
}
let stride = if stride == 0 {
(columns + 7) / 8
} else {
stride.unsigned_abs() as i32
};
let total_bytes = (stride * rows) as usize;
let input_data = unsafe { std::slice::from_raw_parts(data, total_bytes) };
let mut writer = BitWriter::new();
let mut reference_line = vec![0u8; stride as usize];
for row in 0..rows as usize {
let row_start = row * stride as usize;
let row_end = row_start + stride as usize;
let row_data = &input_data[row_start..row_end];
encode_g4_row(&mut writer, row_data, &reference_line, columns as usize);
reference_line.copy_from_slice(row_data);
}
writer.write_bits(0x001, 12);
writer.write_bits(0x001, 12);
let buffer = Buffer::from_data(&writer.flush());
BUFFERS.insert(buffer)
}
const WHITE_TERM: [(u16, u8); 64] = [
(0x35, 8), (0x07, 6), (0x07, 4), (0x08, 4), (0x0B, 4), (0x0C, 4), (0x0E, 4), (0x0F, 4), (0x13, 5), (0x14, 5), (0x07, 5), (0x08, 5), (0x08, 6), (0x03, 6), (0x34, 6), (0x35, 6), (0x2A, 6), (0x2B, 6), (0x27, 7), (0x0C, 7), (0x08, 7), (0x17, 7), (0x03, 7), (0x04, 7), (0x28, 7), (0x2B, 7), (0x13, 7), (0x24, 7), (0x18, 7), (0x02, 8), (0x03, 8), (0x1A, 8), (0x1B, 8), (0x12, 8), (0x13, 8), (0x14, 8), (0x15, 8), (0x16, 8), (0x17, 8), (0x28, 8), (0x29, 8), (0x2A, 8), (0x2B, 8), (0x2C, 8), (0x2D, 8), (0x04, 8), (0x05, 8), (0x0A, 8), (0x0B, 8), (0x52, 8), (0x53, 8), (0x54, 8), (0x55, 8), (0x24, 8), (0x25, 8), (0x58, 8), (0x59, 8), (0x5A, 8), (0x5B, 8), (0x4A, 8), (0x4B, 8), (0x32, 8), (0x33, 8), (0x34, 8), ];
const BLACK_TERM: [(u16, u8); 64] = [
(0x37, 10), (0x02, 3), (0x03, 2), (0x02, 2), (0x03, 3), (0x03, 4), (0x02, 4), (0x03, 5), (0x05, 6), (0x04, 6), (0x04, 7), (0x05, 7), (0x07, 7), (0x04, 8), (0x07, 8), (0x18, 9), (0x17, 10), (0x18, 10), (0x08, 10), (0x67, 11), (0x68, 11), (0x6C, 11), (0x37, 11), (0x28, 11), (0x17, 11), (0x18, 11), (0xCA, 12), (0xCB, 12), (0xCC, 12), (0xCD, 12), (0x68, 12), (0x69, 12), (0x6A, 12), (0x6B, 12), (0xD2, 12), (0xD3, 12), (0xD4, 12), (0xD5, 12), (0xD6, 12), (0xD7, 12), (0x6C, 12), (0x6D, 12), (0xDA, 12), (0xDB, 12), (0x54, 12), (0x55, 12), (0x56, 12), (0x57, 12), (0x64, 12), (0x65, 12), (0x52, 12), (0x53, 12), (0x24, 12), (0x37, 12), (0x38, 12), (0x27, 12), (0x28, 12), (0x58, 12), (0x59, 12), (0x2B, 12), (0x2C, 12), (0x5A, 12), (0x66, 12), (0x67, 12), ];
const WHITE_MAKEUP: [(u16, u8); 40] = [
(0x1B, 5), (0x12, 5), (0x17, 6), (0x37, 7), (0x36, 8), (0x37, 8), (0x64, 8), (0x65, 8), (0x68, 8), (0x67, 8), (0xCC, 9), (0xCD, 9), (0xD2, 9), (0xD3, 9), (0xD4, 9), (0xD5, 9), (0xD6, 9), (0xD7, 9), (0xD8, 9), (0xD9, 9), (0xDA, 9), (0xDB, 9), (0x98, 9), (0x99, 9), (0x9A, 9), (0x18, 6), (0x9B, 9), (0x08, 11), (0x0C, 11), (0x0D, 11), (0x12, 12), (0x13, 12), (0x14, 12), (0x15, 12), (0x16, 12), (0x17, 12), (0x1C, 12), (0x1D, 12), (0x1E, 12), (0x1F, 12), ];
const BLACK_MAKEUP: [(u16, u8); 40] = [
(0x0F, 10), (0xC8, 12), (0xC9, 12), (0x5B, 12), (0x33, 12), (0x34, 12), (0x35, 12), (0x6C, 13), (0x6D, 13), (0x4A, 13), (0x4B, 13), (0x4C, 13), (0x4D, 13), (0x72, 13), (0x73, 13), (0x74, 13), (0x75, 13), (0x76, 13), (0x77, 13), (0x52, 13), (0x53, 13), (0x54, 13), (0x55, 13), (0x5A, 13), (0x5B, 13), (0x64, 13), (0x65, 13), (0x08, 11), (0x0C, 11), (0x0D, 11), (0x12, 12), (0x13, 12), (0x14, 12), (0x15, 12), (0x16, 12), (0x17, 12), (0x1C, 12), (0x1D, 12), (0x1E, 12), (0x1F, 12), ];
struct BitWriter {
output: Vec<u8>,
current_byte: u32,
bits_pending: u8,
}
impl BitWriter {
fn new() -> Self {
Self {
output: Vec::new(),
current_byte: 0,
bits_pending: 0,
}
}
fn write_bits(&mut self, code: u16, nbits: u8) {
let mut remaining = nbits;
let mut bits = code;
while remaining > 0 {
let space = 8 - self.bits_pending;
if remaining >= space {
self.current_byte |= ((bits >> (remaining - space)) as u32) & ((1u32 << space) - 1);
self.output.push(self.current_byte as u8);
remaining -= space;
bits &= (1u16 << remaining) - 1;
self.current_byte = 0;
self.bits_pending = 0;
} else {
self.current_byte |=
((bits as u32) & ((1u32 << remaining) - 1)) << (space - remaining);
self.bits_pending += remaining;
remaining = 0;
}
}
}
fn flush(mut self) -> Vec<u8> {
if self.bits_pending > 0 {
self.output.push(self.current_byte as u8);
}
self.output
}
}
fn emit_run_length(writer: &mut BitWriter, length: usize, color: u8) {
let (term_table, makeup_table) = if color == 0 {
(&WHITE_TERM[..], &WHITE_MAKEUP[..])
} else {
(&BLACK_TERM[..], &BLACK_MAKEUP[..])
};
let mut remaining = length;
while remaining >= 64 {
let makeup_idx = if remaining >= 2560 {
39 } else {
let idx = (remaining / 64) - 1;
idx.min(makeup_table.len() - 1)
};
let (code, nbits) = makeup_table[makeup_idx];
writer.write_bits(code, nbits);
let coded_length = (makeup_idx + 1) * 64;
remaining -= coded_length;
}
let (code, nbits) = term_table[remaining];
writer.write_bits(code, nbits);
}
fn get_pixel(row: &[u8], col: usize) -> u8 {
let byte_idx = col / 8;
let bit_idx = 7 - (col % 8);
if byte_idx < row.len() {
(row[byte_idx] >> bit_idx) & 1
} else {
0
}
}
fn count_run(row: &[u8], start: usize, columns: usize, target_color: u8) -> usize {
let mut count = 0;
let mut pos = start;
while pos < columns {
if get_pixel(row, pos) != target_color {
break;
}
count += 1;
pos += 1;
}
count
}
fn encode_g3_row(writer: &mut BitWriter, row: &[u8], columns: usize) {
writer.write_bits(0x001, 12);
let mut pos = 0;
let mut is_white = true;
while pos < columns {
let target = if is_white { 0u8 } else { 1u8 };
let run = count_run(row, pos, columns, target);
emit_run_length(writer, run, target);
pos += run;
is_white = !is_white;
}
}
fn next_changing(row: &[u8], start: usize, columns: usize, target_color: u8) -> usize {
let mut pos = start;
while pos < columns && get_pixel(row, pos) == target_color {
pos += 1;
}
pos
}
fn encode_g4_row(writer: &mut BitWriter, current: &[u8], reference: &[u8], columns: usize) {
let mut a0: isize = -1; let mut a0_color: u8 = 0;
while (a0 as usize) < columns {
let a0_pos = if a0 < 0 { 0usize } else { a0 as usize };
let a1 = if a0 < 0 {
if get_pixel(current, 0) != 0 {
0
} else {
next_changing(current, 0, columns, 0)
}
} else {
next_changing(current, a0_pos, columns, a0_color)
};
let a2 = if a1 < columns {
let a1_color = get_pixel(current, a1);
next_changing(current, a1, columns, a1_color)
} else {
columns
};
let b1 = {
let mut pos = if a0 < 0 { 0usize } else { a0_pos };
if pos < columns && get_pixel(reference, pos) == a0_color {
pos = next_changing(reference, pos, columns, a0_color);
} else if a0 < 0 && get_pixel(reference, 0) != a0_color {
pos = 0;
}
if a0 >= 0 && pos <= a0_pos {
if pos < columns {
let c = get_pixel(reference, pos);
pos = next_changing(reference, pos, columns, c);
if pos < columns && get_pixel(reference, pos) == a0_color {
pos = next_changing(reference, pos, columns, a0_color);
}
}
}
pos
};
let b2 = if b1 < columns {
let b1_color = get_pixel(reference, b1);
next_changing(reference, b1, columns, b1_color)
} else {
columns
};
if b2 < a1 {
writer.write_bits(0x01, 4); a0 = b2 as isize;
} else {
let diff = a1 as isize - b1 as isize;
if diff >= -3 && diff <= 3 {
match diff {
0 => writer.write_bits(0x1, 1), 1 => writer.write_bits(0x03, 3), -1 => writer.write_bits(0x02, 3), 2 => writer.write_bits(0x03, 6), -2 => writer.write_bits(0x02, 6), 3 => writer.write_bits(0x03, 7), -3 => writer.write_bits(0x02, 7), _ => unreachable!(),
}
a0 = a1 as isize;
a0_color = 1 - a0_color;
} else {
writer.write_bits(0x01, 3); let run_a0a1 = a1.saturating_sub(a0_pos);
let run_a1a2 = a2.saturating_sub(a1);
emit_run_length(writer, run_a0a1, a0_color);
emit_run_length(writer, run_a1a2, 1 - a0_color);
a0 = a2 as isize;
}
}
if a0 as usize >= columns {
break;
}
if a0 >= 0 {
a0_color = get_pixel(current, a0 as usize);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_compressed_buffer(_ctx: Handle) -> Handle {
COMPRESSED_BUFFERS.insert(CompressedBuffer {
refs: 1, ..Default::default()
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_keep_compressed_buffer(_ctx: Handle, cbuf: Handle) -> Handle {
if let Some(cb) = COMPRESSED_BUFFERS.get(cbuf) {
cb.lock().unwrap().refs += 1;
}
cbuf
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_compressed_buffer(_ctx: Handle, cbuf: Handle) {
if let Some(cb) = COMPRESSED_BUFFERS.get(cbuf) {
let mut guard = cb.lock().unwrap();
guard.refs -= 1;
if guard.refs <= 0 {
if guard.buffer != 0 {
crate::ffi::buffer::fz_drop_buffer(_ctx, guard.buffer);
}
drop(guard);
COMPRESSED_BUFFERS.remove(cbuf);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_compressed_buffer_size(cbuf: Handle) -> usize {
if let Some(cb) = COMPRESSED_BUFFERS.get(cbuf) {
let guard = cb.lock().unwrap();
if let Some(buf) = BUFFERS.get(guard.buffer) {
return buf.lock().unwrap().len();
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_compressed_buffer_set_data(_ctx: Handle, cbuf: Handle, buffer: Handle) {
if let Some(cb) = COMPRESSED_BUFFERS.get(cbuf) {
cb.lock().unwrap().buffer = buffer;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_compressed_buffer_get_data(_ctx: Handle, cbuf: Handle) -> Handle {
if let Some(cb) = COMPRESSED_BUFFERS.get(cbuf) {
return cb.lock().unwrap().buffer;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_compressed_buffer_set_type(_ctx: Handle, cbuf: Handle, image_type: i32) {
if let Some(cb) = COMPRESSED_BUFFERS.get(cbuf) {
cb.lock().unwrap().params.image_type = ImageType::from_i32(image_type);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_compressed_buffer_get_type(_ctx: Handle, cbuf: Handle) -> i32 {
if let Some(cb) = COMPRESSED_BUFFERS.get(cbuf) {
return cb.lock().unwrap().params.image_type as i32;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_recognize_image_format(_ctx: Handle, data: *const u8) -> i32 {
if data.is_null() {
return ImageType::Unknown as i32;
}
let bytes = unsafe { std::slice::from_raw_parts(data, 8) };
if bytes.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
return ImageType::Png as i32;
}
if bytes.starts_with(&[0xFF, 0xD8, 0xFF]) {
return ImageType::Jpeg as i32;
}
if bytes.starts_with(b"GIF87a") || bytes.starts_with(b"GIF89a") {
return ImageType::Gif as i32;
}
if bytes.starts_with(b"BM") {
return ImageType::Bmp as i32;
}
if bytes.starts_with(&[0x49, 0x49, 0x2A, 0x00]) || bytes.starts_with(&[0x4D, 0x4D, 0x00, 0x2A])
{
return ImageType::Tiff as i32;
}
if bytes.starts_with(&[0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20]) {
return ImageType::Jpx as i32;
}
if bytes.starts_with(b"8BPS") {
return ImageType::Psd as i32;
}
if bytes.starts_with(b"P1")
|| bytes.starts_with(b"P2")
|| bytes.starts_with(b"P3")
|| bytes.starts_with(b"P4")
|| bytes.starts_with(b"P5")
|| bytes.starts_with(b"P6")
{
return ImageType::Pnm as i32;
}
ImageType::Unknown as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_image_type_name(image_type: i32) -> *const std::ffi::c_char {
let t = ImageType::from_i32(image_type);
match t {
ImageType::Unknown => c"unknown".as_ptr(),
ImageType::Raw => c"raw".as_ptr(),
ImageType::Fax => c"fax".as_ptr(),
ImageType::Flate => c"flate".as_ptr(),
ImageType::Lzw => c"lzw".as_ptr(),
ImageType::Rld => c"rld".as_ptr(),
ImageType::Brotli => c"brotli".as_ptr(),
ImageType::Bmp => c"bmp".as_ptr(),
ImageType::Gif => c"gif".as_ptr(),
ImageType::Jbig2 => c"jbig2".as_ptr(),
ImageType::Jpeg => c"jpeg".as_ptr(),
ImageType::Jpx => c"jpx".as_ptr(),
ImageType::Jxr => c"jxr".as_ptr(),
ImageType::Png => c"png".as_ptr(),
ImageType::Pnm => c"pnm".as_ptr(),
ImageType::Tiff => c"tiff".as_ptr(),
ImageType::Psd => c"psd".as_ptr(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_lookup_image_type(name: *const std::ffi::c_char) -> i32 {
if name.is_null() {
return ImageType::Unknown as i32;
}
let name_str = unsafe { std::ffi::CStr::from_ptr(name).to_str().unwrap_or("") };
ImageType::from_name(name_str) as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_inflate(
_ctx: Handle,
dest: *mut u8,
dest_length: *mut usize,
source: *const u8,
source_length: usize,
) -> i32 {
if dest.is_null() || dest_length.is_null() || source.is_null() {
return -1;
}
let source_slice = unsafe { std::slice::from_raw_parts(source, source_length) };
let dest_capacity = unsafe { *dest_length };
let mut decoder = flate2::read::ZlibDecoder::new(source_slice);
let mut decompressed = Vec::with_capacity(dest_capacity);
if decoder.read_to_end(&mut decompressed).is_ok() {
let len = decompressed.len().min(dest_capacity);
unsafe {
std::ptr::copy_nonoverlapping(decompressed.as_ptr(), dest, len);
*dest_length = len;
}
return 0;
}
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_decompress_brotli(
_ctx: Handle,
dest: *mut u8,
dest_length: *mut usize,
source: *const u8,
source_length: usize,
) -> i32 {
if dest.is_null() || dest_length.is_null() || source.is_null() {
return -1;
}
let source_slice = unsafe { std::slice::from_raw_parts(source, source_length) };
let dest_capacity = unsafe { *dest_length };
let mut decompressed = Vec::with_capacity(dest_capacity);
let mut decoder = brotli::Decompressor::new(source_slice, 4096);
if decoder.read_to_end(&mut decompressed).is_ok() {
let len = decompressed.len().min(dest_capacity);
unsafe {
std::ptr::copy_nonoverlapping(decompressed.as_ptr(), dest, len);
*dest_length = len;
}
return 0;
}
-1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deflate_level_from_i32() {
assert_eq!(DeflateLevel::from_i32(0), DeflateLevel::None);
assert_eq!(DeflateLevel::from_i32(1), DeflateLevel::BestSpeed);
assert_eq!(DeflateLevel::from_i32(9), DeflateLevel::Best);
assert_eq!(DeflateLevel::from_i32(-1), DeflateLevel::Default);
assert_eq!(DeflateLevel::from_i32(5), DeflateLevel::Default);
}
#[test]
fn test_brotli_level_from_i32() {
assert_eq!(BrotliLevel::from_i32(0), BrotliLevel::None);
assert_eq!(BrotliLevel::from_i32(1), BrotliLevel::BestSpeed);
assert_eq!(BrotliLevel::from_i32(11), BrotliLevel::Best);
assert_eq!(BrotliLevel::from_i32(6), BrotliLevel::Default);
}
#[test]
fn test_deflate_bound() {
let bound = fz_deflate_bound(1, 1000);
assert!(bound > 1000);
}
#[test]
fn test_brotli_bound() {
let bound = fz_brotli_bound(1, 1000);
assert!(bound > 1000);
}
#[test]
fn test_deflate_roundtrip() {
let ctx = 1;
let original = b"Hello, World! This is a test of deflate compression.";
let mut compressed_len = fz_deflate_bound(ctx, original.len());
let mut compressed = vec![0u8; compressed_len];
fz_deflate(
ctx,
compressed.as_mut_ptr(),
&mut compressed_len,
original.as_ptr(),
original.len(),
DeflateLevel::Default as i32,
);
assert!(compressed_len > 0);
assert!(compressed_len < original.len() + 20);
let mut decompressed_len = original.len() * 2;
let mut decompressed = vec![0u8; decompressed_len];
let result = fz_inflate(
ctx,
decompressed.as_mut_ptr(),
&mut decompressed_len,
compressed.as_ptr(),
compressed_len,
);
assert_eq!(result, 0);
assert_eq!(decompressed_len, original.len());
assert_eq!(&decompressed[..decompressed_len], original);
}
#[test]
fn test_brotli_roundtrip() {
let ctx = 1;
let original = b"Hello, World! This is a test of brotli compression.";
let mut compressed_len = fz_brotli_bound(ctx, original.len());
let mut compressed = vec![0u8; compressed_len];
fz_compress_brotli(
ctx,
compressed.as_mut_ptr(),
&mut compressed_len,
original.as_ptr(),
original.len(),
BrotliLevel::Default as i32,
);
assert!(compressed_len > 0);
let mut decompressed_len = original.len() * 2;
let mut decompressed = vec![0u8; decompressed_len];
let result = fz_decompress_brotli(
ctx,
decompressed.as_mut_ptr(),
&mut decompressed_len,
compressed.as_ptr(),
compressed_len,
);
assert_eq!(result, 0);
assert_eq!(decompressed_len, original.len());
assert_eq!(&decompressed[..decompressed_len], original);
}
#[test]
fn test_deflate_to_buffer() {
let ctx = 1;
let original = b"Test data for buffer compression";
let buffer = fz_deflate_to_buffer(ctx, original.as_ptr(), original.len(), -1);
assert!(buffer > 0);
crate::ffi::buffer::fz_drop_buffer(ctx, buffer);
}
#[test]
fn test_brotli_to_buffer() {
let ctx = 1;
let original = b"Test data for brotli buffer compression";
let buffer = fz_brotli_to_buffer(ctx, original.as_ptr(), original.len(), 6);
assert!(buffer > 0);
crate::ffi::buffer::fz_drop_buffer(ctx, buffer);
}
#[test]
fn test_image_type_from_name() {
assert_eq!(ImageType::from_name("png"), ImageType::Png);
assert_eq!(ImageType::from_name("PNG"), ImageType::Png);
assert_eq!(ImageType::from_name("jpeg"), ImageType::Jpeg);
assert_eq!(ImageType::from_name("jpg"), ImageType::Jpeg);
assert_eq!(ImageType::from_name("unknown_format"), ImageType::Unknown);
}
#[test]
fn test_image_type_name() {
assert_eq!(ImageType::Png.name(), "png");
assert_eq!(ImageType::Jpeg.name(), "jpeg");
assert_eq!(ImageType::Unknown.name(), "unknown");
}
#[test]
fn test_recognize_image_format_png() {
let png_header = [0x89u8, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
assert_eq!(
fz_recognize_image_format(1, png_header.as_ptr()),
ImageType::Png as i32
);
}
#[test]
fn test_recognize_image_format_jpeg() {
let jpeg_header = [0xFFu8, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46];
assert_eq!(
fz_recognize_image_format(1, jpeg_header.as_ptr()),
ImageType::Jpeg as i32
);
}
#[test]
fn test_recognize_image_format_gif() {
let gif_header = b"GIF89a\x00\x00";
assert_eq!(
fz_recognize_image_format(1, gif_header.as_ptr()),
ImageType::Gif as i32
);
}
#[test]
fn test_recognize_image_format_unknown() {
let unknown = [0x00u8; 8];
assert_eq!(
fz_recognize_image_format(1, unknown.as_ptr()),
ImageType::Unknown as i32
);
}
#[test]
fn test_compressed_buffer_lifecycle() {
let ctx = 1;
let cbuf = fz_new_compressed_buffer(ctx);
assert!(cbuf > 0);
fz_compressed_buffer_set_type(ctx, cbuf, ImageType::Flate as i32);
assert_eq!(
fz_compressed_buffer_get_type(ctx, cbuf),
ImageType::Flate as i32
);
let cbuf2 = fz_keep_compressed_buffer(ctx, cbuf);
assert_eq!(cbuf, cbuf2);
fz_drop_compressed_buffer(ctx, cbuf);
let type_after_first_drop = fz_compressed_buffer_get_type(ctx, cbuf);
assert_eq!(type_after_first_drop, ImageType::Flate as i32);
fz_drop_compressed_buffer(ctx, cbuf);
let type_after_final_drop = fz_compressed_buffer_get_type(ctx, cbuf);
assert_eq!(type_after_final_drop, ImageType::Unknown as i32);
}
#[test]
fn test_ccitt_g3_basic() {
let ctx = 1;
let data = [0x00u8];
let buffer = fz_compress_ccitt_fax_g3(ctx, data.as_ptr(), 8, 1, 1);
assert!(buffer > 0);
crate::ffi::buffer::fz_drop_buffer(ctx, buffer);
}
#[test]
fn test_ccitt_g4_basic() {
let ctx = 1;
let data = [0x00u8];
let buffer = fz_compress_ccitt_fax_g4(ctx, data.as_ptr(), 8, 1, 1);
assert!(buffer > 0);
crate::ffi::buffer::fz_drop_buffer(ctx, buffer);
}
#[test]
fn test_deflate_levels() {
let ctx = 1;
let data = b"Test compression with different levels";
for level in [0, 1, 9, -1] {
let mut len = fz_deflate_bound(ctx, data.len());
let mut compressed = vec![0u8; len];
fz_deflate(
ctx,
compressed.as_mut_ptr(),
&mut len,
data.as_ptr(),
data.len(),
level,
);
assert!(len > 0);
}
}
#[test]
fn test_new_deflated_data() {
let ctx = 1;
let data = b"Data to compress into new allocation";
let mut len = 0usize;
let ptr = fz_new_deflated_data(ctx, &mut len, data.as_ptr(), data.len(), -1);
assert!(!ptr.is_null());
assert!(len > 0);
unsafe {
let _ = Vec::from_raw_parts(ptr, len, len);
}
}
#[test]
fn test_new_brotli_data() {
let ctx = 1;
let data = b"Data to compress with brotli";
let mut len = 0usize;
let ptr = fz_new_brotli_data(ctx, &mut len, data.as_ptr(), data.len(), 6);
assert!(!ptr.is_null());
assert!(len > 0);
unsafe {
let _ = Vec::from_raw_parts(ptr, len, len);
}
}
#[test]
fn test_lookup_image_type() {
let name = c"png";
assert_eq!(fz_lookup_image_type(name.as_ptr()), ImageType::Png as i32);
let name = c"jpeg";
assert_eq!(fz_lookup_image_type(name.as_ptr()), ImageType::Jpeg as i32);
}
#[test]
fn test_image_type_name_ffi() {
let name = fz_image_type_name(ImageType::Png as i32);
assert!(!name.is_null());
let name = fz_image_type_name(ImageType::Unknown as i32);
assert!(!name.is_null());
}
}