use super::ffi_safety::{cstr_to_str, cstr_to_string};
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::sync::{LazyLock, Mutex};
use crate::ffi::stext::Rect;
use crate::ffi::{Handle, HandleStore};
pub static WRITERS: LazyLock<HandleStore<DocumentWriter>> = LazyLock::new(HandleStore::new);
pub static WRITER_DEVICES: LazyLock<HandleStore<WriterDevice>> = LazyLock::new(HandleStore::new);
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriterFormat {
Pdf = 0,
Svg = 1,
Text = 2,
Html = 3,
Xhtml = 4,
Odt = 5,
Docx = 6,
Ps = 7,
Pcl = 8,
Pclm = 9,
Pwg = 10,
Cbz = 11,
Csv = 12,
Pdfocr = 13,
Png = 14,
Jpeg = 15,
Pam = 16,
Pnm = 17,
Pgm = 18,
Ppm = 19,
Pbm = 20,
Pkm = 21,
}
impl WriterFormat {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"pdf" => Some(WriterFormat::Pdf),
"svg" => Some(WriterFormat::Svg),
"text" | "txt" => Some(WriterFormat::Text),
"html" => Some(WriterFormat::Html),
"xhtml" => Some(WriterFormat::Xhtml),
"odt" => Some(WriterFormat::Odt),
"docx" => Some(WriterFormat::Docx),
"ps" | "postscript" => Some(WriterFormat::Ps),
"pcl" => Some(WriterFormat::Pcl),
"pclm" => Some(WriterFormat::Pclm),
"pwg" => Some(WriterFormat::Pwg),
"cbz" => Some(WriterFormat::Cbz),
"csv" => Some(WriterFormat::Csv),
"pdfocr" | "ocr" => Some(WriterFormat::Pdfocr),
"png" => Some(WriterFormat::Png),
"jpeg" | "jpg" => Some(WriterFormat::Jpeg),
"pam" => Some(WriterFormat::Pam),
"pnm" => Some(WriterFormat::Pnm),
"pgm" => Some(WriterFormat::Pgm),
"ppm" => Some(WriterFormat::Ppm),
"pbm" => Some(WriterFormat::Pbm),
"pkm" => Some(WriterFormat::Pkm),
_ => None,
}
}
pub fn extension(&self) -> &'static str {
match self {
WriterFormat::Pdf => "pdf",
WriterFormat::Svg => "svg",
WriterFormat::Text => "txt",
WriterFormat::Html => "html",
WriterFormat::Xhtml => "xhtml",
WriterFormat::Odt => "odt",
WriterFormat::Docx => "docx",
WriterFormat::Ps => "ps",
WriterFormat::Pcl => "pcl",
WriterFormat::Pclm => "pclm",
WriterFormat::Pwg => "pwg",
WriterFormat::Cbz => "cbz",
WriterFormat::Csv => "csv",
WriterFormat::Pdfocr => "pdf",
WriterFormat::Png => "png",
WriterFormat::Jpeg => "jpg",
WriterFormat::Pam => "pam",
WriterFormat::Pnm => "pnm",
WriterFormat::Pgm => "pgm",
WriterFormat::Ppm => "ppm",
WriterFormat::Pbm => "pbm",
WriterFormat::Pkm => "pkm",
}
}
pub fn is_multipage(&self) -> bool {
matches!(
self,
WriterFormat::Pdf
| WriterFormat::Svg
| WriterFormat::Text
| WriterFormat::Html
| WriterFormat::Xhtml
| WriterFormat::Odt
| WriterFormat::Docx
| WriterFormat::Ps
| WriterFormat::Pcl
| WriterFormat::Pclm
| WriterFormat::Pwg
| WriterFormat::Cbz
| WriterFormat::Csv
| WriterFormat::Pdfocr
)
}
pub fn is_image(&self) -> bool {
matches!(
self,
WriterFormat::Png
| WriterFormat::Jpeg
| WriterFormat::Pam
| WriterFormat::Pnm
| WriterFormat::Pgm
| WriterFormat::Ppm
| WriterFormat::Pbm
| WriterFormat::Pkm
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriterState {
Created,
PageInProgress,
Closed,
}
#[derive(Debug, Clone, Default)]
pub struct WriterOptions {
pub raw: String,
pub options: HashMap<String, String>,
pub resolution: Option<f32>,
pub compression: Option<i32>,
pub quality: Option<i32>,
pub colorspace: Option<String>,
pub page_size: Option<String>,
pub linearize: bool,
pub encrypt: bool,
pub ocr_language: Option<String>,
}
impl WriterOptions {
pub fn parse(opts_str: &str) -> Self {
let mut options = WriterOptions {
raw: opts_str.to_string(),
..Default::default()
};
for part in opts_str.split(',') {
let part = part.trim();
if part.is_empty() {
continue;
}
if let Some((key, value)) = part.split_once('=') {
let key = key.trim().to_lowercase();
let value = value.trim();
options.options.insert(key.clone(), value.to_string());
match key.as_str() {
"resolution" | "res" | "dpi" => {
options.resolution = value.parse().ok();
}
"compression" => {
options.compression = value.parse().ok();
}
"quality" => {
options.quality = value.parse().ok();
}
"colorspace" | "cs" => {
options.colorspace = Some(value.to_string());
}
"pagesize" | "page-size" => {
options.page_size = Some(value.to_string());
}
"language" | "lang" | "ocr-language" => {
options.ocr_language = Some(value.to_string());
}
_ => {}
}
} else {
match part.to_lowercase().as_str() {
"linearize" | "linear" => options.linearize = true,
"encrypt" | "encrypted" => options.encrypt = true,
_ => {
options.options.insert(part.to_string(), String::new());
}
}
}
}
options
}
pub fn get(&self, key: &str) -> Option<&String> {
self.options.get(key)
}
pub fn has(&self, key: &str) -> bool {
self.options.contains_key(key)
}
}
#[derive(Debug, Clone)]
pub struct WriterPage {
pub index: i32,
pub mediabox: Rect,
pub content: Vec<u8>,
}
pub struct DocumentWriter {
pub format: WriterFormat,
pub path: Option<String>,
pub output: Option<Handle>, pub buffer: Option<Handle>, pub options: WriterOptions,
pub state: WriterState,
pub pages: Vec<WriterPage>,
pub current_page: Option<WriterPage>,
pub current_device: Option<Handle>,
pub page_count: i32,
}
impl DocumentWriter {
pub fn new(format: WriterFormat, path: Option<String>, options: WriterOptions) -> Self {
Self {
format,
path,
output: None,
buffer: None,
options,
state: WriterState::Created,
pages: Vec::new(),
current_page: None,
current_device: None,
page_count: 0,
}
}
pub fn with_output(format: WriterFormat, output: Handle, options: WriterOptions) -> Self {
let mut writer = Self::new(format, None, options);
writer.output = Some(output);
writer
}
pub fn with_buffer(format: WriterFormat, buffer: Handle, options: WriterOptions) -> Self {
let mut writer = Self::new(format, None, options);
writer.buffer = Some(buffer);
writer
}
pub fn begin_page(&mut self, mediabox: Rect) -> bool {
if self.state == WriterState::Closed {
return false;
}
if self.state == WriterState::PageInProgress {
self.end_page();
}
self.current_page = Some(WriterPage {
index: self.page_count,
mediabox,
content: Vec::new(),
});
self.state = WriterState::PageInProgress;
true
}
pub fn end_page(&mut self) -> bool {
if self.state != WriterState::PageInProgress {
return false;
}
if let Some(page) = self.current_page.take() {
self.pages.push(page);
self.page_count += 1;
}
self.current_device = None;
self.state = WriterState::Created;
true
}
pub fn close(&mut self) -> bool {
if self.state == WriterState::Closed {
return false;
}
if self.state == WriterState::PageInProgress {
self.end_page();
}
self.write_output();
self.state = WriterState::Closed;
true
}
fn write_output(&self) {
match self.format {
WriterFormat::Pdf => self.write_pdf(),
WriterFormat::Svg => self.write_svg(),
WriterFormat::Text => self.write_text(),
WriterFormat::Html | WriterFormat::Xhtml => self.write_html(),
WriterFormat::Cbz => self.write_cbz(),
WriterFormat::Png
| WriterFormat::Jpeg
| WriterFormat::Pam
| WriterFormat::Pnm
| WriterFormat::Pgm
| WriterFormat::Ppm
| WriterFormat::Pbm
| WriterFormat::Pkm => self.write_image(),
_ => {
}
}
}
fn write_pdf(&self) {
use flate2::Compression;
use flate2::write::ZlibEncoder;
use std::io::Write;
let mut pdf_data = Vec::new();
pdf_data.extend_from_slice(b"%PDF-1.7\n");
pdf_data.extend_from_slice(b"%\xE2\xE3\xCF\xD3\n");
let mut obj_offsets = Vec::new();
let mut obj_num = 1;
obj_offsets.push(pdf_data.len());
let catalog = format!(
"{} 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n",
obj_num
);
pdf_data.extend_from_slice(catalog.as_bytes());
obj_num += 1;
obj_offsets.push(pdf_data.len());
let page_refs: String = (0..self.pages.len())
.map(|i| format!("{} 0 R", 3 + i * 2 + 1))
.collect::<Vec<_>>()
.join(" ");
let pages = format!(
"{} 0 obj\n<< /Type /Pages /Kids [ {} ] /Count {} >>\nendobj\n",
obj_num,
page_refs,
self.pages.len()
);
pdf_data.extend_from_slice(pages.as_bytes());
obj_num += 1;
for page in &self.pages {
let content_obj_num = obj_num;
let page_obj_num = obj_num + 1;
obj_offsets.push(pdf_data.len());
let raw_content = &page.content;
const FLATE_MIN_SIZE: usize = 128;
let (stream_bytes, use_flate) = if raw_content.len() < FLATE_MIN_SIZE {
(raw_content.clone(), false)
} else {
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
if encoder.write_all(raw_content).is_ok() {
if let Ok(compressed) = encoder.finish() {
if compressed.len() < raw_content.len() {
(compressed, true)
} else {
(raw_content.clone(), false)
}
} else {
(raw_content.clone(), false)
}
} else {
(raw_content.clone(), false)
}
};
let filter_entry = if use_flate {
" /Filter /FlateDecode"
} else {
""
};
let stream_header = format!(
"{} 0 obj\n<< /Length {}{} >>\nstream\n",
content_obj_num,
stream_bytes.len(),
filter_entry,
);
pdf_data.extend_from_slice(stream_header.as_bytes());
pdf_data.extend_from_slice(&stream_bytes);
pdf_data.extend_from_slice(b"\nendstream\nendobj\n");
obj_num += 1;
obj_offsets.push(pdf_data.len());
let page_obj = format!(
"{} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [ {} {} {} {} ] /Contents {} 0 R >>\nendobj\n",
page_obj_num,
page.mediabox.x0,
page.mediabox.y0,
page.mediabox.x1,
page.mediabox.y1,
content_obj_num,
);
pdf_data.extend_from_slice(page_obj.as_bytes());
obj_num += 1;
}
let xref_offset = pdf_data.len();
pdf_data.extend_from_slice(b"xref\n");
pdf_data.extend_from_slice(format!("0 {}\n", obj_num).as_bytes());
pdf_data.extend_from_slice(b"0000000000 65535 f \n");
for offset in &obj_offsets {
pdf_data.extend_from_slice(format!("{:010} 00000 n \n", offset).as_bytes());
}
pdf_data.extend_from_slice(b"trailer\n");
pdf_data.extend_from_slice(format!("<< /Size {} /Root 1 0 R >>\n", obj_num).as_bytes());
pdf_data.extend_from_slice(b"startxref\n");
pdf_data.extend_from_slice(format!("{}\n", xref_offset).as_bytes());
pdf_data.extend_from_slice(b"%%EOF\n");
if let Some(ref path) = self.path {
let _ = std::fs::write(path, &pdf_data);
}
}
fn write_svg(&self) {
for (i, page) in self.pages.iter().enumerate() {
let width = page.mediabox.x1 - page.mediabox.x0;
let height = page.mediabox.y1 - page.mediabox.y0;
let svg = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="{}" height="{}" viewBox="{} {} {} {}">
<!-- Page {} -->
</svg>
"#,
width,
height,
page.mediabox.x0,
page.mediabox.y0,
width,
height,
i + 1
);
if let Some(ref path) = self.path {
let page_path = if self.pages.len() > 1 {
format!("{}_{}.svg", path.trim_end_matches(".svg"), i + 1)
} else {
path.clone()
};
let _ = std::fs::write(page_path, svg.as_bytes());
}
}
}
fn write_text(&self) {
let mut text = String::new();
for (i, _page) in self.pages.iter().enumerate() {
if i > 0 {
text.push_str("\n\n--- Page Break ---\n\n");
}
text.push_str(&format!("--- Page {} ---\n", i + 1));
}
if let Some(ref path) = self.path {
let _ = std::fs::write(path, text.as_bytes());
}
}
fn write_html(&self) {
let doctype = if self.format == WriterFormat::Xhtml {
r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
"#
} else {
"<!DOCTYPE html>\n"
};
let mut html = String::from(doctype);
html.push_str(
"<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>Document</title>\n</head>\n<body>\n",
);
for (i, _page) in self.pages.iter().enumerate() {
html.push_str(&format!("<div class=\"page\" id=\"page-{}\">\n", i + 1));
html.push_str("</div>\n");
}
html.push_str("</body>\n</html>\n");
if let Some(ref path) = self.path {
let _ = std::fs::write(path, html.as_bytes());
}
}
fn write_cbz(&self) {
let Some(ref path) = self.path else { return };
let mut archive = Vec::new();
let mut central_dir = Vec::new();
let mut offsets = Vec::new();
for (i, page) in self.pages.iter().enumerate() {
let width = (page.mediabox.x1 - page.mediabox.x0).max(1.0) as u32;
let height = (page.mediabox.y1 - page.mediabox.y0).max(1.0) as u32;
let png_data = Self::generate_png(width, height);
let filename = format!("page_{:04}.png", i + 1);
let name_bytes = filename.as_bytes();
let crc = Self::crc32_compute(&png_data);
let size = png_data.len() as u32;
offsets.push(archive.len() as u32);
archive.extend_from_slice(&0x04034b50u32.to_le_bytes()); archive.extend_from_slice(&20u16.to_le_bytes()); archive.extend_from_slice(&0u16.to_le_bytes()); archive.extend_from_slice(&0u16.to_le_bytes()); archive.extend_from_slice(&0u16.to_le_bytes()); archive.extend_from_slice(&0u16.to_le_bytes()); archive.extend_from_slice(&crc.to_le_bytes());
archive.extend_from_slice(&size.to_le_bytes()); archive.extend_from_slice(&size.to_le_bytes()); archive.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
archive.extend_from_slice(&0u16.to_le_bytes()); archive.extend_from_slice(name_bytes);
archive.extend_from_slice(&png_data);
central_dir.extend_from_slice(&0x02014b50u32.to_le_bytes());
central_dir.extend_from_slice(&20u16.to_le_bytes()); central_dir.extend_from_slice(&20u16.to_le_bytes()); central_dir.extend_from_slice(&0u16.to_le_bytes()); central_dir.extend_from_slice(&0u16.to_le_bytes()); central_dir.extend_from_slice(&0u16.to_le_bytes()); central_dir.extend_from_slice(&0u16.to_le_bytes()); central_dir.extend_from_slice(&crc.to_le_bytes());
central_dir.extend_from_slice(&size.to_le_bytes());
central_dir.extend_from_slice(&size.to_le_bytes());
central_dir.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
central_dir.extend_from_slice(&0u16.to_le_bytes()); central_dir.extend_from_slice(&0u16.to_le_bytes()); central_dir.extend_from_slice(&0u16.to_le_bytes()); central_dir.extend_from_slice(&0u16.to_le_bytes()); central_dir.extend_from_slice(&0u32.to_le_bytes()); central_dir.extend_from_slice(&offsets[i].to_le_bytes());
central_dir.extend_from_slice(name_bytes);
}
let cd_offset = archive.len() as u32;
archive.extend_from_slice(¢ral_dir);
archive.extend_from_slice(&0x06054b50u32.to_le_bytes());
archive.extend_from_slice(&0u16.to_le_bytes()); archive.extend_from_slice(&0u16.to_le_bytes()); archive.extend_from_slice(&(self.pages.len() as u16).to_le_bytes());
archive.extend_from_slice(&(self.pages.len() as u16).to_le_bytes());
archive.extend_from_slice(&(central_dir.len() as u32).to_le_bytes());
archive.extend_from_slice(&cd_offset.to_le_bytes());
archive.extend_from_slice(&0u16.to_le_bytes());
let _ = std::fs::write(path, &archive);
}
fn crc32_compute(data: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFFFFFF;
for &byte in data {
crc ^= byte as u32;
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
}
}
!crc
}
fn write_image(&self) {
for (i, page) in self.pages.iter().enumerate() {
if let Some(ref path) = self.path {
let ext = self.format.extension();
let page_path = if self.pages.len() > 1 {
let base = path.trim_end_matches(&format!(".{}", ext));
format!("{}_{}.{}", base, i + 1, ext)
} else {
path.clone()
};
let width = (page.mediabox.x1 - page.mediabox.x0).max(1.0) as u32;
let height = (page.mediabox.y1 - page.mediabox.y0).max(1.0) as u32;
let image_data = match self.format {
WriterFormat::Png => Self::generate_png(width, height),
WriterFormat::Pam => Self::generate_pam(width, height),
WriterFormat::Pnm | WriterFormat::Ppm => Self::generate_ppm(width, height),
WriterFormat::Pgm => Self::generate_pgm(width, height),
WriterFormat::Pbm => Self::generate_pbm(width, height),
_ => {
Self::generate_png(width, height)
}
};
let _ = std::fs::write(page_path, &image_data);
}
}
}
fn generate_png(width: u32, height: u32) -> Vec<u8> {
use flate2::Compression;
use flate2::write::ZlibEncoder;
use std::io::Write;
let mut out = Vec::new();
out.extend_from_slice(&[137, 80, 78, 71, 13, 10, 26, 10]);
let mut ihdr_data = Vec::with_capacity(13);
ihdr_data.extend_from_slice(&width.to_be_bytes());
ihdr_data.extend_from_slice(&height.to_be_bytes());
ihdr_data.push(8); ihdr_data.push(2); ihdr_data.push(0); ihdr_data.push(0); ihdr_data.push(0); Self::write_png_chunk(&mut out, b"IHDR", &ihdr_data);
let row_bytes = 1 + (width as usize) * 3; let mut raw = Vec::with_capacity(row_bytes * height as usize);
for _ in 0..height {
raw.push(0); raw.extend(std::iter::repeat_n(0xFF_u8, width as usize * 3));
}
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::fast());
let _ = encoder.write_all(&raw);
let compressed = encoder.finish().unwrap_or_default();
Self::write_png_chunk(&mut out, b"IDAT", &compressed);
Self::write_png_chunk(&mut out, b"IEND", &[]);
out
}
fn write_png_chunk(out: &mut Vec<u8>, chunk_type: &[u8; 4], data: &[u8]) {
out.extend_from_slice(&(data.len() as u32).to_be_bytes());
out.extend_from_slice(chunk_type);
out.extend_from_slice(data);
let crc = Self::png_crc32(chunk_type, data);
out.extend_from_slice(&crc.to_be_bytes());
}
fn png_crc32(chunk_type: &[u8], data: &[u8]) -> u32 {
let mut table = [0u32; 256];
for n in 0..256u32 {
let mut c = n;
for _ in 0..8 {
if c & 1 != 0 {
c = 0xEDB8_8320 ^ (c >> 1);
} else {
c >>= 1;
}
}
table[n as usize] = c;
}
let mut crc: u32 = 0xFFFF_FFFF;
for &b in chunk_type.iter().chain(data.iter()) {
crc = table[((crc ^ b as u32) & 0xFF) as usize] ^ (crc >> 8);
}
crc ^ 0xFFFF_FFFF
}
fn generate_pam(width: u32, height: u32) -> Vec<u8> {
let header = format!(
"P7\nWIDTH {}\nHEIGHT {}\nDEPTH 3\nMAXVAL 255\nTUPLTYPE RGB\nENDHDR\n",
width, height
);
let pixel_count = (width as usize) * (height as usize) * 3;
let mut data = Vec::with_capacity(header.len() + pixel_count);
data.extend_from_slice(header.as_bytes());
data.extend(std::iter::repeat_n(0xFF_u8, pixel_count));
data
}
fn generate_ppm(width: u32, height: u32) -> Vec<u8> {
let header = format!("P6\n{} {}\n255\n", width, height);
let pixel_count = (width as usize) * (height as usize) * 3;
let mut data = Vec::with_capacity(header.len() + pixel_count);
data.extend_from_slice(header.as_bytes());
data.extend(std::iter::repeat_n(0xFF_u8, pixel_count));
data
}
fn generate_pgm(width: u32, height: u32) -> Vec<u8> {
let header = format!("P5\n{} {}\n255\n", width, height);
let pixel_count = (width as usize) * (height as usize);
let mut data = Vec::with_capacity(header.len() + pixel_count);
data.extend_from_slice(header.as_bytes());
data.extend(std::iter::repeat_n(0xFF_u8, pixel_count));
data
}
fn generate_pbm(width: u32, height: u32) -> Vec<u8> {
let header = format!("P4\n{} {}\n", width, height);
let row_bytes = (width as usize + 7) / 8;
let pixel_bytes = row_bytes * height as usize;
let mut data = Vec::with_capacity(header.len() + pixel_bytes);
data.extend_from_slice(header.as_bytes());
data.extend(std::iter::repeat_n(0x00_u8, pixel_bytes));
data
}
}
pub struct WriterDevice {
pub writer: Handle,
pub page_index: i32,
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_has_option(
_ctx: Handle,
opts: *const c_char,
key: *const c_char,
val: *mut *const c_char,
) -> i32 {
if opts.is_null() || key.is_null() {
return 0;
}
let opts_str = unsafe {
match CStr::from_ptr(opts).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
let key_str = unsafe {
match CStr::from_ptr(key).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
let options = WriterOptions::parse(opts_str);
if let Some(value) = options.get(key_str) {
if !val.is_null() {
if let Ok(cstr) = CString::new(value.as_str()) {
unsafe {
*val = cstr.into_raw();
}
}
}
1
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_option_eq(a: *const c_char, b: *const c_char) -> i32 {
if a.is_null() || b.is_null() {
return 0;
}
let a_str = unsafe {
match CStr::from_ptr(a).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
let b_str = unsafe {
match CStr::from_ptr(b).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
let a_first = a_str.split(',').next().unwrap_or("");
if a_first == b_str { 1 } else { 0 }
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_copy_option(
_ctx: Handle,
val: *const c_char,
dest: *mut c_char,
maxlen: usize,
) -> usize {
if val.is_null() || dest.is_null() || maxlen == 0 {
return 0;
}
let val_str = unsafe {
match CStr::from_ptr(val).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
let value_end = val_str.find(',').unwrap_or(val_str.len());
let value = &val_str[..value_end];
let bytes = value.as_bytes();
let copy_len = bytes.len().min(maxlen - 1);
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest as *mut u8, copy_len);
*dest.add(copy_len) = 0; }
if bytes.len() >= maxlen {
bytes.len() + 1 - maxlen
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_document_writer(
_ctx: Handle,
path: *const c_char,
format: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let format_str = if format.is_null() {
path_str
.as_ref()
.and_then(|p| p.rsplit('.').next())
.unwrap_or("pdf")
} else {
unsafe { CStr::from_ptr(format).to_str().unwrap_or("pdf") }
};
let writer_format = WriterFormat::from_str(format_str).unwrap_or(WriterFormat::Pdf);
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(writer_format, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_document_writer_with_output(
_ctx: Handle,
out: Handle,
format: *const c_char,
options: *const c_char,
) -> Handle {
let format_str = if format.is_null() {
"pdf"
} else {
unsafe { CStr::from_ptr(format).to_str().unwrap_or("pdf") }
};
let writer_format = WriterFormat::from_str(format_str).unwrap_or(WriterFormat::Pdf);
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(writer_format, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_document_writer_with_buffer(
_ctx: Handle,
buf: Handle,
format: *const c_char,
options: *const c_char,
) -> Handle {
let format_str = if format.is_null() {
"pdf"
} else {
unsafe { CStr::from_ptr(format).to_str().unwrap_or("pdf") }
};
let writer_format = WriterFormat::from_str(format_str).unwrap_or(WriterFormat::Pdf);
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_buffer(writer_format, buf, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pdf_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pdf, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pdf_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Pdf, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_svg_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Svg, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_svg_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Svg, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_text_writer(
_ctx: Handle,
format: *const c_char,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let format_str = if format.is_null() {
"text"
} else {
unsafe { CStr::from_ptr(format).to_str().unwrap_or("text") }
};
let writer_format = match format_str {
"html" => WriterFormat::Html,
"xhtml" => WriterFormat::Xhtml,
_ => WriterFormat::Text,
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(writer_format, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_text_writer_with_output(
_ctx: Handle,
format: *const c_char,
out: Handle,
options: *const c_char,
) -> Handle {
let format_str = if format.is_null() {
"text"
} else {
unsafe { CStr::from_ptr(format).to_str().unwrap_or("text") }
};
let writer_format = match format_str {
"html" => WriterFormat::Html,
"xhtml" => WriterFormat::Xhtml,
_ => WriterFormat::Text,
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(writer_format, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_odt_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Odt, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_odt_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Odt, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_docx_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Docx, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_docx_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Docx, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_ps_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Ps, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_ps_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Ps, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pcl_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pcl, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pcl_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Pcl, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pclm_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pclm, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pclm_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Pclm, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pwg_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pwg, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pwg_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Pwg, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_cbz_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Cbz, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_cbz_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Cbz, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_csv_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Csv, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_csv_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Csv, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pdfocr_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pdfocr, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pdfocr_writer_with_output(
_ctx: Handle,
out: Handle,
options: *const c_char,
) -> Handle {
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::with_output(WriterFormat::Pdfocr, out, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_png_pixmap_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Png, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_jpeg_pixmap_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Jpeg, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pam_pixmap_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pam, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pnm_pixmap_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pnm, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pgm_pixmap_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pgm, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_ppm_pixmap_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Ppm, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pbm_pixmap_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pbm, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pkm_pixmap_writer(
_ctx: Handle,
path: *const c_char,
options: *const c_char,
) -> Handle {
let path_str = if path.is_null() {
None
} else {
unsafe { CStr::from_ptr(path).to_str().ok().map(String::from) }
};
let opts = if options.is_null() {
WriterOptions::default()
} else {
let opts_str = unsafe { CStr::from_ptr(options).to_str().unwrap_or("") };
WriterOptions::parse(opts_str)
};
let writer = DocumentWriter::new(WriterFormat::Pkm, path_str, opts);
WRITERS.insert(writer)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_begin_page(
_ctx: Handle,
wri: Handle,
mediabox_x0: f32,
mediabox_y0: f32,
mediabox_x1: f32,
mediabox_y1: f32,
) -> Handle {
let mediabox = Rect {
x0: mediabox_x0,
y0: mediabox_y0,
x1: mediabox_x1,
y1: mediabox_y1,
};
if let Some(writer_arc) = WRITERS.get(wri) {
let mut writer = writer_arc.lock().unwrap();
if writer.begin_page(mediabox) {
let device = WriterDevice {
writer: wri,
page_index: writer.page_count,
};
let dev_handle = WRITER_DEVICES.insert(device);
writer.current_device = Some(dev_handle);
return dev_handle;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_end_page(_ctx: Handle, wri: Handle) {
if let Some(writer_arc) = WRITERS.get(wri) {
let mut writer = writer_arc.lock().unwrap();
if let Some(dev_handle) = writer.current_device {
WRITER_DEVICES.remove(dev_handle);
}
writer.end_page();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_write_document(_ctx: Handle, wri: Handle, doc: Handle) {
let count = crate::ffi::document::fz_count_pages(0, doc);
if count <= 0 {
return;
}
let page_bounds: Vec<Rect> = {
let doc_arc = match crate::ffi::DOCUMENTS.get(doc) {
Some(d) => d,
None => return,
};
let doc_guard = match doc_arc.lock() {
Ok(g) => g,
Err(_) => return,
};
let default_rect = Rect {
x0: 0.0,
y0: 0.0,
x1: 612.0,
y1: 792.0,
};
let data = doc_guard.data();
let parsed = parse_mediaboxes_from_pdf(data, count as usize);
(0..count as usize)
.map(|i| parsed.get(i).copied().unwrap_or(default_rect))
.collect()
};
if let Some(writer_arc) = WRITERS.get(wri) {
let mut writer = writer_arc.lock().unwrap();
for mediabox in &page_bounds {
writer.begin_page(*mediabox);
writer.end_page();
}
}
}
fn parse_mediaboxes_from_pdf(data: &[u8], max_pages: usize) -> Vec<Rect> {
let mut results = Vec::with_capacity(max_pages);
let page_pattern = b"/Type /Page";
let mut pos = 0;
while pos + page_pattern.len() < data.len() && results.len() < max_pages {
if let Some(offset) = find_bytes(&data[pos..], page_pattern) {
let abs = pos + offset;
let after = abs + page_pattern.len();
if data.get(after) == Some(&b's') {
pos = after;
continue;
}
let search_end = (after + 512).min(data.len());
if let Some(mb) = extract_mediabox(&data[abs..search_end]) {
results.push(mb);
} else {
results.push(Rect {
x0: 0.0,
y0: 0.0,
x1: 612.0,
y1: 792.0,
});
}
pos = after;
} else {
break;
}
}
results
}
fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|w| w == needle)
}
fn extract_mediabox(data: &[u8]) -> Option<Rect> {
let pattern = b"/MediaBox";
let offset = find_bytes(data, pattern)?;
let after = offset + pattern.len();
let rest = &data[after..];
let mut i = 0;
while i < rest.len() && rest[i].is_ascii_whitespace() {
i += 1;
}
if i >= rest.len() || rest[i] != b'[' {
return None;
}
i += 1;
let mut nums = [0.0f32; 4];
for num in &mut nums {
while i < rest.len() && rest[i].is_ascii_whitespace() {
i += 1;
}
let start = i;
while i < rest.len() && (rest[i].is_ascii_digit() || rest[i] == b'.' || rest[i] == b'-') {
i += 1;
}
if start == i {
return None;
}
let s = std::str::from_utf8(&rest[start..i]).ok()?;
*num = s.parse::<f32>().ok()?;
}
Some(Rect {
x0: nums[0],
y0: nums[1],
x1: nums[2],
y1: nums[3],
})
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_close_document_writer(_ctx: Handle, wri: Handle) {
if let Some(writer_arc) = WRITERS.get(wri) {
let mut writer = writer_arc.lock().unwrap();
writer.close();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_document_writer(_ctx: Handle, wri: Handle) {
if let Some(writer_arc) = WRITERS.get(wri) {
{
let mut writer = writer_arc.lock().unwrap();
if writer.state != WriterState::Closed {
writer.close();
}
}
}
WRITERS.remove(wri);
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_document_writer_format(wri: Handle) -> i32 {
if let Some(writer_arc) = WRITERS.get(wri) {
let writer = writer_arc.lock().unwrap();
writer.format as i32
} else {
-1
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_document_writer_page_count(wri: Handle) -> i32 {
if let Some(writer_arc) = WRITERS.get(wri) {
let writer = writer_arc.lock().unwrap();
writer.page_count
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_document_writer_is_closed(wri: Handle) -> i32 {
if let Some(writer_arc) = WRITERS.get(wri) {
let writer = writer_arc.lock().unwrap();
if writer.state == WriterState::Closed {
1
} else {
0
}
} else {
1
}
}
static OCR_PROGRESS_CALLBACKS: LazyLock<Mutex<HashMap<Handle, OcrProgressCallback>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub struct OcrProgressCallback {
pub callback: extern "C" fn(Handle, *mut std::ffi::c_void, i32, i32) -> i32,
pub user_data: *mut std::ffi::c_void,
}
unsafe impl Send for OcrProgressCallback {}
unsafe impl Sync for OcrProgressCallback {}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pdfocr_writer_set_progress(
_ctx: Handle,
wri: Handle,
progress: extern "C" fn(Handle, *mut std::ffi::c_void, i32, i32) -> i32,
user_data: *mut std::ffi::c_void,
) {
let mut callbacks = OCR_PROGRESS_CALLBACKS.lock().unwrap();
callbacks.insert(
wri,
OcrProgressCallback {
callback: progress,
user_data,
},
);
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn test_writer_format_from_str() {
assert_eq!(WriterFormat::from_str("pdf"), Some(WriterFormat::Pdf));
assert_eq!(WriterFormat::from_str("PDF"), Some(WriterFormat::Pdf));
assert_eq!(WriterFormat::from_str("svg"), Some(WriterFormat::Svg));
assert_eq!(WriterFormat::from_str("png"), Some(WriterFormat::Png));
assert_eq!(WriterFormat::from_str("jpeg"), Some(WriterFormat::Jpeg));
assert_eq!(WriterFormat::from_str("jpg"), Some(WriterFormat::Jpeg));
assert_eq!(WriterFormat::from_str("unknown"), None);
}
#[test]
fn test_writer_format_extension() {
assert_eq!(WriterFormat::Pdf.extension(), "pdf");
assert_eq!(WriterFormat::Svg.extension(), "svg");
assert_eq!(WriterFormat::Png.extension(), "png");
assert_eq!(WriterFormat::Jpeg.extension(), "jpg");
}
#[test]
fn test_writer_format_multipage() {
assert!(WriterFormat::Pdf.is_multipage());
assert!(WriterFormat::Svg.is_multipage());
assert!(!WriterFormat::Png.is_multipage());
assert!(!WriterFormat::Jpeg.is_multipage());
}
#[test]
fn test_writer_options_parse() {
let opts = WriterOptions::parse("resolution=300,quality=85,linearize");
assert_eq!(opts.resolution, Some(300.0));
assert_eq!(opts.quality, Some(85));
assert!(opts.linearize);
assert!(!opts.encrypt);
}
#[test]
fn test_writer_options_has() {
let opts = WriterOptions::parse("foo=bar,baz");
assert!(opts.has("foo"));
assert!(opts.has("baz"));
assert!(!opts.has("qux"));
}
#[test]
fn test_new_document_writer() {
let ctx = 1;
let path = CString::new("/tmp/test.pdf").unwrap();
let format = CString::new("pdf").unwrap();
let wri = fz_new_document_writer(ctx, path.as_ptr(), format.as_ptr(), std::ptr::null());
assert!(wri > 0);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_pdf_writer() {
let ctx = 1;
let path = CString::new("/tmp/test_pdf.pdf").unwrap();
let wri = fz_new_pdf_writer(ctx, path.as_ptr(), std::ptr::null());
assert!(wri > 0);
let dev = fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
assert!(dev > 0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_svg_writer() {
let ctx = 1;
let path = CString::new("/tmp/test.svg").unwrap();
let wri = fz_new_svg_writer(ctx, path.as_ptr(), std::ptr::null());
assert!(wri > 0);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_text_writer() {
let ctx = 1;
let format = CString::new("text").unwrap();
let path = CString::new("/tmp/test.txt").unwrap();
let wri = fz_new_text_writer(ctx, format.as_ptr(), path.as_ptr(), std::ptr::null());
assert!(wri > 0);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_png_writer() {
let ctx = 1;
let path = CString::new("/tmp/test.png").unwrap();
let wri = fz_new_png_pixmap_writer(ctx, path.as_ptr(), std::ptr::null());
assert!(wri > 0);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_option_eq() {
let a1 = CString::new("foo").unwrap();
let a2 = CString::new("foo,bar").unwrap();
let b = CString::new("foo").unwrap();
assert_eq!(fz_option_eq(a1.as_ptr(), b.as_ptr()), 1);
assert_eq!(fz_option_eq(a2.as_ptr(), b.as_ptr()), 1);
let c = CString::new("bar").unwrap();
assert_eq!(fz_option_eq(a1.as_ptr(), c.as_ptr()), 0);
}
#[test]
fn test_copy_option() {
let ctx = 1;
let val = CString::new("hello,world").unwrap();
let mut dest = [0i8; 32];
let overflow = fz_copy_option(ctx, val.as_ptr(), dest.as_mut_ptr(), 32);
assert_eq!(overflow, 0);
let result = unsafe { CStr::from_ptr(dest.as_ptr()) };
assert_eq!(result.to_str().unwrap(), "hello");
}
#[test]
fn test_writer_page_count() {
let ctx = 1;
let wri = fz_new_pdf_writer(ctx, std::ptr::null(), std::ptr::null());
assert_eq!(fz_document_writer_page_count(wri), 0);
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
assert_eq!(fz_document_writer_page_count(wri), 1);
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
assert_eq!(fz_document_writer_page_count(wri), 2);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_writer_is_closed() {
let ctx = 1;
let wri = fz_new_pdf_writer(ctx, std::ptr::null(), std::ptr::null());
assert_eq!(fz_document_writer_is_closed(wri), 0);
fz_close_document_writer(ctx, wri);
assert_eq!(fz_document_writer_is_closed(wri), 1);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_all_format_writers() {
let ctx = 1;
let writers = vec![
fz_new_pdf_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_svg_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_odt_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_docx_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_ps_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_pcl_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_pclm_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_pwg_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_cbz_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_csv_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_pdfocr_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_png_pixmap_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_jpeg_pixmap_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_pam_pixmap_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_pnm_pixmap_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_pgm_pixmap_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_ppm_pixmap_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_pbm_pixmap_writer(ctx, std::ptr::null(), std::ptr::null()),
fz_new_pkm_pixmap_writer(ctx, std::ptr::null(), std::ptr::null()),
];
for wri in writers {
assert!(wri > 0);
fz_drop_document_writer(ctx, wri);
}
}
#[test]
fn test_has_option_null_opts() {
let key = CString::new("resolution").unwrap();
assert_eq!(
fz_has_option(0, std::ptr::null(), key.as_ptr(), std::ptr::null_mut()),
0
);
}
#[test]
fn test_has_option_null_key() {
let opts = CString::new("resolution=300").unwrap();
assert_eq!(
fz_has_option(0, opts.as_ptr(), std::ptr::null(), std::ptr::null_mut()),
0
);
}
#[test]
fn test_has_option_found() {
let opts = CString::new("resolution=300,quality=85").unwrap();
let key = CString::new("resolution").unwrap();
let mut val: *const std::ffi::c_char = std::ptr::null();
assert_eq!(fz_has_option(0, opts.as_ptr(), key.as_ptr(), &mut val), 1);
}
#[test]
fn test_has_option_not_found() {
let opts = CString::new("resolution=300").unwrap();
let key = CString::new("nonexistent").unwrap();
assert_eq!(
fz_has_option(0, opts.as_ptr(), key.as_ptr(), std::ptr::null_mut()),
0
);
}
#[test]
fn test_option_eq_null() {
let a = CString::new("foo").unwrap();
assert_eq!(fz_option_eq(a.as_ptr(), std::ptr::null()), 0);
assert_eq!(fz_option_eq(std::ptr::null(), a.as_ptr()), 0);
}
#[test]
fn test_copy_option_null_val() {
let mut dest = [0i8; 32];
assert_eq!(
fz_copy_option(0, std::ptr::null(), dest.as_mut_ptr(), 32),
0
);
}
#[test]
fn test_copy_option_null_dest() {
let val = CString::new("hello").unwrap();
assert_eq!(fz_copy_option(0, val.as_ptr(), std::ptr::null_mut(), 32), 0);
}
#[test]
fn test_copy_option_zero_maxlen() {
let val = CString::new("hello").unwrap();
let mut dest = [0i8; 32];
assert_eq!(fz_copy_option(0, val.as_ptr(), dest.as_mut_ptr(), 0), 0);
}
#[test]
fn test_new_document_writer_null_path() {
let format = CString::new("pdf").unwrap();
let wri = fz_new_document_writer(0, std::ptr::null(), format.as_ptr(), std::ptr::null());
assert!(wri > 0);
fz_drop_document_writer(0, wri);
}
#[test]
fn test_new_document_writer_null_format() {
let path = CString::new("/tmp/out.pdf").unwrap();
let wri = fz_new_document_writer(0, path.as_ptr(), std::ptr::null(), std::ptr::null());
assert!(wri > 0);
fz_drop_document_writer(0, wri);
}
#[test]
fn test_writer_format() {
let wri = fz_new_pdf_writer(0, std::ptr::null(), std::ptr::null());
assert_eq!(fz_document_writer_format(wri), 0);
fz_drop_document_writer(0, wri);
}
#[test]
fn test_writer_format_invalid_handle() {
assert_eq!(fz_document_writer_format(0), -1);
}
#[test]
fn test_begin_page_invalid_writer() {
assert_eq!(fz_begin_page(0, 0, 0.0, 0.0, 612.0, 792.0), 0);
}
#[test]
fn test_writer_with_output() {
let format = CString::new("pdf").unwrap();
let wri = fz_new_document_writer_with_output(0, 0, format.as_ptr(), std::ptr::null());
assert!(wri > 0);
fz_drop_document_writer(0, wri);
}
#[test]
fn test_writer_with_buffer() {
let format = CString::new("pdf").unwrap();
let wri = fz_new_document_writer_with_buffer(0, 0, format.as_ptr(), std::ptr::null());
assert!(wri > 0);
fz_drop_document_writer(0, wri);
}
#[test]
fn test_writer_options_encrypt() {
let opts = WriterOptions::parse("encrypt");
assert!(opts.encrypt);
}
#[test]
fn test_writer_options_get() {
let opts = WriterOptions::parse("resolution=300");
assert_eq!(opts.get("resolution"), Some(&"300".to_string()));
}
#[test]
fn test_writer_format_is_image() {
assert!(WriterFormat::Png.is_image());
assert!(WriterFormat::Jpeg.is_image());
assert!(!WriterFormat::Pdf.is_image());
}
#[test]
fn test_writer_format_postscript() {
assert_eq!(WriterFormat::from_str("postscript"), Some(WriterFormat::Ps));
}
#[test]
fn test_writer_format_ocr() {
assert_eq!(WriterFormat::from_str("ocr"), Some(WriterFormat::Pdfocr));
}
#[test]
fn test_writer_begin_page_when_closed() {
let ctx = 1;
let path = CString::new("/tmp/closed.pdf").unwrap();
let wri = fz_new_pdf_writer(ctx, path.as_ptr(), std::ptr::null());
fz_close_document_writer(ctx, wri);
let dev = fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
assert_eq!(dev, 0);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_writer_end_page_when_no_page() {
let ctx = 1;
let wri = fz_new_pdf_writer(ctx, std::ptr::null(), std::ptr::null());
fz_end_page(ctx, wri);
assert_eq!(fz_document_writer_page_count(wri), 0);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_writer_close_already_closed() {
let ctx = 1;
let wri = fz_new_pdf_writer(ctx, std::ptr::null(), std::ptr::null());
fz_close_document_writer(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_writer_options_empty_parts() {
let opts = WriterOptions::parse(" , , ");
assert!(opts.options.is_empty());
}
#[test]
fn test_writer_options_boolean_flags() {
let opts = WriterOptions::parse("linearize,encrypt");
assert!(opts.linearize);
assert!(opts.encrypt);
}
#[test]
fn test_writer_options_pagesize() {
let opts = WriterOptions::parse("pagesize=letter");
assert_eq!(opts.page_size, Some("letter".to_string()));
}
#[test]
fn test_writer_options_ocr_language() {
let opts = WriterOptions::parse("language=eng");
assert_eq!(opts.ocr_language, Some("eng".to_string()));
}
#[test]
fn test_copy_option_overflow() {
let ctx = 1;
let val = CString::new("hello").unwrap();
let mut dest = [0i8; 4];
let overflow = fz_copy_option(ctx, val.as_ptr(), dest.as_mut_ptr(), 4);
assert!(overflow > 0);
}
#[test]
fn test_fz_has_option_with_val_ptr() {
let opts = CString::new("resolution=300").unwrap();
let key = CString::new("resolution").unwrap();
let mut val: *const std::ffi::c_char = std::ptr::null();
assert_eq!(fz_has_option(0, opts.as_ptr(), key.as_ptr(), &mut val), 1);
}
#[test]
fn test_writer_format_txt() {
assert_eq!(WriterFormat::from_str("txt"), Some(WriterFormat::Text));
}
#[test]
fn test_writer_format_all_image_formats() {
assert_eq!(WriterFormat::from_str("pam"), Some(WriterFormat::Pam));
assert_eq!(WriterFormat::from_str("pnm"), Some(WriterFormat::Pnm));
assert_eq!(WriterFormat::from_str("pgm"), Some(WriterFormat::Pgm));
assert_eq!(WriterFormat::from_str("ppm"), Some(WriterFormat::Ppm));
assert_eq!(WriterFormat::from_str("pbm"), Some(WriterFormat::Pbm));
assert_eq!(WriterFormat::from_str("pkm"), Some(WriterFormat::Pkm));
}
#[test]
fn test_writer_format_all_multipage() {
assert!(WriterFormat::Cbz.is_multipage());
assert!(WriterFormat::Csv.is_multipage());
assert!(WriterFormat::Odt.is_multipage());
assert!(WriterFormat::Docx.is_multipage());
assert!(WriterFormat::Ps.is_multipage());
assert!(WriterFormat::Pcl.is_multipage());
assert!(WriterFormat::Pclm.is_multipage());
assert!(WriterFormat::Pwg.is_multipage());
}
#[test]
fn test_pdf_writer_with_path_writes_file() {
let tmp = tempfile::NamedTempFile::new().unwrap();
let path = tmp.path().to_path_buf();
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let ctx = 1;
let wri = fz_new_pdf_writer(ctx, path_c.as_ptr(), std::ptr::null());
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
let content = std::fs::read(&path).unwrap();
assert!(content.starts_with(b"%PDF"));
}
#[test]
fn test_svg_writer_multipage() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("out.svg");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let ctx = 1;
let wri = fz_new_svg_writer(ctx, path_c.as_ptr(), std::ptr::null());
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_begin_page(ctx, wri, 0.0, 0.0, 595.0, 842.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
let page2 = tmp.path().join("out_2.svg");
assert!(path.exists() || page2.exists());
}
#[test]
fn test_cbz_writer_writes_zip() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("out.cbz");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let ctx = 1;
let wri = fz_new_cbz_writer(ctx, path_c.as_ptr(), std::ptr::null());
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
let content = std::fs::read(&path).unwrap();
assert!(content.starts_with(&[0x50, 0x4b, 0x03, 0x04]));
}
#[test]
fn test_fz_write_document() {
let tmp = tempfile::TempDir::new().unwrap();
let pdf_path = tmp.path().join("doc.pdf");
let out_path = tmp.path().join("out.pdf");
let doc = crate::cpdf::document::CpdfDocument::blank(612.0, 792.0, 2).unwrap();
std::fs::write(&pdf_path, doc.to_bytes()).unwrap();
let doc_handle = crate::ffi::document::fz_open_document(
0,
CString::new(pdf_path.to_str().unwrap()).unwrap().as_ptr(),
);
assert!(doc_handle > 0);
let wri = fz_new_document_writer(
0,
CString::new(out_path.to_str().unwrap()).unwrap().as_ptr(),
CString::new("pdf").unwrap().as_ptr(),
std::ptr::null(),
);
assert!(wri > 0);
fz_write_document(0, wri, doc_handle);
fz_close_document_writer(0, wri);
fz_drop_document_writer(0, wri);
crate::ffi::document::fz_drop_document(0, doc_handle);
assert!(out_path.exists());
let content = std::fs::read(&out_path).unwrap();
assert!(content.starts_with(b"%PDF"));
}
#[test]
fn test_fz_write_document_invalid_doc() {
let wri = fz_new_pdf_writer(0, std::ptr::null(), std::ptr::null());
fz_write_document(0, wri, 0);
fz_drop_document_writer(0, wri);
}
#[test]
fn test_fz_pdfocr_writer_set_progress() {
let wri = fz_new_pdfocr_writer(0, std::ptr::null(), std::ptr::null());
extern "C" fn progress_cb(_: u64, _: *mut std::ffi::c_void, _: i32, _: i32) -> i32 {
0
}
fz_pdfocr_writer_set_progress(0, wri, progress_cb, std::ptr::null_mut());
fz_drop_document_writer(0, wri);
}
#[test]
fn test_text_writer_html_format() {
let ctx = 1;
let format = CString::new("html").unwrap();
let path = CString::new("/tmp/test.html").unwrap();
let wri = fz_new_text_writer(ctx, format.as_ptr(), path.as_ptr(), std::ptr::null());
assert!(wri > 0);
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_text_writer_xhtml_format() {
let ctx = 1;
let format = CString::new("xhtml").unwrap();
let path = CString::new("/tmp/test.xhtml").unwrap();
let wri = fz_new_text_writer(ctx, format.as_ptr(), path.as_ptr(), std::ptr::null());
assert!(wri > 0);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_odt_writer_writes_file() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("out.odt");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let ctx = 1;
let wri = fz_new_odt_writer(ctx, path_c.as_ptr(), std::ptr::null());
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_ps_writer_writes_file() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("out.ps");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let ctx = 1;
let wri = fz_new_ps_writer(ctx, path_c.as_ptr(), std::ptr::null());
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_pcl_writer_writes_file() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("out.pcl");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let ctx = 1;
let wri = fz_new_pcl_writer(ctx, path_c.as_ptr(), std::ptr::null());
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_pwg_writer_writes_file() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("out.pwg");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let ctx = 1;
let wri = fz_new_pwg_writer(ctx, path_c.as_ptr(), std::ptr::null());
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_text_writer_writes_file() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("out.txt");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let format = CString::new("text").unwrap();
let ctx = 1;
let wri = fz_new_text_writer(ctx, format.as_ptr(), path_c.as_ptr(), std::ptr::null());
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
assert!(path.exists());
}
#[test]
fn test_pdf_writer_compression_large_content() {
let tmp = tempfile::NamedTempFile::new().unwrap();
let path = tmp.path().to_path_buf();
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let ctx = 1;
let wri = fz_new_pdf_writer(ctx, path_c.as_ptr(), std::ptr::null());
fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
fz_end_page(ctx, wri);
fz_close_document_writer(ctx, wri);
fz_drop_document_writer(ctx, wri);
let content = std::fs::read(&path).unwrap();
assert!(content.starts_with(b"%PDF"));
assert!(content.windows(b"endobj".len()).any(|w| w == b"endobj"));
}
#[test]
fn test_drop_document_writer_invalid_handle() {
fz_drop_document_writer(0, 0);
}
#[test]
fn test_end_page_removes_device() {
let ctx = 1;
let wri = fz_new_pdf_writer(ctx, std::ptr::null(), std::ptr::null());
let dev = fz_begin_page(ctx, wri, 0.0, 0.0, 612.0, 792.0);
assert!(dev > 0);
fz_end_page(ctx, wri);
assert_eq!(fz_document_writer_page_count(wri), 1);
fz_drop_document_writer(ctx, wri);
}
#[test]
fn test_writer_format_infer_from_path() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("doc.svg");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let wri = fz_new_document_writer(0, path_c.as_ptr(), std::ptr::null(), std::ptr::null());
assert!(wri > 0);
assert_eq!(fz_document_writer_format(wri), 1);
fz_drop_document_writer(0, wri);
}
#[test]
fn test_writer_options_parse_resolution() {
let opts = WriterOptions::parse("resolution=150,res=200,dpi=300");
assert_eq!(opts.resolution, Some(300.0));
}
#[test]
fn test_writer_options_parse_compression() {
let opts = WriterOptions::parse("compression=0");
assert_eq!(opts.compression, Some(0));
}
#[test]
fn test_writer_options_parse_colorspace() {
let opts = WriterOptions::parse("colorspace=gray,cs=rgb");
assert_eq!(opts.colorspace, Some("rgb".to_string()));
}
#[test]
fn test_writer_options_parse_page_size() {
let opts = WriterOptions::parse("page-size=a4,pagesize=letter");
assert_eq!(opts.page_size, Some("letter".to_string()));
}
#[test]
fn test_writer_options_parse_ocr_language() {
let opts = WriterOptions::parse("ocr-language=eng,lang=deu");
assert_eq!(opts.ocr_language, Some("deu".to_string()));
}
#[test]
fn test_writer_options_parse_linear() {
let opts = WriterOptions::parse("linear");
assert!(opts.linearize);
}
#[test]
fn test_writer_options_parse_encrypted() {
let opts = WriterOptions::parse("encrypted");
assert!(opts.encrypt);
}
}