1use otf_pdf_syntax::lexer::Token;
5use otf_pdf_syntax::parser::Parser;
6use otf_pdf_syntax::{Dict, Error, Limits, Name, Object, PdfString, Result, Strictness};
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct Operation {
11 pub operator: Vec<u8>,
13 pub operands: Vec<Object>,
16}
17
18const MAX_OPERANDS: usize = 4096;
20
21pub fn parse_content(bytes: &[u8], limits: &Limits) -> Result<Vec<Operation>> {
24 let mut parser = Parser::new(bytes, 0, limits, Strictness::Lenient);
25 let mut ops = Vec::new();
26 let mut operands: Vec<Object> = Vec::new();
27 loop {
28 let save = parser.lexer().pos();
29 let token = match parser.lexer().next_token() {
30 Ok(t) => t,
31 Err(Error::UnexpectedEof) => break,
33 Err(e) => return Err(e),
34 };
35 match token {
36 Token::Eof => break,
37 Token::Keyword(b"BI") => {
38 limits.charge(1)?;
39 let pos = parser.lexer().pos();
40 let rest = bytes.get(pos..).unwrap_or_default();
41 let (dict, data, used) = inline_image(rest, limits);
42 parser.lexer().set_pos(pos + used);
43 ops.push(Operation {
44 operator: b"BI".to_vec(),
45 operands: vec![Object::Dict(dict), Object::String(PdfString::literal(data))],
46 });
47 operands.clear();
48 }
49 Token::Keyword(k) if !matches!(k, b"true" | b"false" | b"null") => {
50 limits.charge(1)?;
51 ops.push(Operation {
52 operator: k.to_vec(),
53 operands: std::mem::take(&mut operands),
54 });
55 }
56 Token::ArrayEnd | Token::DictEnd | Token::BraceClose => {}
58 Token::BraceOpen => {}
60 _ => {
61 parser.lexer().set_pos(save);
62 match parser.parse_object() {
63 Ok(obj) => {
64 if operands.len() < MAX_OPERANDS {
65 operands.push(obj);
66 }
67 }
68 Err(e @ Error::Limit(_)) => return Err(e),
69 Err(Error::UnexpectedEof) => break,
70 Err(_) => {
71 parser.lexer().set_pos(save + 1);
73 }
74 }
75 }
76 }
77 }
78 Ok(ops)
79}
80
81fn inline_image(bytes: &[u8], limits: &Limits) -> (Dict, Vec<u8>, usize) {
84 let mut parser = Parser::new(bytes, 0, limits, Strictness::Lenient);
85 let mut dict = Dict::new();
86 loop {
87 let save = parser.lexer().pos();
88 match parser.lexer().next_token() {
89 Ok(Token::Keyword(b"ID")) | Ok(Token::Eof) | Err(_) => break,
90 Ok(Token::Name(key)) => match parser.parse_object() {
91 Ok(value) => {
92 dict.insert(key, value);
93 }
94 Err(_) => break,
95 },
96 Ok(_) => {
97 parser.lexer().set_pos(save + 1);
98 }
99 }
100 }
101 let start = (parser.lexer().pos() + 1).min(bytes.len());
103 let data = bytes.get(start..).unwrap_or_default();
104 let end = (0..data.len().saturating_sub(1))
107 .find(|&i| {
108 data.get(i..i + 2) == Some(b"EI")
109 && (i == 0
110 || data
111 .get(i - 1)
112 .is_some_and(|&b| otf_pdf_syntax::lexer::is_whitespace(b)))
113 && data
114 .get(i + 2)
115 .is_none_or(|&b| otf_pdf_syntax::lexer::is_whitespace(b))
116 && data
117 .get(i + 2..(i + 12).min(data.len()))
118 .unwrap_or_default()
119 .iter()
120 .all(|&b| b == b'\n' || b == b'\r' || b == b'\t' || (0x20..0x7f).contains(&b))
121 })
122 .unwrap_or(data.len());
123 let mut image = data.get(..end).unwrap_or_default().to_vec();
124 if image
126 .last()
127 .is_some_and(|&b| otf_pdf_syntax::lexer::is_whitespace(b))
128 {
129 image.pop();
130 }
131 (dict, image, (start + end + 2).min(bytes.len()))
132}
133
134pub(crate) fn name_at(operands: &[Object], i: usize) -> Option<&Name> {
136 match operands.get(i) {
137 Some(Object::Name(n)) => Some(n),
138 _ => None,
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 fn ops(input: &[u8]) -> Vec<Operation> {
147 parse_content(input, &Limits::default()).unwrap()
148 }
149
150 #[test]
151 fn operators_and_operands() {
152 let o = ops(b"q 1 0 0 1 10 20 cm BT /F1 12 Tf (Hi) Tj [(A) -50 (B)] TJ ET Q");
153 let names: Vec<_> = o
154 .iter()
155 .map(|op| String::from_utf8_lossy(&op.operator).into_owned())
156 .collect();
157 assert_eq!(names, ["q", "cm", "BT", "Tf", "Tj", "TJ", "ET", "Q"]);
158 assert_eq!(o[1].operands.len(), 6);
159 assert_eq!(o[3].operands[0], Object::Name(Name::from("F1")));
160 assert_eq!(o[5].operands[0].as_array().unwrap().len(), 3);
161 }
162
163 #[test]
164 fn inline_image() {
165 let o = ops(b"q BI /W 2 /H 1 /CS /G /BPC 8 ID \x00EI\xff EI Q");
166 assert_eq!(o.len(), 3);
167 assert_eq!(o[1].operator, b"BI");
168 assert_eq!(o[1].operands[0].as_dict().unwrap().get_int(b"W"), Some(2));
169 assert_eq!(o[1].operands[1].as_string(), Some(&b"\x00EI\xff"[..]));
171 assert_eq!(o[2].operator, b"Q");
172 }
173
174 #[test]
175 fn junk_is_skipped() {
176 let o = ops(b"1 2 ] >> } { m (unterminated");
177 assert_eq!(o.len(), 1);
178 assert_eq!(o[0].operator, b"m");
179 assert_eq!(o[0].operands.len(), 2);
180 assert!(ops(b"").is_empty());
181 assert!(ops(b"BI /W 1").len() == 1);
182 }
183
184 #[test]
185 fn keyword_objects_are_operands() {
186 let o = ops(b"true false null d0");
187 assert_eq!(
188 o[0].operands,
189 [Object::Bool(true), Object::Bool(false), Object::Null]
190 );
191 }
192}