1use pdfrum_common::kurbo::{Affine, Rect};
38
39use crate::{Array, Name, Object, PdfString, Resolve, Resolved, Stream};
40
41#[derive(Debug, Clone, Default, PartialEq)]
73pub struct Dict(Vec<(Name, Object)>);
74
75impl Dict {
76 #[must_use]
78 pub fn new() -> Self {
79 Self(Vec::new())
80 }
81
82 #[must_use]
84 pub fn from_pairs(
85 pairs: impl IntoIterator<Item = (impl Into<Name>, impl Into<Object>)>,
86 ) -> Self {
87 pairs
88 .into_iter()
89 .map(|(k, v)| (k.into(), v.into()))
90 .collect()
91 }
92
93 pub fn push(&mut self, key: impl Into<Name>, value: impl Into<Object>) {
100 self.0.push((key.into(), value.into()));
101 }
102
103 pub fn insert(&mut self, key: impl Into<Name>, value: impl Into<Object>) {
106 let key = key.into();
107 let value = value.into();
108 if let Some(entry) = self.0.iter_mut().find(|entry| entry.0 == key) {
109 entry.1 = value;
110 } else {
111 self.0.push((key, value));
112 }
113 }
114
115 pub fn remove(&mut self, key: &Name) -> Option<Object> {
117 self.0
118 .iter()
119 .position(|(k, _)| k == key)
120 .map(|index| self.0.remove(index).1)
121 }
122
123 #[must_use]
125 pub fn len(&self) -> usize {
126 self.0.len()
127 }
128
129 #[must_use]
131 pub fn is_empty(&self) -> bool {
132 self.0.is_empty()
133 }
134
135 pub fn iter(&self) -> impl Iterator<Item = &(Name, Object)> {
137 self.0.iter()
138 }
139
140 pub fn keys(&self) -> impl Iterator<Item = &Name> {
142 self.0.iter().map(|(k, _)| k)
143 }
144
145 #[must_use]
147 pub fn contains_key(&self, key: &Name) -> bool {
148 self.raw(key).is_some()
149 }
150
151 #[must_use]
157 pub fn raw(&self, key: &Name) -> Option<&Object> {
158 self.0.iter().rev().find(|(k, _)| k == key).map(|(_, v)| v)
159 }
160
161 #[must_use]
169 pub fn direct_int(&self, key: &Name) -> Option<i64> {
170 self.raw(key)?.as_number()?.as_int()
171 }
172
173 #[must_use]
178 pub fn name(&self, key: &Name) -> Option<&Name> {
179 self.raw(key)?.as_name()
180 }
181
182 #[must_use]
186 pub fn bool(&self, key: &Name) -> Option<bool> {
187 self.raw(key)?.as_bool()
188 }
189
190 #[must_use]
194 pub fn number_obj(&self, key: &Name) -> Option<&Object> {
195 self.raw(key)?.as_number()
196 }
197
198 #[must_use]
200 pub fn string(&self, key: &Name) -> Option<&PdfString> {
201 self.raw(key)?.as_string()
202 }
203
204 #[must_use]
212 pub fn get<'a>(&'a self, key: &Name, r: &impl Resolve) -> Option<Resolved<'a>> {
213 self.raw(key)?.resolve(r).ok()
214 }
215
216 #[must_use]
221 pub fn int(&self, key: &Name, r: &impl Resolve) -> Option<i64> {
222 self.get(key, r)?.as_direct()?.as_int()
223 }
224
225 #[must_use]
227 pub fn number(&self, key: &Name, r: &impl Resolve) -> Option<f32> {
228 self.get(key, r)?.as_direct()?.number()
229 }
230
231 #[must_use]
234 pub fn byte_string(&self, key: &Name, r: &impl Resolve) -> Option<Vec<u8>> {
235 Some(self.get(key, r)?.as_direct()?.to_byte_string())
236 }
237
238 #[must_use]
240 pub fn text(&self, key: &Name, r: &impl Resolve) -> Option<String> {
241 Some(self.get(key, r)?.as_direct()?.to_text())
242 }
243
244 #[must_use]
253 pub fn dict(&self, key: &Name, r: &impl Resolve) -> Option<Dict> {
254 self.get(key, r)?.as_direct()?.as_dict().cloned()
255 }
256
257 #[must_use]
259 pub fn array(&self, key: &Name, r: &impl Resolve) -> Option<Array> {
260 self.get(key, r)?.as_direct()?.as_array().cloned()
261 }
262
263 #[must_use]
265 pub fn stream(&self, key: &Name, r: &impl Resolve) -> Option<Stream> {
266 self.get(key, r)?.as_direct()?.as_stream().cloned()
267 }
268
269 #[must_use]
271 pub fn reference(&self, key: &Name) -> Option<crate::ObjRef> {
272 self.raw(key)?.as_ref_id()
273 }
274
275 #[must_use]
280 pub fn rect(&self, key: &Name, r: &impl Resolve) -> Rect {
281 self.array(key, r)
282 .map_or_else(|| Rect::new(0.0, 0.0, 0.0, 0.0), |a| a.as_rect())
283 }
284
285 #[must_use]
288 pub fn matrix(&self, key: &Name, r: &impl Resolve) -> Affine {
289 self.array(key, r)
290 .map_or(Affine::IDENTITY, |a| a.as_matrix())
291 }
292}
293
294impl FromIterator<(Name, Object)> for Dict {
295 fn from_iter<I: IntoIterator<Item = (Name, Object)>>(iter: I) -> Self {
296 let mut dict = Self::new();
297 for (k, v) in iter {
298 dict.push(k, v);
299 }
300 dict
301 }
302}
303
304impl<'a> IntoIterator for &'a Dict {
305 type Item = &'a (Name, Object);
306 type IntoIter = std::slice::Iter<'a, (Name, Object)>;
307
308 fn into_iter(self) -> Self::IntoIter {
309 self.0.iter()
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use pdfrum_common::kurbo::{Affine, Rect};
316
317 use crate::test_resolve::TestStore;
318 use crate::{Array, Dict, Name, NoResolve, ObjRef, Object, PdfString, Stream, names};
319
320 fn key(s: &str) -> Name {
321 Name::from(s)
322 }
323
324 #[test]
328 fn iteration_follows_document_order_not_sort_order() {
329 let dict = Dict::from_pairs([
330 (key("the-dictionary"), Object::Dict(Dict::new())),
331 (key("the-array"), Object::Array(Array::new())),
332 (key("the-number"), Object::Int(42)),
333 ]);
334 let order: Vec<_> = dict.keys().filter_map(Name::as_str).collect();
335 assert_eq!(order, ["the-dictionary", "the-array", "the-number"]);
336 }
337
338 #[test]
339 fn last_duplicate_wins_on_lookup_and_both_are_kept() {
340 let dict = Dict::from_pairs([
341 (key("K"), Object::Int(1)),
342 (key("K"), Object::Int(2)),
343 (key("K"), Object::Int(3)),
344 ]);
345 assert_eq!(dict.raw(&key("K")), Some(&Object::Int(3)));
346 assert_eq!(dict.direct_int(&key("K")), Some(3));
347 assert_eq!(dict.len(), 3, "the file said it three times");
348 }
349
350 #[test]
352 fn name_accessor_is_type_filtered_but_byte_string_coerces() {
353 let dict = Dict::from_pairs([
354 (key("bool"), Object::Bool(false)),
355 (key("num"), Object::Real(0.23)),
356 (key("string"), Object::Str(PdfString::literal(b"ium"))),
357 (key("name"), Object::Name(key("Pdf"))),
358 ]);
359
360 assert_eq!(dict.name(&key("invalid")), None);
361 assert_eq!(dict.name(&key("bool")), None);
362 assert_eq!(dict.name(&key("num")), None);
363 assert_eq!(dict.name(&key("string")), None);
364 assert_eq!(dict.name(&key("name")), Some(&key("Pdf")));
365
366 assert_eq!(dict.byte_string(&key("invalid"), &NoResolve), None);
367 assert_eq!(
368 dict.byte_string(&key("bool"), &NoResolve).as_deref(),
369 Some(&b"false"[..])
370 );
371 assert_eq!(
372 dict.byte_string(&key("num"), &NoResolve).as_deref(),
373 Some(&b".23"[..])
374 );
375 assert_eq!(
376 dict.byte_string(&key("string"), &NoResolve).as_deref(),
377 Some(&b"ium"[..])
378 );
379 assert_eq!(
380 dict.byte_string(&key("name"), &NoResolve).as_deref(),
381 Some(&b"Pdf"[..])
382 );
383 }
384
385 #[test]
386 fn boolean_accessor_rejects_integers() {
387 let dict = Dict::from_pairs([
388 (key("flag"), Object::Bool(true)),
389 (key("one"), Object::Int(1)),
390 ]);
391 assert_eq!(dict.bool(&key("flag")), Some(true));
392 assert_eq!(dict.bool(&key("one")), None, "an Int(1) is not a boolean");
393 }
394
395 #[test]
396 fn direct_int_ignores_indirection_while_int_follows_it() {
397 let store = TestStore::from_pairs([(3, Object::Int(99))]);
398 let dict = Dict::from_pairs([
399 (names::PREV.clone(), Object::Ref(ObjRef::new(3, 0))),
400 (names::LENGTH.clone(), Object::Ref(ObjRef::new(3, 0))),
401 ]);
402 assert_eq!(dict.direct_int(names::PREV), None);
405 assert_eq!(dict.int(names::LENGTH, &store), Some(99));
407 }
408
409 #[test]
410 fn resolution_stops_after_one_level() {
411 let store =
412 TestStore::from_pairs([(1, Object::Ref(ObjRef::new(2, 0))), (2, Object::Int(7))]);
413 let dict = Dict::from_pairs([(key("K"), Object::Ref(ObjRef::new(1, 0)))]);
414 assert_eq!(dict.int(&key("K"), &store), None);
416 assert_eq!(dict.number(&key("K"), &store), None);
417 assert_eq!(
419 dict.get(&key("K"), &store).as_deref(),
420 Some(&Object::Ref(ObjRef::new(2, 0)))
421 );
422 }
423
424 #[test]
425 fn dangling_references_read_as_absent() {
426 let store = TestStore::default();
427 let dict = Dict::from_pairs([(key("K"), Object::Ref(ObjRef::new(9, 0)))]);
428 assert!(dict.get(&key("K"), &store).is_none());
429 assert_eq!(dict.int(&key("K"), &store), None);
430 assert_eq!(dict.dict(&key("K"), &store), None);
431 }
432
433 #[test]
436 fn dict_accessor_accepts_a_stream() {
437 let inner = Dict::from_pairs([(names::LENGTH.clone(), Object::Int(3))]);
438 let stream = Stream::new(inner.clone(), b"abc".to_vec().into());
439 let store = TestStore::from_pairs([(5, Object::Stream(Box::new(stream)))]);
440 let dict = Dict::from_pairs([(key("S"), Object::Ref(ObjRef::new(5, 0)))]);
441
442 assert_eq!(dict.dict(&key("S"), &store), Some(inner));
443 assert!(dict.stream(&key("S"), &store).is_some());
444 assert_eq!(dict.array(&key("S"), &store), None);
445 }
446
447 #[test]
449 fn rect_and_matrix_need_exactly_the_right_element_count() {
450 let four = Object::Array(Array::of([
451 Object::Int(1),
452 Object::Int(2),
453 Object::Int(3),
454 Object::Int(4),
455 ]));
456 let three = Object::Array(Array::of([Object::Int(1), Object::Int(2), Object::Int(3)]));
457 let six = Object::Array(
458 (1..=6)
459 .map(|i| Object::Int(i64::from(i)))
460 .collect::<Array>(),
461 );
462
463 let dict = Dict::from_pairs([
464 (key("four"), four),
465 (key("three"), three),
466 (key("six"), six),
467 ]);
468
469 assert_eq!(
470 dict.rect(&key("four"), &NoResolve),
471 Rect::new(1.0, 2.0, 3.0, 4.0)
472 );
473 assert_eq!(
474 dict.rect(&key("three"), &NoResolve),
475 Rect::new(0.0, 0.0, 0.0, 0.0)
476 );
477 assert_eq!(
478 dict.rect(&key("missing"), &NoResolve),
479 Rect::new(0.0, 0.0, 0.0, 0.0)
480 );
481
482 assert_eq!(
483 dict.matrix(&key("six"), &NoResolve),
484 Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
485 );
486 assert_eq!(dict.matrix(&key("four"), &NoResolve), Affine::IDENTITY);
487 assert_eq!(dict.matrix(&key("missing"), &NoResolve), Affine::IDENTITY);
488 }
489
490 #[test]
491 fn missing_keys_read_as_their_fallbacks_everywhere() {
492 let dict = Dict::new();
493 let absent = key("nope");
494 assert!(dict.is_empty());
495 assert!(!dict.contains_key(&absent));
496 assert_eq!(dict.raw(&absent), None);
497 assert_eq!(dict.int(&absent, &NoResolve), None);
498 assert_eq!(dict.number(&absent, &NoResolve), None);
499 assert_eq!(dict.name(&absent), None);
500 assert_eq!(dict.bool(&absent), None);
501 assert_eq!(dict.string(&absent), None);
502 assert_eq!(dict.text(&absent, &NoResolve), None);
503 assert_eq!(dict.reference(&absent), None);
504 }
505
506 #[test]
507 fn parsed_nulls_are_stored_like_any_other_value() {
508 let dict = Dict::from_pairs([(key("K"), Object::Null)]);
509 assert!(dict.contains_key(&key("K")));
510 assert_eq!(dict.raw(&key("K")), Some(&Object::Null));
511 assert_eq!(dict.int(&key("K"), &NoResolve), None);
512 }
513
514 #[test]
515 fn insert_replaces_in_place_and_appends_when_new() {
516 let mut d = Dict::new();
517 d.push(Name::from("A"), Object::Int(1));
518 d.push(Name::from("B"), Object::Int(2));
519 d.insert(Name::from("B"), Object::Int(20));
520 assert_eq!(d.raw(&Name::from("B")), Some(&Object::Int(20)));
521 let keys: Vec<_> = d.iter().map(|(k, _)| k.clone()).collect();
522 assert_eq!(keys, vec![Name::from("A"), Name::from("B")]);
523 d.insert(Name::from("C"), Object::Int(3));
524 let keys: Vec<_> = d.iter().map(|(k, _)| k.clone()).collect();
525 assert_eq!(
526 keys,
527 vec![Name::from("A"), Name::from("B"), Name::from("C")]
528 );
529 }
530
531 #[test]
532 fn remove_returns_the_value_and_drops_the_key() {
533 let mut d = Dict::new();
534 d.push(Name::from("A"), Object::Int(1));
535 d.push(Name::from("B"), Object::Null);
536 assert_eq!(d.remove(&Name::from("A")), Some(Object::Int(1)));
537 assert_eq!(d.raw(&Name::from("A")), None);
538 assert_eq!(d.raw(&Name::from("B")), Some(&Object::Null));
539 assert_eq!(d.len(), 1);
540 assert_eq!(d.remove(&Name::from("A")), None);
541 }
542}