1use crate::error::{Error, Result};
5use crate::lexer::{Lexer, Token};
6use crate::object::Object;
7use crate::parser::{NoResolve, Parser};
8
9pub struct ObjStm {
16 data: Vec<u8>,
17 first: usize,
18 offsets: Vec<usize>,
20}
21
22impl ObjStm {
23 pub fn parse(data: Vec<u8>, n: usize, first: usize) -> Result<ObjStm> {
26 let offsets = {
27 let mut lexer = Lexer::new(&data);
28 let mut offsets = Vec::with_capacity(n);
29 for _ in 0..n {
30 expect_int(&mut lexer)?; offsets.push(expect_int(&mut lexer)?);
32 }
33 offsets
34 };
35 Ok(ObjStm {
36 data,
37 first,
38 offsets,
39 })
40 }
41
42 pub fn object(&self, index: u32) -> Result<Object> {
44 self.object_spanned(index).map(|pair| pair.0)
45 }
46
47 pub fn object_spanned(&self, index: u32) -> Result<(Object, (usize, usize))> {
50 let offset = *self.offsets.get(index as usize).ok_or_else(|| {
51 Error::Other(format!(
52 "object stream index {index} out of range (N = {})",
53 self.offsets.len()
54 ))
55 })?;
56 let pos = self
57 .first
58 .checked_add(offset)
59 .filter(|&p| p <= self.data.len())
60 .ok_or_else(|| {
61 Error::Other(format!(
62 "object stream offset {offset} lies outside the stream"
63 ))
64 })?;
65 let mut parser = Parser::at(&self.data, pos);
66 let object = parser.parse_object(&NoResolve)?;
67 Ok((object, (pos, parser.pos())))
68 }
69}
70
71pub fn extract(stream_data: &[u8], n: usize, first: usize, index: u32) -> Result<Object> {
75 if index as usize >= n {
76 return Err(Error::Other(format!(
77 "object stream index {index} out of range (N = {n})"
78 )));
79 }
80 ObjStm::parse(stream_data.to_vec(), n, first)?.object(index)
81}
82
83fn expect_int(lexer: &mut Lexer) -> Result<usize> {
85 match lexer.next_token()? {
86 Token::Int(v) if v >= 0 => Ok(v as usize),
87 _ => Err(Error::Syntax {
88 offset: lexer.pos(),
89 msg: "malformed object stream header".to_string(),
90 }),
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 fn build_stream(objects: &[(u32, &str)]) -> (Vec<u8>, usize, usize) {
101 let mut header = String::new();
102 let mut bodies = String::new();
103 for (num, body) in objects {
104 header.push_str(&format!("{} {} ", num, bodies.len()));
105 bodies.push_str(body);
106 bodies.push(' ');
107 }
108 let first = header.len();
109 header.push_str(&bodies);
110 (header.into_bytes(), objects.len(), first)
111 }
112
113 #[test]
114 fn extracts_both_objects() {
115 let (data, n, first) = build_stream(&[(11, "<< /A 1 >>"), (12, "(hi)")]);
116 let obj0 = extract(&data, n, first, 0).unwrap();
117 assert_eq!(obj0.as_dict().unwrap().get_int("A"), Some(1));
118 let obj1 = extract(&data, n, first, 1).unwrap();
119 assert_eq!(obj1, Object::String(b"hi".to_vec()));
120 }
121
122 #[test]
123 fn extracts_from_testkit_payload() {
124 let (_, payload) = pdfboss_testkit::objstm_payload(&[(3, "[1 2 3]"), (9, "/Name")]);
125 let n = 2;
127 let first = payload.iter().position(|&b| b == b'\n').unwrap() + 1;
128 let array = extract(&payload, n, first, 0).unwrap();
129 assert_eq!(array.as_array().unwrap().len(), 3);
130 let name = extract(&payload, n, first, 1).unwrap();
131 assert_eq!(name.as_name().map(|nm| nm.0.as_str()), Some("Name"));
132 }
133
134 #[test]
135 fn index_out_of_range_errors() {
136 let (data, n, first) = build_stream(&[(1, "true"), (2, "null")]);
137 assert!(extract(&data, n, first, 2).is_err());
138 assert!(extract(&data, n, first, u32::MAX).is_err());
139 }
140
141 #[test]
142 fn malformed_header_errors() {
143 assert!(extract(b"/NotAnInt 0 true", 1, 12, 0).is_err());
144 assert!(extract(b"5 -3 true", 1, 5, 0).is_err());
145 }
146
147 #[test]
148 fn offset_beyond_data_errors() {
149 assert!(extract(b"1 999\ntrue", 1, 6, 0).is_err());
150 }
151
152 #[test]
153 fn object_spanned_reports_reparseable_range() {
154 let (data, n, first) = build_stream(&[(11, "<< /A 1 >>"), (12, "(hi)")]);
155 let stm = ObjStm::parse(data.clone(), n, first).unwrap();
156 for index in 0..2u32 {
157 let (object, (start, end)) = stm.object_spanned(index).unwrap();
158 assert!(start >= first && end <= data.len() && start < end);
159 let reparsed = Parser::at(&data, start).parse_object(&NoResolve).unwrap();
160 assert_eq!(reparsed, object);
161 assert_eq!(stm.object(index).unwrap(), object);
162 }
163 }
164}