use std::ffi::{CStr, CString, c_char};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr;
use super::buffer::{fz_buffer_data, fz_drop_buffer};
use super::colorspace::FZ_COLORSPACE_RGB;
use super::compat::{fz_new_pixmap_from_page, fz_new_stext_page_from_page};
use super::document::{
fz_bound_page, fz_count_pages, fz_drop_document, fz_drop_page, fz_load_page, fz_open_document,
};
use super::enhanced::print_production::{mp_quick_validate, mp_repair_pdf};
use super::pixmap::{
fz_drop_pixmap, fz_pixmap_height, fz_pixmap_samples, fz_pixmap_samples_size, fz_pixmap_width,
};
use super::stext::{fz_drop_stext_page, fz_stext_page_as_text};
use super::write_pixmap::fz_new_buffer_from_pixmap_as_png;
use super::{BUFFERS, DOCUMENTS, Handle};
#[repr(C)]
pub struct MpPdfInfo {
pub page_count: i32,
pub is_encrypted: i32,
pub needs_password: i32,
pub version: *mut c_char,
pub title: *mut c_char,
pub author: *mut c_char,
pub subject: *mut c_char,
pub creator: *mut c_char,
}
#[repr(C)]
pub struct MpPageDimensions {
pub width: f32,
pub height: f32,
}
#[repr(C)]
pub struct MpRenderedPage {
pub data: *mut u8,
pub data_len: usize,
pub width: i32,
pub height: i32,
}
#[repr(C)]
pub struct MpExtractedText {
pub text: *mut c_char,
pub text_len: usize,
pub pages_processed: i32,
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_get_pdf_info(pdf_path: *const c_char, info_out: *mut MpPdfInfo) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() {
return -1;
}
if info_out.is_null() {
return -2;
}
let doc_handle = fz_open_document(0, pdf_path);
if doc_handle == 0 {
return -3;
}
let page_count = fz_count_pages(0, doc_handle);
let (version, title, author, subject, creator, is_encrypted, needs_password) =
if let Some(doc) = DOCUMENTS.get(doc_handle) {
if let Ok(guard) = doc.lock() {
let data = guard.data();
let version = extract_pdf_version(data);
let title = extract_metadata(data, b"/Title");
let author = extract_metadata(data, b"/Author");
let subject = extract_metadata(data, b"/Subject");
let creator = extract_metadata(data, b"/Creator");
let is_encrypted = if data
.windows(8)
.any(|w| w == b"/Encrypt" || w.starts_with(b"/Encrypt "))
{
1
} else {
0
};
(version, title, author, subject, creator, is_encrypted, 0)
} else {
(None, None, None, None, None, 0, 0)
}
} else {
(None, None, None, None, None, 0, 0)
};
fz_drop_document(0, doc_handle);
unsafe {
(*info_out).page_count = page_count;
(*info_out).is_encrypted = is_encrypted;
(*info_out).needs_password = needs_password;
(*info_out).version = string_to_c(version);
(*info_out).title = string_to_c(title);
(*info_out).author = string_to_c(author);
(*info_out).subject = string_to_c(subject);
(*info_out).creator = string_to_c(creator);
}
0
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_get_pdf_info: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_get_pdf_info: panic: {}", s)
} else {
"mp_get_pdf_info: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_free_pdf_info(info: *mut MpPdfInfo) {
if info.is_null() {
return;
}
unsafe {
if !(*info).version.is_null() {
let _ = CString::from_raw((*info).version);
(*info).version = ptr::null_mut();
}
if !(*info).title.is_null() {
let _ = CString::from_raw((*info).title);
(*info).title = ptr::null_mut();
}
if !(*info).author.is_null() {
let _ = CString::from_raw((*info).author);
(*info).author = ptr::null_mut();
}
if !(*info).subject.is_null() {
let _ = CString::from_raw((*info).subject);
(*info).subject = ptr::null_mut();
}
if !(*info).creator.is_null() {
let _ = CString::from_raw((*info).creator);
(*info).creator = ptr::null_mut();
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_get_page_count(pdf_path: *const c_char) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() {
return -1;
}
let doc_handle = fz_open_document(0, pdf_path);
if doc_handle == 0 {
return -3;
}
let count = fz_count_pages(0, doc_handle);
fz_drop_document(0, doc_handle);
count
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_get_page_count: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_get_page_count: panic: {}", s)
} else {
"mp_get_page_count: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_get_page_dimensions(
pdf_path: *const c_char,
page_num: i32,
dims_out: *mut MpPageDimensions,
) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() {
return -1;
}
if dims_out.is_null() {
return -2;
}
let doc_handle = fz_open_document(0, pdf_path);
if doc_handle == 0 {
return -3;
}
let page_count = fz_count_pages(0, doc_handle);
if page_num < 0 || page_num >= page_count {
fz_drop_document(0, doc_handle);
return -4;
}
let page_handle = fz_load_page(0, doc_handle, page_num);
if page_handle == 0 {
fz_drop_document(0, doc_handle);
return -4;
}
let bounds = fz_bound_page(0, page_handle);
unsafe {
(*dims_out).width = bounds.x1 - bounds.x0;
(*dims_out).height = bounds.y1 - bounds.y0;
}
fz_drop_page(0, page_handle);
fz_drop_document(0, doc_handle);
0
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_get_page_dimensions: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_get_page_dimensions: panic: {}", s)
} else {
"mp_get_page_dimensions: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_extract_text(
pdf_path: *const c_char,
result_out: *mut MpExtractedText,
) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() {
return -1;
}
if result_out.is_null() {
return -2;
}
let doc_handle = fz_open_document(0, pdf_path);
if doc_handle == 0 {
return -3;
}
let page_count = fz_count_pages(0, doc_handle);
let mut all_text = String::new();
let mut pages_processed = 0;
for page_num in 0..page_count {
let page_handle = fz_load_page(0, doc_handle, page_num);
if page_handle == 0 {
continue;
}
let stext_handle = fz_new_stext_page_from_page(0, page_handle, ptr::null());
if stext_handle != 0 {
let text_ptr = fz_stext_page_as_text(0, stext_handle);
if !text_ptr.is_null() {
if let Ok(text) = unsafe { CStr::from_ptr(text_ptr) }.to_str() {
if !all_text.is_empty() && !text.is_empty() {
all_text.push('\n');
}
all_text.push_str(text);
}
}
fz_drop_stext_page(0, stext_handle);
}
fz_drop_page(0, page_handle);
pages_processed += 1;
}
fz_drop_document(0, doc_handle);
let text_len = all_text.len();
let text_ptr = match CString::new(all_text) {
Ok(cs) => cs.into_raw(),
Err(_) => ptr::null_mut(),
};
unsafe {
(*result_out).text = text_ptr;
(*result_out).text_len = text_len;
(*result_out).pages_processed = pages_processed;
}
0
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_extract_text: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_extract_text: panic: {}", s)
} else {
"mp_extract_text: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_extract_page_text(pdf_path: *const c_char, page_num: i32) -> *mut c_char {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() {
return ptr::null_mut();
}
let doc_handle = fz_open_document(0, pdf_path);
if doc_handle == 0 {
return ptr::null_mut();
}
let page_count = fz_count_pages(0, doc_handle);
if page_num < 0 || page_num >= page_count {
fz_drop_document(0, doc_handle);
return ptr::null_mut();
}
let page_handle = fz_load_page(0, doc_handle, page_num);
if page_handle == 0 {
fz_drop_document(0, doc_handle);
return ptr::null_mut();
}
let mut result: *mut c_char = ptr::null_mut();
let stext_handle = fz_new_stext_page_from_page(0, page_handle, ptr::null());
if stext_handle != 0 {
let text_ptr = fz_stext_page_as_text(0, stext_handle);
if !text_ptr.is_null() {
if let Ok(text) = unsafe { CStr::from_ptr(text_ptr) }.to_str() {
if let Ok(cs) = CString::new(text) {
result = cs.into_raw();
}
}
}
fz_drop_stext_page(0, stext_handle);
}
fz_drop_page(0, page_handle);
fz_drop_document(0, doc_handle);
result
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_extract_page_text: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_extract_page_text: panic: {}", s)
} else {
"mp_extract_page_text: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_render_page_to_png(
pdf_path: *const c_char,
page_num: i32,
scale: f32,
result_out: *mut MpRenderedPage,
) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() {
return -1;
}
if result_out.is_null() {
return -2;
}
let doc_handle = fz_open_document(0, pdf_path);
if doc_handle == 0 {
return -3;
}
let page_count = fz_count_pages(0, doc_handle);
if page_num < 0 || page_num >= page_count {
fz_drop_document(0, doc_handle);
return -4;
}
let page_handle = fz_load_page(0, doc_handle, page_num);
if page_handle == 0 {
fz_drop_document(0, doc_handle);
return -4;
}
let ctm = super::geometry::fz_matrix {
a: scale,
b: 0.0,
c: 0.0,
d: scale,
e: 0.0,
f: 0.0,
};
let pix_handle = fz_new_pixmap_from_page(0, page_handle, ctm, FZ_COLORSPACE_RGB, 0);
if pix_handle == 0 {
fz_drop_page(0, page_handle);
fz_drop_document(0, doc_handle);
return -5;
}
let width = fz_pixmap_width(0, pix_handle);
let height = fz_pixmap_height(0, pix_handle);
let buf_handle = fz_new_buffer_from_pixmap_as_png(0, pix_handle);
fz_drop_pixmap(0, pix_handle);
fz_drop_page(0, page_handle);
fz_drop_document(0, doc_handle);
if buf_handle == 0 {
return -5;
}
let mut len: usize = 0;
let data_ptr = fz_buffer_data(0, buf_handle, &mut len);
if data_ptr.is_null() || len == 0 {
fz_drop_buffer(0, buf_handle);
return -5;
}
let mut data = vec![0u8; len];
unsafe {
ptr::copy_nonoverlapping(data_ptr, data.as_mut_ptr(), len);
}
fz_drop_buffer(0, buf_handle);
let data_ptr = Box::into_raw(data.into_boxed_slice()) as *mut u8;
unsafe {
(*result_out).data = data_ptr;
(*result_out).data_len = len;
(*result_out).width = width;
(*result_out).height = height;
}
0
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_render_page_to_png: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_render_page_to_png: panic: {}", s)
} else {
"mp_render_page_to_png: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_render_page_to_rgb(
pdf_path: *const c_char,
page_num: i32,
scale: f32,
result_out: *mut MpRenderedPage,
) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() {
return -1;
}
if result_out.is_null() {
return -2;
}
let doc_handle = fz_open_document(0, pdf_path);
if doc_handle == 0 {
return -3;
}
let page_count = fz_count_pages(0, doc_handle);
if page_num < 0 || page_num >= page_count {
fz_drop_document(0, doc_handle);
return -4;
}
let page_handle = fz_load_page(0, doc_handle, page_num);
if page_handle == 0 {
fz_drop_document(0, doc_handle);
return -4;
}
let ctm = super::geometry::fz_matrix {
a: scale,
b: 0.0,
c: 0.0,
d: scale,
e: 0.0,
f: 0.0,
};
let pix_handle = fz_new_pixmap_from_page(0, page_handle, ctm, FZ_COLORSPACE_RGB, 0);
if pix_handle == 0 {
fz_drop_page(0, page_handle);
fz_drop_document(0, doc_handle);
return -5;
}
let width = fz_pixmap_width(0, pix_handle);
let height = fz_pixmap_height(0, pix_handle);
let samples_ptr = fz_pixmap_samples(0, pix_handle);
let samples_size = fz_pixmap_samples_size(0, pix_handle);
let data_ptr = if !samples_ptr.is_null() && samples_size > 0 {
let mut data = vec![0u8; samples_size];
unsafe {
ptr::copy_nonoverlapping(samples_ptr, data.as_mut_ptr(), samples_size);
}
Box::into_raw(data.into_boxed_slice()) as *mut u8
} else {
ptr::null_mut()
};
fz_drop_pixmap(0, pix_handle);
fz_drop_page(0, page_handle);
fz_drop_document(0, doc_handle);
if data_ptr.is_null() {
return -5;
}
unsafe {
(*result_out).data = data_ptr;
(*result_out).data_len = samples_size;
(*result_out).width = width;
(*result_out).height = height;
}
0
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_render_page_to_rgb: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_render_page_to_rgb: panic: {}", s)
} else {
"mp_render_page_to_rgb: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_free_rendered_page(result: *mut MpRenderedPage) {
if result.is_null() {
return;
}
unsafe {
if !(*result).data.is_null() && (*result).data_len > 0 {
let slice_ptr = ptr::slice_from_raw_parts_mut((*result).data, (*result).data_len);
let _ = Box::from_raw(slice_ptr);
(*result).data = ptr::null_mut();
(*result).data_len = 0;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_merge_pdf_files(
input_paths: *const *const c_char,
input_count: i32,
output_path: *const c_char,
) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if input_paths.is_null() || output_path.is_null() || input_count <= 0 {
return -1;
}
super::enhanced::mp_merge_pdfs(0, input_paths, input_count, output_path)
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_merge_pdf_files: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_merge_pdf_files: panic: {}", s)
} else {
"mp_merge_pdf_files: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_split_pdf_to_pages(pdf_path: *const c_char, output_dir: *const c_char) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() || output_dir.is_null() {
return -1;
}
super::enhanced::mp_split_pdf(0, pdf_path, output_dir)
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_split_pdf_to_pages: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_split_pdf_to_pages: panic: {}", s)
} else {
"mp_split_pdf_to_pages: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_copy_pages(
pdf_path: *const c_char,
output_path: *const c_char,
page_numbers: *const i32,
page_count: i32,
) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() || output_path.is_null() || page_numbers.is_null() || page_count <= 0
{
return -1;
}
let input_path = match c_str_to_string(pdf_path) {
Some(s) => s,
None => return -2,
};
let out_path = match c_str_to_string(output_path) {
Some(s) => s,
None => return -2,
};
let data = match std::fs::read(&input_path) {
Ok(d) => d,
Err(_) => return -3,
};
let pages: Vec<i32> =
unsafe { std::slice::from_raw_parts(page_numbers, page_count as usize).to_vec() };
let total_pages = {
let doc_handle = fz_open_document(0, pdf_path);
if doc_handle == 0 {
return -3;
}
let count = fz_count_pages(0, doc_handle);
fz_drop_document(0, doc_handle);
count
};
for &page in &pages {
if page < 0 || page >= total_pages {
return -4;
}
}
if pages.len() == total_pages as usize {
let mut all_in_order = true;
for (i, &page) in pages.iter().enumerate() {
if page != i as i32 {
all_in_order = false;
break;
}
}
if all_in_order {
return match std::fs::write(&out_path, &data) {
Ok(_) => 0,
Err(_) => -5,
};
}
}
let temp_dir = std::env::temp_dir();
let mut temp_files = Vec::new();
for (i, &page) in pages.iter().enumerate() {
let temp_path = temp_dir
.join(format!("micropdf_page_{}_{}.pdf", std::process::id(), i))
.to_string_lossy()
.to_string();
if crate::enhanced::page_copy::copy_page_complete(
&input_path,
page as usize,
&temp_path,
)
.is_err()
{
for tf in &temp_files {
let _ = std::fs::remove_file(tf);
}
return -5;
}
temp_files.push(temp_path);
}
let result = if temp_files.len() == 1 {
std::fs::copy(&temp_files[0], &out_path)
.map(|_| 0)
.unwrap_or(-5)
} else {
let temp_strings: Vec<String> = temp_files.clone();
match crate::enhanced::page_ops::merge_pdf(&temp_strings, &out_path) {
Ok(_) => 0,
Err(_) => -5,
}
};
for tf in &temp_files {
let _ = std::fs::remove_file(tf);
}
result
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_copy_pages: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_copy_pages: panic: {}", s)
} else {
"mp_copy_pages: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_is_valid_pdf(pdf_path: *const c_char) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() {
return -1;
}
unsafe { mp_quick_validate(pdf_path) as i32 }
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_is_valid_pdf: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_is_valid_pdf: panic: {}", s)
} else {
"mp_is_valid_pdf: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_repair_damaged_pdf(
pdf_path: *const c_char,
output_path: *const c_char,
) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if pdf_path.is_null() || output_path.is_null() {
return -1;
}
unsafe { mp_repair_pdf(pdf_path, output_path) as i32 }
})) {
Ok(result) => result,
Err(panic_info) => {
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
format!("mp_repair_damaged_pdf: panic: {}", s)
} else if let Some(s) = panic_info.downcast_ref::<String>() {
format!("mp_repair_damaged_pdf: panic: {}", s)
} else {
"mp_repair_damaged_pdf: panic (unknown payload)".to_string()
};
eprintln!("{}", msg);
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_free_bytes(data: *mut u8, len: usize) {
if !data.is_null() && len > 0 {
unsafe {
let slice_ptr = ptr::slice_from_raw_parts_mut(data, len);
let _ = Box::from_raw(slice_ptr);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn mp_free_extracted_text(result: *mut MpExtractedText) {
if result.is_null() {
return;
}
unsafe {
if !(*result).text.is_null() {
let _ = CString::from_raw((*result).text);
(*result).text = ptr::null_mut();
(*result).text_len = 0;
}
}
}
fn string_to_c(s: Option<String>) -> *mut c_char {
match s {
Some(string) => match CString::new(string) {
Ok(cs) => cs.into_raw(),
Err(_) => ptr::null_mut(),
},
None => ptr::null_mut(),
}
}
fn c_str_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
unsafe { CStr::from_ptr(ptr).to_str().ok().map(|s| s.to_string()) }
}
fn extract_pdf_version(data: &[u8]) -> Option<String> {
if data.len() < 8 || !data.starts_with(b"%PDF-") {
return None;
}
let start = 5;
let mut end = start;
while end < data.len() && end < 10 {
let b = data[end];
if b == b'\n' || b == b'\r' || b == b' ' {
break;
}
end += 1;
}
std::str::from_utf8(&data[start..end])
.ok()
.map(|s| s.to_string())
}
fn extract_metadata(data: &[u8], key: &[u8]) -> Option<String> {
let key_len = key.len();
for i in 0..data.len().saturating_sub(key_len + 2) {
if &data[i..i + key_len] == key {
let after_key = i + key_len;
let mut start = after_key;
while start < data.len() && (data[start] == b' ' || data[start] == b'\n') {
start += 1;
}
if start >= data.len() {
continue;
}
if data[start] == b'(' {
start += 1;
let mut end = start;
let mut depth = 1;
while end < data.len() && depth > 0 {
match data[end] {
b'(' => depth += 1,
b')' => depth -= 1,
b'\\' if end + 1 < data.len() => end += 1, _ => {}
}
if depth > 0 {
end += 1;
}
}
if depth == 0 {
return std::str::from_utf8(&data[start..end])
.ok()
.map(|s| s.to_string());
}
} else if data[start] == b'<' && start + 1 < data.len() && data[start + 1] != b'<' {
start += 1;
let mut end = start;
while end < data.len() && data[end] != b'>' {
end += 1;
}
let hex = &data[start..end];
let mut bytes = Vec::new();
for i in (0..hex.len()).step_by(2) {
if i + 1 < hex.len() {
if let (Some(h), Some(l)) = (hex_val(hex[i]), hex_val(hex[i + 1])) {
bytes.push((h << 4) | l);
}
}
}
return String::from_utf8(bytes).ok();
}
}
}
None
}
fn hex_val(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::io::Write;
fn create_test_pdf() -> Vec<u8> {
let pdf = b"%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /Resources 4 0 R /MediaBox [0 0 612 792] /Contents 5 0 R >>
endobj
4 0 obj
<< /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >>
endobj
5 0 obj
<< /Length 55 >>
stream
BT
/F1 18 Tf
50 700 Td
(Hello, World!) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000229 00000 n
0000000325 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
429
%%EOF
";
pdf.to_vec()
}
fn create_test_pdf_file() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let id = COUNTER.fetch_add(1, Ordering::SeqCst);
let path = format!("/tmp/test_convenience_{}_{}.pdf", std::process::id(), id);
let mut file = fs::File::create(&path).unwrap();
file.write_all(&create_test_pdf()).unwrap();
path
}
fn create_multipage_pdf() -> Vec<u8> {
let pdf = b"%PDF-1.4
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
2 0 obj << /Type /Pages /Kids [3 0 R 4 0 R 5 0 R] /Count 3 >> endobj
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >> endobj
4 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] >> endobj
5 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 842 595] >> endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000131 00000 n
0000000210 00000 n
0000000289 00000 n
trailer << /Size 6 /Root 1 0 R >>
startxref
368
%%EOF";
pdf.to_vec()
}
fn create_multipage_pdf_file() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let id = COUNTER.fetch_add(1, Ordering::SeqCst);
let path = format!("/tmp/test_multipage_{}_{}.pdf", std::process::id(), id);
let mut file = fs::File::create(&path).unwrap();
file.write_all(&create_multipage_pdf()).unwrap();
path
}
#[test]
fn test_pdf_version_extraction() {
let data = b"%PDF-1.7\n%\xE2\xE3\xCF\xD3\n";
let version = extract_pdf_version(data);
assert_eq!(version, Some("1.7".to_string()));
}
#[test]
fn test_pdf_version_extraction_14() {
let data = b"%PDF-1.4\r\n";
let version = extract_pdf_version(data);
assert_eq!(version, Some("1.4".to_string()));
}
#[test]
fn test_pdf_version_extraction_20() {
let data = b"%PDF-2.0 ";
let version = extract_pdf_version(data);
assert_eq!(version, Some("2.0".to_string()));
}
#[test]
fn test_pdf_version_extraction_invalid() {
let data = b"Not a PDF";
let version = extract_pdf_version(data);
assert_eq!(version, None);
let data = b"%PDF";
let version = extract_pdf_version(data);
assert_eq!(version, None);
let version = extract_pdf_version(b"");
assert_eq!(version, None);
}
#[test]
fn test_hex_val() {
assert_eq!(hex_val(b'0'), Some(0));
assert_eq!(hex_val(b'1'), Some(1));
assert_eq!(hex_val(b'5'), Some(5));
assert_eq!(hex_val(b'9'), Some(9));
assert_eq!(hex_val(b'a'), Some(10));
assert_eq!(hex_val(b'b'), Some(11));
assert_eq!(hex_val(b'c'), Some(12));
assert_eq!(hex_val(b'd'), Some(13));
assert_eq!(hex_val(b'e'), Some(14));
assert_eq!(hex_val(b'f'), Some(15));
assert_eq!(hex_val(b'A'), Some(10));
assert_eq!(hex_val(b'B'), Some(11));
assert_eq!(hex_val(b'C'), Some(12));
assert_eq!(hex_val(b'D'), Some(13));
assert_eq!(hex_val(b'E'), Some(14));
assert_eq!(hex_val(b'F'), Some(15));
assert_eq!(hex_val(b'g'), None);
assert_eq!(hex_val(b'G'), None);
assert_eq!(hex_val(b'z'), None);
assert_eq!(hex_val(b' '), None);
assert_eq!(hex_val(b'\n'), None);
}
#[test]
fn test_metadata_extraction_literal() {
let data = b"<< /Title (Hello World) /Author (Test) >>";
let title = extract_metadata(data, b"/Title");
assert_eq!(title, Some("Hello World".to_string()));
let author = extract_metadata(data, b"/Author");
assert_eq!(author, Some("Test".to_string()));
}
#[test]
fn test_metadata_extraction_literal_nested_parens() {
let data = b"<< /Title (Hello (World)) >>";
let title = extract_metadata(data, b"/Title");
assert_eq!(title, Some("Hello (World)".to_string()));
}
#[test]
fn test_metadata_extraction_literal_escaped() {
let data = b"<< /Title (Hello\\)World) >>";
let title = extract_metadata(data, b"/Title");
assert!(title.is_some());
}
#[test]
fn test_metadata_extraction_hex() {
let data = b"<< /Title <48656C6C6F> >>";
let title = extract_metadata(data, b"/Title");
assert_eq!(title, Some("Hello".to_string()));
}
#[test]
fn test_metadata_extraction_hex_lowercase() {
let data = b"<< /Title <48656c6c6f> >>";
let title = extract_metadata(data, b"/Title");
assert_eq!(title, Some("Hello".to_string()));
}
#[test]
fn test_metadata_extraction_not_found() {
let data = b"<< /Title (Test) >>";
let author = extract_metadata(data, b"/Author");
assert_eq!(author, None);
}
#[test]
fn test_metadata_extraction_empty_data() {
let data = b"";
let result = extract_metadata(data, b"/Title");
assert_eq!(result, None);
}
#[test]
fn test_string_to_c() {
let ptr = string_to_c(Some("Hello".to_string()));
assert!(!ptr.is_null());
unsafe {
let cs = CStr::from_ptr(ptr);
assert_eq!(cs.to_str().unwrap(), "Hello");
let _ = CString::from_raw(ptr);
}
let ptr = string_to_c(None);
assert!(ptr.is_null());
let ptr = string_to_c(Some("".to_string()));
assert!(!ptr.is_null());
unsafe {
let cs = CStr::from_ptr(ptr);
assert_eq!(cs.to_str().unwrap(), "");
let _ = CString::from_raw(ptr);
}
let ptr = string_to_c(Some("Hello 世界".to_string()));
assert!(!ptr.is_null());
unsafe {
let cs = CStr::from_ptr(ptr);
assert_eq!(cs.to_str().unwrap(), "Hello 世界");
let _ = CString::from_raw(ptr);
}
}
#[test]
fn test_string_to_c_with_null_byte() {
let ptr = string_to_c(Some("Hello\0World".to_string()));
assert!(ptr.is_null());
}
#[test]
fn test_c_str_to_string() {
let cs = CString::new("Hello").unwrap();
let result = c_str_to_string(cs.as_ptr());
assert_eq!(result, Some("Hello".to_string()));
let result = c_str_to_string(ptr::null());
assert_eq!(result, None);
let cs = CString::new("").unwrap();
let result = c_str_to_string(cs.as_ptr());
assert_eq!(result, Some("".to_string()));
let cs = CString::new("Hello 世界").unwrap();
let result = c_str_to_string(cs.as_ptr());
assert_eq!(result, Some("Hello 世界".to_string()));
}
#[test]
fn test_mp_get_pdf_info_null_path() {
let mut info = MpPdfInfo {
page_count: 0,
is_encrypted: 0,
needs_password: 0,
version: ptr::null_mut(),
title: ptr::null_mut(),
author: ptr::null_mut(),
subject: ptr::null_mut(),
creator: ptr::null_mut(),
};
let result = mp_get_pdf_info(ptr::null(), &mut info);
assert_eq!(result, -1);
}
#[test]
fn test_mp_get_pdf_info_null_output() {
let path = CString::new("/tmp/test.pdf").unwrap();
let result = mp_get_pdf_info(path.as_ptr(), ptr::null_mut());
assert_eq!(result, -2);
}
#[test]
fn test_mp_get_pdf_info_nonexistent_file() {
let path = CString::new("/tmp/nonexistent_file_12345.pdf").unwrap();
let mut info = MpPdfInfo {
page_count: 0,
is_encrypted: 0,
needs_password: 0,
version: ptr::null_mut(),
title: ptr::null_mut(),
author: ptr::null_mut(),
subject: ptr::null_mut(),
creator: ptr::null_mut(),
};
let result = mp_get_pdf_info(path.as_ptr(), &mut info);
assert_eq!(result, -3);
}
#[test]
fn test_mp_get_pdf_info_valid() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let mut info = MpPdfInfo {
page_count: 0,
is_encrypted: 0,
needs_password: 0,
version: ptr::null_mut(),
title: ptr::null_mut(),
author: ptr::null_mut(),
subject: ptr::null_mut(),
creator: ptr::null_mut(),
};
let result = mp_get_pdf_info(path.as_ptr(), &mut info);
assert_eq!(result, 0);
assert_eq!(info.page_count, 1);
assert_eq!(info.is_encrypted, 0);
if !info.version.is_null() {
let version = unsafe { CStr::from_ptr(info.version).to_str().unwrap() };
assert_eq!(version, "1.4");
}
mp_free_pdf_info(&mut info);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_free_pdf_info_null() {
mp_free_pdf_info(ptr::null_mut());
}
#[test]
fn test_mp_free_pdf_info_empty() {
let mut info = MpPdfInfo {
page_count: 0,
is_encrypted: 0,
needs_password: 0,
version: ptr::null_mut(),
title: ptr::null_mut(),
author: ptr::null_mut(),
subject: ptr::null_mut(),
creator: ptr::null_mut(),
};
mp_free_pdf_info(&mut info);
}
#[test]
fn test_mp_free_pdf_info_with_data() {
let mut info = MpPdfInfo {
page_count: 1,
is_encrypted: 0,
needs_password: 0,
version: CString::new("1.4").unwrap().into_raw(),
title: CString::new("Title").unwrap().into_raw(),
author: CString::new("Author").unwrap().into_raw(),
subject: ptr::null_mut(),
creator: ptr::null_mut(),
};
mp_free_pdf_info(&mut info);
assert!(info.version.is_null());
assert!(info.title.is_null());
assert!(info.author.is_null());
}
#[test]
fn test_mp_get_page_count_null_path() {
let result = mp_get_page_count(ptr::null());
assert_eq!(result, -1);
}
#[test]
fn test_mp_get_page_count_nonexistent() {
let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
let result = mp_get_page_count(path.as_ptr());
assert_eq!(result, -3);
}
#[test]
fn test_mp_get_page_count_valid() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let result = mp_get_page_count(path.as_ptr());
assert_eq!(result, 1);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_get_page_count_multipage() {
let pdf_path = create_multipage_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let result = mp_get_page_count(path.as_ptr());
assert_eq!(result, 3);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_get_page_dimensions_null_path() {
let mut dims = MpPageDimensions {
width: 0.0,
height: 0.0,
};
let result = mp_get_page_dimensions(ptr::null(), 0, &mut dims);
assert_eq!(result, -1);
}
#[test]
fn test_mp_get_page_dimensions_null_output() {
let path = CString::new("/tmp/test.pdf").unwrap();
let result = mp_get_page_dimensions(path.as_ptr(), 0, ptr::null_mut());
assert_eq!(result, -2);
}
#[test]
fn test_mp_get_page_dimensions_nonexistent() {
let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
let mut dims = MpPageDimensions {
width: 0.0,
height: 0.0,
};
let result = mp_get_page_dimensions(path.as_ptr(), 0, &mut dims);
assert_eq!(result, -3);
}
#[test]
fn test_mp_get_page_dimensions_invalid_page() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let mut dims = MpPageDimensions {
width: 0.0,
height: 0.0,
};
let result = mp_get_page_dimensions(path.as_ptr(), -1, &mut dims);
assert_eq!(result, -4);
let result = mp_get_page_dimensions(path.as_ptr(), 100, &mut dims);
assert_eq!(result, -4);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_get_page_dimensions_valid() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let mut dims = MpPageDimensions {
width: 0.0,
height: 0.0,
};
let result = mp_get_page_dimensions(path.as_ptr(), 0, &mut dims);
assert_eq!(result, 0);
assert!((dims.width - 612.0).abs() < 1.0);
assert!((dims.height - 792.0).abs() < 1.0);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_extract_text_null_path() {
let mut result = MpExtractedText {
text: ptr::null_mut(),
text_len: 0,
pages_processed: 0,
};
let ret = mp_extract_text(ptr::null(), &mut result);
assert_eq!(ret, -1);
}
#[test]
fn test_mp_extract_text_null_output() {
let path = CString::new("/tmp/test.pdf").unwrap();
let ret = mp_extract_text(path.as_ptr(), ptr::null_mut());
assert_eq!(ret, -2);
}
#[test]
fn test_mp_extract_text_nonexistent() {
let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
let mut result = MpExtractedText {
text: ptr::null_mut(),
text_len: 0,
pages_processed: 0,
};
let ret = mp_extract_text(path.as_ptr(), &mut result);
assert_eq!(ret, -3);
}
#[test]
fn test_mp_extract_text_valid() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let mut result = MpExtractedText {
text: ptr::null_mut(),
text_len: 0,
pages_processed: 0,
};
let ret = mp_extract_text(path.as_ptr(), &mut result);
assert_eq!(ret, 0);
assert_eq!(result.pages_processed, 1);
mp_free_extracted_text(&mut result);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_extract_page_text_null_path() {
let result = mp_extract_page_text(ptr::null(), 0);
assert!(result.is_null());
}
#[test]
fn test_mp_extract_page_text_nonexistent() {
let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
let result = mp_extract_page_text(path.as_ptr(), 0);
assert!(result.is_null());
}
#[test]
fn test_mp_extract_page_text_invalid_page() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let result = mp_extract_page_text(path.as_ptr(), -1);
assert!(result.is_null());
let result = mp_extract_page_text(path.as_ptr(), 100);
assert!(result.is_null());
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_extract_page_text_valid() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let result = mp_extract_page_text(path.as_ptr(), 0);
if !result.is_null() {
unsafe {
let _ = CString::from_raw(result);
}
}
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_free_extracted_text_null() {
mp_free_extracted_text(ptr::null_mut());
}
#[test]
fn test_mp_free_extracted_text_empty() {
let mut result = MpExtractedText {
text: ptr::null_mut(),
text_len: 0,
pages_processed: 0,
};
mp_free_extracted_text(&mut result);
}
#[test]
fn test_mp_free_extracted_text_with_data() {
let mut result = MpExtractedText {
text: CString::new("Test text").unwrap().into_raw(),
text_len: 9,
pages_processed: 1,
};
mp_free_extracted_text(&mut result);
assert!(result.text.is_null());
assert_eq!(result.text_len, 0);
}
#[test]
fn test_mp_render_page_to_png_null_path() {
let mut result = MpRenderedPage {
data: ptr::null_mut(),
data_len: 0,
width: 0,
height: 0,
};
let ret = mp_render_page_to_png(ptr::null(), 0, 1.0, &mut result);
assert_eq!(ret, -1);
}
#[test]
fn test_mp_render_page_to_png_null_output() {
let path = CString::new("/tmp/test.pdf").unwrap();
let ret = mp_render_page_to_png(path.as_ptr(), 0, 1.0, ptr::null_mut());
assert_eq!(ret, -2);
}
#[test]
fn test_mp_render_page_to_png_nonexistent() {
let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
let mut result = MpRenderedPage {
data: ptr::null_mut(),
data_len: 0,
width: 0,
height: 0,
};
let ret = mp_render_page_to_png(path.as_ptr(), 0, 1.0, &mut result);
assert_eq!(ret, -3);
}
#[test]
fn test_mp_render_page_to_png_invalid_page() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let mut result = MpRenderedPage {
data: ptr::null_mut(),
data_len: 0,
width: 0,
height: 0,
};
let ret = mp_render_page_to_png(path.as_ptr(), -1, 1.0, &mut result);
assert_eq!(ret, -4);
let ret = mp_render_page_to_png(path.as_ptr(), 100, 1.0, &mut result);
assert_eq!(ret, -4);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_render_page_to_png_valid() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let mut result = MpRenderedPage {
data: ptr::null_mut(),
data_len: 0,
width: 0,
height: 0,
};
let ret = mp_render_page_to_png(path.as_ptr(), 0, 1.0, &mut result);
if ret == 0 {
assert!(!result.data.is_null());
assert!(result.data_len > 0);
assert!(result.width > 0);
assert!(result.height > 0);
mp_free_rendered_page(&mut result);
}
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_render_page_to_rgb_null_path() {
let mut result = MpRenderedPage {
data: ptr::null_mut(),
data_len: 0,
width: 0,
height: 0,
};
let ret = mp_render_page_to_rgb(ptr::null(), 0, 1.0, &mut result);
assert_eq!(ret, -1);
}
#[test]
fn test_mp_render_page_to_rgb_null_output() {
let path = CString::new("/tmp/test.pdf").unwrap();
let ret = mp_render_page_to_rgb(path.as_ptr(), 0, 1.0, ptr::null_mut());
assert_eq!(ret, -2);
}
#[test]
fn test_mp_render_page_to_rgb_nonexistent() {
let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
let mut result = MpRenderedPage {
data: ptr::null_mut(),
data_len: 0,
width: 0,
height: 0,
};
let ret = mp_render_page_to_rgb(path.as_ptr(), 0, 1.0, &mut result);
assert_eq!(ret, -3);
}
#[test]
fn test_mp_render_page_to_rgb_invalid_page() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let mut result = MpRenderedPage {
data: ptr::null_mut(),
data_len: 0,
width: 0,
height: 0,
};
let ret = mp_render_page_to_rgb(path.as_ptr(), -1, 1.0, &mut result);
assert_eq!(ret, -4);
let ret = mp_render_page_to_rgb(path.as_ptr(), 100, 1.0, &mut result);
assert_eq!(ret, -4);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_render_page_to_rgb_valid() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let mut result = MpRenderedPage {
data: ptr::null_mut(),
data_len: 0,
width: 0,
height: 0,
};
let ret = mp_render_page_to_rgb(path.as_ptr(), 0, 1.0, &mut result);
if ret == 0 {
assert!(!result.data.is_null());
assert!(result.data_len > 0);
assert!(result.width > 0);
assert!(result.height > 0);
mp_free_rendered_page(&mut result);
}
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_free_rendered_page_null() {
mp_free_rendered_page(ptr::null_mut());
}
#[test]
fn test_mp_free_rendered_page_empty() {
let mut result = MpRenderedPage {
data: ptr::null_mut(),
data_len: 0,
width: 0,
height: 0,
};
mp_free_rendered_page(&mut result);
}
#[test]
fn test_mp_free_rendered_page_with_data() {
let data = vec![1u8, 2, 3, 4, 5];
let len = data.len();
let mut result = MpRenderedPage {
data: Box::into_raw(data.into_boxed_slice()) as *mut u8,
data_len: len,
width: 100,
height: 100,
};
mp_free_rendered_page(&mut result);
assert!(result.data.is_null());
assert_eq!(result.data_len, 0);
}
#[test]
fn test_mp_merge_pdf_files_null_paths() {
let output = CString::new("/tmp/output.pdf").unwrap();
let ret = mp_merge_pdf_files(ptr::null(), 2, output.as_ptr());
assert_eq!(ret, -1);
}
#[test]
fn test_mp_merge_pdf_files_null_output() {
let path1 = CString::new("/tmp/test1.pdf").unwrap();
let paths: [*const c_char; 1] = [path1.as_ptr()];
let ret = mp_merge_pdf_files(paths.as_ptr(), 1, ptr::null());
assert_eq!(ret, -1);
}
#[test]
fn test_mp_merge_pdf_files_zero_count() {
let output = CString::new("/tmp/output.pdf").unwrap();
let path1 = CString::new("/tmp/test1.pdf").unwrap();
let paths: [*const c_char; 1] = [path1.as_ptr()];
let ret = mp_merge_pdf_files(paths.as_ptr(), 0, output.as_ptr());
assert_eq!(ret, -1);
}
#[test]
fn test_mp_merge_pdf_files_negative_count() {
let output = CString::new("/tmp/output.pdf").unwrap();
let path1 = CString::new("/tmp/test1.pdf").unwrap();
let paths: [*const c_char; 1] = [path1.as_ptr()];
let ret = mp_merge_pdf_files(paths.as_ptr(), -1, output.as_ptr());
assert_eq!(ret, -1);
}
#[test]
fn test_mp_merge_pdf_files_nonexistent() {
let path1 = CString::new("/tmp/nonexistent_merge_1.pdf").unwrap();
let path2 = CString::new("/tmp/nonexistent_merge_2.pdf").unwrap();
let paths: [*const c_char; 2] = [path1.as_ptr(), path2.as_ptr()];
let output = CString::new("/tmp/merge_output_test.pdf").unwrap();
let ret = mp_merge_pdf_files(paths.as_ptr(), 2, output.as_ptr());
assert!(ret < 0);
}
#[test]
fn test_mp_merge_pdf_files_valid() {
let pdf_path1 = create_test_pdf_file();
let pdf_path2 = create_test_pdf_file();
let output_path = format!("/tmp/merge_output_{}.pdf", std::process::id());
let path1 = CString::new(pdf_path1.clone()).unwrap();
let path2 = CString::new(pdf_path2.clone()).unwrap();
let output = CString::new(output_path.clone()).unwrap();
let paths: [*const c_char; 2] = [path1.as_ptr(), path2.as_ptr()];
let ret = mp_merge_pdf_files(paths.as_ptr(), 2, output.as_ptr());
let _ = ret;
let _ = fs::remove_file(&pdf_path1);
let _ = fs::remove_file(&pdf_path2);
let _ = fs::remove_file(&output_path);
}
#[test]
fn test_mp_split_pdf_to_pages_null_path() {
let output = CString::new("/tmp/output_dir").unwrap();
let ret = mp_split_pdf_to_pages(ptr::null(), output.as_ptr());
assert_eq!(ret, -1);
}
#[test]
fn test_mp_split_pdf_to_pages_null_output() {
let path = CString::new("/tmp/test.pdf").unwrap();
let ret = mp_split_pdf_to_pages(path.as_ptr(), ptr::null());
assert_eq!(ret, -1);
}
#[test]
fn test_mp_split_pdf_to_pages_nonexistent() {
let path = CString::new("/tmp/nonexistent_split_12345.pdf").unwrap();
let output = CString::new("/tmp/split_output_dir").unwrap();
let ret = mp_split_pdf_to_pages(path.as_ptr(), output.as_ptr());
assert!(ret < 0); }
#[test]
fn test_mp_split_pdf_to_pages_valid() {
let pdf_path = create_multipage_pdf_file();
let output_dir = format!("/tmp/split_output_{}", std::process::id());
let _ = fs::create_dir_all(&output_dir);
let path = CString::new(pdf_path.clone()).unwrap();
let output = CString::new(output_dir.clone()).unwrap();
let ret = mp_split_pdf_to_pages(path.as_ptr(), output.as_ptr());
if ret > 0 {
assert_eq!(ret, 3); }
let _ = fs::remove_file(&pdf_path);
let _ = fs::remove_dir_all(&output_dir);
}
#[test]
fn test_mp_copy_pages_null_path() {
let output = CString::new("/tmp/output.pdf").unwrap();
let pages = [0, 1];
let ret = mp_copy_pages(ptr::null(), output.as_ptr(), pages.as_ptr(), 2);
assert_eq!(ret, -1);
}
#[test]
fn test_mp_copy_pages_null_output() {
let path = CString::new("/tmp/test.pdf").unwrap();
let pages = [0, 1];
let ret = mp_copy_pages(path.as_ptr(), ptr::null(), pages.as_ptr(), 2);
assert_eq!(ret, -1);
}
#[test]
fn test_mp_copy_pages_null_pages() {
let path = CString::new("/tmp/test.pdf").unwrap();
let output = CString::new("/tmp/output.pdf").unwrap();
let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), ptr::null(), 2);
assert_eq!(ret, -1);
}
#[test]
fn test_mp_copy_pages_zero_count() {
let path = CString::new("/tmp/test.pdf").unwrap();
let output = CString::new("/tmp/output.pdf").unwrap();
let pages = [0, 1];
let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), 0);
assert_eq!(ret, -1);
}
#[test]
fn test_mp_copy_pages_negative_count() {
let path = CString::new("/tmp/test.pdf").unwrap();
let output = CString::new("/tmp/output.pdf").unwrap();
let pages = [0, 1];
let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), -1);
assert_eq!(ret, -1);
}
#[test]
fn test_mp_copy_pages_invalid_page_number() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let output_path = format!("/tmp/output_copy_{}.pdf", std::process::id());
let output = CString::new(output_path.clone()).unwrap();
let pages = [-1];
let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), 1);
assert_eq!(ret, -4);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_copy_pages_out_of_range_page() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let output_path = format!("/tmp/output_copy_{}.pdf", std::process::id());
let output = CString::new(output_path.clone()).unwrap();
let pages = [100];
let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), 1);
assert_eq!(ret, -4);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_copy_pages_valid_all_pages() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let output_path = format!("/tmp/output_copy_all_{}.pdf", std::process::id());
let output = CString::new(output_path.clone()).unwrap();
let pages = [0];
let ret = mp_copy_pages(path.as_ptr(), output.as_ptr(), pages.as_ptr(), 1);
assert_eq!(ret, 0);
assert!(fs::metadata(&output_path).is_ok());
let _ = fs::remove_file(&pdf_path);
let _ = fs::remove_file(&output_path);
}
#[test]
fn test_mp_is_valid_pdf_null_path() {
let ret = mp_is_valid_pdf(ptr::null());
assert_eq!(ret, -1);
}
#[test]
fn test_mp_is_valid_pdf_nonexistent() {
let path = CString::new("/tmp/nonexistent_12345.pdf").unwrap();
let ret = mp_is_valid_pdf(path.as_ptr());
assert!(ret <= 0);
}
#[test]
fn test_mp_is_valid_pdf_valid() {
let pdf_path = create_test_pdf_file();
let path = CString::new(pdf_path.clone()).unwrap();
let ret = mp_is_valid_pdf(path.as_ptr());
assert_eq!(ret, 1);
let _ = fs::remove_file(&pdf_path);
}
#[test]
fn test_mp_is_valid_pdf_invalid_content() {
let path_str = format!("/tmp/not_a_pdf_{}.pdf", std::process::id());
let mut file = fs::File::create(&path_str).unwrap();
file.write_all(b"This is not a PDF file").unwrap();
let path = CString::new(path_str.clone()).unwrap();
let ret = mp_is_valid_pdf(path.as_ptr());
assert_eq!(ret, 0);
let _ = fs::remove_file(&path_str);
}
#[test]
fn test_mp_repair_damaged_pdf_null_path() {
let output = CString::new("/tmp/output.pdf").unwrap();
let ret = mp_repair_damaged_pdf(ptr::null(), output.as_ptr());
assert_eq!(ret, -1);
}
#[test]
fn test_mp_repair_damaged_pdf_null_output() {
let path = CString::new("/tmp/test.pdf").unwrap();
let ret = mp_repair_damaged_pdf(path.as_ptr(), ptr::null());
assert_eq!(ret, -1);
}
#[test]
fn test_mp_repair_damaged_pdf_nonexistent() {
let path = CString::new("/tmp/nonexistent_repair_12345.pdf").unwrap();
let output = CString::new("/tmp/repair_output.pdf").unwrap();
let ret = mp_repair_damaged_pdf(path.as_ptr(), output.as_ptr());
assert!(ret < 0);
}
#[test]
fn test_mp_repair_damaged_pdf_valid() {
let pdf_path = create_test_pdf_file();
let output_path = format!("/tmp/repair_output_{}.pdf", std::process::id());
let path = CString::new(pdf_path.clone()).unwrap();
let output = CString::new(output_path.clone()).unwrap();
let ret = mp_repair_damaged_pdf(path.as_ptr(), output.as_ptr());
assert!(ret <= 0);
let _ = fs::remove_file(&pdf_path);
let _ = fs::remove_file(&output_path);
}
#[test]
fn test_mp_free_bytes_null() {
mp_free_bytes(ptr::null_mut(), 0);
}
#[test]
fn test_mp_free_bytes_zero_len() {
let data = vec![1u8, 2, 3];
let ptr = Box::into_raw(data.into_boxed_slice()) as *mut u8;
mp_free_bytes(ptr, 0);
unsafe {
let slice_ptr = ptr::slice_from_raw_parts_mut(ptr, 3);
let _ = Box::from_raw(slice_ptr);
}
}
#[test]
fn test_mp_free_bytes_valid() {
let data = vec![1u8, 2, 3, 4, 5];
let len = data.len();
let ptr = Box::into_raw(data.into_boxed_slice()) as *mut u8;
mp_free_bytes(ptr, len);
}
#[test]
fn test_metadata_with_multiple_keys() {
let data = b"<< /Title (First) >> << /Title (Second) >>";
let title = extract_metadata(data, b"/Title");
assert_eq!(title, Some("First".to_string()));
}
#[test]
fn test_metadata_with_whitespace() {
let data = b"<< /Title (Hello World) >>";
let title = extract_metadata(data, b"/Title");
assert_eq!(title, Some("Hello World".to_string()));
}
#[test]
fn test_metadata_with_newline() {
let data = b"<< /Title\n(Hello World) >>";
let title = extract_metadata(data, b"/Title");
assert_eq!(title, Some("Hello World".to_string()));
}
#[test]
fn test_pdf_version_with_space() {
let data = b"%PDF-1.5 more data";
let version = extract_pdf_version(data);
assert_eq!(version, Some("1.5".to_string()));
}
#[test]
fn test_string_to_c_long_string() {
let long_string = "a".repeat(10000);
let ptr = string_to_c(Some(long_string.clone()));
assert!(!ptr.is_null());
unsafe {
let cs = CStr::from_ptr(ptr);
assert_eq!(cs.to_str().unwrap().len(), 10000);
let _ = CString::from_raw(ptr);
}
}
#[test]
fn test_c_str_to_string_long() {
let long_string = "a".repeat(10000);
let cs = CString::new(long_string.clone()).unwrap();
let result = c_str_to_string(cs.as_ptr());
assert_eq!(result.unwrap().len(), 10000);
}
}