1use crate::error::PdfError;
25
26#[derive(Debug, Clone, PartialEq)]
28pub struct Token<'a> {
29 pub start: usize,
30 pub end: usize,
31 pub kind: TokenKind<'a>,
32}
33
34#[derive(Debug, Clone, PartialEq)]
36pub enum TokenKind<'a> {
37 Integer(i64),
40 Real(f64),
43 Name(Vec<u8>),
46 LiteralString(Vec<u8>),
51 HexString(Vec<u8>),
55 Keyword(&'a [u8]),
62 ArrayStart,
64 ArrayEnd,
66 DictStart,
68 DictEnd,
70}
71
72pub struct Lexer<'a> {
75 input: &'a [u8],
76 pos: usize,
77}
78
79impl<'a> Lexer<'a> {
80 pub fn new(input: &'a [u8]) -> Self {
81 Self { input, pos: 0 }
82 }
83
84 pub fn position(&self) -> usize {
86 self.pos
87 }
88
89 pub fn seek(&mut self, pos: usize) {
94 self.pos = pos.min(self.input.len());
95 }
96
97 pub fn peek_byte(&self, offset: usize) -> Option<u8> {
100 self.input.get(self.pos + offset).copied()
101 }
102
103 pub fn slice(&self, start: usize, end: usize) -> &'a [u8] {
107 let s = start.min(self.input.len());
108 let e = end.min(self.input.len()).max(s);
109 &self.input[s..e]
110 }
111
112 pub fn input(&self) -> &'a [u8] {
115 self.input
116 }
117
118 pub fn next_token(&mut self) -> Result<Option<Token<'a>>, PdfError> {
120 self.skip_whitespace_and_comments();
121 if self.pos >= self.input.len() {
122 return Ok(None);
123 }
124 let start = self.pos;
125 let b = self.input[self.pos];
126 let tok = match b {
127 b'[' => {
128 self.pos += 1;
129 Token {
130 start,
131 end: self.pos,
132 kind: TokenKind::ArrayStart,
133 }
134 }
135 b']' => {
136 self.pos += 1;
137 Token {
138 start,
139 end: self.pos,
140 kind: TokenKind::ArrayEnd,
141 }
142 }
143 b'<' => {
144 if self.peek_byte(1) == Some(b'<') {
145 self.pos += 2;
146 Token {
147 start,
148 end: self.pos,
149 kind: TokenKind::DictStart,
150 }
151 } else {
152 self.read_hex_string(start)?
153 }
154 }
155 b'>' => {
156 if self.peek_byte(1) == Some(b'>') {
157 self.pos += 2;
158 Token {
159 start,
160 end: self.pos,
161 kind: TokenKind::DictEnd,
162 }
163 } else {
164 return Err(PdfError::other(format!(
165 "PDF lexer: unexpected `>` at byte {start} (need `>>` for dict end)"
166 )));
167 }
168 }
169 b'(' => self.read_literal_string(start)?,
170 b'/' => self.read_name(start)?,
171 b'+' | b'-' | b'.' | b'0'..=b'9' => self.read_number(start)?,
172 _ => self.read_keyword(start)?,
173 };
174 Ok(Some(tok))
175 }
176
177 fn skip_whitespace_and_comments(&mut self) {
180 while self.pos < self.input.len() {
181 let b = self.input[self.pos];
182 if is_whitespace(b) {
183 self.pos += 1;
184 } else if b == b'%' {
185 while self.pos < self.input.len()
186 && self.input[self.pos] != b'\n'
187 && self.input[self.pos] != b'\r'
188 {
189 self.pos += 1;
190 }
191 } else {
192 break;
193 }
194 }
195 }
196
197 fn read_number(&mut self, start: usize) -> Result<Token<'a>, PdfError> {
198 let mut end = start;
199 if self.input.get(end).is_some_and(|&b| b == b'+' || b == b'-') {
201 end += 1;
202 }
203 let mut saw_dot = false;
204 let mut saw_digit = false;
205 while end < self.input.len() {
206 let b = self.input[end];
207 if b.is_ascii_digit() {
208 saw_digit = true;
209 end += 1;
210 } else if b == b'.' && !saw_dot {
211 saw_dot = true;
212 end += 1;
213 } else {
214 break;
215 }
216 }
217 if !saw_digit {
218 return self.read_keyword(start);
222 }
223 self.pos = end;
224 let text = std::str::from_utf8(&self.input[start..end])
225 .map_err(|_| PdfError::other(format!("PDF lexer: non-UTF-8 number at byte {start}")))?;
226 let kind = if saw_dot {
227 let f = text.parse::<f64>().map_err(|_| {
228 PdfError::other(format!("PDF lexer: invalid real `{text}` at byte {start}"))
229 })?;
230 TokenKind::Real(f)
231 } else {
232 let n = text.parse::<i64>().map_err(|_| {
233 PdfError::other(format!(
234 "PDF lexer: invalid integer `{text}` at byte {start}"
235 ))
236 })?;
237 TokenKind::Integer(n)
238 };
239 Ok(Token { start, end, kind })
240 }
241
242 fn read_name(&mut self, start: usize) -> Result<Token<'a>, PdfError> {
243 debug_assert_eq!(self.input[start], b'/');
244 let mut end = start + 1;
245 let mut decoded = Vec::with_capacity(16);
246 while end < self.input.len() {
247 let b = self.input[end];
248 if is_delimiter(b) || is_whitespace(b) {
249 break;
250 }
251 if b == b'#' {
252 let h1 = self.input.get(end + 1).copied().ok_or_else(|| {
256 PdfError::other(format!("PDF lexer: truncated #xx escape at byte {end}"))
257 })?;
258 let h2 = self.input.get(end + 2).copied().ok_or_else(|| {
259 PdfError::other(format!("PDF lexer: truncated #xx escape at byte {end}"))
260 })?;
261 let hi = hex_digit(h1).ok_or_else(|| {
262 PdfError::other(format!(
263 "PDF lexer: bad hex digit `{h1}` in name #xx at byte {end}"
264 ))
265 })?;
266 let lo = hex_digit(h2).ok_or_else(|| {
267 PdfError::other(format!(
268 "PDF lexer: bad hex digit `{h2}` in name #xx at byte {end}"
269 ))
270 })?;
271 decoded.push((hi << 4) | lo);
272 end += 3;
273 } else {
274 decoded.push(b);
275 end += 1;
276 }
277 }
278 self.pos = end;
279 Ok(Token {
280 start,
281 end,
282 kind: TokenKind::Name(decoded),
283 })
284 }
285
286 fn read_literal_string(&mut self, start: usize) -> Result<Token<'a>, PdfError> {
287 debug_assert_eq!(self.input[start], b'(');
288 let mut end = start + 1;
289 let mut depth = 1u32;
290 let mut decoded = Vec::with_capacity(32);
291 while end < self.input.len() {
292 let b = self.input[end];
293 if b == b'\\' {
294 end += 1;
295 if end >= self.input.len() {
296 break;
297 }
298 let esc = self.input[end];
299 match esc {
300 b'n' => {
301 decoded.push(b'\n');
302 end += 1;
303 }
304 b'r' => {
305 decoded.push(b'\r');
306 end += 1;
307 }
308 b't' => {
309 decoded.push(b'\t');
310 end += 1;
311 }
312 b'b' => {
313 decoded.push(0x08);
314 end += 1;
315 }
316 b'f' => {
317 decoded.push(0x0C);
318 end += 1;
319 }
320 b'\\' => {
321 decoded.push(b'\\');
322 end += 1;
323 }
324 b'(' => {
325 decoded.push(b'(');
326 end += 1;
327 }
328 b')' => {
329 decoded.push(b')');
330 end += 1;
331 }
332 b'\n' => {
333 end += 1;
335 }
336 b'\r' => {
337 end += 1;
338 if end < self.input.len() && self.input[end] == b'\n' {
339 end += 1;
340 }
341 }
342 b'0'..=b'7' => {
343 let mut v = (esc - b'0') as u16;
345 end += 1;
346 for _ in 0..2 {
347 if end < self.input.len() && (b'0'..=b'7').contains(&self.input[end]) {
348 v = v * 8 + (self.input[end] - b'0') as u16;
349 end += 1;
350 } else {
351 break;
352 }
353 }
354 decoded.push((v & 0xFF) as u8);
355 }
356 other => {
357 decoded.push(other);
361 end += 1;
362 }
363 }
364 continue;
365 }
366 if b == b'(' {
367 depth += 1;
368 decoded.push(b'(');
369 end += 1;
370 continue;
371 }
372 if b == b')' {
373 depth -= 1;
374 if depth == 0 {
375 end += 1;
376 self.pos = end;
377 return Ok(Token {
378 start,
379 end,
380 kind: TokenKind::LiteralString(decoded),
381 });
382 }
383 decoded.push(b')');
384 end += 1;
385 continue;
386 }
387 if b == b'\r' {
390 decoded.push(b'\n');
391 end += 1;
392 if end < self.input.len() && self.input[end] == b'\n' {
393 end += 1;
394 }
395 continue;
396 }
397 decoded.push(b);
398 end += 1;
399 }
400 Err(PdfError::other(format!(
401 "PDF lexer: unterminated literal string starting at byte {start}"
402 )))
403 }
404
405 fn read_hex_string(&mut self, start: usize) -> Result<Token<'a>, PdfError> {
406 debug_assert_eq!(self.input[start], b'<');
407 let mut end = start + 1;
408 let mut nibble: Option<u8> = None;
409 let mut decoded = Vec::with_capacity(16);
410 while end < self.input.len() {
411 let b = self.input[end];
412 if b == b'>' {
413 if let Some(hi) = nibble {
414 decoded.push(hi << 4);
416 }
417 end += 1;
418 self.pos = end;
419 return Ok(Token {
420 start,
421 end,
422 kind: TokenKind::HexString(decoded),
423 });
424 }
425 if is_whitespace(b) {
426 end += 1;
427 continue;
428 }
429 let h = hex_digit(b).ok_or_else(|| {
430 PdfError::other(format!(
431 "PDF lexer: bad hex digit `{b}` in hex string at byte {end}"
432 ))
433 })?;
434 match nibble {
435 None => nibble = Some(h),
436 Some(hi) => {
437 decoded.push((hi << 4) | h);
438 nibble = None;
439 }
440 }
441 end += 1;
442 }
443 Err(PdfError::other(format!(
444 "PDF lexer: unterminated hex string starting at byte {start}"
445 )))
446 }
447
448 fn read_keyword(&mut self, start: usize) -> Result<Token<'a>, PdfError> {
449 let mut end = start;
450 while end < self.input.len() {
451 let b = self.input[end];
452 if is_whitespace(b) || is_delimiter(b) {
453 break;
454 }
455 end += 1;
456 }
457 if end == start {
458 self.pos += 1;
461 return Err(PdfError::other(format!(
462 "PDF lexer: unrecognised byte `{}` at byte {start}",
463 self.input[start]
464 )));
465 }
466 self.pos = end;
467 Ok(Token {
468 start,
469 end,
470 kind: TokenKind::Keyword(&self.input[start..end]),
471 })
472 }
473}
474
475fn is_whitespace(b: u8) -> bool {
476 matches!(b, 0x00 | b'\t' | b'\n' | 0x0C | b'\r' | b' ')
478}
479
480fn is_delimiter(b: u8) -> bool {
481 matches!(
483 b,
484 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
485 )
486}
487
488fn hex_digit(b: u8) -> Option<u8> {
489 match b {
490 b'0'..=b'9' => Some(b - b'0'),
491 b'a'..=b'f' => Some(b - b'a' + 10),
492 b'A'..=b'F' => Some(b - b'A' + 10),
493 _ => None,
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500
501 fn tokenize(input: &[u8]) -> Vec<TokenKind<'_>> {
502 let mut lex = Lexer::new(input);
503 let mut out = Vec::new();
504 while let Some(t) = lex.next_token().unwrap() {
505 out.push(t.kind);
506 }
507 out
508 }
509
510 #[test]
511 fn integers_and_reals() {
512 assert_eq!(
513 tokenize(b"42 -7 0 +3"),
514 vec![
515 TokenKind::Integer(42),
516 TokenKind::Integer(-7),
517 TokenKind::Integer(0),
518 TokenKind::Integer(3),
519 ]
520 );
521 let toks = tokenize(b"0.5 -1.25 .75");
522 assert_eq!(toks.len(), 3);
523 match toks[0] {
524 TokenKind::Real(f) => assert!((f - 0.5).abs() < 1e-9),
525 _ => panic!("expected real"),
526 }
527 match toks[1] {
528 TokenKind::Real(f) => assert!((f + 1.25).abs() < 1e-9),
529 _ => panic!("expected real"),
530 }
531 match toks[2] {
532 TokenKind::Real(f) => assert!((f - 0.75).abs() < 1e-9),
533 _ => panic!("expected real"),
534 }
535 }
536
537 #[test]
538 fn names_decode_hex_escapes() {
539 let toks = tokenize(b"/Pages /a#20b /dc#3Arights");
540 assert_eq!(toks.len(), 3);
541 assert_eq!(toks[0], TokenKind::Name(b"Pages".to_vec()));
542 assert_eq!(toks[1], TokenKind::Name(b"a b".to_vec()));
543 assert_eq!(toks[2], TokenKind::Name(b"dc:rights".to_vec()));
544 }
545
546 #[test]
547 fn literal_string_with_escapes() {
548 let toks = tokenize(b"(hello\\nworld) (\\(c\\))");
549 assert_eq!(toks.len(), 2);
550 assert_eq!(toks[0], TokenKind::LiteralString(b"hello\nworld".to_vec()));
551 assert_eq!(toks[1], TokenKind::LiteralString(b"(c)".to_vec()));
552 }
553
554 #[test]
555 fn literal_string_balanced_parens() {
556 let toks = tokenize(b"(a (b (c) d) e)");
559 assert_eq!(toks.len(), 1);
560 assert_eq!(toks[0], TokenKind::LiteralString(b"a (b (c) d) e".to_vec()));
561 }
562
563 #[test]
564 fn literal_string_octal_escapes() {
565 let toks = tokenize(b"(\\101BC)");
567 assert_eq!(toks, vec![TokenKind::LiteralString(b"ABC".to_vec())]);
568 }
569
570 #[test]
571 fn hex_string_decodes_pairs() {
572 let toks = tokenize(b"<48656C6C6F>");
573 assert_eq!(toks, vec![TokenKind::HexString(b"Hello".to_vec())]);
574 }
575
576 #[test]
577 fn hex_string_odd_nibble_left_aligned() {
578 let toks = tokenize(b"<F>");
580 assert_eq!(toks, vec![TokenKind::HexString(vec![0xF0])]);
581 }
582
583 #[test]
584 fn hex_string_with_whitespace_between_digits() {
585 let toks = tokenize(b"<48 65 6C 6C 6F>");
586 assert_eq!(toks, vec![TokenKind::HexString(b"Hello".to_vec())]);
587 }
588
589 #[test]
590 fn dict_and_array_markers() {
591 let toks = tokenize(b"<< /Type /Page >> [1 2 3]");
592 assert_eq!(toks.len(), 9);
593 assert_eq!(toks[0], TokenKind::DictStart);
594 assert_eq!(toks[1], TokenKind::Name(b"Type".to_vec()));
595 assert_eq!(toks[2], TokenKind::Name(b"Page".to_vec()));
596 assert_eq!(toks[3], TokenKind::DictEnd);
597 assert_eq!(toks[4], TokenKind::ArrayStart);
598 assert_eq!(toks[5], TokenKind::Integer(1));
599 assert_eq!(toks[6], TokenKind::Integer(2));
600 assert_eq!(toks[7], TokenKind::Integer(3));
601 assert_eq!(toks[8], TokenKind::ArrayEnd);
602 }
603
604 #[test]
605 fn keywords_pass_through() {
606 let toks =
607 tokenize(b"obj endobj stream endstream R xref trailer startxref true false null");
608 assert_eq!(toks.len(), 11);
609 assert_eq!(toks[0], TokenKind::Keyword(b"obj"));
610 assert_eq!(toks[1], TokenKind::Keyword(b"endobj"));
611 assert_eq!(toks[2], TokenKind::Keyword(b"stream"));
612 assert_eq!(toks[3], TokenKind::Keyword(b"endstream"));
613 assert_eq!(toks[4], TokenKind::Keyword(b"R"));
614 assert_eq!(toks[5], TokenKind::Keyword(b"xref"));
615 assert_eq!(toks[6], TokenKind::Keyword(b"trailer"));
616 assert_eq!(toks[7], TokenKind::Keyword(b"startxref"));
617 assert_eq!(toks[8], TokenKind::Keyword(b"true"));
618 assert_eq!(toks[9], TokenKind::Keyword(b"false"));
619 assert_eq!(toks[10], TokenKind::Keyword(b"null"));
620 }
621
622 #[test]
623 fn comments_are_skipped_like_whitespace() {
624 let toks = tokenize(b"% header comment\n42 % trailing\n/Foo");
625 assert_eq!(
626 toks,
627 vec![TokenKind::Integer(42), TokenKind::Name(b"Foo".to_vec())]
628 );
629 }
630
631 #[test]
632 fn position_advances_after_each_token() {
633 let mut lex = Lexer::new(b"42 /foo");
634 let t1 = lex.next_token().unwrap().unwrap();
635 assert_eq!(t1.start, 0);
636 assert_eq!(t1.end, 2);
637 let t2 = lex.next_token().unwrap().unwrap();
638 assert_eq!(t2.start, 3);
639 assert_eq!(t2.end, 7);
640 assert!(lex.next_token().unwrap().is_none());
641 }
642
643 #[test]
644 fn seek_and_slice_helpers() {
645 let mut lex = Lexer::new(b"obj\nstream\nABCDEF\nendstream\nendobj\n");
646 let _ = lex.next_token().unwrap();
648 let stream_tok = lex.next_token().unwrap().unwrap();
650 assert!(matches!(stream_tok.kind, TokenKind::Keyword(b"stream")));
651 let data_start = stream_tok.end + 1; let data_end = data_start + 6; assert_eq!(lex.slice(data_start, data_end), b"ABCDEF");
656 lex.seek(data_end);
657 let t = lex.next_token().unwrap().unwrap();
659 assert_eq!(t.kind, TokenKind::Keyword(b"endstream"));
660 }
661
662 #[test]
663 fn unterminated_literal_string_is_error() {
664 let mut lex = Lexer::new(b"(hello");
665 assert!(lex.next_token().is_err());
666 }
667
668 #[test]
669 fn unterminated_hex_string_is_error() {
670 let mut lex = Lexer::new(b"<48656C");
671 assert!(lex.next_token().is_err());
672 }
673}