use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq)]
pub enum Object {
Null,
Boolean(bool),
Integer(i64),
Real(f64),
String(Vec<u8>),
Name(String),
Array(Vec<Object>),
Dictionary(std::collections::HashMap<String, Object>),
Stream {
dict: std::collections::HashMap<String, Object>,
data: bytes::Bytes,
},
Reference(ObjectRef),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObjectRef {
pub id: u32,
pub gen: u16,
}
impl ObjectRef {
pub fn new(id: u32, gen: u16) -> Self {
Self { id, gen }
}
}
impl std::fmt::Display for ObjectRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {} R", self.id, self.gen)
}
}
impl Object {
pub fn type_name(&self) -> &'static str {
match self {
Object::Null => "Null",
Object::Boolean(_) => "Boolean",
Object::Integer(_) => "Integer",
Object::Real(_) => "Real",
Object::String(_) => "String",
Object::Name(_) => "Name",
Object::Array(_) => "Array",
Object::Dictionary(_) => "Dictionary",
Object::Stream { .. } => "Stream",
Object::Reference(_) => "Reference",
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
Object::Integer(i) => Some(*i),
_ => None,
}
}
pub fn as_name(&self) -> Option<&str> {
match self {
Object::Name(s) => Some(s),
_ => None,
}
}
pub fn as_dict(&self) -> Option<&std::collections::HashMap<String, Object>> {
match self {
Object::Dictionary(d) => Some(d),
Object::Stream { dict, .. } => Some(dict),
_ => None,
}
}
pub fn as_array(&self) -> Option<&Vec<Object>> {
match self {
Object::Array(arr) => Some(arr),
_ => None,
}
}
pub fn as_reference(&self) -> Option<ObjectRef> {
match self {
Object::Reference(r) => Some(*r),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Object::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn as_real(&self) -> Option<f64> {
match self {
Object::Real(r) => Some(*r),
_ => None,
}
}
pub fn as_string(&self) -> Option<&[u8]> {
match self {
Object::String(s) => Some(s),
_ => None,
}
}
pub fn is_null(&self) -> bool {
matches!(self, Object::Null)
}
pub fn decode_stream_data(&self) -> Result<Vec<u8>> {
self.decode_stream_data_with_decryption(None, 0, 0)
}
pub fn decode_stream_data_with_decryption(
&self,
decryption_fn: Option<&dyn Fn(&[u8]) -> Result<Vec<u8>>>,
obj_num: u32,
gen_num: u32,
) -> Result<Vec<u8>> {
match self {
Object::Stream { dict, data } => {
let decrypted_data = if let Some(decrypt) = decryption_fn {
log::debug!(
"Decrypting stream for object {} {} (length: {} bytes)",
obj_num,
gen_num,
data.len()
);
match decrypt(data) {
Ok(data) => {
log::debug!("Decryption successful: {} bytes", data.len());
data
},
Err(e) => {
log::error!(
"Decryption failed for object {} {}: {}",
obj_num,
gen_num,
e
);
return Err(e);
},
}
} else {
data.to_vec()
};
let filters = dict
.get("Filter")
.map(extract_filter_names)
.unwrap_or_default();
if filters.is_empty() {
Ok(decrypted_data)
} else {
let decode_params = extract_decode_params(dict.get("DecodeParms"));
crate::decoders::decode_stream_with_params(
&decrypted_data,
&filters,
decode_params.as_ref(),
)
}
},
Object::Dictionary(dict) => {
log::warn!("Dictionary used where Stream expected, treating as empty stream");
let filters = dict
.get("Filter")
.map(extract_filter_names)
.unwrap_or_default();
if filters.is_empty() {
Ok(Vec::new())
} else {
let decode_params = extract_decode_params(dict.get("DecodeParms"));
crate::decoders::decode_stream_with_params(
&[],
&filters,
decode_params.as_ref(),
)
}
},
_ => Err(Error::InvalidObjectType {
expected: "Stream".to_string(),
found: self.type_name().to_string(),
}),
}
}
}
fn extract_filter_names(filter_obj: &Object) -> Vec<String> {
match filter_obj {
Object::Name(name) => vec![name.clone()],
Object::Array(arr) => arr
.iter()
.filter_map(|obj| obj.as_name().map(|s| s.to_string()))
.collect(),
_ => vec![],
}
}
fn extract_decode_params(params_obj: Option<&Object>) -> Option<crate::decoders::DecodeParams> {
let dict = match params_obj? {
Object::Dictionary(d) => d,
Object::Array(arr) => {
arr.iter().filter_map(|obj| obj.as_dict()).next()?
},
_ => return None,
};
let predictor = dict
.get("Predictor")
.and_then(|obj| obj.as_integer())
.unwrap_or(1);
let columns = dict
.get("Columns")
.and_then(|obj| obj.as_integer())
.unwrap_or(1) as usize;
let colors = dict
.get("Colors")
.and_then(|obj| obj.as_integer())
.unwrap_or(1) as usize;
let bits_per_component = dict
.get("BitsPerComponent")
.and_then(|obj| obj.as_integer())
.unwrap_or(8) as usize;
Some(crate::decoders::DecodeParams {
predictor,
columns,
colors,
bits_per_component,
})
}
pub fn extract_ccitt_params(params_obj: Option<&Object>) -> Option<crate::decoders::CcittParams> {
extract_ccitt_params_with_width(params_obj, None)
}
pub fn extract_ccitt_params_with_width(
params_obj: Option<&Object>,
image_width: Option<u32>,
) -> Option<crate::decoders::CcittParams> {
let dict = match params_obj? {
Object::Dictionary(d) => d,
Object::Array(arr) => {
arr.iter().filter_map(|obj| obj.as_dict()).next()?
},
_ => return None,
};
let k = dict.get("K").and_then(|obj| obj.as_integer()).unwrap_or(-1);
let columns = dict
.get("Columns")
.and_then(|obj| obj.as_integer())
.map(|v| v as u32)
.or(image_width)
.unwrap_or(1);
let rows = dict
.get("Rows")
.and_then(|obj| obj.as_integer())
.map(|v| v as u32);
let black_is_1 = dict
.get("BlackIs1")
.and_then(|obj| obj.as_bool())
.unwrap_or(false);
let end_of_line = dict
.get("EndOfLine")
.and_then(|obj| obj.as_bool())
.unwrap_or(false);
let encoded_byte_align = dict
.get("EncodedByteAlign")
.and_then(|obj| obj.as_bool())
.unwrap_or(false);
let end_of_block = dict
.get("EndOfBlock")
.and_then(|obj| obj.as_bool())
.unwrap_or(true);
Some(crate::decoders::CcittParams {
k,
columns,
rows,
black_is_1,
end_of_line,
encoded_byte_align,
end_of_block,
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_object_integer() {
let obj = Object::Integer(42);
assert_eq!(obj.as_integer(), Some(42));
assert!(obj.as_name().is_none());
assert!(!obj.is_null());
}
#[test]
fn test_object_name() {
let obj = Object::Name("Type".to_string());
assert_eq!(obj.as_name(), Some("Type"));
assert!(obj.as_integer().is_none());
}
#[test]
fn test_object_bool() {
let obj = Object::Boolean(true);
assert_eq!(obj.as_bool(), Some(true));
}
#[test]
#[allow(clippy::approx_constant)]
fn test_object_real() {
let obj = Object::Real(3.14);
assert_eq!(obj.as_real(), Some(3.14));
}
#[test]
fn test_object_string() {
let obj = Object::String(b"Hello".to_vec());
assert_eq!(obj.as_string(), Some(&b"Hello"[..]));
}
#[test]
fn test_object_null() {
let obj = Object::Null;
assert!(obj.is_null());
assert!(obj.as_integer().is_none());
}
#[test]
fn test_object_array() {
let obj = Object::Array(vec![Object::Integer(1), Object::Integer(2)]);
let arr = obj.as_array().unwrap();
assert_eq!(arr.len(), 2);
assert_eq!(arr[0].as_integer(), Some(1));
}
#[test]
fn test_object_dictionary() {
let mut dict = HashMap::new();
dict.insert("Type".to_string(), Object::Name("Page".to_string()));
let obj = Object::Dictionary(dict);
let d = obj.as_dict().unwrap();
assert_eq!(d.get("Type").unwrap().as_name(), Some("Page"));
}
#[test]
fn test_object_stream_dict_access() {
let mut dict = HashMap::new();
dict.insert("Length".to_string(), Object::Integer(100));
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"stream data"),
};
let d = obj.as_dict().unwrap();
assert_eq!(d.get("Length").unwrap().as_integer(), Some(100));
}
#[test]
fn test_object_reference() {
let obj_ref = ObjectRef::new(10, 0);
let obj = Object::Reference(obj_ref);
assert_eq!(obj.as_reference(), Some(obj_ref));
assert_eq!(obj_ref.id, 10);
assert_eq!(obj_ref.gen, 0);
}
#[test]
fn test_object_ref_display() {
let obj_ref = ObjectRef::new(10, 0);
assert_eq!(format!("{}", obj_ref), "10 0 R");
}
#[test]
fn test_object_clone() {
let obj = Object::Integer(42);
let cloned = obj.clone();
assert_eq!(obj, cloned);
}
#[test]
fn test_object_ref_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(ObjectRef::new(1, 0));
set.insert(ObjectRef::new(2, 0));
set.insert(ObjectRef::new(1, 0));
assert_eq!(set.len(), 2); }
#[test]
fn test_decode_stream_no_filter() {
let mut dict = HashMap::new();
dict.insert("Length".to_string(), Object::Integer(5));
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"Hello"),
};
let decoded = obj.decode_stream_data().unwrap();
assert_eq!(decoded, b"Hello");
}
#[test]
fn test_decode_stream_single_filter() {
let mut dict = HashMap::new();
dict.insert("Filter".to_string(), Object::Name("ASCIIHexDecode".to_string()));
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"48656C6C6F"), };
let decoded = obj.decode_stream_data().unwrap();
assert_eq!(decoded, b"Hello");
}
#[test]
fn test_decode_stream_filter_array() {
let mut dict = HashMap::new();
dict.insert(
"Filter".to_string(),
Object::Array(vec![Object::Name("ASCIIHexDecode".to_string())]),
);
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"48656C6C6F"),
};
let decoded = obj.decode_stream_data().unwrap();
assert_eq!(decoded, b"Hello");
}
#[test]
fn test_decode_stream_not_a_stream() {
let obj = Object::Integer(42);
let result = obj.decode_stream_data();
assert!(result.is_err());
match result {
Err(Error::InvalidObjectType { expected, found }) => {
assert_eq!(expected, "Stream");
assert_eq!(found, "Integer");
},
_ => panic!("Expected InvalidObjectType error"),
}
}
#[test]
fn test_decode_dictionary_as_stream() {
let mut dict = HashMap::new();
dict.insert("Length".to_string(), Object::Integer(0));
let obj = Object::Dictionary(dict);
let decoded = obj.decode_stream_data().unwrap();
assert!(decoded.is_empty());
}
#[test]
fn test_decode_dictionary_as_stream_with_filter() {
let mut dict = HashMap::new();
dict.insert("Filter".to_string(), Object::Name("ASCIIHexDecode".to_string()));
let obj = Object::Dictionary(dict);
let decoded = obj.decode_stream_data().unwrap();
assert!(decoded.is_empty());
}
#[test]
fn test_extract_filter_names_single() {
let filter = Object::Name("FlateDecode".to_string());
let names = extract_filter_names(&filter);
assert_eq!(names, vec!["FlateDecode"]);
}
#[test]
fn test_extract_filter_names_array() {
let filter = Object::Array(vec![
Object::Name("ASCII85Decode".to_string()),
Object::Name("FlateDecode".to_string()),
]);
let names = extract_filter_names(&filter);
assert_eq!(names, vec!["ASCII85Decode", "FlateDecode"]);
}
#[test]
fn test_extract_filter_names_invalid() {
let filter = Object::Integer(42);
let names = extract_filter_names(&filter);
assert!(names.is_empty());
}
#[test]
fn test_type_name_all_variants() {
assert_eq!(Object::Null.type_name(), "Null");
assert_eq!(Object::Boolean(true).type_name(), "Boolean");
assert_eq!(Object::Integer(0).type_name(), "Integer");
assert_eq!(Object::Real(0.0).type_name(), "Real");
assert_eq!(Object::String(vec![]).type_name(), "String");
assert_eq!(Object::Name("X".to_string()).type_name(), "Name");
assert_eq!(Object::Array(vec![]).type_name(), "Array");
assert_eq!(Object::Dictionary(HashMap::new()).type_name(), "Dictionary");
assert_eq!(
Object::Stream {
dict: HashMap::new(),
data: bytes::Bytes::new()
}
.type_name(),
"Stream"
);
assert_eq!(Object::Reference(ObjectRef::new(1, 0)).type_name(), "Reference");
}
#[test]
fn test_as_integer_returns_none_for_non_integer() {
assert!(Object::Null.as_integer().is_none());
assert!(Object::Boolean(true).as_integer().is_none());
assert!(Object::Real(1.0).as_integer().is_none());
assert!(Object::String(vec![]).as_integer().is_none());
assert!(Object::Name("X".to_string()).as_integer().is_none());
assert!(Object::Array(vec![]).as_integer().is_none());
assert!(Object::Dictionary(HashMap::new()).as_integer().is_none());
assert!(Object::Reference(ObjectRef::new(1, 0))
.as_integer()
.is_none());
}
#[test]
fn test_as_name_returns_none_for_non_name() {
assert!(Object::Null.as_name().is_none());
assert!(Object::Integer(1).as_name().is_none());
assert!(Object::Boolean(true).as_name().is_none());
assert!(Object::Real(1.0).as_name().is_none());
assert!(Object::String(vec![]).as_name().is_none());
assert!(Object::Array(vec![]).as_name().is_none());
}
#[test]
fn test_as_dict_returns_none_for_non_dict() {
assert!(Object::Null.as_dict().is_none());
assert!(Object::Integer(1).as_dict().is_none());
assert!(Object::Boolean(true).as_dict().is_none());
assert!(Object::Real(1.0).as_dict().is_none());
assert!(Object::String(vec![]).as_dict().is_none());
assert!(Object::Name("X".to_string()).as_dict().is_none());
assert!(Object::Array(vec![]).as_dict().is_none());
assert!(Object::Reference(ObjectRef::new(1, 0)).as_dict().is_none());
}
#[test]
fn test_as_array_returns_none_for_non_array() {
assert!(Object::Null.as_array().is_none());
assert!(Object::Integer(1).as_array().is_none());
assert!(Object::Dictionary(HashMap::new()).as_array().is_none());
assert!(Object::Name("X".to_string()).as_array().is_none());
}
#[test]
fn test_as_reference_returns_none_for_non_reference() {
assert!(Object::Null.as_reference().is_none());
assert!(Object::Integer(1).as_reference().is_none());
assert!(Object::Name("X".to_string()).as_reference().is_none());
assert!(Object::Dictionary(HashMap::new()).as_reference().is_none());
}
#[test]
fn test_as_bool_returns_none_for_non_bool() {
assert!(Object::Null.as_bool().is_none());
assert!(Object::Integer(1).as_bool().is_none());
assert!(Object::Real(1.0).as_bool().is_none());
assert!(Object::String(vec![]).as_bool().is_none());
}
#[test]
fn test_as_real_returns_none_for_non_real() {
assert!(Object::Null.as_real().is_none());
assert!(Object::Integer(1).as_real().is_none());
assert!(Object::Boolean(true).as_real().is_none());
assert!(Object::String(vec![]).as_real().is_none());
}
#[test]
fn test_as_string_returns_none_for_non_string() {
assert!(Object::Null.as_string().is_none());
assert!(Object::Integer(1).as_string().is_none());
assert!(Object::Boolean(true).as_string().is_none());
assert!(Object::Real(1.0).as_string().is_none());
assert!(Object::Name("X".to_string()).as_string().is_none());
}
#[test]
fn test_is_null_returns_false_for_non_null() {
assert!(!Object::Integer(0).is_null());
assert!(!Object::Boolean(false).is_null());
assert!(!Object::Real(0.0).is_null());
assert!(!Object::String(vec![]).is_null());
assert!(!Object::Name("".to_string()).is_null());
assert!(!Object::Array(vec![]).is_null());
assert!(!Object::Dictionary(HashMap::new()).is_null());
}
#[test]
fn test_object_ref_display_with_non_zero_gen() {
let obj_ref = ObjectRef::new(42, 3);
assert_eq!(format!("{}", obj_ref), "42 3 R");
}
#[test]
fn test_object_ref_equality() {
let a = ObjectRef::new(5, 0);
let b = ObjectRef::new(5, 0);
let c = ObjectRef::new(5, 1);
let d = ObjectRef::new(6, 0);
assert_eq!(a, b);
assert_ne!(a, c);
assert_ne!(a, d);
}
#[test]
fn test_object_ref_copy() {
let a = ObjectRef::new(10, 0);
let b = a; assert_eq!(a, b);
assert_eq!(a.id, 10);
}
#[test]
fn test_extract_filter_names_array_with_non_names() {
let filter = Object::Array(vec![
Object::Name("FlateDecode".to_string()),
Object::Integer(42),
Object::Name("LZWDecode".to_string()),
]);
let names = extract_filter_names(&filter);
assert_eq!(names, vec!["FlateDecode", "LZWDecode"]);
}
#[test]
fn test_extract_filter_names_empty_array() {
let filter = Object::Array(vec![]);
let names = extract_filter_names(&filter);
assert!(names.is_empty());
}
#[test]
fn test_extract_filter_names_null() {
let filter = Object::Null;
let names = extract_filter_names(&filter);
assert!(names.is_empty());
}
#[test]
fn test_extract_filter_names_boolean() {
let filter = Object::Boolean(true);
let names = extract_filter_names(&filter);
assert!(names.is_empty());
}
#[test]
fn test_extract_decode_params_none() {
let result = extract_decode_params(None);
assert!(result.is_none());
}
#[test]
fn test_extract_decode_params_defaults() {
let dict = Object::Dictionary(HashMap::new());
let result = extract_decode_params(Some(&dict)).unwrap();
assert_eq!(result.predictor, 1);
assert_eq!(result.columns, 1);
assert_eq!(result.colors, 1);
assert_eq!(result.bits_per_component, 8);
}
#[test]
fn test_extract_decode_params_custom_values() {
let mut d = HashMap::new();
d.insert("Predictor".to_string(), Object::Integer(12));
d.insert("Columns".to_string(), Object::Integer(800));
d.insert("Colors".to_string(), Object::Integer(3));
d.insert("BitsPerComponent".to_string(), Object::Integer(16));
let dict = Object::Dictionary(d);
let result = extract_decode_params(Some(&dict)).unwrap();
assert_eq!(result.predictor, 12);
assert_eq!(result.columns, 800);
assert_eq!(result.colors, 3);
assert_eq!(result.bits_per_component, 16);
}
#[test]
fn test_extract_decode_params_from_array() {
let mut d = HashMap::new();
d.insert("Predictor".to_string(), Object::Integer(15));
d.insert("Columns".to_string(), Object::Integer(640));
let dict = Object::Dictionary(d);
let arr = Object::Array(vec![dict]);
let result = extract_decode_params(Some(&arr)).unwrap();
assert_eq!(result.predictor, 15);
assert_eq!(result.columns, 640);
}
#[test]
fn test_extract_decode_params_invalid_type() {
let obj = Object::Integer(42);
let result = extract_decode_params(Some(&obj));
assert!(result.is_none());
}
#[test]
fn test_extract_decode_params_null_object() {
let obj = Object::Null;
let result = extract_decode_params(Some(&obj));
assert!(result.is_none());
}
#[test]
fn test_extract_decode_params_empty_array() {
let arr = Object::Array(vec![]);
let result = extract_decode_params(Some(&arr));
assert!(result.is_none());
}
#[test]
fn test_extract_decode_params_array_with_only_non_dicts() {
let arr = Object::Array(vec![Object::Integer(1), Object::Null]);
let result = extract_decode_params(Some(&arr));
assert!(result.is_none());
}
#[test]
fn test_extract_ccitt_params_none() {
let result = extract_ccitt_params(None);
assert!(result.is_none());
}
#[test]
fn test_extract_ccitt_params_defaults() {
let dict = Object::Dictionary(HashMap::new());
let result = extract_ccitt_params(Some(&dict)).unwrap();
assert_eq!(result.k, -1); assert_eq!(result.columns, 1);
assert!(result.rows.is_none());
assert!(!result.black_is_1);
assert!(!result.end_of_line);
assert!(!result.encoded_byte_align);
assert!(result.end_of_block);
}
#[test]
fn test_extract_ccitt_params_custom_values() {
let mut d = HashMap::new();
d.insert("K".to_string(), Object::Integer(0));
d.insert("Columns".to_string(), Object::Integer(1728));
d.insert("Rows".to_string(), Object::Integer(2376));
d.insert("BlackIs1".to_string(), Object::Boolean(true));
d.insert("EndOfLine".to_string(), Object::Boolean(true));
d.insert("EncodedByteAlign".to_string(), Object::Boolean(true));
d.insert("EndOfBlock".to_string(), Object::Boolean(false));
let dict = Object::Dictionary(d);
let result = extract_ccitt_params(Some(&dict)).unwrap();
assert_eq!(result.k, 0);
assert_eq!(result.columns, 1728);
assert_eq!(result.rows, Some(2376));
assert!(result.black_is_1);
assert!(result.end_of_line);
assert!(result.encoded_byte_align);
assert!(!result.end_of_block);
}
#[test]
fn test_extract_ccitt_params_invalid_type() {
let obj = Object::Integer(42);
let result = extract_ccitt_params(Some(&obj));
assert!(result.is_none());
}
#[test]
fn test_extract_ccitt_params_from_array() {
let mut d = HashMap::new();
d.insert("K".to_string(), Object::Integer(-1));
d.insert("Columns".to_string(), Object::Integer(612));
let dict = Object::Dictionary(d);
let arr = Object::Array(vec![dict]);
let result = extract_ccitt_params(Some(&arr)).unwrap();
assert_eq!(result.k, -1);
assert_eq!(result.columns, 612);
}
#[test]
fn test_extract_ccitt_params_with_width_override() {
let dict = Object::Dictionary(HashMap::new());
let result = extract_ccitt_params_with_width(Some(&dict), Some(2550)).unwrap();
assert_eq!(result.columns, 2550);
}
#[test]
fn test_extract_ccitt_params_with_width_columns_takes_precedence() {
let mut d = HashMap::new();
d.insert("Columns".to_string(), Object::Integer(1000));
let dict = Object::Dictionary(d);
let result = extract_ccitt_params_with_width(Some(&dict), Some(2550)).unwrap();
assert_eq!(result.columns, 1000);
}
#[test]
fn test_extract_ccitt_params_with_width_none_params() {
let result = extract_ccitt_params_with_width(None, Some(200));
assert!(result.is_none());
}
#[test]
fn test_extract_ccitt_params_with_width_no_override_no_columns() {
let dict = Object::Dictionary(HashMap::new());
let result = extract_ccitt_params_with_width(Some(&dict), None).unwrap();
assert_eq!(result.columns, 1);
}
#[test]
fn test_decode_stream_with_decryption_no_decrypt() {
let mut dict = HashMap::new();
dict.insert("Length".to_string(), Object::Integer(5));
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"Hello"),
};
let decoded = obj.decode_stream_data_with_decryption(None, 0, 0).unwrap();
assert_eq!(decoded, b"Hello");
}
#[test]
fn test_decode_stream_with_decryption_fn() {
let mut dict = HashMap::new();
dict.insert("Length".to_string(), Object::Integer(5));
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"Hello"),
};
let decrypt_fn = |data: &[u8]| -> Result<Vec<u8>> { Ok(data.to_vec()) };
let decoded = obj
.decode_stream_data_with_decryption(Some(&decrypt_fn), 1, 0)
.unwrap();
assert_eq!(decoded, b"Hello");
}
#[test]
fn test_decode_stream_with_decryption_fn_that_transforms() {
let mut dict = HashMap::new();
dict.insert("Length".to_string(), Object::Integer(5));
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"\x01\x02\x03"),
};
let decrypt_fn =
|data: &[u8]| -> Result<Vec<u8>> { Ok(data.iter().map(|b| b ^ 0xFF).collect()) };
let decoded = obj
.decode_stream_data_with_decryption(Some(&decrypt_fn), 1, 0)
.unwrap();
assert_eq!(decoded, vec![0xFE, 0xFD, 0xFC]);
}
#[test]
fn test_decode_stream_with_decryption_fn_error() {
let mut dict = HashMap::new();
dict.insert("Length".to_string(), Object::Integer(5));
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"Hello"),
};
let decrypt_fn =
|_data: &[u8]| -> Result<Vec<u8>> { Err(Error::InvalidPdf("decrypt fail".into())) };
let result = obj.decode_stream_data_with_decryption(Some(&decrypt_fn), 1, 0);
assert!(result.is_err());
}
#[test]
fn test_decode_stream_not_a_stream_with_decryption() {
let obj = Object::Name("NotAStream".to_string());
let result = obj.decode_stream_data_with_decryption(None, 0, 0);
assert!(result.is_err());
if let Err(Error::InvalidObjectType { expected, found }) = result {
assert_eq!(expected, "Stream");
assert_eq!(found, "Name");
} else {
panic!("Expected InvalidObjectType error");
}
}
#[test]
fn test_decode_stream_preserves_leading_cr_lf() {
let mut dict = HashMap::new();
dict.insert("Length".to_string(), Object::Integer(7));
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"\r\nHello"),
};
let decoded = obj.decode_stream_data().unwrap();
assert_eq!(decoded, b"\r\nHello");
}
#[test]
fn test_decode_dictionary_as_stream_empty() {
let dict = HashMap::new();
let obj = Object::Dictionary(dict);
let decoded = obj.decode_stream_data().unwrap();
assert!(decoded.is_empty());
}
#[test]
fn test_decode_stream_with_decode_params() {
let mut dict = HashMap::new();
dict.insert("Filter".to_string(), Object::Name("ASCIIHexDecode".to_string()));
let mut decode_params = HashMap::new();
decode_params.insert("Predictor".to_string(), Object::Integer(1));
dict.insert("DecodeParms".to_string(), Object::Dictionary(decode_params));
let obj = Object::Stream {
dict,
data: bytes::Bytes::from_static(b"48656C6C6F"),
};
let decoded = obj.decode_stream_data().unwrap();
assert_eq!(decoded, b"Hello");
}
#[test]
fn test_object_equality() {
assert_eq!(Object::Null, Object::Null);
assert_eq!(Object::Boolean(true), Object::Boolean(true));
assert_ne!(Object::Boolean(true), Object::Boolean(false));
assert_eq!(Object::Integer(42), Object::Integer(42));
assert_ne!(Object::Integer(42), Object::Integer(43));
assert_eq!(Object::String(b"abc".to_vec()), Object::String(b"abc".to_vec()));
assert_ne!(Object::String(b"abc".to_vec()), Object::String(b"def".to_vec()));
assert_ne!(Object::Null, Object::Integer(0));
}
#[test]
fn test_as_bool_false() {
assert_eq!(Object::Boolean(false).as_bool(), Some(false));
}
#[test]
fn test_as_dict_on_stream_returns_stream_dict() {
let mut dict = HashMap::new();
dict.insert("Type".to_string(), Object::Name("XObject".to_string()));
dict.insert("Subtype".to_string(), Object::Name("Image".to_string()));
let obj = Object::Stream {
dict: dict.clone(),
data: bytes::Bytes::from_static(b"image data"),
};
let result_dict = obj.as_dict().unwrap();
assert_eq!(result_dict.len(), 2);
assert_eq!(result_dict.get("Type").unwrap().as_name(), Some("XObject"));
assert_eq!(result_dict.get("Subtype").unwrap().as_name(), Some("Image"));
}
}