use crate::error::Error;
use crate::object::Object;
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::box_collection)] pub enum Operator {
Td {
tx: f32,
ty: f32,
},
TD {
tx: f32,
ty: f32,
},
Tm {
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
},
TStar,
Tj {
text: Vec<u8>,
},
TJ {
array: Vec<TextElement>,
},
Quote {
text: Vec<u8>,
},
DoubleQuote {
word_space: f32,
char_space: f32,
text: Vec<u8>,
},
Tc {
char_space: f32,
},
Tw {
word_space: f32,
},
Tz {
scale: f32,
},
TL {
leading: f32,
},
Tf {
font: String,
size: f32,
},
Tr {
render: u8,
},
Ts {
rise: f32,
},
SaveState,
RestoreState,
Cm {
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
},
SetFillRgb {
r: f32,
g: f32,
b: f32,
},
SetStrokeRgb {
r: f32,
g: f32,
b: f32,
},
SetFillGray {
gray: f32,
},
SetStrokeGray {
gray: f32,
},
SetFillCmyk {
c: f32,
m: f32,
y: f32,
k: f32,
},
SetStrokeCmyk {
c: f32,
m: f32,
y: f32,
k: f32,
},
SetFillColorSpace {
name: String,
},
SetStrokeColorSpace {
name: String,
},
SetFillColor {
components: Vec<f32>,
},
SetStrokeColor {
components: Vec<f32>,
},
SetFillColorN {
components: Vec<f32>,
name: Option<Box<String>>,
},
SetStrokeColorN {
components: Vec<f32>,
name: Option<Box<String>>,
},
BeginText,
EndText,
Do {
name: String,
},
MoveTo {
x: f32,
y: f32,
},
LineTo {
x: f32,
y: f32,
},
CurveTo {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
},
CurveToV {
x2: f32,
y2: f32,
x3: f32,
y3: f32,
},
CurveToY {
x1: f32,
y1: f32,
x3: f32,
y3: f32,
},
ClosePath,
Rectangle {
x: f32,
y: f32,
width: f32,
height: f32,
},
Stroke,
CloseStroke,
Fill,
FillEvenOdd,
CloseFillStroke,
FillStroke,
FillStrokeEvenOdd,
CloseFillStrokeEvenOdd,
EndPath,
ClipNonZero,
ClipEvenOdd,
SetLineWidth {
width: f32,
},
SetDash {
array: Vec<f32>,
phase: f32,
},
SetLineCap {
cap_style: u8,
},
SetLineJoin {
join_style: u8,
},
SetMiterLimit {
limit: f32,
},
SetRenderingIntent {
intent: String,
},
SetFlatness {
tolerance: f32,
},
SetExtGState {
dict_name: String,
},
PaintShading {
name: String,
},
InlineImage {
dict: Box<std::collections::HashMap<String, Object>>,
data: Vec<u8>,
},
BeginMarkedContent {
tag: String,
},
BeginMarkedContentDict {
tag: String,
properties: Box<Object>,
},
EndMarkedContent,
Other {
name: String,
operands: Box<Vec<Object>>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum TextElement {
String(Vec<u8>),
Offset(f32),
}
impl Operator {
pub fn is_color_setting(&self) -> bool {
matches!(
self,
Operator::SetFillRgb { .. }
| Operator::SetStrokeRgb { .. }
| Operator::SetFillGray { .. }
| Operator::SetStrokeGray { .. }
| Operator::SetFillCmyk { .. }
| Operator::SetStrokeCmyk { .. }
| Operator::SetFillColorSpace { .. }
| Operator::SetStrokeColorSpace { .. }
| Operator::SetFillColor { .. }
| Operator::SetStrokeColor { .. }
| Operator::SetFillColorN { .. }
| Operator::SetStrokeColorN { .. }
)
}
pub fn validate_operands_for_raw_operator(operator_name: &str, operands: &[Object]) -> crate::error::Result<()> {
match operator_name {
"m" | "l" | "c" | "v" | "y" | "h" | "re" => validate_path_construction_operands(operator_name, operands),
"Td" | "TD" | "Tm" | "T*" => validate_text_positioning_operands(operator_name, operands),
"Tj" | "TJ" | "'" | "\"" => validate_text_showing_operands(operator_name, operands),
"Tc" | "Tw" | "Tz" | "TL" | "Tf" | "Tr" | "Ts" => validate_text_state_operands(operator_name, operands),
"q" | "Q" | "cm" => validate_graphics_state_operands(operator_name, operands),
"rg" | "RG" | "g" | "G" | "k" | "K" => validate_color_operands(operator_name, operands),
"BT" | "ET" => validate_text_object_operands(operator_name, operands),
"Do" => validate_xobject_operands(operator_name, operands),
_ => {
tracing::trace!(
operator = operator_name,
"no operand validation implemented for operator"
);
Ok(())
}
}
}
}
fn validate_path_construction_operands(operator_name: &str, operands: &[Object]) -> crate::error::Result<()> {
match operator_name {
"m" => {
if operands.len() != 2 {
return Err(Error::InvalidPdf(format!(
"Operator 'm' (moveto) requires 2 operands (x, y), got {}",
operands.len()
)));
}
}
"l" => {
if operands.len() != 2 {
return Err(Error::InvalidPdf(format!(
"Operator 'l' (lineto) requires 2 operands (x, y), got {}",
operands.len()
)));
}
}
"c" => {
if operands.len() != 6 {
return Err(Error::InvalidPdf(format!(
"Operator 'c' (curveto) requires 6 operands (x1, y1, x2, y2, x3, y3), got {}",
operands.len()
)));
}
}
"v" => {
if operands.len() != 4 {
return Err(Error::InvalidPdf(format!(
"Operator 'v' (curveto) requires 4 operands (x2, y2, x3, y3), got {}",
operands.len()
)));
}
}
"y" => {
if operands.len() != 4 {
return Err(Error::InvalidPdf(format!(
"Operator 'y' (curveto) requires 4 operands (x1, y1, x3, y3), got {}",
operands.len()
)));
}
}
"h" => {
if !operands.is_empty() {
return Err(Error::InvalidPdf(format!(
"Operator 'h' (closepath) requires 0 operands, got {}",
operands.len()
)));
}
}
"re" => {
if operands.len() != 4 {
return Err(Error::InvalidPdf(format!(
"Operator 're' (rectangle) requires 4 operands (x, y, width, height), got {}",
operands.len()
)));
}
}
_ => {}
}
Ok(())
}
fn validate_text_positioning_operands(operator_name: &str, operands: &[Object]) -> crate::error::Result<()> {
match operator_name {
"Td" => {
if operands.len() != 2 {
return Err(Error::InvalidPdf(format!(
"Operator 'Td' requires 2 operands (tx, ty), got {}",
operands.len()
)));
}
}
"TD" => {
if operands.len() != 2 {
return Err(Error::InvalidPdf(format!(
"Operator 'TD' requires 2 operands (tx, ty), got {}",
operands.len()
)));
}
}
"Tm" => {
if operands.len() != 6 {
return Err(Error::InvalidPdf(format!(
"Operator 'Tm' requires 6 operands (a, b, c, d, e, f), got {}",
operands.len()
)));
}
}
"T*" => {
if !operands.is_empty() {
return Err(Error::InvalidPdf(format!(
"Operator 'T*' requires 0 operands, got {}",
operands.len()
)));
}
}
_ => {}
}
Ok(())
}
fn validate_text_showing_operands(operator_name: &str, operands: &[Object]) -> crate::error::Result<()> {
match operator_name {
"Tj" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'Tj' requires 1 operand (string), got {}",
operands.len()
)));
}
}
"TJ" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'TJ' requires 1 operand (array), got {}",
operands.len()
)));
}
}
"'" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator ''' requires 1 operand (string), got {}",
operands.len()
)));
}
}
"\"" => {
if operands.len() != 3 {
return Err(Error::InvalidPdf(format!(
"Operator '\"' requires 3 operands (word_space, char_space, string), got {}",
operands.len()
)));
}
}
_ => {}
}
Ok(())
}
fn validate_text_state_operands(operator_name: &str, operands: &[Object]) -> crate::error::Result<()> {
match operator_name {
"Tc" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'Tc' requires 1 operand (char_space), got {}",
operands.len()
)));
}
}
"Tw" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'Tw' requires 1 operand (word_space), got {}",
operands.len()
)));
}
}
"Tz" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'Tz' requires 1 operand (scale), got {}",
operands.len()
)));
}
}
"TL" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'TL' requires 1 operand (leading), got {}",
operands.len()
)));
}
}
"Tf" => {
if operands.len() != 2 {
return Err(Error::InvalidPdf(format!(
"Operator 'Tf' requires 2 operands (font, size), got {}",
operands.len()
)));
}
}
"Tr" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'Tr' requires 1 operand (render), got {}",
operands.len()
)));
}
}
"Ts" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'Ts' requires 1 operand (rise), got {}",
operands.len()
)));
}
}
_ => {}
}
Ok(())
}
fn validate_graphics_state_operands(operator_name: &str, operands: &[Object]) -> crate::error::Result<()> {
match operator_name {
"q" | "Q" => {
if !operands.is_empty() {
return Err(Error::InvalidPdf(format!(
"Operator '{}' requires 0 operands, got {}",
operator_name,
operands.len()
)));
}
}
"cm" => {
if operands.len() != 6 {
return Err(Error::InvalidPdf(format!(
"Operator 'cm' requires 6 operands (a, b, c, d, e, f), got {}",
operands.len()
)));
}
}
_ => {}
}
Ok(())
}
fn validate_color_operands(operator_name: &str, operands: &[Object]) -> crate::error::Result<()> {
match operator_name {
"rg" => {
if operands.len() != 3 {
return Err(Error::InvalidPdf(format!(
"Operator 'rg' requires 3 operands (r, g, b), got {}",
operands.len()
)));
}
}
"RG" => {
if operands.len() != 3 {
return Err(Error::InvalidPdf(format!(
"Operator 'RG' requires 3 operands (r, g, b), got {}",
operands.len()
)));
}
}
"g" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'g' requires 1 operand (gray), got {}",
operands.len()
)));
}
}
"G" => {
if operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'G' requires 1 operand (gray), got {}",
operands.len()
)));
}
}
"k" => {
if operands.len() != 4 {
return Err(Error::InvalidPdf(format!(
"Operator 'k' requires 4 operands (c, m, y, k), got {}",
operands.len()
)));
}
}
"K" => {
if operands.len() != 4 {
return Err(Error::InvalidPdf(format!(
"Operator 'K' requires 4 operands (c, m, y, k), got {}",
operands.len()
)));
}
}
_ => {}
}
Ok(())
}
fn validate_text_object_operands(operator_name: &str, operands: &[Object]) -> crate::error::Result<()> {
match operator_name {
"BT" | "ET" => {
if !operands.is_empty() {
return Err(Error::InvalidPdf(format!(
"Operator '{}' requires 0 operands, got {}",
operator_name,
operands.len()
)));
}
}
_ => {}
}
Ok(())
}
fn validate_xobject_operands(operator_name: &str, operands: &[Object]) -> crate::error::Result<()> {
if operator_name == "Do" && operands.len() != 1 {
return Err(Error::InvalidPdf(format!(
"Operator 'Do' requires 1 operand (name), got {}",
operands.len()
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_operator_td() {
let op = Operator::Td { tx: 10.0, ty: 20.0 };
match op {
Operator::Td { tx, ty } => {
assert_eq!(tx, 10.0);
assert_eq!(ty, 20.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_tm() {
let op = Operator::Tm {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: 100.0,
f: 200.0,
};
match op {
Operator::Tm { a, b, c, d, e, f } => {
assert_eq!(a, 1.0);
assert_eq!(b, 0.0);
assert_eq!(c, 0.0);
assert_eq!(d, 1.0);
assert_eq!(e, 100.0);
assert_eq!(f, 200.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_tj() {
let op = Operator::Tj {
text: b"Hello".to_vec(),
};
match op {
Operator::Tj { text } => {
assert_eq!(text, b"Hello");
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_tf() {
let op = Operator::Tf {
font: "F1".to_string(),
size: 12.0,
};
match op {
Operator::Tf { font, size } => {
assert_eq!(font, "F1");
assert_eq!(size, 12.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_rgb() {
let op = Operator::SetFillRgb { r: 1.0, g: 0.0, b: 0.0 };
match op {
Operator::SetFillRgb { r, g, b } => {
assert_eq!(r, 1.0);
assert_eq!(g, 0.0);
assert_eq!(b, 0.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_text_element_string() {
let elem = TextElement::String(b"Text".to_vec());
match elem {
TextElement::String(s) => {
assert_eq!(s, b"Text");
}
_ => panic!("Wrong element type"),
}
}
#[test]
fn test_text_element_offset() {
let elem = TextElement::Offset(-100.0);
match elem {
TextElement::Offset(offset) => {
assert_eq!(offset, -100.0);
}
_ => panic!("Wrong element type"),
}
}
#[test]
fn test_operator_clone() {
let op1 = Operator::Tj { text: b"Test".to_vec() };
let op2 = op1.clone();
assert_eq!(op1, op2);
}
#[test]
fn test_operator_save_restore() {
let save = Operator::SaveState;
let restore = Operator::RestoreState;
assert!(matches!(save, Operator::SaveState));
assert!(matches!(restore, Operator::RestoreState));
}
#[test]
fn test_operator_other() {
let op = Operator::Other {
name: "Do".to_string(),
operands: Box::new(vec![Object::Name("Im1".to_string())]),
};
match op {
Operator::Other { name, operands } => {
assert_eq!(name, "Do");
assert_eq!(operands.len(), 1);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_enum_size() {
let size = std::mem::size_of::<Operator>();
assert!(size <= 40, "Operator enum too large: {} bytes (expected <= 40)", size);
}
#[test]
fn test_text_element_eq() {
let elem1 = TextElement::String(b"Test".to_vec());
let elem2 = TextElement::String(b"Test".to_vec());
assert_eq!(elem1, elem2);
let elem3 = TextElement::Offset(10.0);
let elem4 = TextElement::Offset(10.0);
assert_eq!(elem3, elem4);
}
#[test]
fn test_validate_moveto_valid() {
let operands = vec![Object::Integer(10), Object::Integer(20)];
assert!(Operator::validate_operands_for_raw_operator("m", &operands).is_ok());
}
#[test]
fn test_validate_moveto_wrong_count() {
let operands = vec![Object::Integer(10)];
let err = Operator::validate_operands_for_raw_operator("m", &operands);
assert!(err.is_err());
let msg = format!("{}", err.unwrap_err());
assert!(msg.contains("moveto"));
assert!(msg.contains("2 operands"));
}
#[test]
fn test_validate_lineto_valid() {
let operands = vec![Object::Real(1.5), Object::Real(2.5)];
assert!(Operator::validate_operands_for_raw_operator("l", &operands).is_ok());
}
#[test]
fn test_validate_lineto_wrong_count() {
let operands = vec![Object::Integer(1), Object::Integer(2), Object::Integer(3)];
let err = Operator::validate_operands_for_raw_operator("l", &operands);
assert!(err.is_err());
}
#[test]
fn test_validate_curveto_valid() {
let operands = vec![
Object::Integer(1),
Object::Integer(2),
Object::Integer(3),
Object::Integer(4),
Object::Integer(5),
Object::Integer(6),
];
assert!(Operator::validate_operands_for_raw_operator("c", &operands).is_ok());
}
#[test]
fn test_validate_curveto_wrong_count() {
let operands = vec![Object::Integer(1), Object::Integer(2)];
assert!(Operator::validate_operands_for_raw_operator("c", &operands).is_err());
}
#[test]
fn test_validate_curveto_v_valid() {
let operands = vec![
Object::Integer(1),
Object::Integer(2),
Object::Integer(3),
Object::Integer(4),
];
assert!(Operator::validate_operands_for_raw_operator("v", &operands).is_ok());
}
#[test]
fn test_validate_curveto_v_wrong_count() {
let operands = vec![Object::Integer(1)];
assert!(Operator::validate_operands_for_raw_operator("v", &operands).is_err());
}
#[test]
fn test_validate_curveto_y_valid() {
let operands = vec![
Object::Integer(1),
Object::Integer(2),
Object::Integer(3),
Object::Integer(4),
];
assert!(Operator::validate_operands_for_raw_operator("y", &operands).is_ok());
}
#[test]
fn test_validate_curveto_y_wrong_count() {
let operands = vec![];
assert!(Operator::validate_operands_for_raw_operator("y", &operands).is_err());
}
#[test]
fn test_validate_closepath_valid() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("h", &operands).is_ok());
}
#[test]
fn test_validate_closepath_wrong_count() {
let operands = vec![Object::Integer(1)];
assert!(Operator::validate_operands_for_raw_operator("h", &operands).is_err());
}
#[test]
fn test_validate_rectangle_valid() {
let operands = vec![
Object::Integer(0),
Object::Integer(0),
Object::Integer(100),
Object::Integer(200),
];
assert!(Operator::validate_operands_for_raw_operator("re", &operands).is_ok());
}
#[test]
fn test_validate_rectangle_wrong_count() {
let operands = vec![Object::Integer(0), Object::Integer(0)];
assert!(Operator::validate_operands_for_raw_operator("re", &operands).is_err());
}
#[test]
fn test_validate_td_valid() {
let operands = vec![Object::Real(10.0), Object::Real(20.0)];
assert!(Operator::validate_operands_for_raw_operator("Td", &operands).is_ok());
}
#[test]
fn test_validate_td_wrong_count() {
let operands = vec![Object::Real(10.0)];
assert!(Operator::validate_operands_for_raw_operator("Td", &operands).is_err());
}
#[test]
fn test_validate_td_uppercase_valid() {
let operands = vec![Object::Integer(5), Object::Integer(10)];
assert!(Operator::validate_operands_for_raw_operator("TD", &operands).is_ok());
}
#[test]
fn test_validate_td_uppercase_wrong_count() {
let operands = vec![];
assert!(Operator::validate_operands_for_raw_operator("TD", &operands).is_err());
}
#[test]
fn test_validate_tm_valid() {
let operands = vec![
Object::Real(1.0),
Object::Real(0.0),
Object::Real(0.0),
Object::Real(1.0),
Object::Real(72.0),
Object::Real(720.0),
];
assert!(Operator::validate_operands_for_raw_operator("Tm", &operands).is_ok());
}
#[test]
fn test_validate_tm_wrong_count() {
let operands = vec![Object::Real(1.0)];
assert!(Operator::validate_operands_for_raw_operator("Tm", &operands).is_err());
}
#[test]
fn test_validate_tstar_valid() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("T*", &operands).is_ok());
}
#[test]
fn test_validate_tstar_wrong_count() {
let operands = vec![Object::Integer(1)];
assert!(Operator::validate_operands_for_raw_operator("T*", &operands).is_err());
}
#[test]
fn test_validate_tj_valid() {
let operands = vec![Object::String(b"Hello".to_vec())];
assert!(Operator::validate_operands_for_raw_operator("Tj", &operands).is_ok());
}
#[test]
fn test_validate_tj_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("Tj", &operands).is_err());
}
#[test]
fn test_validate_tj_array_valid() {
let operands = vec![Object::Array(vec![
Object::String(b"He".to_vec()),
Object::Integer(-120),
Object::String(b"llo".to_vec()),
])];
assert!(Operator::validate_operands_for_raw_operator("TJ", &operands).is_ok());
}
#[test]
fn test_validate_tj_array_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("TJ", &operands).is_err());
}
#[test]
fn test_validate_quote_valid() {
let operands = vec![Object::String(b"text".to_vec())];
assert!(Operator::validate_operands_for_raw_operator("'", &operands).is_ok());
}
#[test]
fn test_validate_quote_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("'", &operands).is_err());
}
#[test]
fn test_validate_double_quote_valid() {
let operands = vec![Object::Real(1.0), Object::Real(2.0), Object::String(b"text".to_vec())];
assert!(Operator::validate_operands_for_raw_operator("\"", &operands).is_ok());
}
#[test]
fn test_validate_double_quote_wrong_count() {
let operands = vec![Object::Real(1.0)];
assert!(Operator::validate_operands_for_raw_operator("\"", &operands).is_err());
}
#[test]
fn test_validate_tc_valid() {
let operands = vec![Object::Real(0.5)];
assert!(Operator::validate_operands_for_raw_operator("Tc", &operands).is_ok());
}
#[test]
fn test_validate_tc_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("Tc", &operands).is_err());
}
#[test]
fn test_validate_tw_valid() {
let operands = vec![Object::Real(1.0)];
assert!(Operator::validate_operands_for_raw_operator("Tw", &operands).is_ok());
}
#[test]
fn test_validate_tw_wrong_count() {
let operands = vec![Object::Real(1.0), Object::Real(2.0)];
assert!(Operator::validate_operands_for_raw_operator("Tw", &operands).is_err());
}
#[test]
fn test_validate_tz_valid() {
let operands = vec![Object::Integer(150)];
assert!(Operator::validate_operands_for_raw_operator("Tz", &operands).is_ok());
}
#[test]
fn test_validate_tz_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("Tz", &operands).is_err());
}
#[test]
fn test_validate_tl_valid() {
let operands = vec![Object::Real(14.0)];
assert!(Operator::validate_operands_for_raw_operator("TL", &operands).is_ok());
}
#[test]
fn test_validate_tl_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("TL", &operands).is_err());
}
#[test]
fn test_validate_tf_valid() {
let operands = vec![Object::Name("F1".to_string()), Object::Real(12.0)];
assert!(Operator::validate_operands_for_raw_operator("Tf", &operands).is_ok());
}
#[test]
fn test_validate_tf_wrong_count() {
let operands = vec![Object::Name("F1".to_string())];
assert!(Operator::validate_operands_for_raw_operator("Tf", &operands).is_err());
}
#[test]
fn test_validate_tr_valid() {
let operands = vec![Object::Integer(0)];
assert!(Operator::validate_operands_for_raw_operator("Tr", &operands).is_ok());
}
#[test]
fn test_validate_tr_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("Tr", &operands).is_err());
}
#[test]
fn test_validate_ts_valid() {
let operands = vec![Object::Real(5.0)];
assert!(Operator::validate_operands_for_raw_operator("Ts", &operands).is_ok());
}
#[test]
fn test_validate_ts_wrong_count() {
let operands = vec![Object::Real(1.0), Object::Real(2.0)];
assert!(Operator::validate_operands_for_raw_operator("Ts", &operands).is_err());
}
#[test]
fn test_validate_save_restore_state_valid() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("q", &operands).is_ok());
assert!(Operator::validate_operands_for_raw_operator("Q", &operands).is_ok());
}
#[test]
fn test_validate_save_state_wrong_count() {
let operands = vec![Object::Integer(1)];
assert!(Operator::validate_operands_for_raw_operator("q", &operands).is_err());
}
#[test]
fn test_validate_restore_state_wrong_count() {
let operands = vec![Object::Integer(1)];
assert!(Operator::validate_operands_for_raw_operator("Q", &operands).is_err());
}
#[test]
fn test_validate_cm_valid() {
let operands = vec![
Object::Real(1.0),
Object::Real(0.0),
Object::Real(0.0),
Object::Real(1.0),
Object::Real(0.0),
Object::Real(0.0),
];
assert!(Operator::validate_operands_for_raw_operator("cm", &operands).is_ok());
}
#[test]
fn test_validate_cm_wrong_count() {
let operands = vec![Object::Real(1.0)];
assert!(Operator::validate_operands_for_raw_operator("cm", &operands).is_err());
}
#[test]
fn test_validate_rg_valid() {
let operands = vec![Object::Real(1.0), Object::Real(0.0), Object::Real(0.0)];
assert!(Operator::validate_operands_for_raw_operator("rg", &operands).is_ok());
}
#[test]
fn test_validate_rg_wrong_count() {
let operands = vec![Object::Real(1.0)];
assert!(Operator::validate_operands_for_raw_operator("rg", &operands).is_err());
}
#[test]
fn test_validate_rg_uppercase_valid() {
let operands = vec![Object::Real(0.0), Object::Real(1.0), Object::Real(0.0)];
assert!(Operator::validate_operands_for_raw_operator("RG", &operands).is_ok());
}
#[test]
fn test_validate_rg_uppercase_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("RG", &operands).is_err());
}
#[test]
fn test_validate_g_valid() {
let operands = vec![Object::Real(0.5)];
assert!(Operator::validate_operands_for_raw_operator("g", &operands).is_ok());
}
#[test]
fn test_validate_g_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("g", &operands).is_err());
}
#[test]
fn test_validate_g_uppercase_valid() {
let operands = vec![Object::Real(0.5)];
assert!(Operator::validate_operands_for_raw_operator("G", &operands).is_ok());
}
#[test]
fn test_validate_g_uppercase_wrong_count() {
let operands = vec![Object::Real(0.5), Object::Real(0.5)];
assert!(Operator::validate_operands_for_raw_operator("G", &operands).is_err());
}
#[test]
fn test_validate_k_valid() {
let operands = vec![
Object::Real(0.0),
Object::Real(0.0),
Object::Real(0.0),
Object::Real(1.0),
];
assert!(Operator::validate_operands_for_raw_operator("k", &operands).is_ok());
}
#[test]
fn test_validate_k_wrong_count() {
let operands = vec![Object::Real(0.0)];
assert!(Operator::validate_operands_for_raw_operator("k", &operands).is_err());
}
#[test]
fn test_validate_k_uppercase_valid() {
let operands = vec![
Object::Real(0.0),
Object::Real(1.0),
Object::Real(0.0),
Object::Real(0.0),
];
assert!(Operator::validate_operands_for_raw_operator("K", &operands).is_ok());
}
#[test]
fn test_validate_k_uppercase_wrong_count() {
let operands = vec![Object::Real(0.0), Object::Real(1.0)];
assert!(Operator::validate_operands_for_raw_operator("K", &operands).is_err());
}
#[test]
fn test_validate_bt_et_valid() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("BT", &operands).is_ok());
assert!(Operator::validate_operands_for_raw_operator("ET", &operands).is_ok());
}
#[test]
fn test_validate_bt_wrong_count() {
let operands = vec![Object::Integer(1)];
assert!(Operator::validate_operands_for_raw_operator("BT", &operands).is_err());
}
#[test]
fn test_validate_et_wrong_count() {
let operands = vec![Object::Integer(1)];
assert!(Operator::validate_operands_for_raw_operator("ET", &operands).is_err());
}
#[test]
fn test_validate_do_valid() {
let operands = vec![Object::Name("Im1".to_string())];
assert!(Operator::validate_operands_for_raw_operator("Do", &operands).is_ok());
}
#[test]
fn test_validate_do_wrong_count() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("Do", &operands).is_err());
}
#[test]
fn test_validate_unknown_operator_passes() {
let operands = vec![Object::Integer(1), Object::Integer(2), Object::Integer(3)];
assert!(Operator::validate_operands_for_raw_operator("xyz_unknown", &operands).is_ok());
}
#[test]
fn test_validate_unknown_operator_empty_operands() {
let operands: Vec<Object> = vec![];
assert!(Operator::validate_operands_for_raw_operator("BMC", &operands).is_ok());
}
#[test]
fn test_operator_td_uppercase() {
let op = Operator::TD { tx: 0.0, ty: -14.0 };
match op {
Operator::TD { tx, ty } => {
assert_eq!(tx, 0.0);
assert_eq!(ty, -14.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_tstar() {
let op = Operator::TStar;
assert!(matches!(op, Operator::TStar));
}
#[test]
fn test_operator_tj_array() {
let op = Operator::TJ {
array: vec![
TextElement::String(b"He".to_vec()),
TextElement::Offset(-120.0),
TextElement::String(b"llo".to_vec()),
],
};
match op {
Operator::TJ { array } => {
assert_eq!(array.len(), 3);
assert!(matches!(&array[0], TextElement::String(s) if s == b"He"));
assert!(matches!(&array[1], TextElement::Offset(o) if *o == -120.0));
assert!(matches!(&array[2], TextElement::String(s) if s == b"llo"));
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_quote() {
let op = Operator::Quote {
text: b"next line".to_vec(),
};
match op {
Operator::Quote { text } => assert_eq!(text, b"next line"),
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_double_quote() {
let op = Operator::DoubleQuote {
word_space: 1.0,
char_space: 2.0,
text: b"quoted".to_vec(),
};
match op {
Operator::DoubleQuote {
word_space,
char_space,
text,
} => {
assert_eq!(word_space, 1.0);
assert_eq!(char_space, 2.0);
assert_eq!(text, b"quoted");
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_tc() {
let op = Operator::Tc { char_space: 0.5 };
assert!(matches!(op, Operator::Tc { char_space } if char_space == 0.5));
}
#[test]
fn test_operator_tw() {
let op = Operator::Tw { word_space: 1.5 };
assert!(matches!(op, Operator::Tw { word_space } if word_space == 1.5));
}
#[test]
fn test_operator_tz() {
let op = Operator::Tz { scale: 150.0 };
assert!(matches!(op, Operator::Tz { scale } if scale == 150.0));
}
#[test]
fn test_operator_tl() {
let op = Operator::TL { leading: 14.0 };
assert!(matches!(op, Operator::TL { leading } if leading == 14.0));
}
#[test]
fn test_operator_tr() {
let op = Operator::Tr { render: 2 };
assert!(matches!(op, Operator::Tr { render } if render == 2));
}
#[test]
fn test_operator_ts() {
let op = Operator::Ts { rise: 5.0 };
assert!(matches!(op, Operator::Ts { rise } if rise == 5.0));
}
#[test]
fn test_operator_cm() {
let op = Operator::Cm {
a: 2.0,
b: 0.0,
c: 0.0,
d: 2.0,
e: 10.0,
f: 20.0,
};
match op {
Operator::Cm { a, b, c, d, e, f } => {
assert_eq!(a, 2.0);
assert_eq!(b, 0.0);
assert_eq!(c, 0.0);
assert_eq!(d, 2.0);
assert_eq!(e, 10.0);
assert_eq!(f, 20.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_stroke_rgb() {
let op = Operator::SetStrokeRgb { r: 0.0, g: 0.5, b: 1.0 };
match op {
Operator::SetStrokeRgb { r, g, b } => {
assert_eq!(r, 0.0);
assert_eq!(g, 0.5);
assert_eq!(b, 1.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_fill_gray() {
let op = Operator::SetFillGray { gray: 0.5 };
assert!(matches!(op, Operator::SetFillGray { gray } if gray == 0.5));
}
#[test]
fn test_operator_stroke_gray() {
let op = Operator::SetStrokeGray { gray: 0.0 };
assert!(matches!(op, Operator::SetStrokeGray { gray } if gray == 0.0));
}
#[test]
fn test_operator_fill_cmyk() {
let op = Operator::SetFillCmyk {
c: 1.0,
m: 0.0,
y: 0.0,
k: 0.0,
};
match op {
Operator::SetFillCmyk { c, m, y, k } => {
assert_eq!(c, 1.0);
assert_eq!(m, 0.0);
assert_eq!(y, 0.0);
assert_eq!(k, 0.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_stroke_cmyk() {
let op = Operator::SetStrokeCmyk {
c: 0.0,
m: 1.0,
y: 0.0,
k: 0.0,
};
match op {
Operator::SetStrokeCmyk { c, m, y, k } => {
assert_eq!(c, 0.0);
assert_eq!(m, 1.0);
assert_eq!(y, 0.0);
assert_eq!(k, 0.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_fill_color_space() {
let op = Operator::SetFillColorSpace {
name: "DeviceRGB".to_string(),
};
match op {
Operator::SetFillColorSpace { name } => assert_eq!(name, "DeviceRGB"),
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_stroke_color_space() {
let op = Operator::SetStrokeColorSpace {
name: "DeviceCMYK".to_string(),
};
match op {
Operator::SetStrokeColorSpace { name } => assert_eq!(name, "DeviceCMYK"),
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_fill_color() {
let op = Operator::SetFillColor {
components: vec![0.1, 0.2, 0.3],
};
match op {
Operator::SetFillColor { components } => {
assert_eq!(components, vec![0.1, 0.2, 0.3]);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_stroke_color() {
let op = Operator::SetStrokeColor { components: vec![0.5] };
match op {
Operator::SetStrokeColor { components } => {
assert_eq!(components, vec![0.5]);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_fill_color_n_with_name() {
let op = Operator::SetFillColorN {
components: vec![0.1, 0.2, 0.3],
name: Some(Box::new("Pattern1".to_string())),
};
match op {
Operator::SetFillColorN { components, name } => {
assert_eq!(components, vec![0.1, 0.2, 0.3]);
assert_eq!(name, Some(Box::new("Pattern1".to_string())));
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_fill_color_n_without_name() {
let op = Operator::SetFillColorN {
components: vec![0.5],
name: None,
};
match op {
Operator::SetFillColorN { components, name } => {
assert_eq!(components, vec![0.5]);
assert!(name.is_none());
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_stroke_color_n() {
let op = Operator::SetStrokeColorN {
components: vec![],
name: Some(Box::new("P1".to_string())),
};
match op {
Operator::SetStrokeColorN { components, name } => {
assert!(components.is_empty());
assert_eq!(name, Some(Box::new("P1".to_string())));
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_begin_end_text() {
assert!(matches!(Operator::BeginText, Operator::BeginText));
assert!(matches!(Operator::EndText, Operator::EndText));
}
#[test]
fn test_operator_do() {
let op = Operator::Do {
name: "Im1".to_string(),
};
match op {
Operator::Do { name } => assert_eq!(name, "Im1"),
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_moveto() {
let op = Operator::MoveTo { x: 100.0, y: 200.0 };
match op {
Operator::MoveTo { x, y } => {
assert_eq!(x, 100.0);
assert_eq!(y, 200.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_lineto() {
let op = Operator::LineTo { x: 300.0, y: 400.0 };
match op {
Operator::LineTo { x, y } => {
assert_eq!(x, 300.0);
assert_eq!(y, 400.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_curveto() {
let op = Operator::CurveTo {
x1: 1.0,
y1: 2.0,
x2: 3.0,
y2: 4.0,
x3: 5.0,
y3: 6.0,
};
match op {
Operator::CurveTo { x1, y1, x2, y2, x3, y3 } => {
assert_eq!(x1, 1.0);
assert_eq!(y1, 2.0);
assert_eq!(x2, 3.0);
assert_eq!(y2, 4.0);
assert_eq!(x3, 5.0);
assert_eq!(y3, 6.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_curveto_v() {
let op = Operator::CurveToV {
x2: 10.0,
y2: 20.0,
x3: 30.0,
y3: 40.0,
};
match op {
Operator::CurveToV { x2, y2, x3, y3 } => {
assert_eq!(x2, 10.0);
assert_eq!(y2, 20.0);
assert_eq!(x3, 30.0);
assert_eq!(y3, 40.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_curveto_y() {
let op = Operator::CurveToY {
x1: 10.0,
y1: 20.0,
x3: 30.0,
y3: 40.0,
};
match op {
Operator::CurveToY { x1, y1, x3, y3 } => {
assert_eq!(x1, 10.0);
assert_eq!(y1, 20.0);
assert_eq!(x3, 30.0);
assert_eq!(y3, 40.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_closepath() {
assert!(matches!(Operator::ClosePath, Operator::ClosePath));
}
#[test]
fn test_operator_rectangle() {
let op = Operator::Rectangle {
x: 10.0,
y: 20.0,
width: 100.0,
height: 50.0,
};
match op {
Operator::Rectangle { x, y, width, height } => {
assert_eq!(x, 10.0);
assert_eq!(y, 20.0);
assert_eq!(width, 100.0);
assert_eq!(height, 50.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_path_painting() {
assert!(matches!(Operator::Stroke, Operator::Stroke));
assert!(matches!(Operator::Fill, Operator::Fill));
assert!(matches!(Operator::FillEvenOdd, Operator::FillEvenOdd));
assert!(matches!(Operator::CloseFillStroke, Operator::CloseFillStroke));
assert!(matches!(Operator::EndPath, Operator::EndPath));
}
#[test]
fn test_operator_clipping() {
assert!(matches!(Operator::ClipNonZero, Operator::ClipNonZero));
assert!(matches!(Operator::ClipEvenOdd, Operator::ClipEvenOdd));
}
#[test]
fn test_operator_set_line_width() {
let op = Operator::SetLineWidth { width: 2.5 };
assert!(matches!(op, Operator::SetLineWidth { width } if width == 2.5));
}
#[test]
fn test_operator_set_dash() {
let op = Operator::SetDash {
array: vec![3.0, 2.0],
phase: 0.0,
};
match op {
Operator::SetDash { array, phase } => {
assert_eq!(array, vec![3.0, 2.0]);
assert_eq!(phase, 0.0);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_set_line_cap() {
let op = Operator::SetLineCap { cap_style: 1 };
assert!(matches!(op, Operator::SetLineCap { cap_style } if cap_style == 1));
}
#[test]
fn test_operator_set_line_join() {
let op = Operator::SetLineJoin { join_style: 2 };
assert!(matches!(op, Operator::SetLineJoin { join_style } if join_style == 2));
}
#[test]
fn test_operator_set_miter_limit() {
let op = Operator::SetMiterLimit { limit: 10.0 };
assert!(matches!(op, Operator::SetMiterLimit { limit } if limit == 10.0));
}
#[test]
fn test_operator_set_rendering_intent() {
let op = Operator::SetRenderingIntent {
intent: "Perceptual".to_string(),
};
match op {
Operator::SetRenderingIntent { intent } => assert_eq!(intent, "Perceptual"),
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_set_flatness() {
let op = Operator::SetFlatness { tolerance: 50.0 };
assert!(matches!(op, Operator::SetFlatness { tolerance } if tolerance == 50.0));
}
#[test]
fn test_operator_set_ext_gstate() {
let op = Operator::SetExtGState {
dict_name: "GS1".to_string(),
};
match op {
Operator::SetExtGState { dict_name } => assert_eq!(dict_name, "GS1"),
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_paint_shading() {
let op = Operator::PaintShading {
name: "Sh1".to_string(),
};
match op {
Operator::PaintShading { name } => assert_eq!(name, "Sh1"),
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_inline_image() {
let mut dict = std::collections::HashMap::new();
dict.insert("W".to_string(), Object::Integer(100));
dict.insert("H".to_string(), Object::Integer(50));
let op = Operator::InlineImage {
dict: Box::new(dict),
data: vec![0xFF; 10],
};
match op {
Operator::InlineImage { dict, data } => {
assert_eq!(dict.len(), 2);
assert_eq!(data.len(), 10);
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_begin_marked_content() {
let op = Operator::BeginMarkedContent {
tag: "Span".to_string(),
};
match op {
Operator::BeginMarkedContent { tag } => assert_eq!(tag, "Span"),
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_begin_marked_content_dict() {
let op = Operator::BeginMarkedContentDict {
tag: "P".to_string(),
properties: Box::new(Object::Name("MCID0".to_string())),
};
match op {
Operator::BeginMarkedContentDict { tag, properties } => {
assert_eq!(tag, "P");
assert!(matches!(*properties, Object::Name(ref n) if n == "MCID0"));
}
_ => panic!("Wrong operator type"),
}
}
#[test]
fn test_operator_end_marked_content() {
assert!(matches!(Operator::EndMarkedContent, Operator::EndMarkedContent));
}
#[test]
fn test_operator_debug_format() {
let op = Operator::Td { tx: 1.0, ty: 2.0 };
let debug = format!("{:?}", op);
assert!(debug.contains("Td"));
assert!(debug.contains("1.0"));
assert!(debug.contains("2.0"));
}
#[test]
fn test_operator_equality() {
let op1 = Operator::SetFillGray { gray: 0.5 };
let op2 = Operator::SetFillGray { gray: 0.5 };
let op3 = Operator::SetFillGray { gray: 0.6 };
assert_eq!(op1, op2);
assert_ne!(op1, op3);
}
#[test]
fn test_text_element_debug_format() {
let elem = TextElement::Offset(-50.0);
let debug = format!("{:?}", elem);
assert!(debug.contains("Offset"));
assert!(debug.contains("-50.0"));
}
#[test]
fn test_text_element_inequality() {
let s1 = TextElement::String(b"abc".to_vec());
let s2 = TextElement::String(b"def".to_vec());
let o1 = TextElement::Offset(10.0);
assert_ne!(s1, s2);
assert_ne!(s1, o1);
}
}