use crate::ffi::colorspace::{
COLORSPACES, Colorspace, ColorspaceType, FZ_COLORSPACE_CMYK, FZ_COLORSPACE_GRAY,
FZ_COLORSPACE_LAB, FZ_COLORSPACE_RGB,
};
use crate::ffi::image::IMAGES;
use crate::ffi::pdf_object::types::{PDF_OBJECTS, PdfObj, PdfObjType};
use crate::ffi::shade::{SHADES, Shade, ShadeType};
use crate::ffi::{BUFFERS, Handle, HandleStore};
use crate::fitz::image::Image;
use std::collections::HashMap;
use std::ffi::{CStr, c_char, c_void};
use std::ptr;
use std::sync::LazyLock;
type ContextHandle = Handle;
type DocumentHandle = Handle;
type PdfObjHandle = Handle;
type FontHandle = Handle;
type ImageHandle = Handle;
type ColorspaceHandle = Handle;
type ShadeHandle = Handle;
type StreamHandle = Handle;
type BufferHandle = Handle;
pub const PDF_SIMPLE_FONT_RESOURCE: i32 = 1;
pub const PDF_CID_FONT_RESOURCE: i32 = 2;
pub const PDF_CJK_FONT_RESOURCE: i32 = 3;
pub const PDF_SIMPLE_ENCODING_LATIN: i32 = 0;
pub const PDF_SIMPLE_ENCODING_GREEK: i32 = 1;
pub const PDF_SIMPLE_ENCODING_CYRILLIC: i32 = 2;
#[derive(Debug, Clone, Default)]
#[repr(C)]
pub struct FontResourceKey {
pub digest: [u8; 16],
pub font_type: i32,
pub encoding: i32,
pub local_xref: i32,
}
#[derive(Debug, Clone, Default)]
#[repr(C)]
pub struct ColorspaceResourceKey {
pub digest: [u8; 16],
pub local_xref: i32,
}
#[derive(Debug, Clone)]
pub struct ResourceStack {
pub resources: PdfObjHandle,
pub next: Option<Handle>,
}
impl Default for ResourceStack {
fn default() -> Self {
Self::new()
}
}
impl ResourceStack {
pub fn new() -> Self {
Self {
resources: 0,
next: None,
}
}
pub fn with_resources(resources: PdfObjHandle) -> Self {
Self {
resources,
next: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Pattern {
pub refs: i32,
pub is_mask: bool,
pub xstep: f32,
pub ystep: f32,
pub matrix: [f32; 6],
pub bbox: [f32; 4],
pub document: DocumentHandle,
pub resources: PdfObjHandle,
pub contents: PdfObjHandle,
pub id: i32,
}
impl Default for Pattern {
fn default() -> Self {
Self::new()
}
}
impl Pattern {
pub fn new() -> Self {
static PATTERN_ID: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1);
Self {
refs: 1,
is_mask: false,
xstep: 0.0,
ystep: 0.0,
matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
bbox: [0.0, 0.0, 0.0, 0.0],
document: 0,
resources: 0,
contents: 0,
id: PATTERN_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub enum FunctionType {
Sampled = 0,
Exponential = 2,
Stitching = 3,
PostScript = 4,
}
#[derive(Debug, Clone)]
pub struct Function {
pub refs: i32,
pub func_type: FunctionType,
pub n_inputs: i32,
pub n_outputs: i32,
pub domain: Vec<f32>,
pub range: Vec<f32>,
pub samples: Vec<f32>,
pub c0: Vec<f32>,
pub c1: Vec<f32>,
pub n: f32,
pub funcs: Vec<Handle>,
pub bounds: Vec<f32>,
pub encode: Vec<f32>,
}
impl Default for Function {
fn default() -> Self {
Self::new()
}
}
impl Function {
pub fn new() -> Self {
Self {
refs: 1,
func_type: FunctionType::Sampled,
n_inputs: 1,
n_outputs: 1,
domain: vec![0.0, 1.0],
range: vec![0.0, 1.0],
samples: Vec::new(),
c0: vec![0.0],
c1: vec![1.0],
n: 1.0,
funcs: Vec::new(),
bounds: Vec::new(),
encode: Vec::new(),
}
}
pub fn eval(&self, input: &[f32], output: &mut [f32]) {
self.run(input, output);
}
fn run(&self, input: &[f32], output: &mut [f32]) {
match self.func_type {
FunctionType::Sampled => self.eval_sampled(input, output),
FunctionType::Exponential => self.eval_exponential(input, output),
FunctionType::Stitching => self.eval_stitching(input, output),
FunctionType::PostScript => self.eval_postscript(input, output),
}
}
fn eval_sampled(&self, input: &[f32], output: &mut [f32]) {
if self.samples.is_empty() || output.is_empty() {
return;
}
let t = input.first().copied().unwrap_or(0.0).clamp(0.0, 1.0);
let idx = (t * (self.samples.len() - 1) as f32) as usize;
for (i, out) in output.iter_mut().enumerate() {
*out = self.samples.get(idx + i).copied().unwrap_or(0.0);
}
}
fn eval_exponential(&self, input: &[f32], output: &mut [f32]) {
let x = input.first().copied().unwrap_or(0.0).clamp(0.0, 1.0);
let x_n = x.powf(self.n);
for (i, out) in output.iter_mut().enumerate() {
let c0 = self.c0.get(i).copied().unwrap_or(0.0);
let c1 = self.c1.get(i).copied().unwrap_or(1.0);
*out = c0 + x_n * (c1 - c0);
}
}
fn eval_stitching(&self, input: &[f32], output: &mut [f32]) {
if self.funcs.is_empty() || output.is_empty() {
return;
}
let x = input.first().copied().unwrap_or(0.0);
let k = self.funcs.len();
let domain_lo = self.domain.first().copied().unwrap_or(0.0);
let domain_hi = self.domain.get(1).copied().unwrap_or(1.0);
let x_clamped = x.clamp(domain_lo, domain_hi);
let mut func_idx = k - 1; for (i, &bound) in self.bounds.iter().enumerate() {
if x_clamped < bound {
func_idx = i;
break;
}
}
func_idx = func_idx.min(k - 1);
let low_bound = if func_idx == 0 {
domain_lo
} else {
self.bounds.get(func_idx - 1).copied().unwrap_or(domain_lo)
};
let high_bound = if func_idx < self.bounds.len() {
self.bounds[func_idx]
} else {
domain_hi
};
let encode_lo = self.encode.get(func_idx * 2).copied().unwrap_or(0.0);
let encode_hi = self.encode.get(func_idx * 2 + 1).copied().unwrap_or(1.0);
let mapped = if (high_bound - low_bound).abs() < f32::EPSILON {
encode_lo
} else {
encode_lo + (x_clamped - low_bound) / (high_bound - low_bound) * (encode_hi - encode_lo)
};
let sub_handle = self.funcs[func_idx];
if let Some(sub_arc) = FUNCTIONS.get(sub_handle) {
let sub = sub_arc.lock().unwrap();
sub.run(&[mapped], output);
} else {
for out in output.iter_mut() {
*out = mapped.clamp(0.0, 1.0);
}
}
}
fn eval_postscript(&self, input: &[f32], output: &mut [f32]) {
if output.is_empty() {
return;
}
if self.samples.is_empty() {
for (i, out) in output.iter_mut().enumerate() {
*out = input.get(i).copied().unwrap_or(0.0);
}
for (i, out) in output.iter_mut().enumerate() {
let lo = self.range.get(i * 2).copied().unwrap_or(0.0);
let hi = self.range.get(i * 2 + 1).copied().unwrap_or(1.0);
*out = out.clamp(lo, hi);
}
return;
}
let mut stack: Vec<f32> = Vec::with_capacity(32);
for &v in input {
stack.push(v);
}
for &raw in &self.samples {
let opcode = raw as i32;
match opcode {
1 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a + b);
}
}
2 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a - b);
}
}
3 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a * b);
}
}
4 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
if b.abs() > f32::EPSILON {
stack.push(a / b);
} else {
stack.push(0.0);
}
}
}
5 => {
if let Some(v) = stack.pop() {
stack.push(-v);
}
}
6 => {
if let Some(v) = stack.pop() {
stack.push(v.abs());
}
}
7 => {
if let Some(v) = stack.pop() {
stack.push(v.floor());
}
}
8 => {
if let Some(v) = stack.pop() {
stack.push(v.ceil());
}
}
9 => {
if let Some(v) = stack.pop() {
stack.push(v.round());
}
}
10 => {
if let Some(v) = stack.pop() {
stack.push(v.trunc());
}
}
11 => {
if let Some(v) = stack.pop() {
stack.push(if v >= 0.0 { v.sqrt() } else { 0.0 });
}
}
12 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
if b.abs() > f32::EPSILON {
stack.push(a % b);
} else {
stack.push(0.0);
}
}
}
13 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a.powf(b));
}
}
14 => {
if let Some(v) = stack.pop() {
stack.push(v.to_radians().sin());
}
}
15 => {
if let Some(v) = stack.pop() {
stack.push(v.to_radians().cos());
}
}
16 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a.atan2(b).to_degrees());
}
}
17 => {
if let Some(v) = stack.pop() {
stack.push(if v > 0.0 { v.log10() } else { 0.0 });
}
}
18 => {
if let Some(v) = stack.pop() {
stack.push(if v > 0.0 { v.ln() } else { 0.0 });
}
}
19 => {
if let Some(&v) = stack.last() {
stack.push(v);
}
}
20 => {
let len = stack.len();
if len >= 2 {
stack.swap(len - 1, len - 2);
}
}
21 => {
stack.pop();
}
22 => {
if let Some(v) = stack.pop() {
let n = v as usize;
let len = stack.len();
if n > 0 && n <= len {
let start = len - n;
let items: Vec<f32> = stack[start..].to_vec();
stack.extend_from_slice(&items);
}
}
}
23 => {
if let Some(v) = stack.pop() {
let n = v as usize;
let len = stack.len();
if n < len {
let val = stack[len - 1 - n];
stack.push(val);
}
}
}
24 => {
if stack.len() >= 2 {
let j = stack.pop().unwrap() as i32;
let n = stack.pop().unwrap() as usize;
let len = stack.len();
if n > 0 && n <= len {
let start = len - n;
let sub = &mut stack[start..];
let shift = ((j % n as i32) + n as i32) as usize % n;
sub.rotate_right(shift);
}
}
}
30 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(if (a - b).abs() < f32::EPSILON {
1.0
} else {
0.0
});
}
}
31 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(if (a - b).abs() >= f32::EPSILON {
1.0
} else {
0.0
});
}
}
32 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(if a > b { 1.0 } else { 0.0 });
}
}
33 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(if a >= b { 1.0 } else { 0.0 });
}
}
34 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(if a < b { 1.0 } else { 0.0 });
}
}
35 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(if a <= b { 1.0 } else { 0.0 });
}
}
40 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap() as i32;
let a = stack.pop().unwrap() as i32;
stack.push((a & b) as f32);
}
}
41 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap() as i32;
let a = stack.pop().unwrap() as i32;
stack.push((a | b) as f32);
}
}
42 => {
if stack.len() >= 2 {
let b = stack.pop().unwrap() as i32;
let a = stack.pop().unwrap() as i32;
stack.push((a ^ b) as f32);
}
}
43 => {
if let Some(v) = stack.pop() {
let i = v as i32;
stack.push((!i) as f32);
}
}
44 => {
if stack.len() >= 2 {
let shift = stack.pop().unwrap() as i32;
let val = stack.pop().unwrap() as i32;
let result = if shift >= 0 {
val.wrapping_shl(shift as u32)
} else {
val.wrapping_shr((-shift) as u32)
};
stack.push(result as f32);
}
}
_ => {
stack.push(raw);
}
}
}
let n_out = output.len();
for i in (0..n_out).rev() {
output[i] = stack.pop().unwrap_or(0.0);
}
for (i, out) in output.iter_mut().enumerate() {
let lo = self.range.get(i * 2).copied().unwrap_or(0.0);
let hi = self.range.get(i * 2 + 1).copied().unwrap_or(1.0);
*out = out.clamp(lo, hi);
}
}
pub fn size(&self) -> usize {
std::mem::size_of::<Function>()
+ self.domain.len() * 4
+ self.range.len() * 4
+ self.samples.len() * 4
+ self.c0.len() * 4
+ self.c1.len() * 4
+ self.funcs.len() * 8
+ self.bounds.len() * 4
+ self.encode.len() * 4
}
}
#[derive(Debug, Default)]
pub struct ResourceTables {
pub fonts: HashMap<[u8; 16], PdfObjHandle>,
pub colorspaces: HashMap<[u8; 16], PdfObjHandle>,
pub images: HashMap<[u8; 16], PdfObjHandle>,
pub patterns: HashMap<[u8; 16], PdfObjHandle>,
pub shadings: HashMap<[u8; 16], PdfObjHandle>,
}
impl ResourceTables {
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
self.fonts.clear();
self.colorspaces.clear();
self.images.clear();
self.patterns.clear();
self.shadings.clear();
}
}
pub static RESOURCE_STACKS: LazyLock<HandleStore<ResourceStack>> = LazyLock::new(HandleStore::new);
pub static PATTERNS: LazyLock<HandleStore<Pattern>> = LazyLock::new(HandleStore::new);
pub static FUNCTIONS: LazyLock<HandleStore<Function>> = LazyLock::new(HandleStore::new);
pub static DOC_RESOURCE_TABLES: LazyLock<
std::sync::Mutex<HashMap<DocumentHandle, ResourceTables>>,
> = LazyLock::new(|| std::sync::Mutex::new(HashMap::new()));
static PDF_ITEM_STORE: LazyLock<std::sync::Mutex<HashMap<PdfObjHandle, usize>>> =
LazyLock::new(|| std::sync::Mutex::new(HashMap::new()));
#[unsafe(no_mangle)]
pub extern "C" fn pdf_store_item(
_ctx: ContextHandle,
key: PdfObjHandle,
val: *mut c_void,
_itemsize: usize,
) {
if key == 0 || val.is_null() {
return;
}
if let Ok(mut store) = PDF_ITEM_STORE.lock() {
store.insert(key, val as usize);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_find_item(
_ctx: ContextHandle,
_drop: *const c_void,
key: PdfObjHandle,
) -> *mut c_void {
if key == 0 {
return ptr::null_mut();
}
if let Ok(store) = PDF_ITEM_STORE.lock() {
if let Some(&addr) = store.get(&key) {
return addr as *mut c_void;
}
}
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_remove_item(_ctx: ContextHandle, _drop: *const c_void, key: PdfObjHandle) {
if key == 0 {
return;
}
if let Ok(mut store) = PDF_ITEM_STORE.lock() {
store.remove(&key);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_empty_store(_ctx: ContextHandle, doc: DocumentHandle) {
let mut tables = DOC_RESOURCE_TABLES.lock().unwrap();
if let Some(t) = tables.get_mut(&doc) {
t.clear();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_purge_locals_from_store(_ctx: ContextHandle, doc: DocumentHandle) {
let mut tables = DOC_RESOURCE_TABLES.lock().unwrap();
if let Some(t) = tables.get_mut(&doc) {
t.clear();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_purge_object_from_store(_ctx: ContextHandle, doc: DocumentHandle, num: i32) {
let handle_to_purge = num as u64;
if let Ok(mut tables) = DOC_RESOURCE_TABLES.lock() {
if let Some(t) = tables.get_mut(&doc) {
t.fonts.retain(|_, &mut v| v != handle_to_purge);
t.colorspaces.retain(|_, &mut v| v != handle_to_purge);
t.images.retain(|_, &mut v| v != handle_to_purge);
t.patterns.retain(|_, &mut v| v != handle_to_purge);
t.shadings.retain(|_, &mut v| v != handle_to_purge);
}
}
if let Ok(mut store) = PDF_ITEM_STORE.lock() {
store.remove(&handle_to_purge);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_find_font_resource(
_ctx: ContextHandle,
doc: DocumentHandle,
_font_type: i32,
_encoding: i32,
_item: FontHandle,
key: *mut FontResourceKey,
) -> PdfObjHandle {
if key.is_null() {
return 0;
}
let tables = DOC_RESOURCE_TABLES.lock().unwrap();
if let Some(t) = tables.get(&doc) {
let digest = unsafe { (*key).digest };
if let Some(&obj) = t.fonts.get(&digest) {
return obj;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_insert_font_resource(
_ctx: ContextHandle,
doc: DocumentHandle,
key: *const FontResourceKey,
obj: PdfObjHandle,
) -> PdfObjHandle {
if key.is_null() {
return 0;
}
let mut tables = DOC_RESOURCE_TABLES.lock().unwrap();
let t = tables.entry(doc).or_default();
let digest = unsafe { (*key).digest };
t.fonts.insert(digest, obj);
obj
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_find_colorspace_resource(
_ctx: ContextHandle,
doc: DocumentHandle,
_item: ColorspaceHandle,
key: *mut ColorspaceResourceKey,
) -> PdfObjHandle {
if key.is_null() {
return 0;
}
let tables = DOC_RESOURCE_TABLES.lock().unwrap();
if let Some(t) = tables.get(&doc) {
let digest = unsafe { (*key).digest };
if let Some(&obj) = t.colorspaces.get(&digest) {
return obj;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_insert_colorspace_resource(
_ctx: ContextHandle,
doc: DocumentHandle,
key: *const ColorspaceResourceKey,
obj: PdfObjHandle,
) -> PdfObjHandle {
if key.is_null() {
return 0;
}
let mut tables = DOC_RESOURCE_TABLES.lock().unwrap();
let t = tables.entry(doc).or_default();
let digest = unsafe { (*key).digest };
t.colorspaces.insert(digest, obj);
obj
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_resource_tables(_ctx: ContextHandle, doc: DocumentHandle) {
let mut tables = DOC_RESOURCE_TABLES.lock().unwrap();
tables.remove(&doc);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_purge_local_resources(_ctx: ContextHandle, doc: DocumentHandle) {
let mut tables = DOC_RESOURCE_TABLES.lock().unwrap();
if let Some(t) = tables.get_mut(&doc) {
t.clear();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_resource_stack(_ctx: ContextHandle, resources: PdfObjHandle) -> Handle {
let stack = ResourceStack::with_resources(resources);
RESOURCE_STACKS.insert(stack)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_push_resource_stack(
_ctx: ContextHandle,
stack: Handle,
resources: PdfObjHandle,
) -> Handle {
let mut new_stack = ResourceStack::with_resources(resources);
new_stack.next = Some(stack);
RESOURCE_STACKS.insert(new_stack)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pop_resource_stack(_ctx: ContextHandle, stack: Handle) -> Handle {
if let Some(stack_arc) = RESOURCE_STACKS.get(stack) {
let s = stack_arc.lock().unwrap();
if let Some(next) = s.next {
RESOURCE_STACKS.remove(stack);
return next;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_resource_stack(_ctx: ContextHandle, stack: Handle) {
RESOURCE_STACKS.remove(stack);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_lookup_resource(
_ctx: ContextHandle,
stack: Handle,
res_type: PdfObjHandle,
name: *const c_char,
) -> PdfObjHandle {
if name.is_null() || stack == 0 {
return 0;
}
let name_str = unsafe { CStr::from_ptr(name) }
.to_str()
.unwrap_or("")
.to_string();
if name_str.is_empty() {
return 0;
}
let type_name = if let Some(arc) = PDF_OBJECTS.get(res_type) {
if let Ok(guard) = arc.lock() {
match &guard.obj_type {
PdfObjType::Name(s) => s.clone(),
_ => return 0,
}
} else {
return 0;
}
} else {
return 0;
};
let mut current = Some(stack);
while let Some(handle) = current {
if let Some(stack_arc) = RESOURCE_STACKS.get(handle) {
let s = stack_arc.lock().unwrap();
let res_dict = s.resources;
let next = s.next;
drop(s);
if res_dict != 0 {
if let Some(res_arc) = PDF_OBJECTS.get(res_dict) {
if let Ok(res_guard) = res_arc.lock() {
if let PdfObjType::Dict(entries) = &res_guard.obj_type {
if let Some((_, sub_dict_obj)) =
entries.iter().find(|(k, _)| k == &type_name)
{
if let PdfObjType::Dict(sub_entries) = &sub_dict_obj.obj_type {
if let Some((_, val)) =
sub_entries.iter().find(|(k, _)| k == &name_str)
{
let result_handle = PDF_OBJECTS.insert(val.clone());
return result_handle;
}
}
}
}
}
}
}
current = next;
} else {
break;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_function(
_ctx: ContextHandle,
_ref: PdfObjHandle,
n_in: i32,
n_out: i32,
) -> Handle {
let mut func = Function::new();
func.n_inputs = n_in;
func.n_outputs = n_out;
func.domain = vec![0.0, 1.0];
func.range = (0..n_out).flat_map(|_| vec![0.0, 1.0]).collect();
FUNCTIONS.insert(func)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_keep_function(_ctx: ContextHandle, func: Handle) -> Handle {
if let Some(func_arc) = FUNCTIONS.get(func) {
let mut f = func_arc.lock().unwrap();
f.refs += 1;
}
func
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_function(_ctx: ContextHandle, func: Handle) {
if let Some(func_arc) = FUNCTIONS.get(func) {
let should_remove = {
let mut f = func_arc.lock().unwrap();
f.refs -= 1;
f.refs <= 0
};
if should_remove {
FUNCTIONS.remove(func);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_function_size(_ctx: ContextHandle, func: Handle) -> usize {
if let Some(func_arc) = FUNCTIONS.get(func) {
let f = func_arc.lock().unwrap();
return f.size();
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_eval_function(
_ctx: ContextHandle,
func: Handle,
input: *const f32,
inlen: i32,
output: *mut f32,
outlen: i32,
) {
if input.is_null() || output.is_null() || inlen <= 0 || outlen <= 0 {
return;
}
if let Some(func_arc) = FUNCTIONS.get(func) {
let f = func_arc.lock().unwrap();
let input_slice = unsafe { std::slice::from_raw_parts(input, inlen as usize) };
let output_slice = unsafe { std::slice::from_raw_parts_mut(output, outlen as usize) };
f.eval(input_slice, output_slice);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_pattern(
_ctx: ContextHandle,
doc: DocumentHandle,
_obj: PdfObjHandle,
) -> Handle {
let mut pattern = Pattern::new();
pattern.document = doc;
PATTERNS.insert(pattern)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_keep_pattern(_ctx: ContextHandle, pat: Handle) -> Handle {
if let Some(pat_arc) = PATTERNS.get(pat) {
let mut p = pat_arc.lock().unwrap();
p.refs += 1;
}
pat
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_pattern(_ctx: ContextHandle, pat: Handle) {
if let Some(pat_arc) = PATTERNS.get(pat) {
let should_remove = {
let mut p = pat_arc.lock().unwrap();
p.refs -= 1;
p.refs <= 0
};
if should_remove {
PATTERNS.remove(pat);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pattern_is_mask(_ctx: ContextHandle, pat: Handle) -> i32 {
if let Some(pat_arc) = PATTERNS.get(pat) {
let p = pat_arc.lock().unwrap();
return if p.is_mask { 1 } else { 0 };
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pattern_xstep(_ctx: ContextHandle, pat: Handle) -> f32 {
if let Some(pat_arc) = PATTERNS.get(pat) {
let p = pat_arc.lock().unwrap();
return p.xstep;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pattern_ystep(_ctx: ContextHandle, pat: Handle) -> f32 {
if let Some(pat_arc) = PATTERNS.get(pat) {
let p = pat_arc.lock().unwrap();
return p.ystep;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_colorspace(_ctx: ContextHandle, obj: PdfObjHandle) -> ColorspaceHandle {
if obj == 0 {
return 0;
}
if let Some(arc) = PDF_OBJECTS.get(obj) {
if let Ok(guard) = arc.lock() {
return load_colorspace_from_obj(&guard);
}
}
0
}
fn load_colorspace_from_obj(obj: &PdfObj) -> ColorspaceHandle {
match &obj.obj_type {
PdfObjType::Name(name) => match name.as_str() {
"DeviceGray" | "G" => FZ_COLORSPACE_GRAY,
"DeviceRGB" | "RGB" => FZ_COLORSPACE_RGB,
"DeviceCMYK" | "CMYK" => FZ_COLORSPACE_CMYK,
"Lab" => FZ_COLORSPACE_LAB,
"CalGray" => FZ_COLORSPACE_GRAY,
"CalRGB" => FZ_COLORSPACE_RGB,
"Pattern" => 0, _ => 0,
},
PdfObjType::Array(elems) => {
if elems.is_empty() {
return 0;
}
if let PdfObjType::Name(family) = &elems[0].obj_type {
match family.as_str() {
"DeviceGray" | "G" => FZ_COLORSPACE_GRAY,
"DeviceRGB" | "RGB" => FZ_COLORSPACE_RGB,
"DeviceCMYK" | "CMYK" => FZ_COLORSPACE_CMYK,
"Lab" => FZ_COLORSPACE_LAB,
"CalGray" => FZ_COLORSPACE_GRAY,
"CalRGB" => FZ_COLORSPACE_RGB,
"ICCBased" => {
let n = if elems.len() > 1 {
extract_int_from_dict(&elems[1], "N").unwrap_or(3)
} else {
3
};
match n {
1 => FZ_COLORSPACE_GRAY,
3 => FZ_COLORSPACE_RGB,
4 => FZ_COLORSPACE_CMYK,
_ => {
let cs = Colorspace {
cs_type: ColorspaceType::Icc,
n,
name: format!("ICCBased({})", n),
base_cs: FZ_COLORSPACE_RGB,
lookup: Vec::new(),
high: 0,
colorants: Vec::new(),
icc_data: Vec::new(),
};
COLORSPACES.insert(cs) + 100 }
}
}
"Indexed" | "I" => {
if elems.len() < 4 {
return 0;
}
let base = load_colorspace_from_obj(&elems[1]);
let hival = match &elems[2].obj_type {
PdfObjType::Int(i) => *i as i32,
_ => return 0,
};
let cs = Colorspace {
cs_type: ColorspaceType::Indexed,
n: 1,
name: format!("Indexed({})", hival),
base_cs: base,
lookup: Vec::new(),
high: hival,
colorants: Vec::new(),
icc_data: Vec::new(),
};
COLORSPACES.insert(cs) + 100
}
"Separation" => {
let cs = Colorspace {
cs_type: ColorspaceType::Separation,
n: 1,
name: "Separation".to_string(),
base_cs: if elems.len() > 2 {
load_colorspace_from_obj(&elems[2])
} else {
FZ_COLORSPACE_CMYK
},
lookup: Vec::new(),
high: 0,
colorants: Vec::new(),
icc_data: Vec::new(),
};
COLORSPACES.insert(cs) + 100
}
"DeviceN" => {
let n = if elems.len() > 1 {
match &elems[1].obj_type {
PdfObjType::Array(names) => names.len() as i32,
_ => 1,
}
} else {
1
};
let cs = Colorspace {
cs_type: ColorspaceType::Separation,
n,
name: format!("DeviceN({})", n),
base_cs: if elems.len() > 2 {
load_colorspace_from_obj(&elems[2])
} else {
FZ_COLORSPACE_CMYK
},
lookup: Vec::new(),
high: 0,
colorants: Vec::new(),
icc_data: Vec::new(),
};
COLORSPACES.insert(cs) + 100
}
_ => 0,
}
} else {
0
}
}
_ => 0,
}
}
fn extract_int_from_dict(obj: &PdfObj, key: &str) -> Option<i32> {
if let PdfObjType::Dict(entries) = &obj.obj_type {
for (k, v) in entries {
if k == key {
if let PdfObjType::Int(i) = &v.obj_type {
return Some(*i as i32);
}
}
}
}
if let PdfObjType::Stream { dict, .. } = &obj.obj_type {
return extract_int_from_dict(dict, key);
}
None
}
fn extract_real_from_dict(obj: &PdfObj, key: &str) -> Option<f32> {
if let PdfObjType::Dict(entries) = &obj.obj_type {
for (k, v) in entries {
if k == key {
match &v.obj_type {
PdfObjType::Real(f) => return Some(*f as f32),
PdfObjType::Int(i) => return Some(*i as f32),
_ => {}
}
}
}
}
if let PdfObjType::Stream { dict, .. } = &obj.obj_type {
return extract_real_from_dict(dict, key);
}
None
}
fn extract_name_from_dict(obj: &PdfObj, key: &str) -> Option<String> {
if let PdfObjType::Dict(entries) = &obj.obj_type {
for (k, v) in entries {
if k == key {
if let PdfObjType::Name(s) = &v.obj_type {
return Some(s.clone());
}
}
}
}
if let PdfObjType::Stream { dict, .. } = &obj.obj_type {
return extract_name_from_dict(dict, key);
}
None
}
fn extract_obj_from_dict<'a>(obj: &'a PdfObj, key: &str) -> Option<&'a PdfObj> {
if let PdfObjType::Dict(entries) = &obj.obj_type {
for (k, v) in entries {
if k == key {
return Some(v);
}
}
}
if let PdfObjType::Stream { dict, .. } = &obj.obj_type {
return extract_obj_from_dict(dict, key);
}
None
}
fn extract_bool_from_dict(obj: &PdfObj, key: &str) -> Option<bool> {
if let PdfObjType::Dict(entries) = &obj.obj_type {
for (k, v) in entries {
if k == key {
match &v.obj_type {
PdfObjType::Bool(b) => return Some(*b),
PdfObjType::Int(i) => return Some(*i != 0),
_ => {}
}
}
}
}
if let PdfObjType::Stream { dict, .. } = &obj.obj_type {
return extract_bool_from_dict(dict, key);
}
None
}
fn extract_float_array_from_dict(obj: &PdfObj, key: &str) -> Option<Vec<f32>> {
let sub = extract_obj_from_dict(obj, key)?;
if let PdfObjType::Array(elems) = &sub.obj_type {
let vals: Vec<f32> = elems
.iter()
.filter_map(|e| match &e.obj_type {
PdfObjType::Real(f) => Some(*f as f32),
PdfObjType::Int(i) => Some(*i as f32),
_ => None,
})
.collect();
if vals.is_empty() { None } else { Some(vals) }
} else {
None
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_document_output_intent(
_ctx: ContextHandle,
doc: DocumentHandle,
) -> ColorspaceHandle {
if let Ok(tables) = DOC_RESOURCE_TABLES.lock() {
if let Some(t) = tables.get(&doc) {
let output_intent_digest = [0xFFu8; 16];
if let Some(&cs) = t.colorspaces.get(&output_intent_digest) {
return cs;
}
}
}
FZ_COLORSPACE_RGB
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_is_tint_colorspace(_ctx: ContextHandle, cs: ColorspaceHandle) -> i32 {
if cs == 0 {
return 0;
}
if cs <= 5 {
return 0;
}
if cs >= 100 {
let real_handle = cs - 100;
if let Some(cs_arc) = COLORSPACES.get(real_handle) {
if let Ok(guard) = cs_arc.lock() {
return i32::from(guard.cs_type == ColorspaceType::Separation);
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_guess_colorspace_components(_ctx: ContextHandle, obj: PdfObjHandle) -> i32 {
if obj == 0 {
return 3; }
if let Some(arc) = PDF_OBJECTS.get(obj) {
if let Ok(guard) = arc.lock() {
match &guard.obj_type {
PdfObjType::Name(name) => {
return match name.as_str() {
"DeviceGray" | "G" | "CalGray" => 1,
"DeviceRGB" | "RGB" | "CalRGB" => 3,
"DeviceCMYK" | "CMYK" => 4,
"Lab" => 3,
_ => 3,
};
}
PdfObjType::Array(elems) => {
if let Some(first) = elems.first() {
if let PdfObjType::Name(family) = &first.obj_type {
return match family.as_str() {
"DeviceGray" | "G" | "CalGray" => 1,
"DeviceRGB" | "RGB" | "CalRGB" => 3,
"DeviceCMYK" | "CMYK" => 4,
"Lab" => 3,
"ICCBased" => {
if elems.len() > 1 {
extract_int_from_dict(&elems[1], "N").unwrap_or(3)
} else {
3
}
}
"Indexed" | "I" => 1,
"Separation" => 1,
"DeviceN" => {
if elems.len() > 1 {
match &elems[1].obj_type {
PdfObjType::Array(names) => names.len() as i32,
_ => 1,
}
} else {
1
}
}
_ => 3,
};
}
}
}
_ => {}
}
}
}
3 }
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_shading(
_ctx: ContextHandle,
_doc: DocumentHandle,
obj: PdfObjHandle,
) -> ShadeHandle {
if obj == 0 {
return 0;
}
let shade = if let Some(arc) = PDF_OBJECTS.get(obj) {
if let Ok(guard) = arc.lock() {
build_shade_from_obj(&guard)
} else {
None
}
} else {
None
};
match shade {
Some(s) => SHADES.insert(s),
None => {
SHADES.insert(Shade::default())
}
}
}
fn build_shade_from_obj(obj: &PdfObj) -> Option<Shade> {
let shading_type = extract_int_from_dict(obj, "ShadingType").unwrap_or(0);
let shade_type = match shading_type {
1 => ShadeType::Function,
2 => ShadeType::Linear,
3 => ShadeType::Radial,
4 => ShadeType::FreeFormTriangle,
5 => ShadeType::LatticeTriangle,
6 => ShadeType::CoonsPatch,
7 => ShadeType::TensorPatch,
_ => ShadeType::None,
};
let mut shade = Shade {
shade_type,
..Default::default()
};
if let Some(cs_obj) = extract_obj_from_dict(obj, "ColorSpace") {
shade.colorspace = load_colorspace_from_obj(cs_obj) as u64;
}
if let Some(bbox) = extract_float_array_from_dict(obj, "BBox") {
if bbox.len() >= 4 {
shade.bbox = [bbox[0], bbox[1], bbox[2], bbox[3]];
}
}
if let Some(bg) = extract_float_array_from_dict(obj, "Background") {
let mut bg_arr = [0.0f32; 4];
for (i, &v) in bg.iter().take(4).enumerate() {
bg_arr[i] = v;
}
shade.background = Some(bg_arr);
}
match shading_type {
2 => {
if let Some(coords) = extract_float_array_from_dict(obj, "Coords") {
if coords.len() >= 4 {
shade.linear_start = crate::ffi::shade::ShadePoint {
x: coords[0],
y: coords[1],
};
shade.linear_end = crate::ffi::shade::ShadePoint {
x: coords[2],
y: coords[3],
};
}
}
if let Some(ext_obj) = extract_obj_from_dict(obj, "Extend") {
if let PdfObjType::Array(ext) = &ext_obj.obj_type {
if ext.len() >= 2 {
shade.extend_start = matches!(&ext[0].obj_type, PdfObjType::Bool(true));
shade.extend_end = matches!(&ext[1].obj_type, PdfObjType::Bool(true));
}
}
}
}
3 => {
if let Some(coords) = extract_float_array_from_dict(obj, "Coords") {
if coords.len() >= 6 {
shade.radial_start = crate::ffi::shade::ShadePoint {
x: coords[0],
y: coords[1],
};
shade.radial_r0 = coords[2];
shade.radial_end = crate::ffi::shade::ShadePoint {
x: coords[3],
y: coords[4],
};
shade.radial_r1 = coords[5];
}
}
if let Some(ext_obj) = extract_obj_from_dict(obj, "Extend") {
if let PdfObjType::Array(ext) = &ext_obj.obj_type {
if ext.len() >= 2 {
shade.extend_start = matches!(&ext[0].obj_type, PdfObjType::Bool(true));
shade.extend_end = matches!(&ext[1].obj_type, PdfObjType::Bool(true));
}
}
}
}
1 => {
if let Some(domain) = extract_float_array_from_dict(obj, "Domain") {
if domain.len() >= 4 {
shade.domain = [domain[0], domain[1], domain[2], domain[3]];
}
}
if let Some(matrix) = extract_float_array_from_dict(obj, "Matrix") {
if matrix.len() >= 6 {
shade.matrix = [
matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5],
];
}
}
}
4..=7 => {
shade.bits_per_coord = extract_int_from_dict(obj, "BitsPerCoordinate").unwrap_or(8);
shade.bits_per_comp = extract_int_from_dict(obj, "BitsPerComponent").unwrap_or(8);
shade.bits_per_flag = extract_int_from_dict(obj, "BitsPerFlag").unwrap_or(2);
}
_ => {}
}
Some(shade)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_sample_shade_function(
_ctx: ContextHandle,
samples: *mut f32,
n: i32,
funcs: i32,
func_handles: *const Handle,
t0: f32,
t1: f32,
) {
if samples.is_null() || n <= 0 || funcs <= 0 || func_handles.is_null() {
return;
}
let n_samples = n as usize;
let n_funcs = funcs as usize;
let handles = unsafe { std::slice::from_raw_parts(func_handles, n_funcs) };
let out_slice = unsafe { std::slice::from_raw_parts_mut(samples, n_samples * n_funcs) };
for i in 0..n_samples {
let t = if n_samples > 1 {
t0 + (t1 - t0) * (i as f32) / ((n_samples - 1) as f32)
} else {
t0
};
let input = [t];
for (j, &fh) in handles.iter().enumerate() {
let idx = i * n_funcs + j;
if let Some(func_arc) = FUNCTIONS.get(fh) {
let f = func_arc.lock().unwrap();
let mut out = [0.0f32; 1];
f.run(&input, &mut out);
out_slice[idx] = out[0];
} else {
out_slice[idx] = t;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_image(
_ctx: ContextHandle,
_doc: DocumentHandle,
obj: PdfObjHandle,
) -> ImageHandle {
if obj == 0 {
return 0;
}
let (w, h) = if let Some(arc) = PDF_OBJECTS.get(obj) {
if let Ok(guard) = arc.lock() {
let width = extract_int_from_dict(&guard, "Width")
.or_else(|| extract_int_from_dict(&guard, "W"))
.unwrap_or(1);
let height = extract_int_from_dict(&guard, "Height")
.or_else(|| extract_int_from_dict(&guard, "H"))
.unwrap_or(1);
(width.max(1), height.max(1))
} else {
(1, 1)
}
} else {
(1, 1)
};
let image = Image::new(w, h, None);
IMAGES.insert(image)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_load_inline_image(
_ctx: ContextHandle,
_doc: DocumentHandle,
_rdb: Handle,
dict: PdfObjHandle,
_file: StreamHandle,
) -> ImageHandle {
if dict == 0 {
return 0;
}
let (w, h) = if let Some(arc) = PDF_OBJECTS.get(dict) {
if let Ok(guard) = arc.lock() {
let width = extract_int_from_dict(&guard, "W")
.or_else(|| extract_int_from_dict(&guard, "Width"))
.unwrap_or(1);
let height = extract_int_from_dict(&guard, "H")
.or_else(|| extract_int_from_dict(&guard, "Height"))
.unwrap_or(1);
(width.max(1), height.max(1))
} else {
(1, 1)
}
} else {
(1, 1)
};
let image = Image::new(w, h, None);
IMAGES.insert(image)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_is_jpx_image(_ctx: ContextHandle, dict: PdfObjHandle) -> i32 {
if dict == 0 {
return 0;
}
if let Some(arc) = PDF_OBJECTS.get(dict) {
if let Ok(guard) = arc.lock() {
if let Some(filter_name) = extract_name_from_dict(&guard, "Filter") {
if filter_name == "JPXDecode" {
return 1;
}
}
if let Some(filter_obj) = extract_obj_from_dict(&guard, "Filter") {
if let PdfObjType::Array(filters) = &filter_obj.obj_type {
for f in filters {
if let PdfObjType::Name(n) = &f.obj_type {
if n == "JPXDecode" {
return 1;
}
}
}
}
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_image(
_ctx: ContextHandle,
_doc: DocumentHandle,
image: ImageHandle,
) -> PdfObjHandle {
if image == 0 {
return 0;
}
let (w, h, cs_name) = if let Some(img_arc) = IMAGES.get(image) {
if let Ok(guard) = img_arc.lock() {
let cs = match guard.colorspace() {
Some(cs) => cs.name().to_string(),
None => "DeviceRGB".to_string(),
};
(guard.width(), guard.height(), cs)
} else {
return 0;
}
} else {
return 0;
};
let entries = vec![
("Type".to_string(), PdfObj::new_name("XObject")),
("Subtype".to_string(), PdfObj::new_name("Image")),
("Width".to_string(), PdfObj::new_int(w as i64)),
("Height".to_string(), PdfObj::new_int(h as i64)),
("BitsPerComponent".to_string(), PdfObj::new_int(8)),
("ColorSpace".to_string(), PdfObj::new_name(&cs_name)),
];
let dict = PdfObj {
obj_type: PdfObjType::Dict(entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
PDF_OBJECTS.insert(dict)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_colorspace(
_ctx: ContextHandle,
_doc: DocumentHandle,
cs: ColorspaceHandle,
) -> PdfObjHandle {
if cs == 0 {
return 0;
}
let name = match cs {
FZ_COLORSPACE_GRAY => Some("DeviceGray"),
FZ_COLORSPACE_RGB => Some("DeviceRGB"),
FZ_COLORSPACE_CMYK => Some("DeviceCMYK"),
FZ_COLORSPACE_LAB => Some("Lab"),
_ => None,
};
if let Some(n) = name {
return PDF_OBJECTS.insert(PdfObj::new_name(n));
}
if cs >= 100 {
let real_handle = cs - 100;
if let Some(cs_arc) = COLORSPACES.get(real_handle) {
if let Ok(guard) = cs_arc.lock() {
let cs_name = match guard.cs_type {
ColorspaceType::Indexed => {
let mut arr = Vec::with_capacity(4);
arr.push(PdfObj::new_name("Indexed"));
let base_name = match guard.base_cs {
FZ_COLORSPACE_GRAY => "DeviceGray",
FZ_COLORSPACE_RGB => "DeviceRGB",
FZ_COLORSPACE_CMYK => "DeviceCMYK",
_ => "DeviceRGB",
};
arr.push(PdfObj::new_name(base_name));
arr.push(PdfObj::new_int(guard.high as i64));
arr.push(PdfObj::new_string(&guard.lookup));
let array_obj = PdfObj {
obj_type: PdfObjType::Array(arr),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
return PDF_OBJECTS.insert(array_obj);
}
ColorspaceType::Separation => {
return PDF_OBJECTS.insert(PdfObj::new_name(&guard.name));
}
ColorspaceType::Icc => {
return PDF_OBJECTS.insert(PdfObj::new_name(&guard.name));
}
_ => guard.name.clone(),
};
return PDF_OBJECTS.insert(PdfObj::new_name(&cs_name));
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_xobject(
_ctx: ContextHandle,
_doc: DocumentHandle,
bbox: *const f32,
matrix: *const f32,
res: PdfObjHandle,
buffer: BufferHandle,
) -> PdfObjHandle {
let mut entries = Vec::with_capacity(8);
entries.push(("Type".to_string(), PdfObj::new_name("XObject")));
entries.push(("Subtype".to_string(), PdfObj::new_name("Form")));
entries.push(("FormType".to_string(), PdfObj::new_int(1)));
let bbox_vals = if !bbox.is_null() {
unsafe { std::slice::from_raw_parts(bbox, 4) }
} else {
&[0.0f32, 0.0, 612.0, 792.0] };
let bbox_arr: Vec<PdfObj> = bbox_vals
.iter()
.map(|&v| PdfObj::new_real(v as f64))
.collect();
entries.push((
"BBox".to_string(),
PdfObj {
obj_type: PdfObjType::Array(bbox_arr),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
));
let matrix_vals = if !matrix.is_null() {
unsafe { std::slice::from_raw_parts(matrix, 6) }
} else {
&[1.0f32, 0.0, 0.0, 1.0, 0.0, 0.0] };
let matrix_arr: Vec<PdfObj> = matrix_vals
.iter()
.map(|&v| PdfObj::new_real(v as f64))
.collect();
entries.push((
"Matrix".to_string(),
PdfObj {
obj_type: PdfObjType::Array(matrix_arr),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
));
if res != 0 {
if let Some(res_arc) = PDF_OBJECTS.get(res) {
if let Ok(guard) = res_arc.lock() {
entries.push(("Resources".to_string(), guard.clone()));
}
}
}
let stream_data = if buffer != 0 {
if let Some(buf_arc) = BUFFERS.get(buffer) {
if let Ok(guard) = buf_arc.lock() {
guard.as_slice().to_vec()
} else {
Vec::new()
}
} else {
Vec::new()
}
} else {
Vec::new()
};
if !stream_data.is_empty() {
entries.push((
"Length".to_string(),
PdfObj::new_int(stream_data.len() as i64),
));
}
let dict_obj = PdfObj {
obj_type: PdfObjType::Dict(entries),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
PDF_OBJECTS.insert(dict_obj)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_update_xobject(
_ctx: ContextHandle,
_doc: DocumentHandle,
xobj: PdfObjHandle,
bbox: *const f32,
matrix: *const f32,
res: PdfObjHandle,
buffer: BufferHandle,
) {
if xobj == 0 {
return;
}
if let Some(arc) = PDF_OBJECTS.get(xobj) {
if let Ok(mut guard) = arc.lock() {
if let PdfObjType::Dict(ref mut entries) = guard.obj_type {
if !bbox.is_null() {
let bbox_vals = unsafe { std::slice::from_raw_parts(bbox, 4) };
let bbox_arr: Vec<PdfObj> = bbox_vals
.iter()
.map(|&v| PdfObj::new_real(v as f64))
.collect();
let bbox_obj = PdfObj {
obj_type: PdfObjType::Array(bbox_arr),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
if let Some(entry) = entries.iter_mut().find(|(k, _)| k == "BBox") {
entry.1 = bbox_obj;
} else {
entries.push(("BBox".to_string(), bbox_obj));
}
}
if !matrix.is_null() {
let matrix_vals = unsafe { std::slice::from_raw_parts(matrix, 6) };
let matrix_arr: Vec<PdfObj> = matrix_vals
.iter()
.map(|&v| PdfObj::new_real(v as f64))
.collect();
let matrix_obj = PdfObj {
obj_type: PdfObjType::Array(matrix_arr),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
if let Some(entry) = entries.iter_mut().find(|(k, _)| k == "Matrix") {
entry.1 = matrix_obj;
} else {
entries.push(("Matrix".to_string(), matrix_obj));
}
}
if res != 0 {
if let Some(res_arc) = PDF_OBJECTS.get(res) {
if let Ok(res_guard) = res_arc.lock() {
if let Some(entry) = entries.iter_mut().find(|(k, _)| k == "Resources")
{
entry.1 = res_guard.clone();
} else {
entries.push(("Resources".to_string(), res_guard.clone()));
}
}
}
}
if buffer != 0 {
if let Some(buf_arc) = BUFFERS.get(buffer) {
if let Ok(buf_guard) = buf_arc.lock() {
let len = buf_guard.as_slice().len() as i64;
if let Some(entry) = entries.iter_mut().find(|(k, _)| k == "Length") {
entry.1 = PdfObj::new_int(len);
} else {
entries.push(("Length".to_string(), PdfObj::new_int(len)));
}
}
}
}
guard.dirty = true;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_xobject_resources(_ctx: ContextHandle, xobj: PdfObjHandle) -> PdfObjHandle {
if xobj == 0 {
return 0;
}
if let Some(arc) = PDF_OBJECTS.get(xobj) {
if let Ok(guard) = arc.lock() {
if let Some(res_obj) = extract_obj_from_dict(&guard, "Resources") {
return PDF_OBJECTS.insert(res_obj.clone());
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_xobject_bbox(_ctx: ContextHandle, xobj: PdfObjHandle, bbox: *mut f32) {
if bbox.is_null() {
return;
}
let mut vals = [0.0f32; 4];
if xobj != 0 {
if let Some(arc) = PDF_OBJECTS.get(xobj) {
if let Ok(guard) = arc.lock() {
if let Some(arr) = extract_float_array_from_dict(&guard, "BBox") {
for (i, &v) in arr.iter().take(4).enumerate() {
vals[i] = v;
}
}
}
}
}
unsafe {
*bbox.add(0) = vals[0];
*bbox.add(1) = vals[1];
*bbox.add(2) = vals[2];
*bbox.add(3) = vals[3];
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_xobject_matrix(_ctx: ContextHandle, xobj: PdfObjHandle, matrix: *mut f32) {
if matrix.is_null() {
return;
}
let mut vals = [1.0f32, 0.0, 0.0, 1.0, 0.0, 0.0];
if xobj != 0 {
if let Some(arc) = PDF_OBJECTS.get(xobj) {
if let Ok(guard) = arc.lock() {
if let Some(arr) = extract_float_array_from_dict(&guard, "Matrix") {
if arr.len() >= 6 {
for (i, &v) in arr.iter().take(6).enumerate() {
vals[i] = v;
}
}
}
}
}
}
unsafe {
for i in 0..6 {
*matrix.add(i) = vals[i];
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_xobject_isolated(_ctx: ContextHandle, xobj: PdfObjHandle) -> i32 {
if xobj == 0 {
return 0;
}
if let Some(arc) = PDF_OBJECTS.get(xobj) {
if let Ok(guard) = arc.lock() {
if let Some(group) = extract_obj_from_dict(&guard, "Group") {
if let Some(isolated) = extract_bool_from_dict(group, "I") {
return i32::from(isolated);
}
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_xobject_knockout(_ctx: ContextHandle, xobj: PdfObjHandle) -> i32 {
if xobj == 0 {
return 0;
}
if let Some(arc) = PDF_OBJECTS.get(xobj) {
if let Ok(guard) = arc.lock() {
if let Some(group) = extract_obj_from_dict(&guard, "Group") {
if let Some(knockout) = extract_bool_from_dict(group, "K") {
return i32::from(knockout);
}
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_xobject_transparency(_ctx: ContextHandle, xobj: PdfObjHandle) -> i32 {
if xobj == 0 {
return 0;
}
if let Some(arc) = PDF_OBJECTS.get(xobj) {
if let Ok(guard) = arc.lock() {
if let Some(group) = extract_obj_from_dict(&guard, "Group") {
if let Some(s) = extract_name_from_dict(group, "S") {
if s == "Transparency" {
return 1;
}
}
return 1;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_xobject_colorspace(
_ctx: ContextHandle,
xobj: PdfObjHandle,
) -> ColorspaceHandle {
if xobj == 0 {
return 0;
}
if let Some(arc) = PDF_OBJECTS.get(xobj) {
if let Ok(guard) = arc.lock() {
if let Some(group) = extract_obj_from_dict(&guard, "Group") {
if let Some(cs_obj) = extract_obj_from_dict(group, "CS") {
return load_colorspace_from_obj(cs_obj);
}
}
}
}
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_font_resource_key() {
let key = FontResourceKey {
digest: [0u8; 16],
font_type: PDF_SIMPLE_FONT_RESOURCE,
encoding: PDF_SIMPLE_ENCODING_LATIN,
local_xref: 0,
};
assert_eq!(key.font_type, 1);
assert_eq!(key.encoding, 0);
}
#[test]
fn test_colorspace_resource_key() {
let key = ColorspaceResourceKey {
digest: [0u8; 16],
local_xref: 0,
};
assert_eq!(key.local_xref, 0);
}
#[test]
fn test_resource_stack() {
let stack = ResourceStack::new();
assert_eq!(stack.resources, 0);
assert!(stack.next.is_none());
let stack2 = ResourceStack::with_resources(123);
assert_eq!(stack2.resources, 123);
}
#[test]
fn test_pattern_new() {
let p1 = Pattern::new();
let p2 = Pattern::new();
assert_ne!(p1.id, p2.id); assert_eq!(p1.refs, 1);
assert!(!p1.is_mask);
}
#[test]
fn test_function_new() {
let f = Function::new();
assert_eq!(f.refs, 1);
assert_eq!(f.func_type, FunctionType::Sampled);
assert_eq!(f.n_inputs, 1);
assert_eq!(f.n_outputs, 1);
}
#[test]
fn test_function_exponential() {
let mut f = Function::new();
f.func_type = FunctionType::Exponential;
f.n = 2.0;
f.c0 = vec![0.0];
f.c1 = vec![1.0];
let mut output = [0.0f32; 1];
f.eval(&[0.5], &mut output);
assert!((output[0] - 0.25).abs() < 0.01); }
#[test]
fn test_resource_tables() {
let mut tables = ResourceTables::new();
assert!(tables.fonts.is_empty());
let digest = [1u8; 16];
tables.fonts.insert(digest, 100);
assert_eq!(tables.fonts.get(&digest), Some(&100));
tables.clear();
assert!(tables.fonts.is_empty());
}
#[test]
fn test_ffi_resource_stack() {
let ctx = 0;
let stack = pdf_new_resource_stack(ctx, 100);
assert!(stack > 0);
let stack2 = pdf_push_resource_stack(ctx, stack, 200);
assert!(stack2 > 0);
assert_ne!(stack2, stack);
let popped = pdf_pop_resource_stack(ctx, stack2);
assert_eq!(popped, stack);
pdf_drop_resource_stack(ctx, stack);
}
#[test]
fn test_ffi_function() {
let ctx = 0;
let func = pdf_load_function(ctx, 0, 1, 3);
assert!(func > 0);
let kept = pdf_keep_function(ctx, func);
assert_eq!(kept, func);
let size = pdf_function_size(ctx, func);
assert!(size > 0);
let input = [0.5f32];
let mut output = [0.0f32; 3];
pdf_eval_function(ctx, func, input.as_ptr(), 1, output.as_mut_ptr(), 3);
pdf_drop_function(ctx, func);
pdf_drop_function(ctx, func);
}
#[test]
fn test_ffi_pattern() {
let ctx = 0;
let doc = 123;
let pat = pdf_load_pattern(ctx, doc, 0);
assert!(pat > 0);
assert_eq!(pdf_pattern_is_mask(ctx, pat), 0);
assert_eq!(pdf_pattern_xstep(ctx, pat), 0.0);
assert_eq!(pdf_pattern_ystep(ctx, pat), 0.0);
let kept = pdf_keep_pattern(ctx, pat);
assert_eq!(kept, pat);
pdf_drop_pattern(ctx, pat);
pdf_drop_pattern(ctx, pat);
}
#[test]
fn test_ffi_font_resource() {
let ctx = 0;
let doc: DocumentHandle = 999;
let mut key = FontResourceKey {
digest: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
font_type: PDF_SIMPLE_FONT_RESOURCE,
encoding: PDF_SIMPLE_ENCODING_LATIN,
local_xref: 0,
};
let found = pdf_find_font_resource(ctx, doc, 1, 0, 0, &mut key);
assert_eq!(found, 0);
let inserted = pdf_insert_font_resource(ctx, doc, &key, 42);
assert_eq!(inserted, 42);
let found2 = pdf_find_font_resource(ctx, doc, 1, 0, 0, &mut key);
assert_eq!(found2, 42);
pdf_drop_resource_tables(ctx, doc);
}
#[test]
fn test_ffi_colorspace_resource() {
let ctx = 0;
let doc: DocumentHandle = 888;
let mut key = ColorspaceResourceKey {
digest: [0xAA; 16],
local_xref: 0,
};
let inserted = pdf_insert_colorspace_resource(ctx, doc, &key, 77);
assert_eq!(inserted, 77);
let found = pdf_find_colorspace_resource(ctx, doc, 0, &mut key);
assert_eq!(found, 77);
pdf_drop_resource_tables(ctx, doc);
}
#[test]
fn test_ffi_empty_store() {
let ctx = 0;
let doc: DocumentHandle = 777;
let key = FontResourceKey {
digest: [0xBB; 16],
font_type: 1,
encoding: 0,
local_xref: 0,
};
pdf_insert_font_resource(ctx, doc, &key, 55);
pdf_empty_store(ctx, doc);
let mut key2 = key;
let found = pdf_find_font_resource(ctx, doc, 1, 0, 0, &mut key2);
assert_eq!(found, 0);
}
#[test]
fn test_pdf_store_item_find_remove() {
let ctx = 0;
let key: PdfObjHandle = 1001;
let mut val: i32 = 42;
pdf_store_item(ctx, 0, std::ptr::null_mut(), 0);
pdf_store_item(ctx, key, std::ptr::null_mut(), 0);
assert!(pdf_find_item(ctx, std::ptr::null(), key).is_null());
pdf_store_item(ctx, key, &mut val as *mut i32 as *mut std::ffi::c_void, 4);
let found = pdf_find_item(ctx, std::ptr::null(), key);
assert!(!found.is_null());
assert_eq!(unsafe { *found.cast::<i32>() }, 42);
pdf_remove_item(ctx, std::ptr::null(), key);
assert!(pdf_find_item(ctx, std::ptr::null(), key).is_null());
}
#[test]
fn test_pdf_find_item_invalid_key() {
assert!(pdf_find_item(0, std::ptr::null(), 0).is_null());
assert!(pdf_find_item(0, std::ptr::null(), 99999).is_null());
}
#[test]
fn test_pdf_purge_locals_from_store() {
let ctx = 0;
let doc: DocumentHandle = 666;
let key = FontResourceKey {
digest: [0xCC; 16],
font_type: 1,
encoding: 0,
local_xref: 0,
};
pdf_insert_font_resource(ctx, doc, &key, 33);
pdf_purge_locals_from_store(ctx, doc);
let mut key2 = key;
assert_eq!(pdf_find_font_resource(ctx, doc, 1, 0, 0, &mut key2), 0);
pdf_drop_resource_tables(ctx, doc);
}
#[test]
fn test_pdf_purge_object_from_store() {
let ctx = 0;
let doc: DocumentHandle = 555;
let key = FontResourceKey {
digest: [0xDD; 16],
font_type: 1,
encoding: 0,
local_xref: 0,
};
pdf_insert_font_resource(ctx, doc, &key, 22);
pdf_purge_object_from_store(ctx, doc, 22);
pdf_drop_resource_tables(ctx, doc);
}
#[test]
fn test_pdf_find_font_resource_null_key() {
assert_eq!(
pdf_find_font_resource(0, 1, 1, 0, 0, std::ptr::null_mut()),
0
);
}
#[test]
fn test_pdf_insert_font_resource_null_key() {
assert_eq!(pdf_insert_font_resource(0, 1, std::ptr::null(), 42), 0);
}
#[test]
fn test_pdf_find_colorspace_resource_null_key() {
assert_eq!(
pdf_find_colorspace_resource(0, 1, 0, std::ptr::null_mut()),
0
);
}
#[test]
fn test_pdf_insert_colorspace_resource_null_key() {
assert_eq!(
pdf_insert_colorspace_resource(0, 1, std::ptr::null(), 77),
0
);
}
#[test]
fn test_pdf_pop_resource_stack_invalid() {
assert_eq!(pdf_pop_resource_stack(0, 0), 0);
assert_eq!(pdf_pop_resource_stack(0, 99999), 0);
}
#[test]
fn test_pdf_keep_drop_function_invalid() {
let kept = pdf_keep_function(0, 99999);
assert_eq!(kept, 99999);
pdf_drop_function(0, 99999);
assert_eq!(pdf_function_size(0, 99999), 0);
}
#[test]
fn test_pdf_eval_function_null_params() {
let func = pdf_load_function(0, 0, 1, 1);
let input = [0.5f32];
let mut output = [0.0f32];
pdf_eval_function(0, func, std::ptr::null(), 1, output.as_mut_ptr(), 1);
pdf_eval_function(0, func, input.as_ptr(), 0, output.as_mut_ptr(), 1);
pdf_eval_function(0, func, input.as_ptr(), 1, std::ptr::null_mut(), 1);
pdf_eval_function(0, func, input.as_ptr(), 1, output.as_mut_ptr(), 0);
pdf_drop_function(0, func);
}
#[test]
fn test_pdf_eval_function_invalid_handle() {
let input = [0.5f32];
let mut output = [0.0f32];
pdf_eval_function(0, 99999, input.as_ptr(), 1, output.as_mut_ptr(), 1);
}
#[test]
fn test_function_sampled() {
let mut f = Function::new();
f.func_type = FunctionType::Sampled;
f.samples = vec![0.0, 0.5, 1.0];
let mut output = [0.0f32; 1];
f.eval(&[0.0], &mut output);
assert!((output[0] - 0.0).abs() < 0.01);
f.eval(&[1.0], &mut output);
assert!((output[0] - 1.0).abs() < 0.01);
}
#[test]
fn test_function_sampled_empty() {
let mut f = Function::new();
f.func_type = FunctionType::Sampled;
f.samples = vec![];
let mut output = [0.0f32; 1];
f.eval(&[0.5], &mut output);
}
#[test]
fn test_function_stitching() {
let sub = pdf_load_function(0, 0, 1, 1);
let mut f = Function::new();
f.func_type = FunctionType::Stitching;
f.domain = vec![0.0, 1.0];
f.bounds = vec![0.5];
f.encode = vec![0.0, 1.0, 0.0, 1.0];
f.funcs = vec![sub];
let mut output = [0.0f32; 1];
f.eval(&[0.25], &mut output);
f.eval(&[0.75], &mut output);
pdf_drop_function(0, sub);
}
#[test]
fn test_function_stitching_empty() {
let mut f = Function::new();
f.func_type = FunctionType::Stitching;
f.funcs = vec![];
let mut output = [0.0f32; 1];
f.eval(&[0.5], &mut output);
}
#[test]
fn test_function_postscript_identity() {
let mut f = Function::new();
f.func_type = FunctionType::PostScript;
f.samples = vec![];
let mut output = [0.0f32; 2];
f.eval(&[0.3, 0.7], &mut output);
assert!((output[0] - 0.3).abs() < 0.01);
assert!((output[1] - 0.7).abs() < 0.01);
}
#[test]
fn test_function_postscript_ops() {
let mut f = Function::new();
f.func_type = FunctionType::PostScript;
f.samples = vec![
1.0, 2.0, 1.0, ];
f.range = vec![0.0, 10.0];
let mut output = [0.0f32; 1];
f.eval(&[1.0, 2.0], &mut output);
}
#[test]
fn test_function_postscript_div_by_zero() {
let mut f = Function::new();
f.func_type = FunctionType::PostScript;
f.samples = vec![1.0, 0.0, 4.0];
f.range = vec![0.0, 10.0];
let mut output = [0.0f32; 1];
f.eval(&[1.0], &mut output);
}
#[test]
fn test_function_postscript_mod_by_zero() {
let mut f = Function::new();
f.func_type = FunctionType::PostScript;
f.samples = vec![5.0, 0.0, 12.0];
f.range = vec![0.0, 10.0];
let mut output = [0.0f32; 1];
f.eval(&[1.0], &mut output);
}
#[test]
fn test_function_postscript_sqrt_negative() {
let mut f = Function::new();
f.func_type = FunctionType::PostScript;
f.samples = vec![-1.0, 11.0];
f.range = vec![0.0, 10.0];
let mut output = [0.0f32; 1];
f.eval(&[1.0], &mut output);
}
#[test]
fn test_function_postscript_log_nonpositive() {
let mut f = Function::new();
f.func_type = FunctionType::PostScript;
f.samples = vec![0.0, 17.0];
f.range = vec![0.0, 10.0];
let mut output = [0.0f32; 1];
f.eval(&[1.0], &mut output);
}
#[test]
fn test_function_postscript_literal_push() {
let mut f = Function::new();
f.func_type = FunctionType::PostScript;
f.samples = vec![99.0];
f.range = vec![0.0, 100.0];
let mut output = [0.0f32; 1];
f.eval(&[], &mut output);
}
#[test]
fn test_pdf_sample_shade_function() {
let func = pdf_load_function(0, 0, 1, 1);
let handles = [func];
let mut samples = [0.0f32; 4];
pdf_sample_shade_function(0, samples.as_mut_ptr(), 2, 1, handles.as_ptr(), 0.0, 1.0);
pdf_drop_function(0, func);
}
#[test]
fn test_pdf_sample_shade_function_null() {
let func = pdf_load_function(0, 0, 1, 1);
let handles = [func];
pdf_sample_shade_function(0, std::ptr::null_mut(), 2, 1, handles.as_ptr(), 0.0, 1.0);
pdf_sample_shade_function(
0,
[0.0f32; 4].as_mut_ptr(),
0,
1,
handles.as_ptr(),
0.0,
1.0,
);
pdf_sample_shade_function(
0,
[0.0f32; 4].as_mut_ptr(),
2,
0,
handles.as_ptr(),
0.0,
1.0,
);
pdf_sample_shade_function(
0,
[0.0f32; 4].as_mut_ptr(),
2,
1,
std::ptr::null(),
0.0,
1.0,
);
pdf_drop_function(0, func);
}
#[test]
fn test_pdf_load_colorspace_null() {
assert_eq!(pdf_load_colorspace(0, 0), 0);
}
#[test]
fn test_pdf_load_colorspace_name() {
use crate::ffi::pdf_object::types::{PDF_OBJECTS, PdfObj, PdfObjType};
let obj = PdfObj {
obj_type: PdfObjType::Name("DeviceGray".to_string()),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
let h = PDF_OBJECTS.insert(obj);
assert_eq!(pdf_load_colorspace(0, h), FZ_COLORSPACE_GRAY);
PDF_OBJECTS.remove(h);
}
#[test]
fn test_pdf_load_colorspace_array_iccbased() {
use crate::ffi::pdf_object::types::{PDF_OBJECTS, PdfObj, PdfObjType};
let stream_dict = PdfObj {
obj_type: PdfObjType::Dict(vec![("N".to_string(), PdfObj::new_int(4))]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
let stream_obj = PdfObj {
obj_type: PdfObjType::Stream {
dict: Box::new(stream_dict),
data: vec![],
},
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
let stream_h = PDF_OBJECTS.insert(stream_obj);
let stream_clone = PDF_OBJECTS.get(stream_h).unwrap().lock().unwrap().clone();
let arr = PdfObj {
obj_type: PdfObjType::Array(vec![PdfObj::new_name("ICCBased"), stream_clone]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
let arr_h = PDF_OBJECTS.insert(arr);
let cs = pdf_load_colorspace(0, arr_h);
assert!(cs == FZ_COLORSPACE_CMYK || cs >= 100);
PDF_OBJECTS.remove(arr_h);
PDF_OBJECTS.remove(stream_h);
}
#[test]
fn test_pdf_document_output_intent() {
assert_eq!(pdf_document_output_intent(0, 99999), FZ_COLORSPACE_RGB);
}
#[test]
fn test_pdf_is_tint_colorspace() {
assert_eq!(pdf_is_tint_colorspace(0, 0), 0);
assert_eq!(pdf_is_tint_colorspace(0, 3), 0);
}
#[test]
fn test_pdf_guess_colorspace_components() {
assert_eq!(pdf_guess_colorspace_components(0, 0), 3);
}
#[test]
fn test_pdf_load_image_null() {
assert_eq!(pdf_load_image(0, 0, 0), 0);
}
#[test]
fn test_pdf_load_image_with_dict() {
use crate::ffi::pdf_object::types::{PDF_OBJECTS, PdfObj, PdfObjType};
let obj = PdfObj {
obj_type: PdfObjType::Dict(vec![
("Width".to_string(), PdfObj::new_int(100)),
("Height".to_string(), PdfObj::new_int(50)),
]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
let h = PDF_OBJECTS.insert(obj);
let img = pdf_load_image(0, 0, h);
assert!(img > 0);
crate::ffi::image::IMAGES.remove(img);
PDF_OBJECTS.remove(h);
}
#[test]
fn test_pdf_load_inline_image_null() {
assert_eq!(pdf_load_inline_image(0, 0, 0, 0, 0), 0);
}
#[test]
fn test_pdf_is_jpx_image() {
assert_eq!(pdf_is_jpx_image(0, 0), 0);
}
#[test]
fn test_pdf_is_jpx_image_filter_name() {
use crate::ffi::pdf_object::types::{PDF_OBJECTS, PdfObj, PdfObjType};
let obj = PdfObj {
obj_type: PdfObjType::Dict(vec![("Filter".to_string(), PdfObj::new_name("JPXDecode"))]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
let h = PDF_OBJECTS.insert(obj);
assert_eq!(pdf_is_jpx_image(0, h), 1);
PDF_OBJECTS.remove(h);
}
#[test]
fn test_pdf_add_image_null() {
assert_eq!(pdf_add_image(0, 0, 0), 0);
}
#[test]
fn test_pdf_add_colorspace_null() {
assert_eq!(pdf_add_colorspace(0, 0, 0), 0);
}
#[test]
fn test_pdf_add_colorspace_device() {
let obj = pdf_add_colorspace(0, 0, FZ_COLORSPACE_RGB);
assert!(obj > 0);
crate::ffi::pdf_object::types::PDF_OBJECTS.remove(obj);
}
#[test]
fn test_pdf_new_xobject() {
let bbox = [0.0f32, 0.0, 612.0, 792.0];
let matrix = [1.0f32, 0.0, 0.0, 1.0, 0.0, 0.0];
let xobj = pdf_new_xobject(0, 0, bbox.as_ptr(), matrix.as_ptr(), 0, 0);
assert!(xobj > 0);
crate::ffi::pdf_object::types::PDF_OBJECTS.remove(xobj);
}
#[test]
fn test_pdf_new_xobject_null_bbox_matrix() {
let xobj = pdf_new_xobject(0, 0, std::ptr::null(), std::ptr::null(), 0, 0);
assert!(xobj > 0);
crate::ffi::pdf_object::types::PDF_OBJECTS.remove(xobj);
}
#[test]
fn test_pdf_xobject_bbox_null() {
pdf_xobject_bbox(0, 0, std::ptr::null_mut());
}
#[test]
fn test_pdf_xobject_matrix_null() {
pdf_xobject_matrix(0, 0, std::ptr::null_mut());
}
#[test]
fn test_pdf_xobject_isolated_knockout_transparency() {
assert_eq!(pdf_xobject_isolated(0, 0), 0);
assert_eq!(pdf_xobject_knockout(0, 0), 0);
assert_eq!(pdf_xobject_transparency(0, 0), 0);
assert_eq!(pdf_xobject_colorspace(0, 0), 0);
}
#[test]
fn test_pdf_xobject_resources() {
assert_eq!(pdf_xobject_resources(0, 0), 0);
}
#[test]
fn test_pdf_load_shading_null() {
assert_eq!(pdf_load_shading(0, 0, 0), 0);
}
#[test]
fn test_function_exponential_multi_output() {
let mut f = Function::new();
f.func_type = FunctionType::Exponential;
f.n_outputs = 2;
f.c0 = vec![0.0, 1.0];
f.c1 = vec![1.0, 0.0];
f.n = 1.0;
let mut output = [0.0f32; 2];
f.eval(&[0.5], &mut output);
assert!((output[0] - 0.5).abs() < 0.01);
assert!((output[1] - 0.5).abs() < 0.01);
}
#[test]
fn test_pdf_store_find_remove_item() {
let key = 42u64;
let mut val: i32 = 123;
pdf_store_item(0, key, &mut val as *mut i32 as *mut std::ffi::c_void, 4);
let found = pdf_find_item(0, std::ptr::null(), key);
assert!(!found.is_null());
pdf_remove_item(0, std::ptr::null(), key);
let after = pdf_find_item(0, std::ptr::null(), key);
assert!(after.is_null());
}
#[test]
fn test_pdf_store_item_null_key() {
let mut val: i32 = 1;
pdf_store_item(0, 0, &mut val as *mut i32 as *mut std::ffi::c_void, 4);
}
#[test]
fn test_pdf_store_item_null_val() {
pdf_store_item(0, 1, std::ptr::null_mut(), 4);
}
#[test]
fn test_pdf_load_shading_valid() {
use crate::ffi::pdf_object::types::{PDF_OBJECTS, PdfObj, PdfObjType};
let obj = PdfObj {
obj_type: PdfObjType::Dict(vec![
("ShadingType".to_string(), PdfObj::new_int(2)),
("ColorSpace".to_string(), PdfObj::new_name("DeviceRGB")),
(
"Coords".to_string(),
PdfObj {
obj_type: PdfObjType::Array(vec![
PdfObj::new_real(0.0),
PdfObj::new_real(0.0),
PdfObj::new_real(100.0),
PdfObj::new_real(100.0),
]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
),
(
"Extend".to_string(),
PdfObj {
obj_type: PdfObjType::Array(vec![
PdfObj::new_bool(true),
PdfObj::new_bool(true),
]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
},
),
]),
marked: false,
dirty: false,
parent_num: 0,
refs: 1,
};
let h = PDF_OBJECTS.insert(obj);
let shade = pdf_load_shading(0, 0, h);
assert!(shade > 0);
crate::ffi::shade::SHADES.remove(shade);
PDF_OBJECTS.remove(h);
}
#[test]
fn test_pdf_lookup_resource_null_name() {
let stack = pdf_new_resource_stack(0, 0);
let font_type = crate::ffi::pdf_object::types::PDF_OBJECTS
.insert(crate::ffi::pdf_object::types::PdfObj::new_name("Font"));
assert_eq!(
pdf_lookup_resource(0, stack, font_type, std::ptr::null()),
0
);
pdf_drop_resource_stack(0, stack);
crate::ffi::pdf_object::types::PDF_OBJECTS.remove(font_type);
}
#[test]
fn test_pdf_lookup_resource_zero_stack() {
let font_type = crate::ffi::pdf_object::types::PDF_OBJECTS
.insert(crate::ffi::pdf_object::types::PdfObj::new_name("Font"));
assert_eq!(
pdf_lookup_resource(
0,
0,
font_type,
std::ffi::CString::new("F1").unwrap().as_ptr()
),
0
);
crate::ffi::pdf_object::types::PDF_OBJECTS.remove(font_type);
}
#[test]
fn test_pdf_push_pop_resource_stack() {
let s1 = pdf_new_resource_stack(0, 10);
let s2 = pdf_push_resource_stack(0, s1, 20);
assert_ne!(s2, 0);
let popped = pdf_pop_resource_stack(0, s2);
assert_eq!(popped, s1);
pdf_drop_resource_stack(0, s1);
}
#[test]
fn test_pdf_load_pattern() {
let pat = pdf_load_pattern(0, 1, 0);
assert!(pat > 0);
assert_eq!(pdf_pattern_is_mask(0, pat), 0);
assert_eq!(pdf_pattern_xstep(0, pat), 0.0);
assert_eq!(pdf_pattern_ystep(0, pat), 0.0);
pdf_drop_pattern(0, pat);
}
}