1use std::borrow::Borrow;
7use std::collections::BTreeMap;
8use std::fmt;
9use std::sync::Arc;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct ObjId {
14 pub num: u32,
16 pub generation: u16,
18}
19
20impl ObjId {
21 pub const fn new(num: u32, generation: u16) -> Self {
23 Self { num, generation }
24 }
25}
26
27impl fmt::Display for ObjId {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 write!(f, "{} {} R", self.num, self.generation)
30 }
31}
32
33#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
35pub struct Name(pub Vec<u8>);
36
37impl Name {
38 pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
40 Self(bytes.into())
41 }
42
43 pub fn as_bytes(&self) -> &[u8] {
45 &self.0
46 }
47}
48
49impl Borrow<[u8]> for Name {
50 fn borrow(&self) -> &[u8] {
51 &self.0
52 }
53}
54
55impl From<&[u8]> for Name {
56 fn from(bytes: &[u8]) -> Self {
57 Self(bytes.to_vec())
58 }
59}
60
61impl<const N: usize> From<&[u8; N]> for Name {
62 fn from(bytes: &[u8; N]) -> Self {
63 Self(bytes.to_vec())
64 }
65}
66
67impl From<&str> for Name {
68 fn from(s: &str) -> Self {
69 Self(s.as_bytes().to_vec())
70 }
71}
72
73impl PartialEq<[u8]> for Name {
74 fn eq(&self, other: &[u8]) -> bool {
75 self.0 == other
76 }
77}
78
79impl<const N: usize> PartialEq<&[u8; N]> for Name {
80 fn eq(&self, other: &&[u8; N]) -> bool {
81 self.0 == other.as_slice()
82 }
83}
84
85impl fmt::Debug for Name {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 write!(f, "/{}", String::from_utf8_lossy(&self.0))
88 }
89}
90
91#[derive(Clone, PartialEq, Eq, Hash, Default)]
93pub struct PdfString {
94 pub bytes: Vec<u8>,
96 pub hex: bool,
98}
99
100impl PdfString {
101 pub fn literal(bytes: impl Into<Vec<u8>>) -> Self {
103 Self {
104 bytes: bytes.into(),
105 hex: false,
106 }
107 }
108
109 pub fn hex(bytes: impl Into<Vec<u8>>) -> Self {
111 Self {
112 bytes: bytes.into(),
113 hex: true,
114 }
115 }
116}
117
118impl fmt::Debug for PdfString {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 if self.hex {
121 write!(f, "<")?;
122 for b in &self.bytes {
123 write!(f, "{b:02x}")?;
124 }
125 write!(f, ">")
126 } else {
127 write!(f, "({})", String::from_utf8_lossy(&self.bytes))
128 }
129 }
130}
131
132#[derive(Clone, PartialEq, Default)]
135pub struct Dict(BTreeMap<Name, Object>);
136
137impl Dict {
138 pub fn new() -> Self {
140 Self::default()
141 }
142
143 pub fn get(&self, key: &[u8]) -> Option<&Object> {
146 self.0.get(key).filter(|v| !matches!(v, Object::Null))
147 }
148
149 pub fn insert(&mut self, key: impl Into<Name>, value: impl Into<Object>) -> Option<Object> {
151 self.0.insert(key.into(), value.into())
152 }
153
154 pub fn remove(&mut self, key: &[u8]) -> Option<Object> {
156 self.0.remove(key)
157 }
158
159 pub fn contains_key(&self, key: &[u8]) -> bool {
161 self.get(key).is_some()
162 }
163
164 pub fn len(&self) -> usize {
166 self.0.len()
167 }
168
169 pub fn is_empty(&self) -> bool {
171 self.0.is_empty()
172 }
173
174 pub fn iter(&self) -> impl Iterator<Item = (&Name, &Object)> {
176 self.0.iter()
177 }
178
179 pub fn get_name(&self, key: &[u8]) -> Option<&[u8]> {
181 self.get(key).and_then(Object::as_name)
182 }
183
184 pub fn get_int(&self, key: &[u8]) -> Option<i64> {
186 self.get(key).and_then(Object::as_int)
187 }
188
189 pub fn get_dict(&self, key: &[u8]) -> Option<&Dict> {
191 self.get(key).and_then(Object::as_dict)
192 }
193
194 pub fn get_array(&self, key: &[u8]) -> Option<&[Object]> {
196 self.get(key).and_then(Object::as_array)
197 }
198
199 pub fn get_ref(&self, key: &[u8]) -> Option<ObjId> {
201 self.get(key).and_then(Object::as_ref)
202 }
203
204 pub fn is_type(&self, ty: &[u8]) -> bool {
206 self.get_name(b"Type") == Some(ty)
207 }
208}
209
210impl fmt::Debug for Dict {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 f.debug_map().entries(self.0.iter()).finish()
213 }
214}
215
216impl FromIterator<(Name, Object)> for Dict {
217 fn from_iter<I: IntoIterator<Item = (Name, Object)>>(iter: I) -> Self {
218 Self(iter.into_iter().collect())
219 }
220}
221
222#[derive(Clone, PartialEq)]
225pub struct Stream {
226 pub dict: Dict,
228 pub raw: Arc<[u8]>,
230}
231
232impl fmt::Debug for Stream {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 write!(f, "{:?} stream[{} bytes]", self.dict, self.raw.len())
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Default)]
240pub enum Object {
241 #[default]
243 Null,
244 Bool(bool),
246 Int(i64),
248 Real(f64),
250 String(PdfString),
252 Name(Name),
254 Array(Vec<Object>),
256 Dict(Dict),
258 Stream(Stream),
260 Ref(ObjId),
262}
263
264impl Object {
265 pub fn type_name(&self) -> &'static str {
267 match self {
268 Self::Null => "null",
269 Self::Bool(_) => "boolean",
270 Self::Int(_) => "integer",
271 Self::Real(_) => "real",
272 Self::String(_) => "string",
273 Self::Name(_) => "name",
274 Self::Array(_) => "array",
275 Self::Dict(_) => "dictionary",
276 Self::Stream(_) => "stream",
277 Self::Ref(_) => "reference",
278 }
279 }
280
281 pub fn as_bool(&self) -> Option<bool> {
283 match self {
284 Self::Bool(b) => Some(*b),
285 _ => None,
286 }
287 }
288
289 pub fn as_int(&self) -> Option<i64> {
291 match self {
292 Self::Int(i) => Some(*i),
293 Self::Real(r) if r.fract() == 0.0 && r.abs() < 9.0e15 => Some(*r as i64),
294 _ => None,
295 }
296 }
297
298 pub fn as_f64(&self) -> Option<f64> {
300 match self {
301 Self::Int(i) => Some(*i as f64),
302 Self::Real(r) => Some(*r),
303 _ => None,
304 }
305 }
306
307 pub fn as_name(&self) -> Option<&[u8]> {
309 match self {
310 Self::Name(n) => Some(n.as_bytes()),
311 _ => None,
312 }
313 }
314
315 pub fn as_string(&self) -> Option<&[u8]> {
317 match self {
318 Self::String(s) => Some(&s.bytes),
319 _ => None,
320 }
321 }
322
323 pub fn as_array(&self) -> Option<&[Object]> {
325 match self {
326 Self::Array(a) => Some(a),
327 _ => None,
328 }
329 }
330
331 pub fn as_dict(&self) -> Option<&Dict> {
333 match self {
334 Self::Dict(d) => Some(d),
335 Self::Stream(s) => Some(&s.dict),
336 _ => None,
337 }
338 }
339
340 pub fn as_stream(&self) -> Option<&Stream> {
342 match self {
343 Self::Stream(s) => Some(s),
344 _ => None,
345 }
346 }
347
348 pub fn as_ref(&self) -> Option<ObjId> {
350 match self {
351 Self::Ref(id) => Some(*id),
352 _ => None,
353 }
354 }
355}
356
357impl From<bool> for Object {
358 fn from(b: bool) -> Self {
359 Self::Bool(b)
360 }
361}
362
363impl From<i64> for Object {
364 fn from(i: i64) -> Self {
365 Self::Int(i)
366 }
367}
368
369impl From<f64> for Object {
370 fn from(r: f64) -> Self {
371 Self::Real(r)
372 }
373}
374
375impl From<Name> for Object {
376 fn from(n: Name) -> Self {
377 Self::Name(n)
378 }
379}
380
381impl From<PdfString> for Object {
382 fn from(s: PdfString) -> Self {
383 Self::String(s)
384 }
385}
386
387impl From<Dict> for Object {
388 fn from(d: Dict) -> Self {
389 Self::Dict(d)
390 }
391}
392
393impl From<Vec<Object>> for Object {
394 fn from(a: Vec<Object>) -> Self {
395 Self::Array(a)
396 }
397}
398
399impl From<ObjId> for Object {
400 fn from(id: ObjId) -> Self {
401 Self::Ref(id)
402 }
403}
404
405impl From<Stream> for Object {
406 fn from(s: Stream) -> Self {
407 Self::Stream(s)
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn dict_null_is_absent() {
417 let mut d = Dict::new();
418 d.insert("A", Object::Null);
419 d.insert("B", 3i64);
420 assert!(d.get(b"A").is_none());
421 assert!(!d.contains_key(b"A"));
422 assert_eq!(d.get_int(b"B"), Some(3));
423 assert_eq!(d.len(), 2);
424 }
425
426 #[test]
427 fn dict_last_key_wins_and_is_sorted() {
428 let d: Dict = [
429 (Name::from("Z"), Object::Int(1)),
430 (Name::from("A"), Object::Int(2)),
431 (Name::from("Z"), Object::Int(3)),
432 ]
433 .into_iter()
434 .collect();
435 let keys: Vec<_> = d.iter().map(|(k, _)| k.as_bytes().to_vec()).collect();
436 assert_eq!(keys, vec![b"A".to_vec(), b"Z".to_vec()]);
437 assert_eq!(d.get_int(b"Z"), Some(3));
438 }
439
440 #[test]
441 fn typed_accessors() {
442 let mut d = Dict::new();
443 d.insert("Type", Name::from("Page"));
444 d.insert("Kids", vec![Object::Ref(ObjId::new(4, 0))]);
445 d.insert("Parent", ObjId::new(2, 0));
446 d.insert("Res", Dict::new());
447 assert!(d.is_type(b"Page"));
448 assert_eq!(d.get_array(b"Kids").map(<[_]>::len), Some(1));
449 assert_eq!(d.get_ref(b"Parent"), Some(ObjId::new(2, 0)));
450 assert!(d.get_dict(b"Res").is_some());
451 assert_eq!(d.remove(b"Res"), Some(Object::Dict(Dict::new())));
452 }
453
454 #[test]
455 fn numbers() {
456 assert_eq!(Object::Real(3.0).as_int(), Some(3));
457 assert_eq!(Object::Real(3.5).as_int(), None);
458 assert_eq!(Object::Real(1e300).as_int(), None);
459 assert_eq!(Object::Int(2).as_f64(), Some(2.0));
460 assert_eq!(Object::Null.as_f64(), None);
461 }
462
463 #[test]
464 fn debug_forms() {
465 assert_eq!(format!("{:?}", Name::from("A B")), "/A B");
466 assert_eq!(format!("{:?}", PdfString::hex(vec![0xab, 1])), "<ab01>");
467 assert_eq!(format!("{:?}", PdfString::literal("hi")), "(hi)");
468 assert_eq!(ObjId::new(3, 1).to_string(), "3 1 R");
469 assert_eq!(Object::Bool(true).type_name(), "boolean");
470 }
471}