use crate::ffi::ffi_safety::{cstr_to_str, raw_to_slice, write_out};
use crate::ffi::{BUFFERS, Handle, HandleStore, STREAMS};
use crate::fitz::geometry::{Matrix, Rect};
use std::collections::HashMap;
use std::ffi::{CStr, CString, c_char, c_void};
use std::ptr;
use std::sync::LazyLock;
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ProcessorRequirements {
#[default]
None = 0,
RequiresDecodedImages = 1,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CullType {
#[default]
PathDrop = 0,
PathFill = 1,
PathStroke = 2,
PathFillStroke = 3,
ClipPathDrop = 4,
ClipPathFill = 5,
ClipPathStroke = 6,
ClipPathFillStroke = 7,
Glyph = 8,
Image = 9,
Shading = 10,
}
impl From<i32> for CullType {
fn from(value: i32) -> Self {
match value {
0 => CullType::PathDrop,
1 => CullType::PathFill,
2 => CullType::PathStroke,
3 => CullType::PathFillStroke,
4 => CullType::ClipPathDrop,
5 => CullType::ClipPathFill,
6 => CullType::ClipPathStroke,
7 => CullType::ClipPathFillStroke,
8 => CullType::Glyph,
9 => CullType::Image,
10 => CullType::Shading,
_ => CullType::PathDrop,
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ProcessorType {
#[default]
Base = 0,
Run = 1,
Buffer = 2,
Output = 3,
Sanitize = 4,
Color = 5,
Vectorize = 6,
}
#[derive(Debug, Clone, Default)]
pub struct PdfGstate {
pub ctm: Matrix,
pub line_width: f32,
pub line_join: i32,
pub line_cap: i32,
pub miter_limit: f32,
pub flatness: f32,
pub dash_array: Vec<f32>,
pub dash_phase: f32,
pub rendering_intent: String,
pub blend_mode: String,
pub stroke_alpha: f32,
pub fill_alpha: f32,
pub stroke_color: Vec<f32>,
pub fill_color: Vec<f32>,
pub stroke_cs: String,
pub fill_cs: String,
pub op_stroke: bool,
pub op_fill: bool,
pub opm: i32,
}
impl PdfGstate {
pub fn new() -> Self {
Self {
ctm: Matrix::IDENTITY,
line_width: 1.0,
line_join: 0,
line_cap: 0,
miter_limit: 10.0,
flatness: 1.0,
dash_array: Vec::new(),
dash_phase: 0.0,
rendering_intent: "RelativeColorimetric".to_string(),
blend_mode: "Normal".to_string(),
stroke_alpha: 1.0,
fill_alpha: 1.0,
stroke_color: vec![0.0],
fill_color: vec![0.0],
stroke_cs: "DeviceGray".to_string(),
fill_cs: "DeviceGray".to_string(),
op_stroke: false,
op_fill: false,
opm: 0,
}
}
}
#[derive(Debug, Clone)]
pub enum PathElement {
MoveTo {
x: f32,
y: f32,
},
LineTo {
x: f32,
y: f32,
},
CurveTo {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
},
CurveV {
x2: f32,
y2: f32,
x3: f32,
y3: f32,
},
CurveY {
x1: f32,
y1: f32,
x3: f32,
y3: f32,
},
ClosePath,
Rect {
x: f32,
y: f32,
w: f32,
h: f32,
},
}
#[derive(Debug, Clone, Default)]
pub struct PathState {
pub elements: Vec<PathElement>,
}
#[derive(Debug, Clone, Default)]
pub struct PdfTextState {
pub char_space: f32,
pub word_space: f32,
pub scale: f32,
pub leading: f32,
pub font_name: String,
pub font: Handle,
pub size: f32,
pub render: i32,
pub rise: f32,
}
impl PdfTextState {
pub fn new() -> Self {
Self {
char_space: 0.0,
word_space: 0.0,
scale: 100.0,
leading: 0.0,
font_name: String::new(),
font: 0,
size: 12.0,
render: 0,
rise: 0.0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PdfTextObjectState {
pub tlm: Matrix,
pub tm: Matrix,
pub text_mode: i32,
pub text_content: Vec<TextSpan>,
pub text_bbox: Rect,
}
#[derive(Debug, Clone)]
pub struct TextSpan {
pub text: String,
pub matrix: Matrix,
pub font_name: String,
pub font_size: f32,
}
#[derive(Debug, Clone, Default)]
pub struct ResourceStack {
pub resources: Vec<Handle>,
}
#[derive(Default)]
pub struct PdfProcessor {
pub refs: i32,
pub closed: bool,
pub proc_type: ProcessorType,
pub requirements: ProcessorRequirements,
pub usage: String,
pub hidden: i32,
pub rstack: ResourceStack,
pub chain: Option<Handle>,
pub gstate_stack: Vec<PdfGstate>,
pub gstate: PdfGstate,
pub path: PathState,
pub text_state: PdfTextState,
pub text_object: Option<PdfTextObjectState>,
pub in_text: bool,
pub output_buffer: Option<Handle>,
pub output_stream: Option<Handle>,
pub device: Option<Handle>,
pub document: Option<Handle>,
pub ahx_encode: bool,
pub newlines: bool,
pub operators: Vec<ProcessedOperator>,
pub struct_parent: i32,
pub transform: Matrix,
}
#[derive(Debug, Clone)]
pub struct ProcessedOperator {
pub name: String,
pub args: Vec<OperatorArg>,
}
#[derive(Debug, Clone)]
pub enum OperatorArg {
Number(f32),
Integer(i32),
String(String),
Name(String),
Array(Vec<OperatorArg>),
Boolean(bool),
}
pub static PDF_PROCESSORS: LazyLock<HandleStore<PdfProcessor>> = LazyLock::new(HandleStore::new);
pub static PDF_GSTATES: LazyLock<HandleStore<PdfGstate>> = LazyLock::new(HandleStore::new);
impl PdfProcessor {
pub fn new(proc_type: ProcessorType) -> Self {
Self {
refs: 1,
closed: false,
proc_type,
gstate: PdfGstate::new(),
gstate_stack: Vec::new(),
text_state: PdfTextState::new(),
..Default::default()
}
}
pub fn push_gstate(&mut self) {
self.gstate_stack.push(self.gstate.clone());
}
pub fn pop_gstate(&mut self) -> bool {
if let Some(gstate) = self.gstate_stack.pop() {
self.gstate = gstate;
true
} else {
false
}
}
pub fn record_op(&mut self, name: &str, args: Vec<OperatorArg>) {
self.operators.push(ProcessedOperator {
name: name.to_string(),
args,
});
}
pub fn write_op(&self, _name: &str, _args: &[OperatorArg]) {
}
fn apply_operator(&mut self, keyword: &str, operands: &[OperatorArg]) {
match keyword {
"q" => {
self.push_gstate();
self.record_op("q", vec![]);
}
"Q" => {
self.pop_gstate();
self.record_op("Q", vec![]);
}
"cm" => {
if operands.len() >= 6 {
let vals = extract_numbers(operands, 6);
let m = Matrix {
a: vals[0],
b: vals[1],
c: vals[2],
d: vals[3],
e: vals[4],
f: vals[5],
};
self.gstate.ctm = self.gstate.ctm.concat(&m);
}
self.record_op("cm", operands.to_vec());
}
"w" => {
if let Some(v) = extract_number(operands, 0) {
self.gstate.line_width = v;
}
self.record_op("w", operands.to_vec());
}
"J" => {
if let Some(v) = extract_int(operands, 0) {
self.gstate.line_cap = v;
}
self.record_op("J", operands.to_vec());
}
"j" => {
if let Some(v) = extract_int(operands, 0) {
self.gstate.line_join = v;
}
self.record_op("j", operands.to_vec());
}
"M" => {
if let Some(v) = extract_number(operands, 0) {
self.gstate.miter_limit = v;
}
self.record_op("M", operands.to_vec());
}
"d" => {
self.record_op("d", operands.to_vec());
}
"ri" => {
if let Some(name) = extract_name(operands, 0) {
self.gstate.rendering_intent = name;
}
self.record_op("ri", operands.to_vec());
}
"i" => {
if let Some(v) = extract_number(operands, 0) {
self.gstate.flatness = v;
}
self.record_op("i", operands.to_vec());
}
"gs" => {
self.record_op("gs", operands.to_vec());
}
"m" => {
if operands.len() >= 2 {
let x = extract_number(operands, 0).unwrap_or(0.0);
let y = extract_number(operands, 1).unwrap_or(0.0);
self.path.elements.push(PathElement::MoveTo { x, y });
}
self.record_op("m", operands.to_vec());
}
"l" => {
if operands.len() >= 2 {
let x = extract_number(operands, 0).unwrap_or(0.0);
let y = extract_number(operands, 1).unwrap_or(0.0);
self.path.elements.push(PathElement::LineTo { x, y });
}
self.record_op("l", operands.to_vec());
}
"c" => {
if operands.len() >= 6 {
let v = extract_numbers(operands, 6);
self.path.elements.push(PathElement::CurveTo {
x1: v[0],
y1: v[1],
x2: v[2],
y2: v[3],
x3: v[4],
y3: v[5],
});
}
self.record_op("c", operands.to_vec());
}
"v" => {
if operands.len() >= 4 {
let v = extract_numbers(operands, 4);
self.path.elements.push(PathElement::CurveV {
x2: v[0],
y2: v[1],
x3: v[2],
y3: v[3],
});
}
self.record_op("v", operands.to_vec());
}
"y" => {
if operands.len() >= 4 {
let v = extract_numbers(operands, 4);
self.path.elements.push(PathElement::CurveY {
x1: v[0],
y1: v[1],
x3: v[2],
y3: v[3],
});
}
self.record_op("y", operands.to_vec());
}
"h" => {
self.path.elements.push(PathElement::ClosePath);
self.record_op("h", vec![]);
}
"re" => {
if operands.len() >= 4 {
let v = extract_numbers(operands, 4);
self.path.elements.push(PathElement::Rect {
x: v[0],
y: v[1],
w: v[2],
h: v[3],
});
}
self.record_op("re", operands.to_vec());
}
"S" => {
self.record_op("S", vec![]);
self.path = PathState::default();
}
"s" => {
self.path.elements.push(PathElement::ClosePath);
self.record_op("s", vec![]);
self.path = PathState::default();
}
"f" | "F" => {
self.record_op("f", vec![]);
self.path = PathState::default();
}
"f*" => {
self.record_op("f*", vec![]);
self.path = PathState::default();
}
"B" => {
self.record_op("B", vec![]);
self.path = PathState::default();
}
"B*" => {
self.record_op("B*", vec![]);
self.path = PathState::default();
}
"b" => {
self.path.elements.push(PathElement::ClosePath);
self.record_op("b", vec![]);
self.path = PathState::default();
}
"b*" => {
self.path.elements.push(PathElement::ClosePath);
self.record_op("b*", vec![]);
self.path = PathState::default();
}
"n" => {
self.record_op("n", vec![]);
self.path = PathState::default();
}
"W" => self.record_op("W", vec![]),
"W*" => self.record_op("W*", vec![]),
"BT" => {
self.in_text = true;
self.text_object = Some(PdfTextObjectState {
tlm: Matrix::IDENTITY,
tm: Matrix::IDENTITY,
text_mode: 0,
text_content: Vec::new(),
text_bbox: Rect::default(),
});
self.record_op("BT", vec![]);
}
"ET" => {
self.in_text = false;
self.text_object = None;
self.record_op("ET", vec![]);
}
"Tc" => {
if let Some(v) = extract_number(operands, 0) {
self.text_state.char_space = v;
}
self.record_op("Tc", operands.to_vec());
}
"Tw" => {
if let Some(v) = extract_number(operands, 0) {
self.text_state.word_space = v;
}
self.record_op("Tw", operands.to_vec());
}
"Tz" => {
if let Some(v) = extract_number(operands, 0) {
self.text_state.scale = v;
}
self.record_op("Tz", operands.to_vec());
}
"TL" => {
if let Some(v) = extract_number(operands, 0) {
self.text_state.leading = v;
}
self.record_op("TL", operands.to_vec());
}
"Tf" => {
if let Some(name) = extract_name(operands, 0) {
self.text_state.font_name = name;
}
if let Some(v) = extract_number(operands, 1) {
self.text_state.size = v;
}
self.record_op("Tf", operands.to_vec());
}
"Tr" => {
if let Some(v) = extract_int(operands, 0) {
self.text_state.render = v;
}
self.record_op("Tr", operands.to_vec());
}
"Ts" => {
if let Some(v) = extract_number(operands, 0) {
self.text_state.rise = v;
}
self.record_op("Ts", operands.to_vec());
}
"Td" => {
if operands.len() >= 2 {
let tx = extract_number(operands, 0).unwrap_or(0.0);
let ty = extract_number(operands, 1).unwrap_or(0.0);
if let Some(ref mut tos) = self.text_object {
let translate = Matrix {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: tx,
f: ty,
};
tos.tlm = tos.tlm.concat(&translate);
tos.tm = tos.tlm;
}
}
self.record_op("Td", operands.to_vec());
}
"TD" => {
if operands.len() >= 2 {
let tx = extract_number(operands, 0).unwrap_or(0.0);
let ty = extract_number(operands, 1).unwrap_or(0.0);
self.text_state.leading = -ty;
if let Some(ref mut tos) = self.text_object {
let translate = Matrix {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: tx,
f: ty,
};
tos.tlm = tos.tlm.concat(&translate);
tos.tm = tos.tlm;
}
}
self.record_op("TD", operands.to_vec());
}
"Tm" => {
if operands.len() >= 6 {
let vals = extract_numbers(operands, 6);
if let Some(ref mut tos) = self.text_object {
tos.tlm = Matrix {
a: vals[0],
b: vals[1],
c: vals[2],
d: vals[3],
e: vals[4],
f: vals[5],
};
tos.tm = tos.tlm;
}
}
self.record_op("Tm", operands.to_vec());
}
"T*" => {
let leading = self.text_state.leading;
if let Some(ref mut tos) = self.text_object {
let translate = Matrix {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: 0.0,
f: -leading,
};
tos.tlm = tos.tlm.concat(&translate);
tos.tm = tos.tlm;
}
self.record_op("T*", vec![]);
}
"Tj" => {
let text = match operands.first() {
Some(OperatorArg::String(s)) => s.clone(),
_ => String::new(),
};
let font_name = self.text_state.font_name.clone();
let font_size = self.text_state.size;
if let Some(ref mut tos) = self.text_object {
tos.text_content.push(TextSpan {
text: text.clone(),
matrix: tos.tm,
font_name,
font_size,
});
}
self.record_op("Tj", operands.to_vec());
}
"TJ" => {
self.record_op("TJ", operands.to_vec());
}
"'" => {
self.apply_operator("T*", &[]);
self.operators.pop(); let text = match operands.first() {
Some(OperatorArg::String(s)) => s.clone(),
_ => String::new(),
};
let font_name = self.text_state.font_name.clone();
let font_size = self.text_state.size;
if let Some(ref mut tos) = self.text_object {
tos.text_content.push(TextSpan {
text: text.clone(),
matrix: tos.tm,
font_name,
font_size,
});
}
self.record_op("'", operands.to_vec());
}
"\"" => {
if let Some(aw) = extract_number(operands, 0) {
self.text_state.word_space = aw;
}
if let Some(ac) = extract_number(operands, 1) {
self.text_state.char_space = ac;
}
self.apply_operator("T*", &[]);
self.operators.pop(); let text = match operands.get(2) {
Some(OperatorArg::String(s)) => s.clone(),
_ => String::new(),
};
let font_name = self.text_state.font_name.clone();
let font_size = self.text_state.size;
if let Some(ref mut tos) = self.text_object {
tos.text_content.push(TextSpan {
text: text.clone(),
matrix: tos.tm,
font_name,
font_size,
});
}
self.record_op("\"", operands.to_vec());
}
"CS" => {
if let Some(name) = extract_name(operands, 0) {
self.gstate.stroke_cs = name;
}
self.record_op("CS", operands.to_vec());
}
"cs" => {
if let Some(name) = extract_name(operands, 0) {
self.gstate.fill_cs = name;
}
self.record_op("cs", operands.to_vec());
}
"SC" | "SCN" => {
let colors: Vec<f32> = operands
.iter()
.filter_map(|a| match a {
OperatorArg::Number(n) => Some(*n),
OperatorArg::Integer(n) => Some(*n as f32),
_ => None,
})
.collect();
if !colors.is_empty() {
self.gstate.stroke_color = colors;
}
self.record_op(keyword, operands.to_vec());
}
"sc" | "scn" => {
let colors: Vec<f32> = operands
.iter()
.filter_map(|a| match a {
OperatorArg::Number(n) => Some(*n),
OperatorArg::Integer(n) => Some(*n as f32),
_ => None,
})
.collect();
if !colors.is_empty() {
self.gstate.fill_color = colors;
}
self.record_op(keyword, operands.to_vec());
}
"G" => {
if let Some(g) = extract_number(operands, 0) {
self.gstate.stroke_cs = "DeviceGray".to_string();
self.gstate.stroke_color = vec![g];
}
self.record_op("G", operands.to_vec());
}
"g" => {
if let Some(g) = extract_number(operands, 0) {
self.gstate.fill_cs = "DeviceGray".to_string();
self.gstate.fill_color = vec![g];
}
self.record_op("g", operands.to_vec());
}
"RG" => {
if operands.len() >= 3 {
let v = extract_numbers(operands, 3);
self.gstate.stroke_cs = "DeviceRGB".to_string();
self.gstate.stroke_color = v;
}
self.record_op("RG", operands.to_vec());
}
"rg" => {
if operands.len() >= 3 {
let v = extract_numbers(operands, 3);
self.gstate.fill_cs = "DeviceRGB".to_string();
self.gstate.fill_color = v;
}
self.record_op("rg", operands.to_vec());
}
"K" => {
if operands.len() >= 4 {
let v = extract_numbers(operands, 4);
self.gstate.stroke_cs = "DeviceCMYK".to_string();
self.gstate.stroke_color = v;
}
self.record_op("K", operands.to_vec());
}
"k" => {
if operands.len() >= 4 {
let v = extract_numbers(operands, 4);
self.gstate.fill_cs = "DeviceCMYK".to_string();
self.gstate.fill_color = v;
}
self.record_op("k", operands.to_vec());
}
"Do" | "sh" | "BI" => {
self.record_op(keyword, operands.to_vec());
}
"d0" | "d1" => {
self.record_op(keyword, operands.to_vec());
}
"MP" | "DP" | "BMC" | "BDC" | "EMC" => {
self.record_op(keyword, operands.to_vec());
}
"BX" | "EX" => self.record_op(keyword, vec![]),
_ => {
self.record_op(keyword, operands.to_vec());
}
}
}
}
fn extract_number(operands: &[OperatorArg], idx: usize) -> Option<f32> {
operands.get(idx).and_then(|a| match a {
OperatorArg::Number(n) => Some(*n),
OperatorArg::Integer(n) => Some(*n as f32),
_ => None,
})
}
fn extract_int(operands: &[OperatorArg], idx: usize) -> Option<i32> {
operands.get(idx).and_then(|a| match a {
OperatorArg::Integer(n) => Some(*n),
OperatorArg::Number(n) => Some(*n as i32),
_ => None,
})
}
fn extract_name(operands: &[OperatorArg], idx: usize) -> Option<String> {
operands.get(idx).and_then(|a| match a {
OperatorArg::Name(s) => Some(s.clone()),
OperatorArg::String(s) => Some(s.clone()),
_ => None,
})
}
fn extract_numbers(operands: &[OperatorArg], count: usize) -> Vec<f32> {
(0..count)
.map(|i| extract_number(operands, i).unwrap_or(0.0))
.collect()
}
#[derive(Debug, Clone)]
enum CsToken {
Number(f32),
Integer(i32),
Name(String),
StringLiteral(String),
HexString(String),
Keyword(String),
}
fn tokenize_content_stream(data: &[u8]) -> Vec<CsToken> {
let mut tokens = Vec::new();
let len = data.len();
let mut pos = 0;
while pos < len {
let b = data[pos];
if b == b' ' || b == b'\t' || b == b'\r' || b == b'\n' || b == b'\x0C' || b == b'\0' {
pos += 1;
continue;
}
if b == b'%' {
while pos < len && data[pos] != b'\n' && data[pos] != b'\r' {
pos += 1;
}
continue;
}
if b == b'(' {
pos += 1;
let mut s = Vec::new();
let mut depth = 1u32;
while pos < len && depth > 0 {
match data[pos] {
b'(' => {
depth += 1;
s.push(b'(');
}
b')' => {
depth -= 1;
if depth > 0 {
s.push(b')');
}
}
b'\\' => {
pos += 1;
if pos < len {
match data[pos] {
b'n' => s.push(b'\n'),
b'r' => s.push(b'\r'),
b't' => s.push(b'\t'),
b'b' => s.push(0x08),
b'f' => s.push(0x0C),
b'(' => s.push(b'('),
b')' => s.push(b')'),
b'\\' => s.push(b'\\'),
d @ b'0'..=b'7' => {
let mut val = (d - b'0') as u32;
for _ in 0..2 {
if pos + 1 < len
&& data[pos + 1] >= b'0'
&& data[pos + 1] <= b'7'
{
pos += 1;
val = val * 8 + (data[pos] - b'0') as u32;
} else {
break;
}
}
s.push((val & 0xFF) as u8);
}
b'\r' => {
if pos + 1 < len && data[pos + 1] == b'\n' {
pos += 1;
}
}
b'\n' => {}
other => s.push(other),
}
}
}
other => s.push(other),
}
pos += 1;
}
tokens.push(CsToken::StringLiteral(
String::from_utf8_lossy(&s).to_string(),
));
continue;
}
if b == b'<' && (pos + 1 >= len || data[pos + 1] != b'<') {
pos += 1;
let mut hex = Vec::new();
while pos < len && data[pos] != b'>' {
let h = data[pos];
if h.is_ascii_hexdigit() {
hex.push(h);
}
pos += 1;
}
if pos < len {
pos += 1; }
if hex.len() % 2 != 0 {
hex.push(b'0');
}
let decoded: Vec<u8> = hex
.chunks(2)
.filter_map(|pair| {
let hi = char::from(pair[0]).to_digit(16)?;
let lo = char::from(pair[1]).to_digit(16)?;
Some((hi * 16 + lo) as u8)
})
.collect();
tokens.push(CsToken::HexString(
String::from_utf8_lossy(&decoded).to_string(),
));
continue;
}
if b == b'/' {
pos += 1;
let start = pos;
while pos < len {
let ch = data[pos];
if ch == b' '
|| ch == b'\t'
|| ch == b'\r'
|| ch == b'\n'
|| ch == b'\x0C'
|| ch == b'\0'
|| ch == b'/'
|| ch == b'('
|| ch == b')'
|| ch == b'<'
|| ch == b'>'
|| ch == b'['
|| ch == b']'
|| ch == b'{'
|| ch == b'}'
|| ch == b'%'
{
break;
}
pos += 1;
}
let name = String::from_utf8_lossy(&data[start..pos]).to_string();
tokens.push(CsToken::Name(name));
continue;
}
if b == b'[' || b == b']' {
tokens.push(CsToken::Keyword(String::from(b as char)));
pos += 1;
continue;
}
if b == b'<' && pos + 1 < len && data[pos + 1] == b'<' {
tokens.push(CsToken::Keyword("<<".to_string()));
pos += 2;
continue;
}
if b == b'>' && pos + 1 < len && data[pos + 1] == b'>' {
tokens.push(CsToken::Keyword(">>".to_string()));
pos += 2;
continue;
}
if b == b'+' || b == b'-' || b == b'.' || b.is_ascii_digit() {
let start = pos;
let mut has_dot = b == b'.';
pos += 1;
while pos < len {
let ch = data[pos];
if ch == b'.' && !has_dot {
has_dot = true;
pos += 1;
} else if ch.is_ascii_digit() {
pos += 1;
} else {
break;
}
}
let num_str = String::from_utf8_lossy(&data[start..pos]);
if has_dot {
let val = num_str.parse::<f32>().unwrap_or(0.0);
tokens.push(CsToken::Number(val));
} else {
match num_str.parse::<i32>() {
Ok(iv) => tokens.push(CsToken::Integer(iv)),
Err(_) => {
let val = num_str.parse::<f32>().unwrap_or(0.0);
tokens.push(CsToken::Number(val));
}
}
}
continue;
}
if b.is_ascii_alphabetic() || b == b'\'' || b == b'"' {
let start = pos;
pos += 1;
while pos < len {
let ch = data[pos];
if ch.is_ascii_alphanumeric() || ch == b'*' || ch == b'\'' || ch == b'"' {
pos += 1;
} else {
break;
}
}
let kw = String::from_utf8_lossy(&data[start..pos]).to_string();
match kw.as_str() {
"true" => tokens.push(CsToken::Keyword("true".to_string())),
"false" => tokens.push(CsToken::Keyword("false".to_string())),
_ => tokens.push(CsToken::Keyword(kw)),
}
continue;
}
pos += 1;
}
tokens
}
fn is_operator_keyword(kw: &str) -> bool {
matches!(
kw,
"q" | "Q"
| "cm"
| "w"
| "J"
| "j"
| "M"
| "d"
| "ri"
| "i"
| "gs"
| "m"
| "l"
| "c"
| "v"
| "y"
| "h"
| "re"
| "S"
| "s"
| "f"
| "F"
| "f*"
| "B"
| "B*"
| "b"
| "b*"
| "n"
| "W"
| "W*"
| "BT"
| "ET"
| "Tc"
| "Tw"
| "Tz"
| "TL"
| "Tf"
| "Tr"
| "Ts"
| "Td"
| "TD"
| "Tm"
| "T*"
| "Tj"
| "TJ"
| "'"
| "\""
| "d0"
| "d1"
| "CS"
| "cs"
| "SC"
| "SCN"
| "sc"
| "scn"
| "G"
| "g"
| "RG"
| "rg"
| "K"
| "k"
| "Do"
| "sh"
| "BI"
| "ID"
| "EI"
| "MP"
| "DP"
| "BMC"
| "BDC"
| "EMC"
| "BX"
| "EX"
| "true"
| "false"
| "null"
)
}
fn tokens_to_operators(tokens: &[CsToken]) -> Vec<(String, Vec<OperatorArg>)> {
let mut result = Vec::new();
let mut operands: Vec<OperatorArg> = Vec::new();
for tok in tokens {
match tok {
CsToken::Keyword(kw) if is_operator_keyword(kw) => {
match kw.as_str() {
"true" => {
operands.push(OperatorArg::Boolean(true));
}
"false" => {
operands.push(OperatorArg::Boolean(false));
}
"null" => {
operands.push(OperatorArg::Integer(0));
}
_ => {
result.push((kw.clone(), std::mem::take(&mut operands)));
}
}
}
CsToken::Number(n) => operands.push(OperatorArg::Number(*n)),
CsToken::Integer(n) => operands.push(OperatorArg::Integer(*n)),
CsToken::Name(s) => operands.push(OperatorArg::Name(s.clone())),
CsToken::StringLiteral(s) => operands.push(OperatorArg::String(s.clone())),
CsToken::HexString(s) => operands.push(OperatorArg::String(s.clone())),
CsToken::Keyword(kw) => {
operands.push(OperatorArg::Name(kw.clone()));
}
}
}
result
}
fn get_stream_data(stm: Handle) -> Option<Vec<u8>> {
if let Some(stream_arc) = STREAMS.get(stm) {
let guard = stream_arc.lock().unwrap();
return Some(guard.data.clone());
}
if let Some(buf_arc) = BUFFERS.get(stm) {
let guard = buf_arc.lock().unwrap();
return Some(guard.data().to_vec());
}
None
}
fn process_stream_on_processor(proc: &mut PdfProcessor, data: &[u8]) {
let tokens = tokenize_content_stream(data);
let ops = tokens_to_operators(&tokens);
for (keyword, operands) in &ops {
proc.apply_operator(keyword, operands);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_processor(_ctx: Handle, size: i32) -> Handle {
let proc_type = match size {
1 => ProcessorType::Run,
2 => ProcessorType::Buffer,
3 => ProcessorType::Output,
4 => ProcessorType::Sanitize,
5 => ProcessorType::Color,
6 => ProcessorType::Vectorize,
_ => ProcessorType::Base,
};
let processor = PdfProcessor::new(proc_type);
PDF_PROCESSORS.insert(processor)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_keep_processor(_ctx: Handle, proc: Handle) -> Handle {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.refs += 1;
}
proc
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_close_processor(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.closed = true;
if let Some(chain) = proc_guard.chain {
drop(proc_guard);
pdf_close_processor(_ctx, chain);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_processor(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.refs -= 1;
if proc_guard.refs <= 0 {
let chain = proc_guard.chain;
drop(proc_guard);
if let Some(chain_handle) = chain {
pdf_drop_processor(_ctx, chain_handle);
}
PDF_PROCESSORS.remove(proc);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_reset_processor(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.closed = false;
proc_guard.gstate = PdfGstate::new();
proc_guard.gstate_stack.clear();
proc_guard.path = PathState::default();
proc_guard.text_state = PdfTextState::new();
proc_guard.text_object = None;
proc_guard.in_text = false;
proc_guard.operators.clear();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_run_processor(
_ctx: Handle,
doc: Handle,
dev: Handle,
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
struct_parent: i32,
usage: *const c_char,
) -> Handle {
let mut processor = PdfProcessor::new(ProcessorType::Run);
processor.document = Some(doc);
processor.device = Some(dev);
processor.transform = Matrix { a, b, c, d, e, f };
processor.struct_parent = struct_parent;
if !usage.is_null() {
if let Ok(s) = unsafe { CStr::from_ptr(usage) }.to_str() {
processor.usage = s.to_string();
}
}
PDF_PROCESSORS.insert(processor)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_buffer_processor(
_ctx: Handle,
buffer: Handle,
ahx_encode: i32,
newlines: i32,
) -> Handle {
let mut processor = PdfProcessor::new(ProcessorType::Buffer);
processor.output_buffer = Some(buffer);
processor.ahx_encode = ahx_encode != 0;
processor.newlines = newlines != 0;
PDF_PROCESSORS.insert(processor)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_output_processor(
_ctx: Handle,
out: Handle,
ahx_encode: i32,
newlines: i32,
) -> Handle {
let mut processor = PdfProcessor::new(ProcessorType::Output);
processor.output_stream = Some(out);
processor.ahx_encode = ahx_encode != 0;
processor.newlines = newlines != 0;
PDF_PROCESSORS.insert(processor)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_sanitize_filter(
_ctx: Handle,
_doc: Handle,
chain: Handle,
struct_parents: i32,
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
) -> Handle {
let mut processor = PdfProcessor::new(ProcessorType::Sanitize);
processor.chain = Some(chain);
processor.struct_parent = struct_parents;
processor.transform = Matrix { a, b, c, d, e, f };
PDF_PROCESSORS.insert(processor)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_color_filter(
_ctx: Handle,
_doc: Handle,
chain: Handle,
struct_parents: i32,
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
) -> Handle {
let mut processor = PdfProcessor::new(ProcessorType::Color);
processor.chain = Some(chain);
processor.struct_parent = struct_parents;
processor.transform = Matrix { a, b, c, d, e, f };
PDF_PROCESSORS.insert(processor)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_new_vectorize_filter(
_ctx: Handle,
_doc: Handle,
chain: Handle,
struct_parents: i32,
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
) -> Handle {
let mut processor = PdfProcessor::new(ProcessorType::Vectorize);
processor.chain = Some(chain);
processor.struct_parent = struct_parents;
processor.transform = Matrix { a, b, c, d, e, f };
PDF_PROCESSORS.insert(processor)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_processor_push_resources(_ctx: Handle, proc: Handle, res: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.rstack.resources.push(res);
if let Some(chain) = proc_guard.chain {
drop(proc_guard);
pdf_processor_push_resources(_ctx, chain, res);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_processor_pop_resources(_ctx: Handle, proc: Handle) -> Handle {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let res = proc_guard.rstack.resources.pop().unwrap_or(0);
if let Some(chain) = proc_guard.chain {
drop(proc_guard);
let _ = pdf_processor_pop_resources(_ctx, chain);
}
return res;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_process_contents(
_ctx: Handle,
proc: Handle,
_doc: Handle,
res: Handle,
stm: Handle,
out_res: *mut Handle,
) {
let stream_data = get_stream_data(stm);
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.rstack.resources.push(res);
proc_guard.push_gstate();
if let Some(data) = stream_data {
process_stream_on_processor(&mut proc_guard, &data);
}
proc_guard.pop_gstate();
proc_guard.rstack.resources.pop();
}
if !out_res.is_null() {
unsafe { *out_res = res };
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_process_annot(_ctx: Handle, proc: Handle, annot: Handle) {
use crate::ffi::annot::ANNOTATIONS;
let annot_info = ANNOTATIONS.get(annot).map(|arc| {
let guard = arc.lock().unwrap();
(
guard.annot_type(),
guard.rect(),
guard.color(),
guard.opacity(),
guard.contents().to_string(),
guard.interior_color().to_vec(),
)
});
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.push_gstate();
if let Some((atype, rect, color, opacity, _contents, interior_color)) = annot_info {
proc_guard.record_op(
"annot",
vec![
OperatorArg::Integer(annot as i32),
OperatorArg::Name(format!("{:?}", atype)),
],
);
if (opacity - 1.0).abs() > f32::EPSILON {
proc_guard.gstate.fill_alpha = opacity;
proc_guard.gstate.stroke_alpha = opacity;
}
match atype {
crate::pdf::annot::AnnotType::Square | crate::pdf::annot::AnnotType::FreeText => {
if let Some([r, g, b]) = color {
proc_guard.apply_operator(
"RG",
&[
OperatorArg::Number(r),
OperatorArg::Number(g),
OperatorArg::Number(b),
],
);
}
if interior_color.len() >= 3 {
proc_guard.apply_operator(
"rg",
&[
OperatorArg::Number(interior_color[0]),
OperatorArg::Number(interior_color[1]),
OperatorArg::Number(interior_color[2]),
],
);
}
proc_guard.apply_operator(
"re",
&[
OperatorArg::Number(rect.x0),
OperatorArg::Number(rect.y0),
OperatorArg::Number(rect.x1 - rect.x0),
OperatorArg::Number(rect.y1 - rect.y0),
],
);
if interior_color.len() >= 3 {
proc_guard.apply_operator("B", &[]);
} else {
proc_guard.apply_operator("S", &[]);
}
}
crate::pdf::annot::AnnotType::Circle => {
if let Some([r, g, b]) = color {
proc_guard.apply_operator(
"RG",
&[
OperatorArg::Number(r),
OperatorArg::Number(g),
OperatorArg::Number(b),
],
);
}
let cx = (rect.x0 + rect.x1) / 2.0;
let cy = (rect.y0 + rect.y1) / 2.0;
let rx = (rect.x1 - rect.x0) / 2.0;
let ry = (rect.y1 - rect.y0) / 2.0;
let k: f32 = 0.5522848;
let kx = rx * k;
let ky = ry * k;
proc_guard.apply_operator(
"m",
&[OperatorArg::Number(cx + rx), OperatorArg::Number(cy)],
);
proc_guard.apply_operator(
"c",
&[
OperatorArg::Number(cx + rx),
OperatorArg::Number(cy + ky),
OperatorArg::Number(cx + kx),
OperatorArg::Number(cy + ry),
OperatorArg::Number(cx),
OperatorArg::Number(cy + ry),
],
);
proc_guard.apply_operator(
"c",
&[
OperatorArg::Number(cx - kx),
OperatorArg::Number(cy + ry),
OperatorArg::Number(cx - rx),
OperatorArg::Number(cy + ky),
OperatorArg::Number(cx - rx),
OperatorArg::Number(cy),
],
);
proc_guard.apply_operator(
"c",
&[
OperatorArg::Number(cx - rx),
OperatorArg::Number(cy - ky),
OperatorArg::Number(cx - kx),
OperatorArg::Number(cy - ry),
OperatorArg::Number(cx),
OperatorArg::Number(cy - ry),
],
);
proc_guard.apply_operator(
"c",
&[
OperatorArg::Number(cx + kx),
OperatorArg::Number(cy - ry),
OperatorArg::Number(cx + rx),
OperatorArg::Number(cy - ky),
OperatorArg::Number(cx + rx),
OperatorArg::Number(cy),
],
);
proc_guard.apply_operator("S", &[]);
}
crate::pdf::annot::AnnotType::Highlight => {
if let Some([r, g, b]) = color {
proc_guard.apply_operator(
"rg",
&[
OperatorArg::Number(r),
OperatorArg::Number(g),
OperatorArg::Number(b),
],
);
}
proc_guard.apply_operator(
"re",
&[
OperatorArg::Number(rect.x0),
OperatorArg::Number(rect.y0),
OperatorArg::Number(rect.x1 - rect.x0),
OperatorArg::Number(rect.y1 - rect.y0),
],
);
proc_guard.apply_operator("f", &[]);
}
crate::pdf::annot::AnnotType::Underline => {
if let Some([r, g, b]) = color {
proc_guard.apply_operator(
"RG",
&[
OperatorArg::Number(r),
OperatorArg::Number(g),
OperatorArg::Number(b),
],
);
}
proc_guard.apply_operator(
"m",
&[OperatorArg::Number(rect.x0), OperatorArg::Number(rect.y0)],
);
proc_guard.apply_operator(
"l",
&[OperatorArg::Number(rect.x1), OperatorArg::Number(rect.y0)],
);
proc_guard.apply_operator("S", &[]);
}
crate::pdf::annot::AnnotType::StrikeOut => {
if let Some([r, g, b]) = color {
proc_guard.apply_operator(
"RG",
&[
OperatorArg::Number(r),
OperatorArg::Number(g),
OperatorArg::Number(b),
],
);
}
let mid_y = (rect.y0 + rect.y1) / 2.0;
proc_guard.apply_operator(
"m",
&[OperatorArg::Number(rect.x0), OperatorArg::Number(mid_y)],
);
proc_guard.apply_operator(
"l",
&[OperatorArg::Number(rect.x1), OperatorArg::Number(mid_y)],
);
proc_guard.apply_operator("S", &[]);
}
crate::pdf::annot::AnnotType::Line => {
if let Some([r, g, b]) = color {
proc_guard.apply_operator(
"RG",
&[
OperatorArg::Number(r),
OperatorArg::Number(g),
OperatorArg::Number(b),
],
);
}
proc_guard.apply_operator(
"m",
&[OperatorArg::Number(rect.x0), OperatorArg::Number(rect.y0)],
);
proc_guard.apply_operator(
"l",
&[OperatorArg::Number(rect.x1), OperatorArg::Number(rect.y1)],
);
proc_guard.apply_operator("S", &[]);
}
_ => {
}
}
} else {
proc_guard.record_op("annot", vec![OperatorArg::Integer(annot as i32)]);
}
proc_guard.pop_gstate();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_process_glyph(_ctx: Handle, proc: Handle, _doc: Handle, res: Handle) {
let stream_data = get_stream_data(res);
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.rstack.resources.push(res);
proc_guard.push_gstate();
if let Some(data) = stream_data {
process_stream_on_processor(&mut proc_guard, &data);
}
proc_guard.pop_gstate();
proc_guard.rstack.resources.pop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_process_raw_contents(_ctx: Handle, proc: Handle, _doc: Handle, stm: Handle) {
let stream_data = get_stream_data(stm);
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
if let Some(data) = stream_data {
process_stream_on_processor(&mut proc_guard, &data);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_count_q_balance(
_ctx: Handle,
_doc: Handle,
_res: Handle,
stm: Handle,
prepend: *mut i32,
append: *mut i32,
) {
let mut missing_q: i32 = 0; let mut depth: i32 = 0;
if let Some(data) = get_stream_data(stm) {
let tokens = tokenize_content_stream(&data);
let ops = tokens_to_operators(&tokens);
for (keyword, _) in &ops {
match keyword.as_str() {
"q" => {
depth += 1;
}
"Q" => {
if depth > 0 {
depth -= 1;
} else {
missing_q += 1;
}
}
_ => {}
}
}
}
if !prepend.is_null() {
unsafe { *prepend = missing_q };
}
if !append.is_null() {
unsafe { *append = depth };
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_w(_ctx: Handle, proc: Handle, linewidth: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.line_width = linewidth;
proc_guard.record_op("w", vec![OperatorArg::Number(linewidth)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_j(_ctx: Handle, proc: Handle, linejoin: i32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.line_join = linejoin;
proc_guard.record_op("j", vec![OperatorArg::Integer(linejoin)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_J(_ctx: Handle, proc: Handle, linecap: i32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.line_cap = linecap;
proc_guard.record_op("J", vec![OperatorArg::Integer(linecap)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_M(_ctx: Handle, proc: Handle, miterlimit: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.miter_limit = miterlimit;
proc_guard.record_op("M", vec![OperatorArg::Number(miterlimit)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_d(
_ctx: Handle,
proc: Handle,
array: *const f32,
array_len: i32,
phase: f32,
) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let dash_array = if !array.is_null() && array_len > 0 {
unsafe { std::slice::from_raw_parts(array, array_len as usize) }.to_vec()
} else {
Vec::new()
};
proc_guard.gstate.dash_array = dash_array.clone();
proc_guard.gstate.dash_phase = phase;
let args: Vec<OperatorArg> = dash_array.iter().map(|&v| OperatorArg::Number(v)).collect();
proc_guard.record_op(
"d",
vec![OperatorArg::Array(args), OperatorArg::Number(phase)],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_ri(_ctx: Handle, proc: Handle, intent: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let intent_str = if !intent.is_null() {
unsafe { CStr::from_ptr(intent) }
.to_str()
.unwrap_or("RelativeColorimetric")
.to_string()
} else {
"RelativeColorimetric".to_string()
};
proc_guard.gstate.rendering_intent = intent_str.clone();
proc_guard.record_op("ri", vec![OperatorArg::Name(intent_str)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_i(_ctx: Handle, proc: Handle, flatness: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.flatness = flatness;
proc_guard.record_op("i", vec![OperatorArg::Number(flatness)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_q(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.push_gstate();
proc_guard.record_op("q", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Q(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.pop_gstate();
proc_guard.record_op("Q", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_cm(
_ctx: Handle,
proc: Handle,
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let new_matrix = Matrix { a, b, c, d, e, f };
proc_guard.gstate.ctm = proc_guard.gstate.ctm.concat(&new_matrix);
proc_guard.record_op(
"cm",
vec![
OperatorArg::Number(a),
OperatorArg::Number(b),
OperatorArg::Number(c),
OperatorArg::Number(d),
OperatorArg::Number(e),
OperatorArg::Number(f),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_gs_begin(_ctx: Handle, proc: Handle, name: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let name_str = if !name.is_null() {
unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("")
} else {
""
};
proc_guard.record_op("gs", vec![OperatorArg::Name(name_str.to_string())]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_gs_BM(_ctx: Handle, proc: Handle, blendmode: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let mode = if !blendmode.is_null() {
unsafe { CStr::from_ptr(blendmode) }
.to_str()
.unwrap_or("Normal")
.to_string()
} else {
"Normal".to_string()
};
proc_guard.gstate.blend_mode = mode;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_gs_ca(_ctx: Handle, proc: Handle, alpha: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.fill_alpha = alpha;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_gs_CA(_ctx: Handle, proc: Handle, alpha: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.stroke_alpha = alpha;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_gs_end(_ctx: Handle, _proc: Handle) {
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_gs_op(_ctx: Handle, proc: Handle, b: i32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.op_fill = b != 0;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_gs_OP(_ctx: Handle, proc: Handle, b: i32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.op_stroke = b != 0;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_gs_OPM(_ctx: Handle, proc: Handle, i: i32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.opm = i;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_m(_ctx: Handle, proc: Handle, x: f32, y: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.path.elements.push(PathElement::MoveTo { x, y });
proc_guard.record_op("m", vec![OperatorArg::Number(x), OperatorArg::Number(y)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_l(_ctx: Handle, proc: Handle, x: f32, y: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.path.elements.push(PathElement::LineTo { x, y });
proc_guard.record_op("l", vec![OperatorArg::Number(x), OperatorArg::Number(y)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_c(
_ctx: Handle,
proc: Handle,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.path.elements.push(PathElement::CurveTo {
x1,
y1,
x2,
y2,
x3,
y3,
});
proc_guard.record_op(
"c",
vec![
OperatorArg::Number(x1),
OperatorArg::Number(y1),
OperatorArg::Number(x2),
OperatorArg::Number(y2),
OperatorArg::Number(x3),
OperatorArg::Number(y3),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_v(_ctx: Handle, proc: Handle, x2: f32, y2: f32, x3: f32, y3: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard
.path
.elements
.push(PathElement::CurveV { x2, y2, x3, y3 });
proc_guard.record_op(
"v",
vec![
OperatorArg::Number(x2),
OperatorArg::Number(y2),
OperatorArg::Number(x3),
OperatorArg::Number(y3),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_y(_ctx: Handle, proc: Handle, x1: f32, y1: f32, x3: f32, y3: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard
.path
.elements
.push(PathElement::CurveY { x1, y1, x3, y3 });
proc_guard.record_op(
"y",
vec![
OperatorArg::Number(x1),
OperatorArg::Number(y1),
OperatorArg::Number(x3),
OperatorArg::Number(y3),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_h(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.path.elements.push(PathElement::ClosePath);
proc_guard.record_op("h", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_re(_ctx: Handle, proc: Handle, x: f32, y: f32, w: f32, h: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard
.path
.elements
.push(PathElement::Rect { x, y, w, h });
proc_guard.record_op(
"re",
vec![
OperatorArg::Number(x),
OperatorArg::Number(y),
OperatorArg::Number(w),
OperatorArg::Number(h),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_S(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("S", vec![]);
proc_guard.path = PathState::default();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_s(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.path.elements.push(PathElement::ClosePath);
proc_guard.record_op("s", vec![]);
proc_guard.path = PathState::default();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_f(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("f", vec![]);
proc_guard.path = PathState::default();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_F(_ctx: Handle, proc: Handle) {
pdf_op_f(_ctx, proc);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_fstar(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("f*", vec![]);
proc_guard.path = PathState::default();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_B(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("B", vec![]);
proc_guard.path = PathState::default();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Bstar(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("B*", vec![]);
proc_guard.path = PathState::default();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_b(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.path.elements.push(PathElement::ClosePath);
proc_guard.record_op("b", vec![]);
proc_guard.path = PathState::default();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_bstar(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.path.elements.push(PathElement::ClosePath);
proc_guard.record_op("b*", vec![]);
proc_guard.path = PathState::default();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_n(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("n", vec![]);
proc_guard.path = PathState::default();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_W(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("W", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Wstar(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("W*", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_BT(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.in_text = true;
proc_guard.text_object = Some(PdfTextObjectState {
tlm: Matrix::IDENTITY,
tm: Matrix::IDENTITY,
text_mode: 0,
text_content: Vec::new(),
text_bbox: Rect::default(),
});
proc_guard.record_op("BT", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_ET(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.in_text = false;
proc_guard.text_object = None;
proc_guard.record_op("ET", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Tc(_ctx: Handle, proc: Handle, charspace: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.text_state.char_space = charspace;
proc_guard.record_op("Tc", vec![OperatorArg::Number(charspace)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Tw(_ctx: Handle, proc: Handle, wordspace: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.text_state.word_space = wordspace;
proc_guard.record_op("Tw", vec![OperatorArg::Number(wordspace)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Tz(_ctx: Handle, proc: Handle, scale: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.text_state.scale = scale;
proc_guard.record_op("Tz", vec![OperatorArg::Number(scale)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_TL(_ctx: Handle, proc: Handle, leading: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.text_state.leading = leading;
proc_guard.record_op("TL", vec![OperatorArg::Number(leading)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Tf(_ctx: Handle, proc: Handle, name: *const c_char, size: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let font_name = if !name.is_null() {
unsafe { CStr::from_ptr(name) }
.to_str()
.unwrap_or("")
.to_string()
} else {
String::new()
};
proc_guard.text_state.font_name = font_name.clone();
proc_guard.text_state.size = size;
proc_guard.record_op(
"Tf",
vec![OperatorArg::Name(font_name), OperatorArg::Number(size)],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Tr(_ctx: Handle, proc: Handle, render: i32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.text_state.render = render;
proc_guard.record_op("Tr", vec![OperatorArg::Integer(render)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Ts(_ctx: Handle, proc: Handle, rise: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.text_state.rise = rise;
proc_guard.record_op("Ts", vec![OperatorArg::Number(rise)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Td(_ctx: Handle, proc: Handle, tx: f32, ty: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
if let Some(ref mut tos) = proc_guard.text_object {
let translate = Matrix {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: tx,
f: ty,
};
tos.tlm = tos.tlm.concat(&translate);
tos.tm = tos.tlm;
}
proc_guard.record_op("Td", vec![OperatorArg::Number(tx), OperatorArg::Number(ty)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_TD(_ctx: Handle, proc: Handle, tx: f32, ty: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.text_state.leading = -ty;
if let Some(ref mut tos) = proc_guard.text_object {
let translate = Matrix {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: tx,
f: ty,
};
tos.tlm = tos.tlm.concat(&translate);
tos.tm = tos.tlm;
}
proc_guard.record_op("TD", vec![OperatorArg::Number(tx), OperatorArg::Number(ty)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Tm(
_ctx: Handle,
proc: Handle,
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
if let Some(ref mut tos) = proc_guard.text_object {
tos.tlm = Matrix { a, b, c, d, e, f };
tos.tm = tos.tlm;
}
proc_guard.record_op(
"Tm",
vec![
OperatorArg::Number(a),
OperatorArg::Number(b),
OperatorArg::Number(c),
OperatorArg::Number(d),
OperatorArg::Number(e),
OperatorArg::Number(f),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Tstar(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let leading = proc_guard.text_state.leading;
if let Some(ref mut tos) = proc_guard.text_object {
let translate = Matrix {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: 0.0,
f: -leading,
};
tos.tlm = tos.tlm.concat(&translate);
tos.tm = tos.tlm;
}
proc_guard.record_op("T*", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Tj(_ctx: Handle, proc: Handle, str: *const c_char, len: usize) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let text = if !str.is_null() {
let bytes = unsafe { std::slice::from_raw_parts(str as *const u8, len) };
String::from_utf8_lossy(bytes).to_string()
} else {
String::new()
};
let font_name = proc_guard.text_state.font_name.clone();
let font_size = proc_guard.text_state.size;
if let Some(ref mut tos) = proc_guard.text_object {
tos.text_content.push(TextSpan {
text: text.clone(),
matrix: tos.tm,
font_name,
font_size,
});
}
proc_guard.record_op("Tj", vec![OperatorArg::String(text)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_TJ(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("TJ", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_squote(_ctx: Handle, proc: Handle, str: *const c_char, len: usize) {
pdf_op_Tstar(_ctx, proc);
pdf_op_Tj(_ctx, proc, str, len);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_dquote(
_ctx: Handle,
proc: Handle,
aw: f32,
ac: f32,
str: *const c_char,
len: usize,
) {
pdf_op_Tw(_ctx, proc, aw);
pdf_op_Tc(_ctx, proc, ac);
pdf_op_squote(_ctx, proc, str, len);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_d0(_ctx: Handle, proc: Handle, wx: f32, wy: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("d0", vec![OperatorArg::Number(wx), OperatorArg::Number(wy)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_d1(
_ctx: Handle,
proc: Handle,
wx: f32,
wy: f32,
llx: f32,
lly: f32,
urx: f32,
ury: f32,
) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op(
"d1",
vec![
OperatorArg::Number(wx),
OperatorArg::Number(wy),
OperatorArg::Number(llx),
OperatorArg::Number(lly),
OperatorArg::Number(urx),
OperatorArg::Number(ury),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_CS(_ctx: Handle, proc: Handle, name: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let cs_name = if !name.is_null() {
unsafe { CStr::from_ptr(name) }
.to_str()
.unwrap_or("DeviceGray")
.to_string()
} else {
"DeviceGray".to_string()
};
proc_guard.gstate.stroke_cs = cs_name.clone();
proc_guard.record_op("CS", vec![OperatorArg::Name(cs_name)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_cs(_ctx: Handle, proc: Handle, name: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let cs_name = if !name.is_null() {
unsafe { CStr::from_ptr(name) }
.to_str()
.unwrap_or("DeviceGray")
.to_string()
} else {
"DeviceGray".to_string()
};
proc_guard.gstate.fill_cs = cs_name.clone();
proc_guard.record_op("cs", vec![OperatorArg::Name(cs_name)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_SC_color(_ctx: Handle, proc: Handle, n: i32, color: *const f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let colors = if !color.is_null() && n > 0 {
unsafe { std::slice::from_raw_parts(color, n as usize) }.to_vec()
} else {
vec![0.0]
};
proc_guard.gstate.stroke_color = colors.clone();
let args: Vec<OperatorArg> = colors.iter().map(|&c| OperatorArg::Number(c)).collect();
proc_guard.record_op("SC", args);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_sc_color(_ctx: Handle, proc: Handle, n: i32, color: *const f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let colors = if !color.is_null() && n > 0 {
unsafe { std::slice::from_raw_parts(color, n as usize) }.to_vec()
} else {
vec![0.0]
};
proc_guard.gstate.fill_color = colors.clone();
let args: Vec<OperatorArg> = colors.iter().map(|&c| OperatorArg::Number(c)).collect();
proc_guard.record_op("sc", args);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_G(_ctx: Handle, proc: Handle, g: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.stroke_cs = "DeviceGray".to_string();
proc_guard.gstate.stroke_color = vec![g];
proc_guard.record_op("G", vec![OperatorArg::Number(g)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_g(_ctx: Handle, proc: Handle, g: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.fill_cs = "DeviceGray".to_string();
proc_guard.gstate.fill_color = vec![g];
proc_guard.record_op("g", vec![OperatorArg::Number(g)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_RG(_ctx: Handle, proc: Handle, r: f32, g: f32, b: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.stroke_cs = "DeviceRGB".to_string();
proc_guard.gstate.stroke_color = vec![r, g, b];
proc_guard.record_op(
"RG",
vec![
OperatorArg::Number(r),
OperatorArg::Number(g),
OperatorArg::Number(b),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_rg(_ctx: Handle, proc: Handle, r: f32, g: f32, b: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.fill_cs = "DeviceRGB".to_string();
proc_guard.gstate.fill_color = vec![r, g, b];
proc_guard.record_op(
"rg",
vec![
OperatorArg::Number(r),
OperatorArg::Number(g),
OperatorArg::Number(b),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_K(_ctx: Handle, proc: Handle, c: f32, m: f32, y: f32, k: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.stroke_cs = "DeviceCMYK".to_string();
proc_guard.gstate.stroke_color = vec![c, m, y, k];
proc_guard.record_op(
"K",
vec![
OperatorArg::Number(c),
OperatorArg::Number(m),
OperatorArg::Number(y),
OperatorArg::Number(k),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_k(_ctx: Handle, proc: Handle, c: f32, m: f32, y: f32, k: f32) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.fill_cs = "DeviceCMYK".to_string();
proc_guard.gstate.fill_color = vec![c, m, y, k];
proc_guard.record_op(
"k",
vec![
OperatorArg::Number(c),
OperatorArg::Number(m),
OperatorArg::Number(y),
OperatorArg::Number(k),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_BI(_ctx: Handle, proc: Handle, image: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("BI", vec![OperatorArg::Integer(image as i32)]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_sh(_ctx: Handle, proc: Handle, name: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let shade_name = if !name.is_null() {
unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("")
} else {
""
};
proc_guard.record_op("sh", vec![OperatorArg::Name(shade_name.to_string())]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Do_image(_ctx: Handle, proc: Handle, name: *const c_char, image: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let img_name = if !name.is_null() {
unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("")
} else {
""
};
proc_guard.record_op(
"Do",
vec![
OperatorArg::Name(img_name.to_string()),
OperatorArg::Integer(image as i32),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_Do_form(_ctx: Handle, proc: Handle, name: *const c_char, form: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let form_name = if !name.is_null() {
unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("")
} else {
""
};
proc_guard.record_op(
"Do",
vec![
OperatorArg::Name(form_name.to_string()),
OperatorArg::Integer(form as i32),
],
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_MP(_ctx: Handle, proc: Handle, tag: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let tag_str = if !tag.is_null() {
unsafe { CStr::from_ptr(tag) }.to_str().unwrap_or("")
} else {
""
};
proc_guard.record_op("MP", vec![OperatorArg::Name(tag_str.to_string())]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_DP(_ctx: Handle, proc: Handle, tag: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let tag_str = if !tag.is_null() {
unsafe { CStr::from_ptr(tag) }.to_str().unwrap_or("")
} else {
""
};
proc_guard.record_op("DP", vec![OperatorArg::Name(tag_str.to_string())]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_BMC(_ctx: Handle, proc: Handle, tag: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let tag_str = if !tag.is_null() {
unsafe { CStr::from_ptr(tag) }.to_str().unwrap_or("")
} else {
""
};
proc_guard.record_op("BMC", vec![OperatorArg::Name(tag_str.to_string())]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_BDC(_ctx: Handle, proc: Handle, tag: *const c_char) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
let tag_str = if !tag.is_null() {
unsafe { CStr::from_ptr(tag) }.to_str().unwrap_or("")
} else {
""
};
proc_guard.record_op("BDC", vec![OperatorArg::Name(tag_str.to_string())]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_EMC(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("EMC", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_BX(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("BX", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_EX(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("EX", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_EOD(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("EOD", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_op_END(_ctx: Handle, proc: Handle) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let mut proc_guard = proc_arc.lock().unwrap();
proc_guard.record_op("END", vec![]);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_processor_get_type(_ctx: Handle, proc: Handle) -> i32 {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let proc_guard = proc_arc.lock().unwrap();
proc_guard.proc_type as i32
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_processor_get_operator_count(_ctx: Handle, proc: Handle) -> i32 {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let proc_guard = proc_arc.lock().unwrap();
proc_guard.operators.len() as i32
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_processor_get_gstate_depth(_ctx: Handle, proc: Handle) -> i32 {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate_stack.len() as i32
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_processor_in_text(_ctx: Handle, proc: Handle) -> i32 {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let proc_guard = proc_arc.lock().unwrap();
if proc_guard.in_text { 1 } else { 0 }
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_processor_get_line_width(_ctx: Handle, proc: Handle) -> f32 {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let proc_guard = proc_arc.lock().unwrap();
proc_guard.gstate.line_width
} else {
1.0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_processor_get_ctm(
_ctx: Handle,
proc: Handle,
a: *mut f32,
b: *mut f32,
c: *mut f32,
d: *mut f32,
e: *mut f32,
f: *mut f32,
) {
if let Some(proc_arc) = PDF_PROCESSORS.get(proc) {
let proc_guard = proc_arc.lock().unwrap();
let ctm = &proc_guard.gstate.ctm;
unsafe {
if !a.is_null() {
*a = ctm.a;
}
if !b.is_null() {
*b = ctm.b;
}
if !c.is_null() {
*c = ctm.c;
}
if !d.is_null() {
*d = ctm.d;
}
if !e.is_null() {
*e = ctm.e;
}
if !f.is_null() {
*f = ctm.f;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_processor_creation() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
assert!(proc > 0);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_run_processor() {
let ctx = 1;
let proc = pdf_new_run_processor(ctx, 0, 0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0, ptr::null());
assert!(proc > 0);
assert_eq!(pdf_processor_get_type(ctx, proc), ProcessorType::Run as i32);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_buffer_processor() {
let ctx = 1;
let proc = pdf_new_buffer_processor(ctx, 0, 0, 1);
assert!(proc > 0);
assert_eq!(
pdf_processor_get_type(ctx, proc),
ProcessorType::Buffer as i32
);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_gstate_stack() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 0);
pdf_op_q(ctx, proc);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 1);
pdf_op_q(ctx, proc);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 2);
pdf_op_Q(ctx, proc);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 1);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_graphics_state_ops() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_w(ctx, proc, 2.5);
assert!((pdf_processor_get_line_width(ctx, proc) - 2.5).abs() < 0.001);
pdf_op_j(ctx, proc, 1);
pdf_op_J(ctx, proc, 2);
pdf_op_M(ctx, proc, 5.0);
pdf_op_i(ctx, proc, 0.5);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 5);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_path_ops() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_m(ctx, proc, 10.0, 20.0);
pdf_op_l(ctx, proc, 100.0, 200.0);
pdf_op_c(ctx, proc, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0);
pdf_op_h(ctx, proc);
pdf_op_S(ctx, proc);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 5);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_text_ops() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_BT(ctx, proc);
assert_eq!(pdf_processor_in_text(ctx, proc), 1);
let font = CString::new("F1").unwrap();
pdf_op_Tf(ctx, proc, font.as_ptr(), 12.0);
pdf_op_Td(ctx, proc, 100.0, 700.0);
let text = CString::new("Hello").unwrap();
pdf_op_Tj(ctx, proc, text.as_ptr(), 5);
pdf_op_ET(ctx, proc);
assert_eq!(pdf_processor_in_text(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_color_ops() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_g(ctx, proc, 0.5);
pdf_op_G(ctx, proc, 0.7);
pdf_op_rg(ctx, proc, 1.0, 0.0, 0.0);
pdf_op_RG(ctx, proc, 0.0, 1.0, 0.0);
pdf_op_k(ctx, proc, 0.0, 1.0, 1.0, 0.0);
pdf_op_K(ctx, proc, 1.0, 0.0, 0.0, 0.0);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 6);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_ctm() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let mut a: f32 = 0.0;
let mut b: f32 = 0.0;
let mut c: f32 = 0.0;
let mut d: f32 = 0.0;
let mut e: f32 = 0.0;
let mut f: f32 = 0.0;
pdf_processor_get_ctm(ctx, proc, &mut a, &mut b, &mut c, &mut d, &mut e, &mut f);
assert!((a - 1.0).abs() < 0.001);
assert!((d - 1.0).abs() < 0.001);
pdf_op_cm(ctx, proc, 1.0, 0.0, 0.0, 1.0, 100.0, 200.0);
pdf_processor_get_ctm(ctx, proc, &mut a, &mut b, &mut c, &mut d, &mut e, &mut f);
assert!((e - 100.0).abs() < 0.001);
assert!((f - 200.0).abs() < 0.001);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_marked_content() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let tag = CString::new("Span").unwrap();
pdf_op_BMC(ctx, proc, tag.as_ptr());
pdf_op_EMC(ctx, proc);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 2);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_sanitize_filter() {
let ctx = 1;
let buffer_proc = pdf_new_buffer_processor(ctx, 0, 0, 1);
let sanitize =
pdf_new_sanitize_filter(ctx, 0, buffer_proc, 0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
assert!(sanitize > 0);
assert_eq!(
pdf_processor_get_type(ctx, sanitize),
ProcessorType::Sanitize as i32
);
pdf_drop_processor(ctx, sanitize);
}
#[test]
fn test_resource_stack() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_processor_push_resources(ctx, proc, 100);
pdf_processor_push_resources(ctx, proc, 200);
let popped1 = pdf_processor_pop_resources(ctx, proc);
assert_eq!(popped1, 200);
let popped2 = pdf_processor_pop_resources(ctx, proc);
assert_eq!(popped2, 100);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_processor_close_reset() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_q(ctx, proc);
pdf_op_w(ctx, proc, 5.0);
pdf_close_processor(ctx, proc);
pdf_reset_processor(ctx, proc);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 0);
assert!((pdf_processor_get_line_width(ctx, proc) - 1.0).abs() < 0.001);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_keep_drop() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let kept = pdf_keep_processor(ctx, proc);
assert_eq!(kept, proc);
pdf_drop_processor(ctx, proc);
assert_eq!(
pdf_processor_get_type(ctx, proc),
ProcessorType::Base as i32
);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_tokenize_basic_operators() {
let data = b"1 0 0 1 100 200 cm";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 7);
match &tokens[6] {
CsToken::Keyword(kw) => assert_eq!(kw, "cm"),
_ => panic!("Expected keyword 'cm'"),
}
}
#[test]
fn test_tokenize_string_literal() {
let data = b"(Hello World) Tj";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 2);
match &tokens[0] {
CsToken::StringLiteral(s) => assert_eq!(s, "Hello World"),
_ => panic!("Expected string literal"),
}
}
#[test]
fn test_tokenize_nested_parens() {
let data = b"(Hello (nested) World) Tj";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 2);
match &tokens[0] {
CsToken::StringLiteral(s) => assert_eq!(s, "Hello (nested) World"),
_ => panic!("Expected string literal with nested parens"),
}
}
#[test]
fn test_tokenize_escape_sequences() {
let data = b"(line1\\nline2) Tj";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 2);
match &tokens[0] {
CsToken::StringLiteral(s) => assert_eq!(s, "line1\nline2"),
_ => panic!("Expected string with escape"),
}
}
#[test]
fn test_tokenize_hex_string() {
let data = b"<48656C6C6F> Tj";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 2);
match &tokens[0] {
CsToken::HexString(s) => assert_eq!(s, "Hello"),
_ => panic!("Expected hex string"),
}
}
#[test]
fn test_tokenize_name_object() {
let data = b"/F1 12 Tf";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 3);
match &tokens[0] {
CsToken::Name(n) => assert_eq!(n, "F1"),
_ => panic!("Expected name object"),
}
}
#[test]
fn test_tokenize_negative_numbers() {
let data = b"-10 -20.5 m";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 3);
match &tokens[0] {
CsToken::Integer(n) => assert_eq!(*n, -10),
_ => panic!("Expected integer -10"),
}
match &tokens[1] {
CsToken::Number(n) => assert!((*n - (-20.5)).abs() < 0.001),
_ => panic!("Expected real -20.5"),
}
}
#[test]
fn test_tokenize_comments() {
let data = b"% this is a comment\n1 0 0 1 0 0 cm";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 7);
}
#[test]
fn test_tokens_to_operators_simple() {
let data = b"q 1 0 0 1 100 200 cm Q";
let tokens = tokenize_content_stream(data);
let ops = tokens_to_operators(&tokens);
assert_eq!(ops.len(), 3);
assert_eq!(ops[0].0, "q");
assert_eq!(ops[0].1.len(), 0);
assert_eq!(ops[1].0, "cm");
assert_eq!(ops[1].1.len(), 6);
assert_eq!(ops[2].0, "Q");
}
#[test]
fn test_tokens_to_operators_color() {
let data = b"1 0 0 rg 0.5 G";
let tokens = tokenize_content_stream(data);
let ops = tokens_to_operators(&tokens);
assert_eq!(ops.len(), 2);
assert_eq!(ops[0].0, "rg");
assert_eq!(ops[0].1.len(), 3);
assert_eq!(ops[1].0, "G");
assert_eq!(ops[1].1.len(), 1);
}
#[test]
fn test_process_contents_with_stream() {
let ctx = 1;
let content = b"q 1 0 0 1 72 720 cm 0.5 g 100 200 300 400 re f Q";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(ctx, 0);
let mut out_res: Handle = 0;
pdf_process_contents(ctx, proc, 0, 42, stm, &mut out_res);
assert_eq!(out_res, 42);
let op_count = pdf_processor_get_operator_count(ctx, proc);
assert!(
op_count >= 6,
"Expected at least 6 operators, got {}",
op_count
);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_empty_stream() {
let ctx = 1;
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(Vec::new()));
let proc = pdf_new_processor(ctx, 0);
let mut out_res: Handle = 0;
pdf_process_contents(ctx, proc, 0, 10, stm, &mut out_res);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 0);
assert_eq!(out_res, 10);
pdf_drop_processor(ctx, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_state_isolation() {
let ctx = 1;
let content = b"5 w";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(ctx, 0);
pdf_op_w(ctx, proc, 2.0);
pdf_process_contents(ctx, proc, 0, 0, stm, ptr::null_mut());
assert!(
(pdf_processor_get_line_width(ctx, proc) - 2.0).abs() < 0.001,
"Expected line width 2.0, got {}",
pdf_processor_get_line_width(ctx, proc)
);
pdf_drop_processor(ctx, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_raw_contents() {
let ctx = 1;
let content = b"q 1 0 0 1 0 0 cm Q";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(ctx, 0);
pdf_process_raw_contents(ctx, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 3);
pdf_drop_processor(ctx, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_raw_contents_no_state_isolation() {
let ctx = 1;
let content = b"5 w";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(ctx, 0);
pdf_op_w(ctx, proc, 2.0);
pdf_process_raw_contents(ctx, proc, 0, stm);
assert!(
(pdf_processor_get_line_width(ctx, proc) - 5.0).abs() < 0.001,
"Expected line width 5.0 from raw processing, got {}",
pdf_processor_get_line_width(ctx, proc)
);
pdf_drop_processor(ctx, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_annot_square() {
use crate::ffi::annot::ANNOTATIONS;
let ctx = 1;
let mut annot_obj = crate::pdf::annot::Annotation::new(
crate::pdf::annot::AnnotType::Square,
Rect::new(10.0, 20.0, 110.0, 70.0),
);
annot_obj.set_color(Some([1.0, 0.0, 0.0]));
let annot = ANNOTATIONS.insert(annot_obj);
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, annot);
let count = pdf_processor_get_operator_count(ctx, proc);
assert!(
count >= 3,
"Expected at least 3 operators for Square annot, got {}",
count
);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
ANNOTATIONS.remove(annot);
}
#[test]
fn test_process_annot_highlight() {
use crate::ffi::annot::ANNOTATIONS;
let ctx = 1;
let annot_obj = crate::pdf::annot::Annotation::highlight(
Rect::new(50.0, 100.0, 200.0, 120.0),
[1.0, 1.0, 0.0],
);
let annot = ANNOTATIONS.insert(annot_obj);
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, annot);
let count = pdf_processor_get_operator_count(ctx, proc);
assert!(
count >= 3,
"Expected at least 3 operators for Highlight annot, got {}",
count
);
pdf_drop_processor(ctx, proc);
ANNOTATIONS.remove(annot);
}
#[test]
fn test_process_annot_missing() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, 999999);
let count = pdf_processor_get_operator_count(ctx, proc);
assert_eq!(count, 1, "Expected 1 marker operator for missing annot");
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_process_glyph() {
let ctx = 1;
let glyph_data = b"500 0 d0 0 0 500 700 re f";
let res = STREAMS.insert(crate::ffi::stream::Stream::from_memory(glyph_data.to_vec()));
let proc = pdf_new_processor(ctx, 0);
pdf_process_glyph(ctx, proc, 0, res);
let count = pdf_processor_get_operator_count(ctx, proc);
assert_eq!(
count, 3,
"Expected 3 operators from glyph stream, got {}",
count
);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
STREAMS.remove(res);
}
#[test]
fn test_process_glyph_empty() {
let ctx = 1;
let res = STREAMS.insert(crate::ffi::stream::Stream::from_memory(Vec::new()));
let proc = pdf_new_processor(ctx, 0);
pdf_process_glyph(ctx, proc, 0, res);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 0);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
STREAMS.remove(res);
}
#[test]
fn test_q_balance_balanced() {
let content = b"q 1 0 0 1 0 0 cm Q";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let mut prepend: i32 = -1;
let mut append: i32 = -1;
pdf_count_q_balance(0, 0, 0, stm, &mut prepend, &mut append);
assert_eq!(prepend, 0, "Balanced stream needs no prepend");
assert_eq!(append, 0, "Balanced stream needs no append");
STREAMS.remove(stm);
}
#[test]
#[allow(non_snake_case)]
fn test_q_balance_missing_Q() {
let content = b"q q 1 0 0 1 0 0 cm Q";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let mut prepend: i32 = -1;
let mut append: i32 = -1;
pdf_count_q_balance(0, 0, 0, stm, &mut prepend, &mut append);
assert_eq!(prepend, 0, "No orphan Q's");
assert_eq!(append, 1, "One unclosed q needs one Q appended");
STREAMS.remove(stm);
}
#[test]
fn test_q_balance_missing_q() {
let content = b"Q";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let mut prepend: i32 = -1;
let mut append: i32 = -1;
pdf_count_q_balance(0, 0, 0, stm, &mut prepend, &mut append);
assert_eq!(prepend, 1, "One orphan Q needs one q prepended");
assert_eq!(append, 0, "No unclosed q's");
STREAMS.remove(stm);
}
#[test]
fn test_q_balance_complex() {
let content = b"Q Q q q q Q";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let mut prepend: i32 = -1;
let mut append: i32 = -1;
pdf_count_q_balance(0, 0, 0, stm, &mut prepend, &mut append);
assert_eq!(prepend, 2, "Two orphan Q's need two q's prepended");
assert_eq!(append, 2, "Two unclosed q's need two Q's appended");
STREAMS.remove(stm);
}
#[test]
fn test_q_balance_empty() {
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(Vec::new()));
let mut prepend: i32 = -1;
let mut append: i32 = -1;
pdf_count_q_balance(0, 0, 0, stm, &mut prepend, &mut append);
assert_eq!(prepend, 0);
assert_eq!(append, 0);
STREAMS.remove(stm);
}
#[test]
fn test_q_balance_null_outputs() {
let content = b"q q Q";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
pdf_count_q_balance(0, 0, 0, stm, ptr::null_mut(), ptr::null_mut());
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_text_operators() {
let ctx = 1;
let content = b"BT /F1 12 Tf 100 700 Td (Hello World) Tj ET";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(ctx, 0);
pdf_process_contents(ctx, proc, 0, 0, stm, ptr::null_mut());
let count = pdf_processor_get_operator_count(ctx, proc);
assert_eq!(count, 5, "Expected 5 text operators, got {}", count);
assert_eq!(pdf_processor_in_text(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_path_operators() {
let ctx = 1;
let content = b"10 20 m 100 200 l 30 40 50 60 70 80 c h S";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(ctx, 0);
pdf_process_contents(ctx, proc, 0, 0, stm, ptr::null_mut());
let count = pdf_processor_get_operator_count(ctx, proc);
assert_eq!(count, 5, "Expected 5 path operators, got {}", count);
pdf_drop_processor(ctx, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_color_operators() {
let ctx = 1;
let content = b"1 0 0 rg 0 1 0 RG 0.5 g 0.7 G 1 0 0 0 k 0 1 0 0 K";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(ctx, 0);
pdf_process_contents(ctx, proc, 0, 0, stm, ptr::null_mut());
let count = pdf_processor_get_operator_count(ctx, proc);
assert_eq!(count, 6, "Expected 6 color operators, got {}", count);
pdf_drop_processor(ctx, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_with_buffer() {
let ctx = 1;
let content = b"q Q";
let buf = BUFFERS.insert(crate::ffi::buffer::Buffer::from_data(content));
let proc = pdf_new_processor(ctx, 0);
pdf_process_contents(ctx, proc, 0, 0, buf, ptr::null_mut());
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 2);
pdf_drop_processor(ctx, proc);
BUFFERS.remove(buf);
}
#[test]
fn test_apply_operator_star_variants() {
let ctx = 1;
let content = b"0 0 100 100 re W* f* 0 0 50 50 re B* 0 0 25 25 re b*";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(ctx, 0);
pdf_process_raw_contents(ctx, proc, 0, stm);
let count = pdf_processor_get_operator_count(ctx, proc);
assert_eq!(count, 7, "Expected 7 operators, got {}", count);
pdf_drop_processor(ctx, proc);
STREAMS.remove(stm);
}
#[test]
fn test_processor_types_output_color_vectorize() {
let ctx = 1;
let buf = BUFFERS.insert(crate::ffi::buffer::Buffer::from_data(b"q Q"));
let out_proc = pdf_new_output_processor(ctx, buf, 0, 0);
assert!(out_proc > 0);
assert_eq!(
pdf_processor_get_type(ctx, out_proc),
ProcessorType::Output as i32
);
pdf_drop_processor(ctx, out_proc);
BUFFERS.remove(buf);
let chain = pdf_new_buffer_processor(ctx, 0, 0, 0);
let color_proc = pdf_new_color_filter(ctx, 0, chain, 0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
assert!(color_proc > 0);
assert_eq!(
pdf_processor_get_type(ctx, color_proc),
ProcessorType::Color as i32
);
pdf_drop_processor(ctx, color_proc);
let chain2 = pdf_new_buffer_processor(ctx, 0, 0, 0);
let vec_proc = pdf_new_vectorize_filter(ctx, 0, chain2, 0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
assert!(vec_proc > 0);
assert_eq!(
pdf_processor_get_type(ctx, vec_proc),
ProcessorType::Vectorize as i32
);
pdf_drop_processor(ctx, vec_proc);
}
#[test]
fn test_invalid_handle_no_crash() {
let ctx = 1;
pdf_op_w(ctx, 999999, 1.0);
pdf_op_q(ctx, 999999);
pdf_op_Q(ctx, 999999);
pdf_op_m(ctx, 999999, 0.0, 0.0);
pdf_op_S(ctx, 999999);
pdf_close_processor(ctx, 999999);
pdf_reset_processor(ctx, 999999);
pdf_drop_processor(ctx, 999999);
assert_eq!(pdf_processor_get_type(ctx, 999999), 0);
assert_eq!(pdf_processor_get_operator_count(ctx, 999999), 0);
assert_eq!(pdf_processor_get_gstate_depth(ctx, 999999), 0);
assert_eq!(pdf_processor_in_text(ctx, 999999), 0);
assert!((pdf_processor_get_line_width(ctx, 999999) - 1.0).abs() < 0.001);
}
#[test]
fn test_null_pointers_in_ops() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_ri(ctx, proc, ptr::null());
pdf_op_gs_begin(ctx, proc, ptr::null());
pdf_op_gs_BM(ctx, proc, ptr::null());
pdf_op_d(ctx, proc, ptr::null(), 0, 0.0);
pdf_op_CS(ctx, proc, ptr::null());
pdf_op_cs(ctx, proc, ptr::null());
pdf_op_SC_color(ctx, proc, 0, ptr::null());
pdf_op_sc_color(ctx, proc, 0, ptr::null());
pdf_op_Tj(ctx, proc, ptr::null(), 0);
pdf_op_sh(ctx, proc, ptr::null());
pdf_op_Do_image(ctx, proc, ptr::null(), 0);
pdf_op_Do_form(ctx, proc, ptr::null(), 0);
pdf_op_MP(ctx, proc, ptr::null());
pdf_op_DP(ctx, proc, ptr::null());
pdf_op_BMC(ctx, proc, ptr::null());
pdf_op_BDC(ctx, proc, ptr::null());
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 15);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_run_processor_with_usage() {
let ctx = 1;
let usage = CString::new("Print").unwrap();
let proc =
pdf_new_run_processor(ctx, 0, 0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0, usage.as_ptr());
assert!(proc > 0);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_op_d_with_array() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let dash = [3.0f32, 2.0];
pdf_op_d(ctx, proc, dash.as_ptr(), 2, 1.0);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 1);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_path_ops_v_y_re() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_m(ctx, proc, 0.0, 0.0);
pdf_op_v(ctx, proc, 10.0, 10.0, 20.0, 20.0);
pdf_op_y(ctx, proc, 5.0, 5.0, 30.0, 30.0);
pdf_op_re(ctx, proc, 0.0, 0.0, 100.0, 50.0);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 4);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_path_painting_ops() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_m(ctx, proc, 0.0, 0.0);
pdf_op_l(ctx, proc, 10.0, 10.0);
pdf_op_s(ctx, proc);
pdf_op_m(ctx, proc, 0.0, 0.0);
pdf_op_re(ctx, proc, 0.0, 0.0, 10.0, 10.0);
pdf_op_f(ctx, proc);
pdf_op_F(ctx, proc);
pdf_op_fstar(ctx, proc);
pdf_op_B(ctx, proc);
pdf_op_Bstar(ctx, proc);
pdf_op_b(ctx, proc);
pdf_op_bstar(ctx, proc);
pdf_op_n(ctx, proc);
pdf_op_W(ctx, proc);
pdf_op_Wstar(ctx, proc);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 15);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_extended_graphics_state() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_gs_ca(ctx, proc, 0.5);
pdf_op_gs_CA(ctx, proc, 0.7);
pdf_op_gs_op(ctx, proc, 1);
pdf_op_gs_OP(ctx, proc, 1);
pdf_op_gs_OPM(ctx, proc, 1);
pdf_op_gs_end(ctx, proc);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_text_squote_dquote() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_BT(ctx, proc);
let font = CString::new("F1").unwrap();
pdf_op_Tf(ctx, proc, font.as_ptr(), 12.0);
let text = CString::new("Hi").unwrap();
pdf_op_squote(ctx, proc, text.as_ptr(), 2);
pdf_op_dquote(ctx, proc, 1.0, 0.5, text.as_ptr(), 2);
pdf_op_ET(ctx, proc);
assert!(pdf_processor_get_operator_count(ctx, proc) >= 5);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_d0_d1_ops() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_d0(ctx, proc, 500.0, 0.0);
pdf_op_d1(ctx, proc, 500.0, 0.0, 0.0, 0.0, 500.0, 700.0);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 2);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_bx_ex_eod_end() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_BX(ctx, proc);
pdf_op_EX(ctx, proc);
pdf_op_EOD(ctx, proc);
pdf_op_END(ctx, proc);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 4);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_processor_get_type_invalid() {
assert_eq!(pdf_processor_get_type(1, 0), 0);
}
#[test]
fn test_process_contents_invalid_stream() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let mut out_res: Handle = 99;
pdf_process_contents(ctx, proc, 0, 42, 999999, &mut out_res);
assert_eq!(out_res, 42);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_process_raw_contents_invalid_stream() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_process_raw_contents(ctx, proc, 0, 999999);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_process_glyph_invalid_stream() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_process_glyph(ctx, proc, 0, 999999);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_count_q_balance_invalid_stream() {
let mut prepend: i32 = -1;
let mut append: i32 = -1;
pdf_count_q_balance(0, 0, 0, 999999, &mut prepend, &mut append);
assert_eq!(prepend, 0);
assert_eq!(append, 0);
}
#[test]
fn test_pop_resources_empty() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let res = pdf_processor_pop_resources(ctx, proc);
assert_eq!(res, 0);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_push_resources_invalid_proc() {
pdf_processor_push_resources(1, 999999, 100);
}
#[test]
fn test_keep_processor_invalid() {
assert_eq!(pdf_keep_processor(1, 999999), 999999);
}
#[test]
fn test_tokenize_octal_escape() {
let data = b"(\\101) Tj";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 2);
match &tokens[0] {
CsToken::StringLiteral(s) => assert_eq!(s, "A"),
_ => panic!("Expected octal escape to produce 'A'"),
}
}
#[test]
fn test_tokenize_hex_odd_length() {
let data = b"<41> Tj";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 2);
match &tokens[0] {
CsToken::HexString(s) => assert_eq!(s, "A"),
_ => panic!("Expected hex A"),
}
}
#[test]
fn test_tokenize_dict_delimiters() {
let data = b"<< /Key 1 >>";
let tokens = tokenize_content_stream(data);
assert!(tokens.len() >= 3);
match &tokens[0] {
CsToken::Keyword(k) => assert_eq!(k, "<<"),
_ => panic!("Expected <<"),
}
}
#[test]
fn test_tokenize_array_delimiters() {
let data = b"[ 1 2 ]";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 4);
}
#[test]
fn test_tokens_to_operators_true_false_null() {
let data = b"true false null";
let tokens = tokenize_content_stream(data);
let ops = tokens_to_operators(&tokens);
assert_eq!(ops.len(), 0);
}
#[test]
fn test_tokens_to_operators_unknown_keyword_as_operand() {
let data = b"1 2 3 q";
let tokens = tokenize_content_stream(data);
let ops = tokens_to_operators(&tokens);
assert_eq!(ops.len(), 1);
assert_eq!(ops[0].0, "q");
assert_eq!(ops[0].1.len(), 3);
}
#[test]
fn test_process_contents_d_ri_gs() {
let content = b"[ 3 2 ] 1 d ri /Perceptual gs";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 3);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_td_tm_tstar() {
let content = b"BT /F1 12 Tf 100 200 TD 1 0 0 1 0 0 Tm T* ET";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 6);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_tj_non_string() {
let content = b"BT /F1 12 Tf 100 100 200 200 300 300 Tm 42 Tj ET";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 5);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_quote_operators() {
let content = b"BT /F1 12 Tf (a) ' (b) \" 1 2 (c) ET";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert!(pdf_processor_get_operator_count(1, proc) >= 4);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_cs_cs_sc_sc() {
let content = b"/DeviceRGB CS /DeviceGray cs 1 0 0 SC 0.5 sc";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 4);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_unknown_operator() {
let content = b"1 2 3 ID";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 1);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_annot_circle() {
use crate::ffi::annot::ANNOTATIONS;
let ctx = 1;
let mut annot_obj = crate::pdf::annot::Annotation::new(
crate::pdf::annot::AnnotType::Circle,
Rect::new(10.0, 20.0, 110.0, 70.0),
);
annot_obj.set_color(Some([0.0, 1.0, 0.0]));
let annot = ANNOTATIONS.insert(annot_obj);
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, annot);
let count = pdf_processor_get_operator_count(ctx, proc);
assert!(count >= 3);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
ANNOTATIONS.remove(annot);
}
#[test]
fn test_process_annot_line() {
use crate::ffi::annot::ANNOTATIONS;
let ctx = 1;
let mut annot_obj = crate::pdf::annot::Annotation::new(
crate::pdf::annot::AnnotType::Line,
Rect::new(0.0, 0.0, 100.0, 100.0),
);
annot_obj.set_color(Some([0.0, 0.0, 1.0]));
let annot = ANNOTATIONS.insert(annot_obj);
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, annot);
let count = pdf_processor_get_operator_count(ctx, proc);
assert!(count >= 3);
pdf_drop_processor(ctx, proc);
ANNOTATIONS.remove(annot);
}
#[test]
fn test_process_annot_underline() {
use crate::ffi::annot::ANNOTATIONS;
let ctx = 1;
let mut annot_obj = crate::pdf::annot::Annotation::new(
crate::pdf::annot::AnnotType::Underline,
Rect::new(50.0, 100.0, 200.0, 120.0),
);
annot_obj.set_color(Some([1.0, 0.0, 0.0]));
let annot = ANNOTATIONS.insert(annot_obj);
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, annot);
let count = pdf_processor_get_operator_count(ctx, proc);
assert!(count >= 3);
pdf_drop_processor(ctx, proc);
ANNOTATIONS.remove(annot);
}
#[test]
fn test_process_annot_strikeout() {
use crate::ffi::annot::ANNOTATIONS;
let ctx = 1;
let mut annot_obj = crate::pdf::annot::Annotation::new(
crate::pdf::annot::AnnotType::StrikeOut,
Rect::new(50.0, 100.0, 200.0, 120.0),
);
annot_obj.set_color(Some([0.0, 1.0, 0.0]));
let annot = ANNOTATIONS.insert(annot_obj);
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, annot);
let count = pdf_processor_get_operator_count(ctx, proc);
assert!(count >= 3);
pdf_drop_processor(ctx, proc);
ANNOTATIONS.remove(annot);
}
#[test]
fn test_process_annot_other_type() {
use crate::ffi::annot::ANNOTATIONS;
let ctx = 1;
let annot_obj =
crate::pdf::annot::Annotation::new(crate::pdf::annot::AnnotType::Text, Rect::default());
let annot = ANNOTATIONS.insert(annot_obj);
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, annot);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 1);
assert_eq!(pdf_processor_get_gstate_depth(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
ANNOTATIONS.remove(annot);
}
#[test]
fn test_process_annot_with_opacity() {
use crate::ffi::annot::ANNOTATIONS;
let ctx = 1;
let mut annot_obj = crate::pdf::annot::Annotation::new(
crate::pdf::annot::AnnotType::Square,
Rect::new(10.0, 20.0, 110.0, 70.0),
);
annot_obj.set_color(Some([1.0, 0.0, 0.0]));
annot_obj.set_opacity(0.5);
let annot = ANNOTATIONS.insert(annot_obj);
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, annot);
assert!(pdf_processor_get_operator_count(ctx, proc) >= 3);
pdf_drop_processor(ctx, proc);
ANNOTATIONS.remove(annot);
}
#[test]
fn test_chain_processor_close_drop() {
let ctx = 1;
let buffer_proc = pdf_new_buffer_processor(ctx, 0, 0, 0);
let sanitize =
pdf_new_sanitize_filter(ctx, 0, buffer_proc, 0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
pdf_close_processor(ctx, sanitize);
pdf_drop_processor(ctx, sanitize);
}
#[test]
fn test_cull_type_from() {
assert_eq!(CullType::from(0), CullType::PathDrop);
assert_eq!(CullType::from(1), CullType::PathFill);
assert_eq!(CullType::from(10), CullType::Shading);
assert_eq!(CullType::from(99), CullType::PathDrop);
}
#[test]
fn test_tokenize_escape_b_f_parens() {
let data = b"(\\b\\f\\(\\)\\\\) Tj";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 2);
match &tokens[0] {
CsToken::StringLiteral(s) => {
assert_eq!(s.len(), 5);
assert_eq!(s.as_bytes()[0], 0x08);
assert_eq!(s.as_bytes()[1], 0x0C);
assert_eq!(s.as_bytes()[2], b'(');
assert_eq!(s.as_bytes()[3], b')');
assert_eq!(s.as_bytes()[4], b'\\');
}
_ => panic!("Expected string with escapes"),
}
}
#[test]
fn test_tokenize_leading_dot_number() {
let data = b".5 1.5 m";
let tokens = tokenize_content_stream(data);
assert_eq!(tokens.len(), 3);
match &tokens[0] {
CsToken::Number(n) => assert!((*n - 0.5).abs() < 0.001),
_ => panic!("Expected number"),
}
}
#[test]
fn test_ctm_null_outputs() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_processor_get_ctm(
ctx,
proc,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_process_contents_invalid_proc() {
let content = b"q Q";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let mut out_res: Handle = 0;
pdf_process_contents(1, 999999, 0, 0, stm, &mut out_res);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_stream_none() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let mut out_res: Handle = 0;
pdf_process_contents(ctx, proc, 0, 0, 999999, &mut out_res);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 0);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_pdf_new_processor_size_variants() {
let ctx = 1;
assert!(pdf_new_processor(ctx, 3) > 0);
let proc4 = pdf_new_processor(ctx, 4);
assert_eq!(
pdf_processor_get_type(ctx, proc4),
ProcessorType::Sanitize as i32
);
pdf_drop_processor(ctx, proc4);
let proc5 = pdf_new_processor(ctx, 5);
assert_eq!(
pdf_processor_get_type(ctx, proc5),
ProcessorType::Color as i32
);
pdf_drop_processor(ctx, proc5);
let proc6 = pdf_new_processor(ctx, 6);
assert_eq!(
pdf_processor_get_type(ctx, proc6),
ProcessorType::Vectorize as i32
);
pdf_drop_processor(ctx, proc6);
}
#[test]
fn test_ri_valid_string() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let intent = CString::new("AbsoluteColorimetric").unwrap();
pdf_op_ri(ctx, proc, intent.as_ptr());
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 1);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_gs_begin_valid_name() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let name = CString::new("GS1").unwrap();
pdf_op_gs_begin(ctx, proc, name.as_ptr());
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 1);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_gs_bm_valid_mode() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let mode = CString::new("Multiply").unwrap();
pdf_op_gs_BM(ctx, proc, mode.as_ptr());
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_sc_sc_color_valid() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let color = [1.0f32, 0.0, 0.0];
pdf_op_SC_color(ctx, proc, 3, color.as_ptr());
pdf_op_sc_color(ctx, proc, 3, color.as_ptr());
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 2);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_cs_cs_valid() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
let name = CString::new("DeviceCMYK").unwrap();
pdf_op_CS(ctx, proc, name.as_ptr());
pdf_op_cs(ctx, proc, name.as_ptr());
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 2);
pdf_drop_processor(ctx, proc);
}
#[test]
fn test_process_contents_f_variant() {
let content = b"0 0 10 10 re F";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 2);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_v_y_re() {
let content = b"0 0 m 10 10 20 20 v 5 5 30 30 y 0 0 50 50 re h";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 5);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_mp_dp_bdc() {
let content = b"MP /Tag DP BMC /Span BDC EMC";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 5);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_bx_ex() {
let content = b"BX EX";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 2);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_sc_scn_operands() {
let content = b"1 0 0 SC 0.5 0.5 0.5 scn";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 2);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_tc_tw_tz_tl_tr_ts() {
let content = b"BT /F1 12 Tf 1 Tc 2 Tw 100 Tz 14 TL 0 Tr 0 Ts (x) Tj ET";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert!(pdf_processor_get_operator_count(1, proc) >= 7);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_do_sh_bi() {
let content = b"/Im1 Do /Sh1 sh";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 2);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_d0_d1() {
let content = b"500 0 d0 500 0 0 0 500 700 d1";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 2);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_tj() {
let content = b"BT /F1 12 Tf (abc) TJ ET";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 4);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_tm_insufficient_operands() {
let content = b"BT /F1 12 Tf 1 2 3 Tm ET";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 4);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_td_td_no_text_object() {
let content = b"100 200 Td 50 100 TD";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 2);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_tstar_no_text_object() {
let content = b"T*";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 1);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_m_l_insufficient() {
let content = b"10 m 20 l";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 2);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_c_insufficient() {
let content = b"1 2 3 4 5 c";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 1);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_cm_insufficient() {
let content = b"1 0 0 1 cm";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 1);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_contents_re_insufficient() {
let content = b"1 2 3 re";
let stm = STREAMS.insert(crate::ffi::stream::Stream::from_memory(content.to_vec()));
let proc = pdf_new_processor(1, 0);
pdf_process_raw_contents(1, proc, 0, stm);
assert_eq!(pdf_processor_get_operator_count(1, proc), 1);
pdf_drop_processor(1, proc);
STREAMS.remove(stm);
}
#[test]
fn test_process_annot_freetext_interior() {
use crate::ffi::annot::ANNOTATIONS;
let ctx = 1;
let mut annot_obj = crate::pdf::annot::Annotation::new(
crate::pdf::annot::AnnotType::FreeText,
Rect::new(10.0, 20.0, 110.0, 70.0),
);
annot_obj.set_color(Some([1.0, 0.0, 0.0]));
annot_obj.set_interior_color(vec![0.5, 0.5, 0.5]);
let annot = ANNOTATIONS.insert(annot_obj);
let proc = pdf_new_processor(ctx, 0);
pdf_process_annot(ctx, proc, annot);
let count = pdf_processor_get_operator_count(ctx, proc);
assert!(count >= 3);
pdf_drop_processor(ctx, proc);
ANNOTATIONS.remove(annot);
}
#[test]
fn test_pdf_op_bi() {
let ctx = 1;
let proc = pdf_new_processor(ctx, 0);
pdf_op_BI(ctx, proc, 42);
assert_eq!(pdf_processor_get_operator_count(ctx, proc), 1);
pdf_drop_processor(ctx, proc);
}
}