use super::document::PdfDocument;
use super::lexer::{Lexer, Token};
use super::object::{Dict, Object, PdfStream};
use crate::{FormatError, Result};
pub(super) fn parse_object(lex: &mut Lexer, doc: Option<&PdfDocument>) -> Result<Object> {
let tok = lex
.next_token()?
.ok_or_else(|| FormatError::Invalid("pdf: unexpected EOF parsing object".into()))?;
object_from_token(tok, lex, doc)
}
pub(super) fn object_from_token(
tok: Token,
lex: &mut Lexer,
doc: Option<&PdfDocument>,
) -> Result<Object> {
match tok {
Token::Null => Ok(Object::Null),
Token::Bool(b) => Ok(Object::Bool(b)),
Token::Real(r) => Ok(Object::Real(r)),
Token::Str(s) => Ok(Object::Str(s)),
Token::Name(n) => Ok(Object::Name(n)),
Token::Int(n) => maybe_reference(n, lex),
Token::ArrayOpen => {
let mut items = Vec::new();
loop {
let t = lex
.next_token()?
.ok_or_else(|| FormatError::Invalid("pdf: unexpected EOF in array".into()))?;
if t == Token::ArrayClose {
break;
}
items.push(object_from_token(t, lex, doc)?);
}
Ok(Object::Array(items))
}
Token::DictOpen => parse_dict_or_stream(lex, doc),
Token::Keyword(k) => Ok(Object::Keyword(k)),
Token::ArrayClose => Err(FormatError::Invalid("pdf: unexpected ']'".into())),
Token::DictClose => Err(FormatError::Invalid("pdf: unexpected '>>'".into())),
}
}
fn maybe_reference(n: i64, lex: &mut Lexer) -> Result<Object> {
let save = lex.pos;
if let Ok(Some(Token::Int(g))) = lex.next_token() {
if let Ok(Some(Token::Keyword(ref k))) = lex.next_token() {
if k == b"R" && n >= 0 && (0..=u16::MAX as i64).contains(&g) {
return Ok(Object::Ref(n as u32, g as u16));
}
}
}
lex.pos = save;
Ok(Object::Int(n))
}
fn parse_dict_or_stream(lex: &mut Lexer, doc: Option<&PdfDocument>) -> Result<Object> {
let mut dict = Dict::new();
loop {
let t = lex
.next_token()?
.ok_or_else(|| FormatError::Invalid("pdf: unexpected EOF in dict".into()))?;
match t {
Token::DictClose => break,
Token::Name(key) => {
let value = parse_object(lex, doc)?;
dict.insert(key, value);
}
other => {
return Err(FormatError::Invalid(format!(
"pdf: dict key must be a name, got {:?}",
other
)));
}
}
}
let save = lex.pos;
match lex.next_token()? {
Some(Token::Keyword(ref k)) if k == b"stream" => parse_stream_body(dict, lex, doc),
_ => {
lex.pos = save;
Ok(Object::Dict(dict))
}
}
}
fn parse_stream_body(mut dict: Dict, lex: &mut Lexer, doc: Option<&PdfDocument>) -> Result<Object> {
for key in ["Filter", "DecodeParms", "DP"] {
if let Some(val) = dict.get(key) {
if references(val) {
let resolved = resolve_shallow(val.clone(), doc);
dict.insert(key.to_string(), resolved);
}
}
}
if lex.peek() == Some(b'\r') {
lex.pos += 1;
}
if lex.peek() == Some(b'\n') {
lex.pos += 1;
}
let start = lex.pos;
let length = match dict.get("Length") {
Some(Object::Int(n)) if *n >= 0 => Some(*n as usize),
Some(Object::Ref(objid, _)) => match doc {
Some(d) => d.get_object(*objid).ok().and_then(|o| match o.as_ref() {
Object::Int(n) if *n >= 0 => Some(*n as usize),
_ => None,
}),
None => None,
},
_ => None,
};
let end = match length {
Some(len) if start + len <= lex.data.len() && endstream_follows(lex.data, start + len) => {
start + len
}
_ => find_endstream(lex.data, start)
.ok_or_else(|| FormatError::Invalid("pdf: 'endstream' not found".into()))?,
};
let rawdata = lex.data[start..end].to_vec();
lex.pos = end;
lex.skip_ws();
if lex.data[lex.pos..].starts_with(b"endstream") {
lex.pos += b"endstream".len();
}
Ok(Object::Stream(PdfStream {
dict,
rawdata,
objid: 0,
genno: 0,
}))
}
fn endstream_follows(data: &[u8], pos: usize) -> bool {
let mut p = pos;
if data.get(p) == Some(&b'\r') {
p += 1;
}
if data.get(p) == Some(&b'\n') {
p += 1;
}
data[p..].starts_with(b"endstream")
}
fn find_endstream(data: &[u8], start: usize) -> Option<usize> {
let needle = b"endstream";
let mut i = start;
while i + needle.len() <= data.len() {
if data[i..].starts_with(needle) {
let mut end = i;
if end > start && data[end - 1] == b'\n' {
end -= 1;
}
if end > start && data[end - 1] == b'\r' {
end -= 1;
}
return Some(end);
}
i += 1;
}
None
}
fn references(obj: &Object) -> bool {
match obj {
Object::Ref(..) => true,
Object::Array(a) => a.iter().any(|x| matches!(x, Object::Ref(..))),
_ => false,
}
}
fn resolve_shallow(obj: Object, doc: Option<&PdfDocument>) -> Object {
let doc = match doc {
Some(d) => d,
None => return obj,
};
let deref = |objid| {
doc.get_object(objid)
.map(|o| (*o).clone())
.unwrap_or(Object::Null)
};
match obj {
Object::Ref(objid, _) => deref(objid),
Object::Array(a) => Object::Array(
a.into_iter()
.map(|x| match x {
Object::Ref(objid, _) => deref(objid),
other => other,
})
.collect(),
),
other => other,
}
}
pub(super) fn expect_int(lex: &mut Lexer) -> Result<i64> {
match lex.next_token()? {
Some(Token::Int(n)) => Ok(n),
other => Err(FormatError::Invalid(format!(
"pdf: expected integer, got {:?}",
other
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_indirect_reference() {
let mut lex = Lexer::new(b"[1 0 R 42 2 0 R]");
let obj = parse_object(&mut lex, None).unwrap();
assert_eq!(
obj,
Object::Array(vec![Object::Ref(1, 0), Object::Int(42), Object::Ref(2, 0),])
);
}
#[test]
fn parse_nested_dict() {
let mut lex = Lexer::new(b"<< /A 1 /B << /C [true (x)] >> >>");
let obj = parse_object(&mut lex, None).unwrap();
let d = obj.as_dict().unwrap();
assert_eq!(d.get("A"), Some(&Object::Int(1)));
let inner = d.get("B").unwrap().as_dict().unwrap();
assert_eq!(
inner.get("C"),
Some(&Object::Array(vec![
Object::Bool(true),
Object::Str(b"x".to_vec())
]))
);
}
}