rust_pdf_box/cos/
lexer.rs1use crate::cos::object::StringFormat;
2use crate::error::{PdfError, PdfResult};
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum Token {
6 Keyword(String),
7 Integer(i64),
8 Real(f64),
9 String(Vec<u8>, StringFormat),
10 Name(String),
11 DictStart, DictEnd, ArrayStart, ArrayEnd, EOF,
16}
17
18pub struct Lexer<'a> {
19 data: &'a [u8],
20 pub pos: usize,
21}
22
23impl<'a> Lexer<'a> {
24 pub fn new(data: &'a [u8]) -> Self {
25 Self { data, pos: 0 }
26 }
27
28 pub fn with_pos(data: &'a [u8], pos: usize) -> Self {
29 Self { data, pos }
30 }
31
32 pub fn pos(&self) -> usize {
33 self.pos
34 }
35
36 pub fn set_pos(&mut self, pos: usize) {
37 self.pos = pos.min(self.data.len());
38 }
39
40 pub fn peek_byte(&self) -> Option<u8> {
41 self.data.get(self.pos).copied()
42 }
43
44 pub fn next_byte(&mut self) -> Option<u8> {
45 if self.pos < self.data.len() {
46 let b = self.data[self.pos];
47 self.pos += 1;
48 Some(b)
49 } else {
50 None
51 }
52 }
53
54 pub fn skip_whitespace_and_comments(&mut self) {
55 while self.pos < self.data.len() {
56 let b = self.data[self.pos];
57 if matches!(b, 0 | 9 | 10 | 12 | 13 | 32) {
58 self.pos += 1;
59 } else if b == b'%' {
60 self.pos += 1;
62 while self.pos < self.data.len() {
63 let c = self.data[self.pos];
64 self.pos += 1;
65 if c == b'\r' || c == b'\n' {
66 break;
67 }
68 }
69 } else {
70 break;
71 }
72 }
73 }
74
75 pub fn next_token(&mut self) -> PdfResult<Token> {
76 self.skip_whitespace_and_comments();
77 if self.pos >= self.data.len() {
78 return Ok(Token::EOF);
79 }
80
81 let start_pos = self.pos;
82 let b = self.data[self.pos];
83
84 match b {
85 b'<' => {
86 self.pos += 1;
87 if self.pos < self.data.len() && self.data[self.pos] == b'<' {
88 self.pos += 1;
89 Ok(Token::DictStart)
90 } else {
91 self.read_hex_string(start_pos)
92 }
93 }
94 b'>' => {
95 self.pos += 1;
96 if self.pos < self.data.len() && self.data[self.pos] == b'>' {
97 self.pos += 1;
98 Ok(Token::DictEnd)
99 } else {
100 Err(PdfError::Parse {
101 offset: start_pos,
102 message: "Unexpected single '>' character".to_string(),
103 })
104 }
105 }
106 b'[' => {
107 self.pos += 1;
108 Ok(Token::ArrayStart)
109 }
110 b']' => {
111 self.pos += 1;
112 Ok(Token::ArrayEnd)
113 }
114 b'(' => self.read_literal_string(start_pos),
115 b'/' => self.read_name(),
116 _ => self.read_number_or_keyword(start_pos),
117 }
118 }
119
120 fn read_name(&mut self) -> PdfResult<Token> {
121 self.pos += 1; let mut name_bytes = Vec::new();
123
124 while self.pos < self.data.len() {
125 let b = self.data[self.pos];
126 if matches!(
127 b,
128 0 | 9
129 | 10
130 | 12
131 | 13
132 | 32
133 | b'('
134 | b')'
135 | b'<'
136 | b'>'
137 | b'['
138 | b']'
139 | b'{'
140 | b'}'
141 | b'/'
142 | b'%'
143 ) {
144 break;
145 }
146 if b == b'#' {
147 self.pos += 1;
148 if self.pos + 1 < self.data.len() {
149 let hex_chars = std::str::from_utf8(&self.data[self.pos..self.pos + 2])
150 .map_err(|_| PdfError::Parse {
151 offset: self.pos,
152 message: "Invalid hex sequence in PDF Name".to_string(),
153 })?;
154 let val = u8::from_str_radix(hex_chars, 16).map_err(|_| PdfError::Parse {
155 offset: self.pos,
156 message: format!("Invalid hex digits in PDF Name: #{hex_chars}"),
157 })?;
158 name_bytes.push(val);
159 self.pos += 2;
160 } else {
161 name_bytes.push(b'#');
162 }
163 } else {
164 name_bytes.push(b);
165 self.pos += 1;
166 }
167 }
168
169 let name_str = String::from_utf8_lossy(&name_bytes).into_owned();
170 Ok(Token::Name(name_str))
171 }
172
173 fn read_literal_string(&mut self, start_pos: usize) -> PdfResult<Token> {
174 self.pos += 1; let mut depth = 1;
176 let mut result = Vec::new();
177
178 while self.pos < self.data.len() {
179 let b = self.data[self.pos];
180 self.pos += 1;
181
182 if b == b'\\' {
183 if self.pos >= self.data.len() {
184 break;
185 }
186 let esc = self.data[self.pos];
187 self.pos += 1;
188 match esc {
189 b'n' => result.push(b'\n'),
190 b'r' => result.push(b'\r'),
191 b't' => result.push(b'\t'),
192 b'b' => result.push(0x08),
193 b'f' => result.push(0x0C),
194 b'(' => result.push(b'('),
195 b')' => result.push(b')'),
196 b'\\' => result.push(b'\\'),
197 b'\r' => {
198 if self.pos < self.data.len() && self.data[self.pos] == b'\n' {
200 self.pos += 1;
201 }
202 }
203 b'\n' => {} b'0'..=b'7' => {
205 let mut oct_val = (esc - b'0') as u32;
207 for _ in 0..2 {
208 if self.pos < self.data.len()
209 && (b'0'..=b'7').contains(&self.data[self.pos])
210 {
211 oct_val = oct_val * 8 + (self.data[self.pos] - b'0') as u32;
212 self.pos += 1;
213 } else {
214 break;
215 }
216 }
217 result.push((oct_val & 0xFF) as u8);
218 }
219 other => result.push(other),
220 }
221 } else if b == b'(' {
222 depth += 1;
223 result.push(b);
224 } else if b == b')' {
225 depth -= 1;
226 if depth == 0 {
227 return Ok(Token::String(result, StringFormat::Literal));
228 } else {
229 result.push(b);
230 }
231 } else {
232 result.push(b);
233 }
234 }
235
236 Err(PdfError::Parse {
237 offset: start_pos,
238 message: "Unterminated literal string".to_string(),
239 })
240 }
241
242 fn read_hex_string(&mut self, start_pos: usize) -> PdfResult<Token> {
243 let mut hex_digits = Vec::new();
244
245 while self.pos < self.data.len() {
246 let b = self.data[self.pos];
247 self.pos += 1;
248
249 if b == b'>' {
250 break;
251 } else if matches!(b, 0 | 9 | 10 | 12 | 13 | 32) {
252 continue;
253 } else if (b'0'..=b'9').contains(&b)
254 || (b'a'..=b'f').contains(&b)
255 || (b'A'..=b'F').contains(&b)
256 {
257 hex_digits.push(b);
258 } else {
259 return Err(PdfError::Parse {
260 offset: self.pos - 1,
261 message: format!("Invalid character in hex string: {:02X}", b),
262 });
263 }
264 }
265
266 if hex_digits.len() % 2 != 0 {
267 hex_digits.push(b'0'); }
269
270 let mut bytes = Vec::with_capacity(hex_digits.len() / 2);
271 for chunk in hex_digits.chunks_exact(2) {
272 let s = std::str::from_utf8(chunk).unwrap();
273 let byte = u8::from_str_radix(s, 16).map_err(|_| PdfError::Parse {
274 offset: start_pos,
275 message: "Failed parsing hex byte".to_string(),
276 })?;
277 bytes.push(byte);
278 }
279
280 Ok(Token::String(bytes, StringFormat::Hex))
281 }
282
283 fn read_number_or_keyword(&mut self, _start_pos: usize) -> PdfResult<Token> {
284 let mut token_bytes = Vec::new();
285
286 while self.pos < self.data.len() {
287 let b = self.data[self.pos];
288 if matches!(
289 b,
290 0 | 9
291 | 10
292 | 12
293 | 13
294 | 32
295 | b'('
296 | b')'
297 | b'<'
298 | b'>'
299 | b'['
300 | b']'
301 | b'{'
302 | b'}'
303 | b'/'
304 | b'%'
305 ) {
306 break;
307 }
308 token_bytes.push(b);
309 self.pos += 1;
310 }
311
312 let s = String::from_utf8_lossy(&token_bytes).into_owned();
313
314 if let Ok(i) = s.parse::<i64>() {
316 return Ok(Token::Integer(i));
317 }
318
319 if let Ok(f) = s.parse::<f64>() {
321 if s.contains('.') {
322 return Ok(Token::Real(f));
323 }
324 }
325
326 Ok(Token::Keyword(s))
327 }
328
329 pub fn read_stream_data(&mut self, length: Option<usize>) -> PdfResult<Vec<u8>> {
331 if self.pos < self.data.len() && self.data[self.pos] == b'\r' {
333 self.pos += 1;
334 }
335 if self.pos < self.data.len() && self.data[self.pos] == b'\n' {
336 self.pos += 1;
337 }
338
339 if let Some(len) = length {
340 if self.pos + len <= self.data.len() {
341 let stream_bytes = self.data[self.pos..self.pos + len].to_vec();
342 self.pos += len;
343 return Ok(stream_bytes);
344 }
345 }
346
347 let start = self.pos;
349 let pattern = b"endstream";
350 if let Some(idx) = self.data[start..]
351 .windows(pattern.len())
352 .position(|w| w == pattern)
353 {
354 let mut end = start + idx;
355 if end > start && self.data[end - 1] == b'\n' {
357 end -= 1;
358 }
359 if end > start && self.data[end - 1] == b'\r' {
360 end -= 1;
361 }
362 let stream_bytes = self.data[start..end].to_vec();
363 self.pos = start + idx;
364 Ok(stream_bytes)
365 } else {
366 Err(PdfError::Parse {
367 offset: start,
368 message: "Missing endstream keyword".to_string(),
369 })
370 }
371 }
372}