use std::fmt;
#[derive(Clone, PartialEq)]
pub enum PdfObj {
Null,
Bool(bool),
Int(i64),
Real(f64),
Name(Vec<u8>),
Str(Vec<u8>),
Array(Vec<PdfObj>),
Dict(PdfDict),
Stream {
dict: PdfDict,
data_offset: usize,
data_len: usize,
},
Ref(u32, u16),
}
impl PdfObj {
pub fn as_int(&self) -> Option<i64> {
match self {
PdfObj::Int(n) => Some(*n),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
PdfObj::Int(n) => Some(*n as f64),
PdfObj::Real(f) => Some(*f),
_ => None,
}
}
pub fn as_name(&self) -> Option<&[u8]> {
match self {
PdfObj::Name(n) => Some(n),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
PdfObj::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_str(&self) -> Option<&[u8]> {
match self {
PdfObj::Str(s) => Some(s),
_ => None,
}
}
pub fn as_array(&self) -> Option<&[PdfObj]> {
match self {
PdfObj::Array(a) => Some(a),
_ => None,
}
}
pub fn as_dict(&self) -> Option<&PdfDict> {
match self {
PdfObj::Dict(d) => Some(d),
PdfObj::Stream { dict, .. } => Some(dict),
_ => None,
}
}
pub fn as_ref(&self) -> Option<(u32, u16)> {
match self {
PdfObj::Ref(n, g) => Some((*n, *g)),
_ => None,
}
}
pub fn to_ref(&self) -> Option<(u32, u16)> {
self.as_ref()
}
}
impl fmt::Debug for PdfObj {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PdfObj::Null => write!(f, "null"),
PdfObj::Bool(b) => write!(f, "{b}"),
PdfObj::Int(n) => write!(f, "{n}"),
PdfObj::Real(r) => write!(f, "{r}"),
PdfObj::Name(n) => write!(f, "/{}", String::from_utf8_lossy(n)),
PdfObj::Str(s) => write!(f, "({:?})", String::from_utf8_lossy(s)),
PdfObj::Array(a) => f.debug_list().entries(a.iter()).finish(),
PdfObj::Dict(d) => write!(f, "{d:?}"),
PdfObj::Stream { dict, data_len, .. } => {
write!(f, "{dict:?} stream({data_len} bytes)")
}
PdfObj::Ref(n, g) => write!(f, "{n} {g} R"),
}
}
}
#[derive(Clone, PartialEq, Default)]
pub struct PdfDict(Vec<(Vec<u8>, PdfObj)>);
impl PdfDict {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn get(&self, key: &[u8]) -> Option<&PdfObj> {
self.0.iter().rev().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn get_name(&self, key: &[u8]) -> Option<&[u8]> {
self.get(key).and_then(|v| v.as_name())
}
pub fn get_int(&self, key: &[u8]) -> Option<i64> {
self.get(key).and_then(|v| v.as_int())
}
pub fn get_f64(&self, key: &[u8]) -> Option<f64> {
self.get(key).and_then(|v| v.as_f64())
}
pub fn get_bool(&self, key: &[u8]) -> Option<bool> {
self.get(key).and_then(|v| v.as_bool())
}
pub fn get_array(&self, key: &[u8]) -> Option<&[PdfObj]> {
self.get(key).and_then(|v| v.as_array())
}
pub fn get_dict(&self, key: &[u8]) -> Option<&PdfDict> {
self.get(key).and_then(|v| v.as_dict())
}
pub fn get_ref(&self, key: &[u8]) -> Option<(u32, u16)> {
self.get(key).and_then(|v| v.as_ref())
}
pub fn get_derefable(&self, key: &[u8]) -> Option<&PdfObj> {
self.get(key)
}
pub fn insert(&mut self, key: Vec<u8>, val: PdfObj) {
if self.0.len() < 100 {
if let Some(entry) = self.0.iter_mut().find(|(k, _)| k == &key) {
entry.1 = val;
return;
}
}
self.0.push((key, val));
}
pub fn entries(&self) -> &[(Vec<u8>, PdfObj)] {
&self.0
}
pub fn into_entries(self) -> Vec<(Vec<u8>, PdfObj)> {
self.0
}
pub fn from_entries(entries: Vec<(Vec<u8>, PdfObj)>) -> Self {
PdfDict(entries)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Debug for PdfDict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<<")?;
for (k, v) in &self.0 {
write!(f, " /{} {v:?}", String::from_utf8_lossy(k))?;
}
write!(f, " >>")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dict_get_and_insert() {
let mut d = PdfDict::new();
d.insert(b"Type".to_vec(), PdfObj::Name(b"Page".to_vec()));
d.insert(b"Count".to_vec(), PdfObj::Int(5));
assert_eq!(d.get_name(b"Type"), Some(b"Page".as_slice()));
assert_eq!(d.get_int(b"Count"), Some(5));
assert_eq!(d.get(b"Missing"), None);
assert_eq!(d.len(), 2);
}
#[test]
fn dict_insert_replaces() {
let mut d = PdfDict::new();
d.insert(b"Key".to_vec(), PdfObj::Int(1));
d.insert(b"Key".to_vec(), PdfObj::Int(2));
assert_eq!(d.get_int(b"Key"), Some(2));
assert_eq!(d.len(), 1);
}
#[test]
fn obj_as_f64_int_and_real() {
assert_eq!(PdfObj::Int(42).as_f64(), Some(42.0));
assert_eq!(PdfObj::Real(2.5).as_f64(), Some(2.5));
assert_eq!(PdfObj::Null.as_f64(), None);
}
#[test]
fn obj_as_dict_from_stream() {
let mut d = PdfDict::new();
d.insert(b"Length".to_vec(), PdfObj::Int(100));
let obj = PdfObj::Stream {
dict: d.clone(),
data_offset: 0,
data_len: 100,
};
assert!(obj.as_dict().is_some());
assert_eq!(obj.as_dict().unwrap().get_int(b"Length"), Some(100));
}
}