Skip to main content

otf_pdf_syntax/
lexer.rs

1//! Tokenizer for PDF syntax (ISO 32000-2 §7.2), also used for content
2//! streams.
3//!
4//! Lenient where real files are: doubled signs, `.5` and `5.` numbers, odd
5//! hex digit counts, junk inside hex strings, overflowing octal escapes and
6//! invalid `#` escapes in names are all accepted. Running out of input inside
7//! a token is [`Error::UnexpectedEof`], so a caller reading a window of a
8//! larger file can retry with more bytes.
9
10use crate::error::{Error, Result};
11use crate::object::{Name, PdfString};
12
13/// One token.
14#[derive(Debug, Clone, PartialEq)]
15pub enum Token<'a> {
16    /// An integer.
17    Int(i64),
18    /// A real number.
19    Real(f64),
20    /// A literal or hex string.
21    String(PdfString),
22    /// A name, `/` stripped and escapes resolved.
23    Name(Name),
24    /// `[`
25    ArrayStart,
26    /// `]`
27    ArrayEnd,
28    /// `<<`
29    DictStart,
30    /// `>>`
31    DictEnd,
32    /// `{`
33    BraceOpen,
34    /// `}`
35    BraceClose,
36    /// Any other run of regular characters: `obj`, `R`, `true`, operators.
37    Keyword(&'a [u8]),
38    /// End of input.
39    Eof,
40}
41
42/// Whether `b` is PDF whitespace.
43pub fn is_whitespace(b: u8) -> bool {
44    matches!(b, b'\0' | b'\t' | b'\n' | b'\x0c' | b'\r' | b' ')
45}
46
47/// Whether `b` is a PDF delimiter.
48pub fn is_delimiter(b: u8) -> bool {
49    matches!(
50        b,
51        b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
52    )
53}
54
55/// Whether `b` is a regular character (neither whitespace nor delimiter).
56pub fn is_regular(b: u8) -> bool {
57    !is_whitespace(b) && !is_delimiter(b)
58}
59
60fn hex_value(b: u8) -> Option<u8> {
61    match b {
62        b'0'..=b'9' => Some(b - b'0'),
63        b'a'..=b'f' => Some(b - b'a' + 10),
64        b'A'..=b'F' => Some(b - b'A' + 10),
65        _ => None,
66    }
67}
68
69/// A tokenizer over a byte buffer.
70#[derive(Debug, Clone)]
71pub struct Lexer<'a> {
72    buf: &'a [u8],
73    pos: usize,
74}
75
76impl<'a> Lexer<'a> {
77    /// A lexer at the start of `buf`.
78    pub fn new(buf: &'a [u8]) -> Self {
79        Self { buf, pos: 0 }
80    }
81
82    /// The whole buffer.
83    pub fn buffer(&self) -> &'a [u8] {
84        self.buf
85    }
86
87    /// Current position in the buffer.
88    pub fn pos(&self) -> usize {
89        self.pos
90    }
91
92    /// Moves to `pos` (clamped to the buffer's end).
93    pub fn set_pos(&mut self, pos: usize) {
94        self.pos = pos.min(self.buf.len());
95    }
96
97    /// Whether the end of the buffer is reached.
98    pub fn at_end(&self) -> bool {
99        self.pos >= self.buf.len()
100    }
101
102    /// The bytes from the current position on.
103    pub fn rest(&self) -> &'a [u8] {
104        self.buf.get(self.pos..).unwrap_or_default()
105    }
106
107    /// The next byte, without consuming it.
108    pub fn peek(&self) -> Option<u8> {
109        self.buf.get(self.pos).copied()
110    }
111
112    fn bump(&mut self) -> Option<u8> {
113        let b = self.peek()?;
114        self.pos += 1;
115        Some(b)
116    }
117
118    /// Skips whitespace and comments.
119    pub fn skip_whitespace(&mut self) {
120        while let Some(b) = self.peek() {
121            if is_whitespace(b) {
122                self.pos += 1;
123            } else if b == b'%' {
124                while let Some(c) = self.peek() {
125                    if c == b'\r' || c == b'\n' {
126                        break;
127                    }
128                    self.pos += 1;
129                }
130            } else {
131                break;
132            }
133        }
134    }
135
136    /// Reads the next token.
137    pub fn next_token(&mut self) -> Result<Token<'a>> {
138        self.skip_whitespace();
139        let Some(b) = self.peek() else {
140            return Ok(Token::Eof);
141        };
142        match b {
143            b'[' => {
144                self.pos += 1;
145                Ok(Token::ArrayStart)
146            }
147            b']' => {
148                self.pos += 1;
149                Ok(Token::ArrayEnd)
150            }
151            b'{' => {
152                self.pos += 1;
153                Ok(Token::BraceOpen)
154            }
155            b'}' => {
156                self.pos += 1;
157                Ok(Token::BraceClose)
158            }
159            b'<' => {
160                if self.buf.get(self.pos + 1) == Some(&b'<') {
161                    self.pos += 2;
162                    Ok(Token::DictStart)
163                } else {
164                    self.pos += 1;
165                    self.hex_string()
166                }
167            }
168            b'>' => {
169                self.pos += 1;
170                if self.peek() == Some(b'>') {
171                    self.pos += 1;
172                    Ok(Token::DictEnd)
173                } else {
174                    // A stray `>`: report it as a keyword so the parser can
175                    // skip it.
176                    Ok(Token::Keyword(b">"))
177                }
178            }
179            b'(' => {
180                self.pos += 1;
181                self.literal_string()
182            }
183            b')' => {
184                self.pos += 1;
185                Ok(Token::Keyword(b")"))
186            }
187            b'/' => {
188                self.pos += 1;
189                Ok(self.name())
190            }
191            b'0'..=b'9' | b'+' | b'-' | b'.' => Ok(self.number()),
192            _ => {
193                let start = self.pos;
194                while self.peek().is_some_and(is_regular) {
195                    self.pos += 1;
196                }
197                Ok(Token::Keyword(
198                    self.buf.get(start..self.pos).unwrap_or_default(),
199                ))
200            }
201        }
202    }
203
204    fn number(&mut self) -> Token<'a> {
205        let mut negative = false;
206        // Lenient: `--5` and `+-5` occur in the wild; readers treat any `-`
207        // among the leading signs as negative.
208        while let Some(b @ (b'+' | b'-')) = self.peek() {
209            negative |= b == b'-';
210            self.pos += 1;
211        }
212        let int_start = self.pos;
213        while self.peek().is_some_and(|b| b.is_ascii_digit()) {
214            self.pos += 1;
215        }
216        let int_digits = self.buf.get(int_start..self.pos).unwrap_or_default();
217        let mut frac_digits: &[u8] = &[];
218        let is_real = self.peek() == Some(b'.');
219        if is_real {
220            self.pos += 1;
221            let frac_start = self.pos;
222            while self.peek().is_some_and(|b| b.is_ascii_digit()) {
223                self.pos += 1;
224            }
225            frac_digits = self.buf.get(frac_start..self.pos).unwrap_or_default();
226            // `1.2.3` and `1.-2`: readers take the leading number and drop the
227            // rest of the regular run.
228            while self
229                .peek()
230                .is_some_and(|b| b == b'.' || b == b'-' || b.is_ascii_digit())
231            {
232                self.pos += 1;
233            }
234        }
235        if !is_real {
236            let mut value: i64 = 0;
237            let mut overflow = false;
238            for &d in int_digits {
239                match value
240                    .checked_mul(10)
241                    .and_then(|v| v.checked_add(i64::from(d - b'0')))
242                {
243                    Some(v) => value = v,
244                    None => {
245                        overflow = true;
246                        break;
247                    }
248                }
249            }
250            if !overflow {
251                return Token::Int(if negative { -value } else { value });
252            }
253        }
254        // The digits are ASCII, so this always parses; `str::parse` rounds
255        // correctly where summing digit by digit would not.
256        let mut text = String::with_capacity(int_digits.len() + frac_digits.len() + 2);
257        text.push('0');
258        text.extend(int_digits.iter().map(|&d| char::from(d)));
259        text.push('.');
260        text.extend(frac_digits.iter().map(|&d| char::from(d)));
261        let value: f64 = text.parse().unwrap_or(0.0);
262        Token::Real(if negative { -value } else { value })
263    }
264
265    fn name(&mut self) -> Token<'a> {
266        let mut out = Vec::new();
267        while let Some(b) = self.peek() {
268            if !is_regular(b) {
269                break;
270            }
271            self.pos += 1;
272            if b == b'#' {
273                let hi = self.peek().and_then(hex_value);
274                let lo = self.buf.get(self.pos + 1).copied().and_then(hex_value);
275                if let (Some(hi), Some(lo)) = (hi, lo) {
276                    out.push(hi << 4 | lo);
277                    self.pos += 2;
278                    continue;
279                }
280            }
281            out.push(b);
282        }
283        Token::Name(Name(out))
284    }
285
286    fn hex_string(&mut self) -> Result<Token<'a>> {
287        let mut out = Vec::new();
288        let mut high: Option<u8> = None;
289        loop {
290            let Some(b) = self.bump() else {
291                return Err(Error::UnexpectedEof);
292            };
293            if b == b'>' {
294                break;
295            }
296            // Whitespace and, leniently, any other junk is skipped.
297            let Some(v) = hex_value(b) else { continue };
298            match high.take() {
299                Some(h) => out.push(h << 4 | v),
300                None => high = Some(v),
301            }
302        }
303        if let Some(h) = high {
304            out.push(h << 4);
305        }
306        Ok(Token::String(PdfString::hex(out)))
307    }
308
309    fn literal_string(&mut self) -> Result<Token<'a>> {
310        let mut out = Vec::new();
311        let mut depth = 1u32;
312        loop {
313            let Some(b) = self.bump() else {
314                return Err(Error::UnexpectedEof);
315            };
316            match b {
317                b'(' => {
318                    depth += 1;
319                    out.push(b);
320                }
321                b')' => {
322                    depth -= 1;
323                    if depth == 0 {
324                        break;
325                    }
326                    out.push(b);
327                }
328                b'\r' => {
329                    // An unescaped end of line reads as `\n`.
330                    if self.peek() == Some(b'\n') {
331                        self.pos += 1;
332                    }
333                    out.push(b'\n');
334                }
335                b'\\' => {
336                    let Some(e) = self.bump() else {
337                        return Err(Error::UnexpectedEof);
338                    };
339                    match e {
340                        b'n' => out.push(b'\n'),
341                        b'r' => out.push(b'\r'),
342                        b't' => out.push(b'\t'),
343                        b'b' => out.push(0x08),
344                        b'f' => out.push(0x0c),
345                        b'0'..=b'7' => {
346                            let mut v = u32::from(e - b'0');
347                            for _ in 0..2 {
348                                match self.peek() {
349                                    Some(d @ b'0'..=b'7') => {
350                                        v = v * 8 + u32::from(d - b'0');
351                                        self.pos += 1;
352                                    }
353                                    _ => break,
354                                }
355                            }
356                            // High-order overflow is ignored (§7.3.4.2).
357                            out.push((v & 0xff) as u8);
358                        }
359                        b'\r' => {
360                            if self.peek() == Some(b'\n') {
361                                self.pos += 1;
362                            }
363                        }
364                        b'\n' => {}
365                        other => out.push(other),
366                    }
367                }
368                other => out.push(other),
369            }
370        }
371        Ok(Token::String(PdfString::literal(out)))
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378
379    fn tokens(input: &[u8]) -> Vec<Token<'_>> {
380        let mut lexer = Lexer::new(input);
381        let mut out = Vec::new();
382        loop {
383            match lexer.next_token().unwrap() {
384                Token::Eof => return out,
385                t => out.push(t),
386            }
387        }
388    }
389
390    fn one(input: &[u8]) -> Token<'_> {
391        let mut t = tokens(input);
392        assert_eq!(t.len(), 1, "{t:?}");
393        t.remove(0)
394    }
395
396    #[test]
397    fn numbers() {
398        assert_eq!(one(b"123"), Token::Int(123));
399        assert_eq!(one(b"-17"), Token::Int(-17));
400        assert_eq!(one(b"+5"), Token::Int(5));
401        assert_eq!(one(b"0"), Token::Int(0));
402        assert_eq!(one(b"34.5"), Token::Real(34.5));
403        assert_eq!(one(b"-.002"), Token::Real(-0.002));
404        assert_eq!(one(b"4."), Token::Real(4.0));
405        assert_eq!(one(b"--5"), Token::Int(-5));
406        assert_eq!(one(b"-"), Token::Int(0));
407        assert_eq!(one(b"."), Token::Real(0.0));
408        assert_eq!(one(b"1.2.3"), Token::Real(1.2));
409        assert_eq!(one(b"0.00-1"), Token::Real(0.0));
410        assert_eq!(
411            one(b"99999999999999999999"),
412            Token::Real(99999999999999999999.0)
413        );
414    }
415
416    #[test]
417    fn number_then_keyword() {
418        assert_eq!(
419            tokens(b"1 0obj"),
420            vec![Token::Int(1), Token::Int(0), Token::Keyword(b"obj")]
421        );
422    }
423
424    #[test]
425    fn literal_strings() {
426        let s = |b: &[u8]| Token::String(PdfString::literal(b.to_vec()));
427        assert_eq!(one(b"(hello)"), s(b"hello"));
428        assert_eq!(one(b"(a (nested) b)"), s(b"a (nested) b"));
429        assert_eq!(
430            one(b"(\\n\\r\\t\\b\\f\\(\\)\\\\)"),
431            s(b"\n\r\t\x08\x0c()\\")
432        );
433        assert_eq!(one(b"(\\101\\60\\0053)"), s(b"A0\x053"));
434        assert_eq!(one(b"(\\777)"), s(b"\xff"));
435        assert_eq!(one(b"(a\\\r\nb\\\nc)"), s(b"abc"));
436        assert_eq!(one(b"(a\r\nb\rc)"), s(b"a\nb\nc"));
437        assert_eq!(one(b"(\\q)"), s(b"q"));
438        assert_eq!(one(b"()"), s(b""));
439    }
440
441    #[test]
442    fn hex_strings() {
443        let h = |b: &[u8]| Token::String(PdfString::hex(b.to_vec()));
444        assert_eq!(one(b"<48656C6c6f>"), h(b"Hello"));
445        assert_eq!(one(b"<901FA>"), h(b"\x90\x1f\xa0"));
446        assert_eq!(one(b"<4 8\n6 5>"), h(b"He"));
447        assert_eq!(one(b"<4z8>"), h(b"H"));
448        assert_eq!(one(b"<>"), h(b""));
449    }
450
451    #[test]
452    fn names() {
453        let n = |b: &[u8]| Token::Name(Name(b.to_vec()));
454        assert_eq!(one(b"/Name1"), n(b"Name1"));
455        assert_eq!(
456            one(b"/A;Name_With-Various***Characters?"),
457            n(b"A;Name_With-Various***Characters?")
458        );
459        assert_eq!(one(b"/lime#20Green"), n(b"lime Green"));
460        assert_eq!(one(b"/paired#28#29parentheses"), n(b"paired()parentheses"));
461        assert_eq!(one(b"/bad#zz"), n(b"bad#zz"));
462        assert_eq!(one(b"/end#"), n(b"end#"));
463        assert_eq!(one(b"/"), n(b""));
464        assert_eq!(tokens(b"/A/B"), vec![n(b"A"), n(b"B")]);
465    }
466
467    #[test]
468    fn delimiters_and_comments() {
469        assert_eq!(
470            tokens(b"<< /K [1 2] >> % comment\n{ } true"),
471            vec![
472                Token::DictStart,
473                Token::Name(Name(b"K".to_vec())),
474                Token::ArrayStart,
475                Token::Int(1),
476                Token::Int(2),
477                Token::ArrayEnd,
478                Token::DictEnd,
479                Token::BraceOpen,
480                Token::BraceClose,
481                Token::Keyword(b"true"),
482            ]
483        );
484    }
485
486    #[test]
487    fn stray_closers() {
488        assert_eq!(
489            tokens(b"> )"),
490            vec![Token::Keyword(b">"), Token::Keyword(b")")]
491        );
492    }
493
494    #[test]
495    fn unterminated_is_eof() {
496        for input in [&b"(abc"[..], b"<414", b"(a\\"] {
497            assert_eq!(Lexer::new(input).next_token(), Err(Error::UnexpectedEof));
498        }
499    }
500
501    #[test]
502    fn position_helpers() {
503        let mut l = Lexer::new(b"  abc");
504        l.skip_whitespace();
505        assert_eq!(l.pos(), 2);
506        assert_eq!(l.rest(), b"abc");
507        l.set_pos(100);
508        assert!(l.at_end());
509        assert_eq!(l.peek(), None);
510        assert_eq!(l.buffer(), b"  abc");
511    }
512}