use crate::error::{Error, Result};
use crate::lexer::{Lexer, Token};
use crate::object::Object;
use crate::parser::{NoResolve, Parser};
pub struct ObjStm {
data: Vec<u8>,
first: usize,
offsets: Vec<usize>,
}
impl ObjStm {
pub fn parse(data: Vec<u8>, n: usize, first: usize) -> Result<ObjStm> {
let offsets = {
let mut lexer = Lexer::new(&data);
let mut offsets = Vec::with_capacity(n);
for _ in 0..n {
expect_int(&mut lexer)?; offsets.push(expect_int(&mut lexer)?);
}
offsets
};
Ok(ObjStm {
data,
first,
offsets,
})
}
pub fn object(&self, index: u32) -> Result<Object> {
self.object_spanned(index).map(|pair| pair.0)
}
pub fn object_spanned(&self, index: u32) -> Result<(Object, (usize, usize))> {
let offset = *self.offsets.get(index as usize).ok_or_else(|| {
Error::Other(format!(
"object stream index {index} out of range (N = {})",
self.offsets.len()
))
})?;
let pos = self
.first
.checked_add(offset)
.filter(|&p| p <= self.data.len())
.ok_or_else(|| {
Error::Other(format!(
"object stream offset {offset} lies outside the stream"
))
})?;
let mut parser = Parser::at(&self.data, pos);
let object = parser.parse_object(&NoResolve)?;
Ok((object, (pos, parser.pos())))
}
}
pub fn extract(stream_data: &[u8], n: usize, first: usize, index: u32) -> Result<Object> {
if index as usize >= n {
return Err(Error::Other(format!(
"object stream index {index} out of range (N = {n})"
)));
}
ObjStm::parse(stream_data.to_vec(), n, first)?.object(index)
}
fn expect_int(lexer: &mut Lexer) -> Result<usize> {
match lexer.next_token()? {
Token::Int(v) if v >= 0 => Ok(v as usize),
_ => Err(Error::Syntax {
offset: lexer.pos(),
msg: "malformed object stream header".to_string(),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_stream(objects: &[(u32, &str)]) -> (Vec<u8>, usize, usize) {
let mut header = String::new();
let mut bodies = String::new();
for (num, body) in objects {
header.push_str(&format!("{} {} ", num, bodies.len()));
bodies.push_str(body);
bodies.push(' ');
}
let first = header.len();
header.push_str(&bodies);
(header.into_bytes(), objects.len(), first)
}
#[test]
fn extracts_both_objects() {
let (data, n, first) = build_stream(&[(11, "<< /A 1 >>"), (12, "(hi)")]);
let obj0 = extract(&data, n, first, 0).unwrap();
assert_eq!(obj0.as_dict().unwrap().get_int("A"), Some(1));
let obj1 = extract(&data, n, first, 1).unwrap();
assert_eq!(obj1, Object::String(b"hi".to_vec()));
}
#[test]
fn extracts_from_testkit_payload() {
let (_, payload) = pdfboss_testkit::objstm_payload(&[(3, "[1 2 3]"), (9, "/Name")]);
let n = 2;
let first = payload.iter().position(|&b| b == b'\n').unwrap() + 1;
let array = extract(&payload, n, first, 0).unwrap();
assert_eq!(array.as_array().unwrap().len(), 3);
let name = extract(&payload, n, first, 1).unwrap();
assert_eq!(name.as_name().map(|nm| nm.0.as_str()), Some("Name"));
}
#[test]
fn index_out_of_range_errors() {
let (data, n, first) = build_stream(&[(1, "true"), (2, "null")]);
assert!(extract(&data, n, first, 2).is_err());
assert!(extract(&data, n, first, u32::MAX).is_err());
}
#[test]
fn malformed_header_errors() {
assert!(extract(b"/NotAnInt 0 true", 1, 12, 0).is_err());
assert!(extract(b"5 -3 true", 1, 5, 0).is_err());
}
#[test]
fn offset_beyond_data_errors() {
assert!(extract(b"1 999\ntrue", 1, 6, 0).is_err());
}
#[test]
fn object_spanned_reports_reparseable_range() {
let (data, n, first) = build_stream(&[(11, "<< /A 1 >>"), (12, "(hi)")]);
let stm = ObjStm::parse(data.clone(), n, first).unwrap();
for index in 0..2u32 {
let (object, (start, end)) = stm.object_spanned(index).unwrap();
assert!(start >= first && end <= data.len() && start < end);
let reparsed = Parser::at(&data, start).parse_object(&NoResolve).unwrap();
assert_eq!(reparsed, object);
assert_eq!(stm.object(index).unwrap(), object);
}
}
}