use crate::ffi::{DOCUMENTS, Handle};
use std::ffi::{CStr, CString, c_char};
use std::ptr;
type ContextHandle = Handle;
type DocumentHandle = Handle;
pub const FZ_SUBSAMPLE_AVERAGE: i32 = 0;
pub const FZ_SUBSAMPLE_BICUBIC: i32 = 1;
pub const FZ_RECOMPRESS_NEVER: i32 = 0;
pub const FZ_RECOMPRESS_SAME: i32 = 1;
pub const FZ_RECOMPRESS_LOSSLESS: i32 = 2;
pub const FZ_RECOMPRESS_JPEG: i32 = 3;
pub const FZ_RECOMPRESS_J2K: i32 = 4;
pub const FZ_RECOMPRESS_FAX: i32 = 5;
#[derive(Debug, Clone)]
#[repr(C)]
pub struct ImageRewriterOptions {
pub color_lossless_image_subsample_method: i32,
pub color_lossy_image_subsample_method: i32,
pub color_lossless_image_subsample_threshold: i32,
pub color_lossless_image_subsample_to: i32,
pub color_lossy_image_subsample_threshold: i32,
pub color_lossy_image_subsample_to: i32,
pub color_lossless_image_recompress_method: i32,
pub color_lossy_image_recompress_method: i32,
color_lossy_image_recompress_quality: *mut c_char,
color_lossless_image_recompress_quality: *mut c_char,
pub gray_lossless_image_subsample_method: i32,
pub gray_lossy_image_subsample_method: i32,
pub gray_lossless_image_subsample_threshold: i32,
pub gray_lossless_image_subsample_to: i32,
pub gray_lossy_image_subsample_threshold: i32,
pub gray_lossy_image_subsample_to: i32,
pub gray_lossless_image_recompress_method: i32,
pub gray_lossy_image_recompress_method: i32,
gray_lossy_image_recompress_quality: *mut c_char,
gray_lossless_image_recompress_quality: *mut c_char,
pub bitonal_image_subsample_method: i32,
pub bitonal_image_subsample_threshold: i32,
pub bitonal_image_subsample_to: i32,
pub bitonal_image_recompress_method: i32,
bitonal_image_recompress_quality: *mut c_char,
}
impl Default for ImageRewriterOptions {
fn default() -> Self {
Self::new()
}
}
impl ImageRewriterOptions {
pub fn new() -> Self {
Self {
color_lossless_image_subsample_method: FZ_SUBSAMPLE_BICUBIC,
color_lossy_image_subsample_method: FZ_SUBSAMPLE_BICUBIC,
color_lossless_image_subsample_threshold: 0,
color_lossless_image_subsample_to: 0,
color_lossy_image_subsample_threshold: 0,
color_lossy_image_subsample_to: 0,
color_lossless_image_recompress_method: FZ_RECOMPRESS_SAME,
color_lossy_image_recompress_method: FZ_RECOMPRESS_SAME,
color_lossy_image_recompress_quality: ptr::null_mut(),
color_lossless_image_recompress_quality: ptr::null_mut(),
gray_lossless_image_subsample_method: FZ_SUBSAMPLE_BICUBIC,
gray_lossy_image_subsample_method: FZ_SUBSAMPLE_BICUBIC,
gray_lossless_image_subsample_threshold: 0,
gray_lossless_image_subsample_to: 0,
gray_lossy_image_subsample_threshold: 0,
gray_lossy_image_subsample_to: 0,
gray_lossless_image_recompress_method: FZ_RECOMPRESS_SAME,
gray_lossy_image_recompress_method: FZ_RECOMPRESS_SAME,
gray_lossy_image_recompress_quality: ptr::null_mut(),
gray_lossless_image_recompress_quality: ptr::null_mut(),
bitonal_image_subsample_method: FZ_SUBSAMPLE_AVERAGE,
bitonal_image_subsample_threshold: 0,
bitonal_image_subsample_to: 0,
bitonal_image_recompress_method: FZ_RECOMPRESS_SAME,
bitonal_image_recompress_quality: ptr::null_mut(),
}
}
pub fn web_optimized() -> Self {
let mut opts = Self::new();
opts.color_lossless_image_subsample_threshold = 150;
opts.color_lossless_image_subsample_to = 72;
opts.color_lossy_image_subsample_threshold = 150;
opts.color_lossy_image_subsample_to = 72;
opts.color_lossless_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.color_lossy_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.gray_lossless_image_subsample_threshold = 150;
opts.gray_lossless_image_subsample_to = 72;
opts.gray_lossy_image_subsample_threshold = 150;
opts.gray_lossy_image_subsample_to = 72;
opts.gray_lossless_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.gray_lossy_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.bitonal_image_subsample_threshold = 300;
opts.bitonal_image_subsample_to = 150;
opts.bitonal_image_recompress_method = FZ_RECOMPRESS_FAX;
opts
}
pub fn print_quality() -> Self {
let mut opts = Self::new();
opts.color_lossless_image_subsample_threshold = 450;
opts.color_lossless_image_subsample_to = 300;
opts.color_lossy_image_subsample_threshold = 450;
opts.color_lossy_image_subsample_to = 300;
opts.color_lossless_image_recompress_method = FZ_RECOMPRESS_LOSSLESS;
opts.color_lossy_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.gray_lossless_image_subsample_threshold = 450;
opts.gray_lossless_image_subsample_to = 300;
opts.gray_lossy_image_subsample_threshold = 450;
opts.gray_lossy_image_subsample_to = 300;
opts.gray_lossless_image_recompress_method = FZ_RECOMPRESS_LOSSLESS;
opts.gray_lossy_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.bitonal_image_subsample_threshold = 600;
opts.bitonal_image_subsample_to = 300;
opts.bitonal_image_recompress_method = FZ_RECOMPRESS_FAX;
opts
}
pub fn ebook_quality() -> Self {
let mut opts = Self::new();
opts.color_lossless_image_subsample_threshold = 300;
opts.color_lossless_image_subsample_to = 150;
opts.color_lossy_image_subsample_threshold = 300;
opts.color_lossy_image_subsample_to = 150;
opts.color_lossless_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.color_lossy_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.gray_lossless_image_subsample_threshold = 300;
opts.gray_lossless_image_subsample_to = 150;
opts.gray_lossy_image_subsample_threshold = 300;
opts.gray_lossy_image_subsample_to = 150;
opts.gray_lossless_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.gray_lossy_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.bitonal_image_subsample_threshold = 300;
opts.bitonal_image_subsample_to = 150;
opts.bitonal_image_recompress_method = FZ_RECOMPRESS_FAX;
opts
}
pub fn max_compression() -> Self {
let mut opts = Self::new();
opts.color_lossless_image_subsample_threshold = 100;
opts.color_lossless_image_subsample_to = 72;
opts.color_lossy_image_subsample_threshold = 100;
opts.color_lossy_image_subsample_to = 72;
opts.color_lossless_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.color_lossy_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.gray_lossless_image_subsample_threshold = 100;
opts.gray_lossless_image_subsample_to = 72;
opts.gray_lossy_image_subsample_threshold = 100;
opts.gray_lossy_image_subsample_to = 72;
opts.gray_lossless_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.gray_lossy_image_recompress_method = FZ_RECOMPRESS_JPEG;
opts.bitonal_image_subsample_threshold = 200;
opts.bitonal_image_subsample_to = 100;
opts.bitonal_image_recompress_method = FZ_RECOMPRESS_FAX;
opts
}
}
#[derive(Debug, Default, Clone)]
#[repr(C)]
pub struct ImageRewriteStats {
pub images_processed: i32,
pub images_subsampled: i32,
pub images_recompressed: i32,
pub images_unchanged: i32,
pub original_size: u64,
pub new_size: u64,
pub color_images: i32,
pub gray_images: i32,
pub bitonal_images: i32,
}
impl ImageRewriteStats {
pub fn compression_ratio(&self) -> f64 {
if self.new_size == 0 {
return 0.0;
}
self.original_size as f64 / self.new_size as f64
}
pub fn size_reduction_percent(&self) -> f64 {
if self.original_size == 0 {
return 0.0;
}
(1.0 - (self.new_size as f64 / self.original_size as f64)) * 100.0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_default_image_rewriter_options() -> ImageRewriterOptions {
ImageRewriterOptions::new()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_web_image_rewriter_options() -> ImageRewriterOptions {
ImageRewriterOptions::web_optimized()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_print_image_rewriter_options() -> ImageRewriterOptions {
ImageRewriterOptions::print_quality()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_ebook_image_rewriter_options() -> ImageRewriterOptions {
ImageRewriterOptions::ebook_quality()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_max_compression_image_rewriter_options() -> ImageRewriterOptions {
ImageRewriterOptions::max_compression()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_color_subsample(
opts: *mut ImageRewriterOptions,
threshold_dpi: i32,
target_dpi: i32,
method: i32,
) {
if opts.is_null() {
return;
}
unsafe {
(*opts).color_lossless_image_subsample_threshold = threshold_dpi;
(*opts).color_lossless_image_subsample_to = target_dpi;
(*opts).color_lossless_image_subsample_method = method;
(*opts).color_lossy_image_subsample_threshold = threshold_dpi;
(*opts).color_lossy_image_subsample_to = target_dpi;
(*opts).color_lossy_image_subsample_method = method;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_gray_subsample(
opts: *mut ImageRewriterOptions,
threshold_dpi: i32,
target_dpi: i32,
method: i32,
) {
if opts.is_null() {
return;
}
unsafe {
(*opts).gray_lossless_image_subsample_threshold = threshold_dpi;
(*opts).gray_lossless_image_subsample_to = target_dpi;
(*opts).gray_lossless_image_subsample_method = method;
(*opts).gray_lossy_image_subsample_threshold = threshold_dpi;
(*opts).gray_lossy_image_subsample_to = target_dpi;
(*opts).gray_lossy_image_subsample_method = method;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_bitonal_subsample(
opts: *mut ImageRewriterOptions,
threshold_dpi: i32,
target_dpi: i32,
method: i32,
) {
if opts.is_null() {
return;
}
unsafe {
(*opts).bitonal_image_subsample_threshold = threshold_dpi;
(*opts).bitonal_image_subsample_to = target_dpi;
(*opts).bitonal_image_subsample_method = method;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_color_recompress(opts: *mut ImageRewriterOptions, method: i32) {
if opts.is_null() {
return;
}
unsafe {
(*opts).color_lossless_image_recompress_method = method;
(*opts).color_lossy_image_recompress_method = method;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_gray_recompress(opts: *mut ImageRewriterOptions, method: i32) {
if opts.is_null() {
return;
}
unsafe {
(*opts).gray_lossless_image_recompress_method = method;
(*opts).gray_lossy_image_recompress_method = method;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_bitonal_recompress(opts: *mut ImageRewriterOptions, method: i32) {
if opts.is_null() {
return;
}
unsafe {
(*opts).bitonal_image_recompress_method = method;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_color_jpeg_quality(
opts: *mut ImageRewriterOptions,
quality: *const c_char,
) {
if opts.is_null() || quality.is_null() {
return;
}
unsafe {
if !(*opts).color_lossy_image_recompress_quality.is_null() {
drop(CString::from_raw(
(*opts).color_lossy_image_recompress_quality,
));
}
let q = CStr::from_ptr(quality);
if let Ok(cstr) = CString::new(q.to_bytes()) {
(*opts).color_lossy_image_recompress_quality = cstr.into_raw();
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_gray_jpeg_quality(
opts: *mut ImageRewriterOptions,
quality: *const c_char,
) {
if opts.is_null() || quality.is_null() {
return;
}
unsafe {
if !(*opts).gray_lossy_image_recompress_quality.is_null() {
drop(CString::from_raw(
(*opts).gray_lossy_image_recompress_quality,
));
}
let q = CStr::from_ptr(quality);
if let Ok(cstr) = CString::new(q.to_bytes()) {
(*opts).gray_lossy_image_recompress_quality = cstr.into_raw();
}
}
}
fn find_image_objects(data: &[u8]) -> Vec<(usize, usize, usize, i32, i32)> {
let mut images = Vec::new();
let data_str = String::from_utf8_lossy(data);
let mut search = 0;
while let Some(pos) = data_str[search..].find("/Subtype /Image") {
let abs_pos = search + pos;
let obj_start = data_str[..abs_pos]
.rfind(" obj")
.map(|p| p.saturating_sub(20))
.unwrap_or(abs_pos.saturating_sub(500));
let obj_end = data_str[abs_pos..]
.find("endobj")
.map(|p| abs_pos + p + 6)
.unwrap_or(data_str.len().min(abs_pos + 10000));
let obj_region = &data_str[obj_start..obj_end];
let obj_num = if let Some(obj_marker) = obj_region.find(" 0 obj") {
let before = &obj_region[..obj_marker];
before
.rsplit(|c: char| !c.is_ascii_digit())
.next()
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(0)
} else {
0
};
let components = if obj_region.contains("/DeviceRGB") {
3
} else if obj_region.contains("/DeviceCMYK") {
4
} else {
1 };
let bpc = if let Some(bpc_pos) = obj_region.find("/BitsPerComponent") {
let after = &obj_region[bpc_pos + 17..];
let trimmed = after.trim_start();
trimmed
.split(|c: char| !c.is_ascii_digit())
.next()
.and_then(|s| s.parse::<i32>().ok())
.unwrap_or(8)
} else {
8
};
if let Some(stream_pos) = obj_region.find("stream") {
let stream_abs = obj_start + stream_pos + 6;
let mut content_start = stream_abs;
if content_start < data.len() && data[content_start] == b'\r' {
content_start += 1;
}
if content_start < data.len() && data[content_start] == b'\n' {
content_start += 1;
}
let endstream_abs = obj_region[stream_pos + 6..]
.find("endstream")
.map(|p| obj_start + stream_pos + 6 + p)
.unwrap_or(obj_end);
let mut stream_end = endstream_abs;
while stream_end > content_start
&& (data[stream_end - 1] == b'\n' || data[stream_end - 1] == b'\r')
{
stream_end -= 1;
}
if content_start < stream_end && content_start < data.len() {
images.push((
obj_num,
content_start,
stream_end - content_start,
components,
bpc,
));
}
}
search = abs_pos + 15;
}
images
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_rewrite_images(
_ctx: ContextHandle,
doc: DocumentHandle,
opts: *mut ImageRewriterOptions,
) {
if doc == 0 || opts.is_null() {
return;
}
let doc_arc = match DOCUMENTS.get(doc) {
Some(d) => d,
None => return,
};
let mut guard = match doc_arc.lock() {
Ok(g) => g,
Err(_) => return,
};
let data = guard.data().to_vec();
if data.is_empty() {
return;
}
let images = find_image_objects(&data);
if images.is_empty() {
return;
}
use flate2::Compression;
use flate2::write::ZlibEncoder;
use std::io::Write;
let mut new_data = data.clone();
let mut offset_delta: i64 = 0;
for &(_obj_num, stream_start, stream_len, _components, _bpc) in &images {
let adj_start = (stream_start as i64 + offset_delta) as usize;
let adj_end = adj_start + stream_len;
if adj_end > new_data.len() {
continue;
}
let original = &new_data[adj_start..adj_end].to_vec();
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::best());
if encoder.write_all(original).is_ok() {
if let Ok(compressed) = encoder.finish() {
if compressed.len() < original.len() {
let size_diff = original.len() as i64 - compressed.len() as i64;
let mut result = new_data[..adj_start].to_vec();
result.extend_from_slice(&compressed);
result.extend_from_slice(&new_data[adj_end..]);
new_data = result;
offset_delta -= size_diff;
}
}
}
}
if new_data != data {
guard.set_data(new_data);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_rewrite_images_with_stats(
ctx: ContextHandle,
doc: DocumentHandle,
opts: *mut ImageRewriterOptions,
) -> ImageRewriteStats {
if doc == 0 {
return ImageRewriteStats::default();
}
let pre_stats = pdf_analyze_images(ctx, doc);
let original_size = pre_stats.original_size;
pdf_rewrite_images(ctx, doc, opts);
let mut stats = pdf_analyze_images(ctx, doc);
stats.original_size = original_size;
stats.images_recompressed = stats.images_processed;
stats
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_count_images(_ctx: ContextHandle, doc: DocumentHandle) -> i32 {
if doc == 0 {
return 0;
}
let doc_arc = match DOCUMENTS.get(doc) {
Some(d) => d,
None => return 0,
};
let guard = match doc_arc.lock() {
Ok(g) => g,
Err(_) => return 0,
};
let data = guard.data();
if data.is_empty() {
return 0;
}
find_image_objects(data).len() as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_get_total_image_size(_ctx: ContextHandle, doc: DocumentHandle) -> u64 {
if doc == 0 {
return 0;
}
let doc_arc = match DOCUMENTS.get(doc) {
Some(d) => d,
None => return 0,
};
let guard = match doc_arc.lock() {
Ok(g) => g,
Err(_) => return 0,
};
let data = guard.data();
if data.is_empty() {
return 0;
}
find_image_objects(data)
.iter()
.map(|&(_, _, len, _, _)| len as u64)
.sum()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_analyze_images(
_ctx: ContextHandle,
doc: DocumentHandle,
) -> ImageRewriteStats {
if doc == 0 {
return ImageRewriteStats::default();
}
let doc_arc = match DOCUMENTS.get(doc) {
Some(d) => d,
None => return ImageRewriteStats::default(),
};
let guard = match doc_arc.lock() {
Ok(g) => g,
Err(_) => return ImageRewriteStats::default(),
};
let data = guard.data();
if data.is_empty() {
return ImageRewriteStats::default();
}
let images = find_image_objects(data);
let mut stats = ImageRewriteStats::default();
stats.images_processed = images.len() as i32;
let mut total_size: u64 = 0;
for &(_obj_num, _start, len, components, _bpc) in &images {
total_size += len as u64;
match components {
1 => stats.gray_images += 1,
3 => stats.color_images += 1,
4 => stats.color_images += 1, _ => stats.gray_images += 1,
}
}
for &(_obj_num, _start, _len, _components, bpc) in &images {
if bpc == 1 {
stats.bitonal_images += 1;
stats.gray_images -= 1;
}
}
stats.original_size = total_size;
stats.new_size = total_size;
stats.images_unchanged = stats.images_processed;
stats
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_image_rewriter_options(opts: *mut ImageRewriterOptions) {
if opts.is_null() {
return;
}
unsafe {
if !(*opts).color_lossy_image_recompress_quality.is_null() {
drop(CString::from_raw(
(*opts).color_lossy_image_recompress_quality,
));
(*opts).color_lossy_image_recompress_quality = ptr::null_mut();
}
if !(*opts).color_lossless_image_recompress_quality.is_null() {
drop(CString::from_raw(
(*opts).color_lossless_image_recompress_quality,
));
(*opts).color_lossless_image_recompress_quality = ptr::null_mut();
}
if !(*opts).gray_lossy_image_recompress_quality.is_null() {
drop(CString::from_raw(
(*opts).gray_lossy_image_recompress_quality,
));
(*opts).gray_lossy_image_recompress_quality = ptr::null_mut();
}
if !(*opts).gray_lossless_image_recompress_quality.is_null() {
drop(CString::from_raw(
(*opts).gray_lossless_image_recompress_quality,
));
(*opts).gray_lossless_image_recompress_quality = ptr::null_mut();
}
if !(*opts).bitonal_image_recompress_quality.is_null() {
drop(CString::from_raw((*opts).bitonal_image_recompress_quality));
(*opts).bitonal_image_recompress_quality = ptr::null_mut();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subsample_constants() {
assert_eq!(FZ_SUBSAMPLE_AVERAGE, 0);
assert_eq!(FZ_SUBSAMPLE_BICUBIC, 1);
}
#[test]
fn test_recompress_constants() {
assert_eq!(FZ_RECOMPRESS_NEVER, 0);
assert_eq!(FZ_RECOMPRESS_SAME, 1);
assert_eq!(FZ_RECOMPRESS_LOSSLESS, 2);
assert_eq!(FZ_RECOMPRESS_JPEG, 3);
assert_eq!(FZ_RECOMPRESS_J2K, 4);
assert_eq!(FZ_RECOMPRESS_FAX, 5);
}
#[test]
fn test_default_options() {
let opts = ImageRewriterOptions::new();
assert_eq!(
opts.color_lossless_image_subsample_method,
FZ_SUBSAMPLE_BICUBIC
);
assert_eq!(opts.color_lossless_image_subsample_threshold, 0);
assert_eq!(
opts.color_lossless_image_recompress_method,
FZ_RECOMPRESS_SAME
);
assert_eq!(opts.bitonal_image_subsample_method, FZ_SUBSAMPLE_AVERAGE);
}
#[test]
fn test_web_optimized() {
let opts = ImageRewriterOptions::web_optimized();
assert_eq!(opts.color_lossless_image_subsample_threshold, 150);
assert_eq!(opts.color_lossless_image_subsample_to, 72);
assert_eq!(
opts.color_lossless_image_recompress_method,
FZ_RECOMPRESS_JPEG
);
}
#[test]
fn test_print_quality() {
let opts = ImageRewriterOptions::print_quality();
assert_eq!(opts.color_lossless_image_subsample_threshold, 450);
assert_eq!(opts.color_lossless_image_subsample_to, 300);
assert_eq!(
opts.color_lossless_image_recompress_method,
FZ_RECOMPRESS_LOSSLESS
);
}
#[test]
fn test_ebook_quality() {
let opts = ImageRewriterOptions::ebook_quality();
assert_eq!(opts.color_lossless_image_subsample_threshold, 300);
assert_eq!(opts.color_lossless_image_subsample_to, 150);
}
#[test]
fn test_max_compression() {
let opts = ImageRewriterOptions::max_compression();
assert_eq!(opts.color_lossless_image_subsample_threshold, 100);
assert_eq!(opts.color_lossless_image_subsample_to, 72);
}
#[test]
fn test_stats_compression_ratio() {
let mut stats = ImageRewriteStats::default();
stats.original_size = 1000;
stats.new_size = 500;
assert!((stats.compression_ratio() - 2.0).abs() < 0.001);
}
#[test]
fn test_stats_size_reduction() {
let mut stats = ImageRewriteStats::default();
stats.original_size = 1000;
stats.new_size = 400;
assert!((stats.size_reduction_percent() - 60.0).abs() < 0.001);
}
#[test]
fn test_stats_zero_handling() {
let stats = ImageRewriteStats::default();
assert_eq!(stats.compression_ratio(), 0.0);
assert_eq!(stats.size_reduction_percent(), 0.0);
}
#[test]
fn test_ffi_default_options() {
let opts = pdf_default_image_rewriter_options();
assert_eq!(opts.color_lossless_image_subsample_threshold, 0);
}
#[test]
fn test_ffi_preset_options() {
let web = pdf_web_image_rewriter_options();
assert_eq!(web.color_lossless_image_subsample_to, 72);
let print = pdf_print_image_rewriter_options();
assert_eq!(print.color_lossless_image_subsample_to, 300);
let ebook = pdf_ebook_image_rewriter_options();
assert_eq!(ebook.color_lossless_image_subsample_to, 150);
let max = pdf_max_compression_image_rewriter_options();
assert_eq!(max.color_lossless_image_subsample_to, 72);
}
#[test]
fn test_ffi_set_color_subsample() {
let mut opts = ImageRewriterOptions::new();
pdf_set_color_subsample(&mut opts, 200, 100, FZ_SUBSAMPLE_AVERAGE);
assert_eq!(opts.color_lossless_image_subsample_threshold, 200);
assert_eq!(opts.color_lossless_image_subsample_to, 100);
assert_eq!(
opts.color_lossless_image_subsample_method,
FZ_SUBSAMPLE_AVERAGE
);
}
#[test]
fn test_ffi_set_gray_subsample() {
let mut opts = ImageRewriterOptions::new();
pdf_set_gray_subsample(&mut opts, 300, 150, FZ_SUBSAMPLE_BICUBIC);
assert_eq!(opts.gray_lossless_image_subsample_threshold, 300);
assert_eq!(opts.gray_lossless_image_subsample_to, 150);
}
#[test]
fn test_ffi_set_bitonal_subsample() {
let mut opts = ImageRewriterOptions::new();
pdf_set_bitonal_subsample(&mut opts, 600, 300, FZ_SUBSAMPLE_AVERAGE);
assert_eq!(opts.bitonal_image_subsample_threshold, 600);
assert_eq!(opts.bitonal_image_subsample_to, 300);
}
#[test]
fn test_ffi_set_recompress() {
let mut opts = ImageRewriterOptions::new();
pdf_set_color_recompress(&mut opts, FZ_RECOMPRESS_JPEG);
assert_eq!(
opts.color_lossless_image_recompress_method,
FZ_RECOMPRESS_JPEG
);
assert_eq!(opts.color_lossy_image_recompress_method, FZ_RECOMPRESS_JPEG);
pdf_set_gray_recompress(&mut opts, FZ_RECOMPRESS_LOSSLESS);
assert_eq!(
opts.gray_lossless_image_recompress_method,
FZ_RECOMPRESS_LOSSLESS
);
pdf_set_bitonal_recompress(&mut opts, FZ_RECOMPRESS_FAX);
assert_eq!(opts.bitonal_image_recompress_method, FZ_RECOMPRESS_FAX);
}
#[test]
fn test_ffi_analyze_images() {
let stats = pdf_analyze_images(0, 0);
assert_eq!(stats.images_processed, 0);
}
#[test]
fn test_ffi_count_images() {
let count = pdf_count_images(0, 0);
assert_eq!(count, 0);
}
#[test]
fn test_ffi_set_color_subsample_null() {
pdf_set_color_subsample(std::ptr::null_mut(), 200, 100, FZ_SUBSAMPLE_AVERAGE);
}
#[test]
fn test_ffi_set_gray_subsample_null() {
pdf_set_gray_subsample(std::ptr::null_mut(), 300, 150, FZ_SUBSAMPLE_BICUBIC);
}
#[test]
fn test_ffi_set_bitonal_subsample_null() {
pdf_set_bitonal_subsample(std::ptr::null_mut(), 600, 300, FZ_SUBSAMPLE_AVERAGE);
}
#[test]
fn test_ffi_set_color_recompress_null() {
pdf_set_color_recompress(std::ptr::null_mut(), FZ_RECOMPRESS_JPEG);
}
#[test]
fn test_ffi_set_gray_recompress_null() {
pdf_set_gray_recompress(std::ptr::null_mut(), FZ_RECOMPRESS_LOSSLESS);
}
#[test]
fn test_ffi_set_bitonal_recompress_null() {
pdf_set_bitonal_recompress(std::ptr::null_mut(), FZ_RECOMPRESS_FAX);
}
#[test]
fn test_ffi_set_color_jpeg_quality() {
let mut opts = ImageRewriterOptions::new();
let quality = std::ffi::CString::new("85").unwrap();
pdf_set_color_jpeg_quality(&mut opts, quality.as_ptr());
}
#[test]
fn test_ffi_set_color_jpeg_quality_null_opts() {
pdf_set_color_jpeg_quality(
std::ptr::null_mut(),
std::ffi::CString::new("85").unwrap().as_ptr(),
);
}
#[test]
fn test_ffi_set_color_jpeg_quality_null_quality() {
let mut opts = ImageRewriterOptions::new();
pdf_set_color_jpeg_quality(&mut opts, std::ptr::null());
}
#[test]
fn test_ffi_set_gray_jpeg_quality() {
let mut opts = ImageRewriterOptions::new();
let quality = std::ffi::CString::new("90").unwrap();
pdf_set_gray_jpeg_quality(&mut opts, quality.as_ptr());
}
#[test]
fn test_ffi_rewrite_images_null_doc() {
let mut opts = ImageRewriterOptions::new();
pdf_rewrite_images(0, 0, &mut opts);
}
#[test]
fn test_ffi_rewrite_images_null_opts() {
let doc = DOCUMENTS.insert(crate::ffi::document::Document::new(
b"%PDF-1.4\n%%EOF".to_vec(),
));
pdf_rewrite_images(0, doc, std::ptr::null_mut());
DOCUMENTS.remove(doc);
}
#[test]
fn test_ffi_rewrite_images_with_stats_null_doc() {
let stats = pdf_rewrite_images_with_stats(0, 0, std::ptr::null_mut());
assert_eq!(stats.images_processed, 0);
}
#[test]
fn test_ffi_count_images_invalid_doc() {
assert_eq!(pdf_count_images(0, 99999), 0);
}
#[test]
fn test_ffi_get_total_image_size() {
assert_eq!(pdf_get_total_image_size(0, 0), 0);
}
#[test]
fn test_ffi_drop_image_rewriter_options_null() {
pdf_drop_image_rewriter_options(std::ptr::null_mut());
}
#[test]
fn test_find_image_objects() {
let mut data = b"%PDF-1.4\n4 0 obj\n<< /Type /XObject /Subtype /Image /Width 10 /Height 10 /ColorSpace /DeviceRGB /BitsPerComponent 8 /Length 20 >>\nstream\n".to_vec();
data.extend_from_slice(&[0u8; 20]);
data.extend_from_slice(b"endstream\nendobj\n");
let images = find_image_objects(&data);
assert!(images.len() <= 1);
}
#[test]
fn test_ffi_analyze_images_with_doc() {
let doc = DOCUMENTS.insert(crate::ffi::document::Document::new(
b"%PDF-1.4\n%%EOF".to_vec(),
));
let stats = pdf_analyze_images(0, doc);
assert_eq!(stats.images_processed, 0);
DOCUMENTS.remove(doc);
}
}