#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Integer(i64),
Real(f64),
LiteralString(Vec<u8>),
HexString(Vec<u8>),
Name(String),
Keyword(String),
DictOpen, DictClose, ArrayOpen, ArrayClose,}
pub struct Lexer<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Lexer<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
pub fn with_pos(data: &'a [u8], pos: usize) -> Self {
Self { data, pos }
}
pub fn pos(&self) -> usize {
self.pos
}
pub fn set_pos(&mut self, pos: usize) {
self.pos = pos.min(self.data.len());
}
pub fn remaining(&self) -> &'a [u8] {
&self.data[self.pos..]
}
pub fn is_eof(&self) -> bool {
self.pos >= self.data.len()
}
pub fn skip_whitespace_and_comments(&mut self) {
while self.pos < self.data.len() {
let b = self.data[self.pos];
if is_whitespace(b) {
self.pos += 1;
} else if b == b'%' {
self.pos += 1;
while self.pos < self.data.len() {
let cb = self.data[self.pos];
self.pos += 1;
if cb == b'\r' || cb == b'\n' {
break;
}
}
} else {
break;
}
}
}
pub fn next_token(&mut self) -> Result<Option<Token>, String> {
self.skip_whitespace_and_comments();
if self.is_eof() {
return Ok(None);
}
let b = self.data[self.pos];
if b == b'<' {
if self.pos + 1 < self.data.len() && self.data[self.pos + 1] == b'<' {
self.pos += 2;
return Ok(Some(Token::DictOpen));
} else {
return self.read_hex_string().map(Some);
}
}
if b == b'>' {
if self.pos + 1 < self.data.len() && self.data[self.pos + 1] == b'>' {
self.pos += 2;
return Ok(Some(Token::DictClose));
} else {
return Err(format!("Unexpected '>' at position {}", self.pos));
}
}
if b == b'[' {
self.pos += 1;
return Ok(Some(Token::ArrayOpen));
}
if b == b']' {
self.pos += 1;
return Ok(Some(Token::ArrayClose));
}
if b == b'/' {
return self.read_name().map(Some);
}
if b == b'(' {
return self.read_literal_string().map(Some);
}
if is_number_start(b) {
let (num_tok, is_num) = self.try_read_number();
if is_num {
return Ok(Some(num_tok));
}
}
self.read_keyword().map(Some)
}
fn read_name(&mut self) -> Result<Token, String> {
self.pos += 1; let mut name_bytes = Vec::new();
while self.pos < self.data.len() {
let b = self.data[self.pos];
if is_whitespace(b) || is_delimiter(b) {
break;
}
if b == b'#' && self.pos + 2 < self.data.len() {
let hex_str = std::str::from_utf8(&self.data[self.pos + 1..self.pos + 3])
.map_err(|e| e.to_string())?;
if let Ok(val) = u8::from_str_radix(hex_str, 16) {
name_bytes.push(val);
self.pos += 3;
continue;
}
}
name_bytes.push(b);
self.pos += 1;
}
let name_str = String::from_utf8_lossy(&name_bytes).to_string();
Ok(Token::Name(name_str))
}
fn read_literal_string(&mut self) -> Result<Token, String> {
self.pos += 1; let mut result = Vec::new();
let mut depth = 1;
while self.pos < self.data.len() {
let b = self.data[self.pos];
self.pos += 1;
if b == b'(' {
depth += 1;
result.push(b);
} else if b == b')' {
depth -= 1;
if depth == 0 {
return Ok(Token::LiteralString(result));
}
result.push(b);
} else if b == b'\\' {
if self.pos >= self.data.len() {
break;
}
let esc = self.data[self.pos];
self.pos += 1;
match esc {
b'n' => result.push(b'\n'),
b'r' => result.push(b'\r'),
b't' => result.push(b'\t'),
b'b' => result.push(0x08),
b'f' => result.push(0x0C),
b'(' => result.push(b'('),
b')' => result.push(b')'),
b'\\' => result.push(b'\\'),
b'\r' => {
if self.pos < self.data.len() && self.data[self.pos] == b'\n' {
self.pos += 1;
}
}
b'\n' => {
}
b'0'..=b'7' => {
let mut oct_val = (esc - b'0') as u32;
let mut count = 1;
while count < 3 && self.pos < self.data.len() {
let ob = self.data[self.pos];
if (b'0'..=b'7').contains(&ob) {
oct_val = (oct_val << 3) + (ob - b'0') as u32;
self.pos += 1;
count += 1;
} else {
break;
}
}
result.push((oct_val & 0xFF) as u8);
}
_ => {
result.push(esc);
}
}
} else {
result.push(b);
}
}
Err("Unterminated literal string in PDF".to_string())
}
fn read_hex_string(&mut self) -> Result<Token, String> {
self.pos += 1; let mut hex_chars = Vec::new();
while self.pos < self.data.len() {
let b = self.data[self.pos];
self.pos += 1;
if b == b'>' {
break;
}
if !is_whitespace(b) {
hex_chars.push(b);
}
}
if hex_chars.len() % 2 != 0 {
hex_chars.push(b'0');
}
let mut bytes = Vec::with_capacity(hex_chars.len() / 2);
for chunk in hex_chars.chunks(2) {
let s = std::str::from_utf8(chunk).map_err(|e| e.to_string())?;
let val = u8::from_str_radix(s, 16).map_err(|e| e.to_string())?;
bytes.push(val);
}
Ok(Token::HexString(bytes))
}
fn try_read_number(&mut self) -> (Token, bool) {
let start = self.pos;
let mut is_real = false;
if self.pos < self.data.len() && (self.data[self.pos] == b'+' || self.data[self.pos] == b'-') {
self.pos += 1;
}
let mut has_digits = false;
while self.pos < self.data.len() {
let b = self.data[self.pos];
if b.is_ascii_digit() {
has_digits = true;
self.pos += 1;
} else if b == b'.' && !is_real {
is_real = true;
self.pos += 1;
} else {
break;
}
}
if has_digits {
let slice = &self.data[start..self.pos];
if let Ok(s) = std::str::from_utf8(slice) {
if is_real {
if let Ok(f) = s.parse::<f64>() {
return (Token::Real(f), true);
}
} else if let Ok(i) = s.parse::<i64>() {
return (Token::Integer(i), true);
}
}
}
self.pos = start;
(Token::Keyword(String::new()), false)
}
fn read_keyword(&mut self) -> Result<Token, String> {
let start = self.pos;
while self.pos < self.data.len() {
let b = self.data[self.pos];
if is_whitespace(b) || is_delimiter(b) {
break;
}
self.pos += 1;
}
let kw_slice = &self.data[start..self.pos];
let kw_str = std::str::from_utf8(kw_slice).map_err(|e| e.to_string())?.to_string();
Ok(Token::Keyword(kw_str))
}
}
fn is_whitespace(b: u8) -> bool {
matches!(b, 0x00 | 0x09 | 0x0A | 0x0C | 0x0D | 0x20)
}
fn is_delimiter(b: u8) -> bool {
matches!(b, b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%')
}
fn is_number_start(b: u8) -> bool {
b.is_ascii_digit() || b == b'+' || b == b'-' || b == b'.'
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lex_numbers() {
let data = b" 123 -456 +78 3.1415 -.002 ";
let mut lexer = Lexer::new(data);
assert_eq!(lexer.next_token().unwrap(), Some(Token::Integer(123)));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Integer(-456)));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Integer(78)));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Real(3.1415)));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Real(-0.002)));
assert_eq!(lexer.next_token().unwrap(), None);
}
#[test]
fn test_lex_names() {
let data = b"/Type /Font#20Name /Page/Kids";
let mut lexer = Lexer::new(data);
assert_eq!(lexer.next_token().unwrap(), Some(Token::Name("Type".into())));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Name("Font Name".into())));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Name("Page".into())));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Name("Kids".into())));
assert_eq!(lexer.next_token().unwrap(), None);
}
#[test]
fn test_lex_strings() {
let data = b"(Hello (nested) World\\n) <48656c6c6f> <41424>";
let mut lexer = Lexer::new(data);
assert_eq!(
lexer.next_token().unwrap(),
Some(Token::LiteralString(b"Hello (nested) World\n".to_vec()))
);
assert_eq!(
lexer.next_token().unwrap(),
Some(Token::HexString(b"Hello".to_vec()))
);
assert_eq!(
lexer.next_token().unwrap(),
Some(Token::HexString(vec![0x41, 0x42, 0x40]))
);
assert_eq!(lexer.next_token().unwrap(), None);
}
#[test]
fn test_lex_delimiters_and_comments() {
let data = b"% header comment\n<< /Key [ 1 2 (str) ] >> % end comment\n";
let mut lexer = Lexer::new(data);
assert_eq!(lexer.next_token().unwrap(), Some(Token::DictOpen));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Name("Key".into())));
assert_eq!(lexer.next_token().unwrap(), Some(Token::ArrayOpen));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Integer(1)));
assert_eq!(lexer.next_token().unwrap(), Some(Token::Integer(2)));
assert_eq!(lexer.next_token().unwrap(), Some(Token::LiteralString(b"str".to_vec())));
assert_eq!(lexer.next_token().unwrap(), Some(Token::ArrayClose));
assert_eq!(lexer.next_token().unwrap(), Some(Token::DictClose));
assert_eq!(lexer.next_token().unwrap(), None);
}
}