1use std::borrow::Borrow;
5
6use crate::hash::FastMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct ObjRef {
11 pub num: u32,
12 pub gen: u16,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub struct Name(pub String);
19
20impl Borrow<str> for Name {
21 fn borrow(&self) -> &str {
22 &self.0
23 }
24}
25
26#[derive(Debug, Clone, Default, PartialEq)]
28pub struct Dict(FastMap<Name, Object>);
29
30impl Dict {
31 pub fn new() -> Dict {
33 Dict::default()
34 }
35
36 pub fn get(&self, key: &str) -> Option<&Object> {
40 self.0.get(key)
41 }
42
43 pub fn insert(&mut self, key: Name, value: Object) -> Option<Object> {
45 self.0.insert(key, value)
46 }
47
48 pub fn remove(&mut self, key: &str) -> Option<Object> {
50 self.0.remove(key)
51 }
52
53 pub fn iter(&self) -> impl Iterator<Item = (&Name, &Object)> {
55 self.0.iter()
56 }
57
58 pub(crate) fn values_mut(&mut self) -> impl Iterator<Item = &mut Object> {
60 self.0.values_mut()
61 }
62
63 pub fn len(&self) -> usize {
65 self.0.len()
66 }
67
68 pub fn is_empty(&self) -> bool {
70 self.0.is_empty()
71 }
72
73 pub fn get_int(&self, key: &str) -> Option<i64> {
75 self.get(key)?.as_int()
76 }
77
78 pub fn get_f64(&self, key: &str) -> Option<f64> {
80 self.get(key)?.as_f64()
81 }
82
83 pub fn get_name(&self, key: &str) -> Option<&Name> {
85 self.get(key)?.as_name()
86 }
87
88 pub fn get_array(&self, key: &str) -> Option<&[Object]> {
90 self.get(key)?.as_array()
91 }
92
93 pub fn get_dict(&self, key: &str) -> Option<&Dict> {
95 self.get(key)?.as_dict()
96 }
97
98 pub fn get_ref(&self, key: &str) -> Option<ObjRef> {
100 self.get(key)?.as_ref()
101 }
102}
103
104#[derive(Debug, Clone, PartialEq)]
106pub struct Stream {
107 pub dict: Dict,
108 pub data: Vec<u8>,
110}
111
112#[derive(Debug, Clone, PartialEq)]
114pub enum Object {
115 Null,
116 Bool(bool),
117 Int(i64),
118 Real(f64),
119 String(Vec<u8>),
121 Name(Name),
122 Array(Vec<Object>),
123 Dict(Dict),
124 Stream(Stream),
125 Ref(ObjRef),
126}
127
128impl Object {
129 pub fn as_bool(&self) -> Option<bool> {
131 match self {
132 Object::Bool(b) => Some(*b),
133 _ => None,
134 }
135 }
136
137 pub fn as_int(&self) -> Option<i64> {
139 match self {
140 Object::Int(i) => Some(*i),
141 _ => None,
142 }
143 }
144
145 pub fn as_f64(&self) -> Option<f64> {
147 match self {
148 Object::Int(i) => Some(*i as f64),
149 Object::Real(r) => Some(*r),
150 _ => None,
151 }
152 }
153
154 pub fn as_str_bytes(&self) -> Option<&[u8]> {
158 match self {
159 Object::String(bytes) => Some(bytes),
160 _ => None,
161 }
162 }
163
164 pub fn as_name(&self) -> Option<&Name> {
166 match self {
167 Object::Name(n) => Some(n),
168 _ => None,
169 }
170 }
171
172 pub fn as_array(&self) -> Option<&[Object]> {
174 match self {
175 Object::Array(items) => Some(items),
176 _ => None,
177 }
178 }
179
180 pub fn as_dict(&self) -> Option<&Dict> {
182 match self {
183 Object::Dict(d) => Some(d),
184 Object::Stream(s) => Some(&s.dict),
185 _ => None,
186 }
187 }
188
189 pub fn as_stream(&self) -> Option<&Stream> {
191 match self {
192 Object::Stream(s) => Some(s),
193 _ => None,
194 }
195 }
196
197 #[allow(clippy::should_implement_trait)] pub fn as_ref(&self) -> Option<ObjRef> {
200 match self {
201 Object::Ref(r) => Some(*r),
202 _ => None,
203 }
204 }
205
206 pub fn is_null(&self) -> bool {
210 matches!(self, Object::Null)
211 }
212}
213
214pub fn pdf_doc_char(code: u8) -> Option<char> {
222 let c = match code {
223 0x09 | 0x0A | 0x0D | 0x20..=0x7E | 0xA1..=0xAC | 0xAE..=0xFF => char::from(code),
224 0x18 => '\u{2D8}',
225 0x19 => '\u{2C7}',
226 0x1A => '\u{2C6}',
227 0x1B => '\u{2D9}',
228 0x1C => '\u{2DD}',
229 0x1D => '\u{2DB}',
230 0x1E => '\u{2DA}',
231 0x1F => '\u{2DC}',
232 0x80 => '\u{2022}',
233 0x81 => '\u{2020}',
234 0x82 => '\u{2021}',
235 0x83 => '\u{2026}',
236 0x84 => '\u{2014}',
237 0x85 => '\u{2013}',
238 0x86 => '\u{192}',
239 0x87 => '\u{2044}',
240 0x88 => '\u{2039}',
241 0x89 => '\u{203A}',
242 0x8A => '\u{2212}',
243 0x8B => '\u{2030}',
244 0x8C => '\u{201E}',
245 0x8D => '\u{201C}',
246 0x8E => '\u{201D}',
247 0x8F => '\u{2018}',
248 0x90 => '\u{2019}',
249 0x91 => '\u{201A}',
250 0x92 => '\u{2122}',
251 0x93 => '\u{FB01}',
252 0x94 => '\u{FB02}',
253 0x95 => '\u{141}',
254 0x96 => '\u{152}',
255 0x97 => '\u{160}',
256 0x98 => '\u{178}',
257 0x99 => '\u{17D}',
258 0x9A => '\u{131}',
259 0x9B => '\u{142}',
260 0x9C => '\u{153}',
261 0x9D => '\u{161}',
262 0x9E => '\u{17E}',
263 0xA0 => '\u{20AC}',
264 _ => return None,
265 };
266 Some(c)
267}
268
269pub fn decode_text_string(bytes: &[u8]) -> String {
275 if let Some(rest) = bytes.strip_prefix(&[0xFE, 0xFF]) {
276 let units = rest
277 .as_chunks::<2>()
278 .0
279 .iter()
280 .map(|pair| u16::from_be_bytes([pair[0], pair[1]]));
281 let mut out: String = std::char::decode_utf16(units)
282 .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER))
283 .collect();
284 if rest.len() % 2 == 1 {
285 out.push(char::REPLACEMENT_CHARACTER);
287 }
288 out
289 } else if let Some(rest) = bytes.strip_prefix(&[0xEF, 0xBB, 0xBF]) {
290 String::from_utf8_lossy(rest).into_owned()
291 } else {
292 bytes
293 .iter()
294 .map(|&b| pdf_doc_char(b).unwrap_or(char::REPLACEMENT_CHARACTER))
295 .collect()
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 fn name(s: &str) -> Name {
304 Name(s.to_string())
305 }
306
307 #[test]
308 fn accessors_return_some_for_matching_variant() {
309 assert_eq!(Object::Bool(true).as_bool(), Some(true));
310 assert_eq!(Object::Bool(false).as_bool(), Some(false));
311 assert_eq!(Object::Int(-7).as_int(), Some(-7));
312 assert_eq!(Object::Real(1.5).as_f64(), Some(1.5));
313 assert_eq!(
314 Object::String(b"abc".to_vec()).as_str_bytes(),
315 Some(b"abc".as_slice())
316 );
317 assert_eq!(Object::Name(name("Type")).as_name(), Some(&name("Type")));
318 let arr = Object::Array(vec![Object::Int(1), Object::Null]);
319 assert_eq!(arr.as_array().map(<[Object]>::len), Some(2));
320 let mut d = Dict::new();
321 d.insert(name("K"), Object::Int(9));
322 assert_eq!(Object::Dict(d.clone()).as_dict(), Some(&d));
323 let r = ObjRef { num: 12, gen: 3 };
324 assert_eq!(Object::Ref(r).as_ref(), Some(r));
325 assert!(Object::Null.is_null());
326 assert!(!Object::Int(0).is_null());
327 }
328
329 #[test]
330 fn accessors_return_none_for_other_variants() {
331 let o = Object::Int(1);
332 assert_eq!(o.as_bool(), None);
333 assert_eq!(o.as_str_bytes(), None);
334 assert_eq!(o.as_name(), None);
335 assert_eq!(o.as_array(), None);
336 assert_eq!(o.as_dict(), None);
337 assert_eq!(o.as_stream(), None);
338 assert_eq!(o.as_ref(), None);
339 assert_eq!(Object::Real(2.0).as_int(), None);
340 assert_eq!(Object::Bool(true).as_f64(), None);
341 assert_eq!(Object::Null.as_f64(), None);
342 }
343
344 #[test]
345 fn as_f64_coerces_int() {
346 assert_eq!(Object::Int(42).as_f64(), Some(42.0));
347 assert_eq!(Object::Int(-3).as_f64(), Some(-3.0));
348 assert_eq!(Object::Real(0.25).as_f64(), Some(0.25));
349 }
350
351 #[test]
352 fn as_dict_sees_stream_dict() {
353 let mut d = Dict::new();
354 d.insert(name("Length"), Object::Int(5));
355 let s = Stream {
356 dict: d.clone(),
357 data: b"hello".to_vec(),
358 };
359 let o = Object::Stream(s.clone());
360 assert_eq!(o.as_dict(), Some(&d));
361 assert_eq!(o.as_stream(), Some(&s));
362 }
363
364 #[test]
365 fn dict_basic_operations() {
366 let mut d = Dict::new();
367 assert!(d.is_empty());
368 assert_eq!(d.len(), 0);
369 assert_eq!(d.insert(name("A"), Object::Int(1)), None);
370 assert_eq!(
371 d.insert(name("A"), Object::Int(2)),
372 Some(Object::Int(1)),
373 "insert returns the previous value"
374 );
375 d.insert(name("B"), Object::Bool(true));
376 assert_eq!(d.len(), 2);
377 assert!(!d.is_empty());
378 assert_eq!(d.get("A"), Some(&Object::Int(2)));
379 assert_eq!(d.get("Missing"), None);
380 let mut keys: Vec<&str> = d.iter().map(|(k, _)| k.0.as_str()).collect();
381 keys.sort_unstable();
382 assert_eq!(keys, vec!["A", "B"]);
383 assert_eq!(d.remove("A"), Some(Object::Int(2)));
384 assert_eq!(d.remove("A"), None);
385 assert_eq!(d.len(), 1);
386 }
387
388 #[test]
389 fn dict_typed_helpers() {
390 let mut inner = Dict::new();
391 inner.insert(name("X"), Object::Null);
392 let mut d = Dict::new();
393 d.insert(name("Int"), Object::Int(7));
394 d.insert(name("Real"), Object::Real(2.5));
395 d.insert(name("Name"), Object::Name(name("Page")));
396 d.insert(name("Arr"), Object::Array(vec![Object::Int(1)]));
397 d.insert(name("Dict"), Object::Dict(inner.clone()));
398 d.insert(name("Ref"), Object::Ref(ObjRef { num: 4, gen: 0 }));
399
400 assert_eq!(d.get_int("Int"), Some(7));
401 assert_eq!(d.get_int("Real"), None, "reals do not coerce to int");
402 assert_eq!(d.get_f64("Int"), Some(7.0), "ints coerce to f64");
403 assert_eq!(d.get_f64("Real"), Some(2.5));
404 assert_eq!(d.get_name("Name"), Some(&name("Page")));
405 assert_eq!(d.get_array("Arr"), Some([Object::Int(1)].as_slice()));
406 assert_eq!(d.get_dict("Dict"), Some(&inner));
407 assert_eq!(d.get_ref("Ref"), Some(ObjRef { num: 4, gen: 0 }));
408
409 assert_eq!(d.get_int("Nope"), None);
411 assert_eq!(d.get_name("Int"), None);
412 assert_eq!(d.get_array("Dict"), None);
413 assert_eq!(d.get_dict("Arr"), None);
414 assert_eq!(d.get_ref("Int"), None);
415 }
416
417 #[test]
419 fn decode_utf16be_with_bom() {
420 assert_eq!(
421 decode_text_string(&[0xFE, 0xFF, 0x00, 0x48, 0x00, 0x69]),
422 "Hi"
423 );
424 assert_eq!(
426 decode_text_string(&[0xFE, 0xFF, 0xD8, 0x34, 0xDD, 0x1E]),
427 "\u{1D11E}"
428 );
429 assert_eq!(decode_text_string(&[0xFE, 0xFF]), "");
431 assert_eq!(decode_text_string(&[0xFE, 0xFF, 0xD8, 0x34]), "\u{FFFD}");
433 assert_eq!(
435 decode_text_string(&[0xFE, 0xFF, 0x00, 0x48, 0x12]),
436 "H\u{FFFD}"
437 );
438 }
439
440 #[test]
442 fn decode_utf8_with_bom() {
443 let mut bytes = vec![0xEF, 0xBB, 0xBF];
444 bytes.extend_from_slice("héllo €".as_bytes());
445 assert_eq!(decode_text_string(&bytes), "héllo €");
446 assert_eq!(decode_text_string(&[0xEF, 0xBB, 0xBF]), "");
447 assert_eq!(decode_text_string(&[0xEF, 0xBB, 0xBF, 0xFF]), "\u{FFFD}");
449 }
450
451 #[test]
453 fn decode_without_bom_uses_pdf_doc_encoding() {
454 assert_eq!(decode_text_string(b"Hello"), "Hello");
455 assert_eq!(decode_text_string(&[0x48, 0xE9]), "Hé");
456 assert_eq!(decode_text_string(&[0xFF]), "ÿ");
457 assert_eq!(decode_text_string(&[]), "");
458 assert_eq!(decode_text_string(&[0xFE]), "\u{FE}");
460 }
461
462 #[test]
465 fn decode_pdf_doc_encoding_accents_at_0x18() {
466 assert_eq!(
467 decode_text_string(&[0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F]),
468 "\u{2D8}\u{2C7}\u{2C6}\u{2D9}\u{2DD}\u{2DB}\u{2DA}\u{2DC}"
469 );
470 }
471
472 #[test]
474 fn decode_pdf_doc_encoding_punctuation_at_0x80() {
475 assert_eq!(
476 decode_text_string(&[0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87]),
477 "•†‡…—–ƒ⁄"
478 );
479 assert_eq!(
480 decode_text_string(&[0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F]),
481 "‹›−‰„“”‘"
482 );
483 assert_eq!(
484 decode_text_string(&[0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97]),
485 "’‚™fiflŁŒŠ"
486 );
487 assert_eq!(
488 decode_text_string(&[0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E]),
489 "ŸŽıłœšž"
490 );
491 assert_eq!(decode_text_string(&[0xA0]), "€");
492 }
493
494 #[test]
497 fn decode_pdf_doc_encoding_agrees_with_latin1_elsewhere() {
498 assert_eq!(
499 decode_text_string(&[0x09, 0x0A, 0x0D, 0x20, 0x7E, 0xA1, 0xE9, 0xFE, 0xFF]),
500 "\t\n\r ~¡éþÿ"
501 );
502 }
503
504 #[test]
507 fn decode_pdf_doc_encoding_undefined_codes_are_replaced() {
508 assert_eq!(decode_text_string(&[0x9F, 0xAD]), "\u{FFFD}\u{FFFD}");
509 assert_eq!(decode_text_string(&[0x00, 0x7F]), "\u{FFFD}\u{FFFD}");
510 }
511}