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(pairs: impl IntoIterator<Item = (Name, Object)>) -> Self {
85 pairs.into_iter().collect()
86 }
87
88 pub fn push(&mut self, key: Name, value: Object) {
95 self.0.push((key, value));
96 }
97
98 pub fn insert(&mut self, key: Name, value: Object) {
101 if let Some(entry) = self.0.iter_mut().find(|entry| entry.0 == key) {
102 entry.1 = value;
103 } else {
104 self.0.push((key, value));
105 }
106 }
107
108 pub fn remove(&mut self, key: &Name) -> Option<Object> {
110 self.0
111 .iter()
112 .position(|(k, _)| k == key)
113 .map(|index| self.0.remove(index).1)
114 }
115
116 #[must_use]
118 pub fn len(&self) -> usize {
119 self.0.len()
120 }
121
122 #[must_use]
124 pub fn is_empty(&self) -> bool {
125 self.0.is_empty()
126 }
127
128 pub fn iter(&self) -> impl Iterator<Item = &(Name, Object)> {
130 self.0.iter()
131 }
132
133 pub fn keys(&self) -> impl Iterator<Item = &Name> {
135 self.0.iter().map(|(k, _)| k)
136 }
137
138 #[must_use]
140 pub fn contains_key(&self, key: &Name) -> bool {
141 self.raw(key).is_some()
142 }
143
144 #[must_use]
150 pub fn raw(&self, key: &Name) -> Option<&Object> {
151 self.0.iter().rev().find(|(k, _)| k == key).map(|(_, v)| v)
152 }
153
154 #[must_use]
162 pub fn direct_int(&self, key: &Name) -> Option<i64> {
163 self.raw(key)?.as_number()?.as_int()
164 }
165
166 #[must_use]
171 pub fn name(&self, key: &Name) -> Option<&Name> {
172 self.raw(key)?.as_name()
173 }
174
175 #[must_use]
179 pub fn bool(&self, key: &Name) -> Option<bool> {
180 self.raw(key)?.as_bool()
181 }
182
183 #[must_use]
187 pub fn number_obj(&self, key: &Name) -> Option<&Object> {
188 self.raw(key)?.as_number()
189 }
190
191 #[must_use]
193 pub fn string(&self, key: &Name) -> Option<&PdfString> {
194 self.raw(key)?.as_string()
195 }
196
197 #[must_use]
205 pub fn get<'a>(&'a self, key: &Name, r: &impl Resolve) -> Option<Resolved<'a>> {
206 self.raw(key)?.resolve(r).ok()
207 }
208
209 #[must_use]
214 pub fn int(&self, key: &Name, r: &impl Resolve) -> Option<i64> {
215 self.get(key, r)?.as_direct()?.as_int()
216 }
217
218 #[must_use]
220 pub fn number(&self, key: &Name, r: &impl Resolve) -> Option<f32> {
221 self.get(key, r)?.as_direct()?.number()
222 }
223
224 #[must_use]
227 pub fn byte_string(&self, key: &Name, r: &impl Resolve) -> Option<Vec<u8>> {
228 Some(self.get(key, r)?.as_direct()?.to_byte_string())
229 }
230
231 #[must_use]
233 pub fn text(&self, key: &Name, r: &impl Resolve) -> Option<String> {
234 Some(self.get(key, r)?.to_text())
235 }
236
237 #[must_use]
246 pub fn dict(&self, key: &Name, r: &impl Resolve) -> Option<Dict> {
247 self.get(key, r)?.as_direct()?.as_dict().cloned()
248 }
249
250 #[must_use]
252 pub fn array(&self, key: &Name, r: &impl Resolve) -> Option<Array> {
253 self.get(key, r)?.as_direct()?.as_array().cloned()
254 }
255
256 #[must_use]
258 pub fn stream(&self, key: &Name, r: &impl Resolve) -> Option<Stream> {
259 self.get(key, r)?.as_direct()?.as_stream().cloned()
260 }
261
262 #[must_use]
264 pub fn reference(&self, key: &Name) -> Option<crate::ObjRef> {
265 self.raw(key)?.as_ref_id()
266 }
267
268 #[must_use]
273 pub fn rect(&self, key: &Name, r: &impl Resolve) -> Rect {
274 self.array(key, r)
275 .map_or_else(|| Rect::new(0.0, 0.0, 0.0, 0.0), |a| a.as_rect())
276 }
277
278 #[must_use]
281 pub fn matrix(&self, key: &Name, r: &impl Resolve) -> Affine {
282 self.array(key, r)
283 .map_or(Affine::IDENTITY, |a| a.as_matrix())
284 }
285}
286
287impl FromIterator<(Name, Object)> for Dict {
288 fn from_iter<I: IntoIterator<Item = (Name, Object)>>(iter: I) -> Self {
289 let mut dict = Self::new();
290 for (k, v) in iter {
291 dict.push(k, v);
292 }
293 dict
294 }
295}
296
297impl<'a> IntoIterator for &'a Dict {
298 type Item = &'a (Name, Object);
299 type IntoIter = std::slice::Iter<'a, (Name, Object)>;
300
301 fn into_iter(self) -> Self::IntoIter {
302 self.0.iter()
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use pdfrum_common::kurbo::{Affine, Rect};
309
310 use crate::test_resolve::TestStore;
311 use crate::{Array, Dict, Name, NoResolve, ObjRef, Object, PdfString, Stream, names};
312
313 fn key(s: &str) -> Name {
314 Name::from(s)
315 }
316
317 #[test]
321 fn iteration_follows_document_order_not_sort_order() {
322 let dict = Dict::from_pairs([
323 (key("the-dictionary"), Object::Dict(Dict::new())),
324 (key("the-array"), Object::Array(Array::new())),
325 (key("the-number"), Object::Int(42)),
326 ]);
327 let order: Vec<_> = dict.keys().filter_map(Name::as_str).collect();
328 assert_eq!(order, ["the-dictionary", "the-array", "the-number"]);
329 }
330
331 #[test]
332 fn last_duplicate_wins_on_lookup_and_both_are_kept() {
333 let dict = Dict::from_pairs([
334 (key("K"), Object::Int(1)),
335 (key("K"), Object::Int(2)),
336 (key("K"), Object::Int(3)),
337 ]);
338 assert_eq!(dict.raw(&key("K")), Some(&Object::Int(3)));
339 assert_eq!(dict.direct_int(&key("K")), Some(3));
340 assert_eq!(dict.len(), 3, "the file said it three times");
341 }
342
343 #[test]
345 fn name_accessor_is_type_filtered_but_byte_string_coerces() {
346 let dict = Dict::from_pairs([
347 (key("bool"), Object::Bool(false)),
348 (key("num"), Object::Real(0.23)),
349 (key("string"), Object::Str(PdfString::literal(b"ium"))),
350 (key("name"), Object::Name(key("Pdf"))),
351 ]);
352
353 assert_eq!(dict.name(&key("invalid")), None);
354 assert_eq!(dict.name(&key("bool")), None);
355 assert_eq!(dict.name(&key("num")), None);
356 assert_eq!(dict.name(&key("string")), None);
357 assert_eq!(dict.name(&key("name")), Some(&key("Pdf")));
358
359 assert_eq!(dict.byte_string(&key("invalid"), &NoResolve), None);
360 assert_eq!(
361 dict.byte_string(&key("bool"), &NoResolve).as_deref(),
362 Some(&b"false"[..])
363 );
364 assert_eq!(
365 dict.byte_string(&key("num"), &NoResolve).as_deref(),
366 Some(&b".23"[..])
367 );
368 assert_eq!(
369 dict.byte_string(&key("string"), &NoResolve).as_deref(),
370 Some(&b"ium"[..])
371 );
372 assert_eq!(
373 dict.byte_string(&key("name"), &NoResolve).as_deref(),
374 Some(&b"Pdf"[..])
375 );
376 }
377
378 #[test]
379 fn boolean_accessor_rejects_integers() {
380 let dict = Dict::from_pairs([
381 (key("flag"), Object::Bool(true)),
382 (key("one"), Object::Int(1)),
383 ]);
384 assert_eq!(dict.bool(&key("flag")), Some(true));
385 assert_eq!(dict.bool(&key("one")), None, "an Int(1) is not a boolean");
386 }
387
388 #[test]
389 fn direct_int_ignores_indirection_while_int_follows_it() {
390 let store = TestStore::from_pairs([(3, Object::Int(99))]);
391 let dict = Dict::from_pairs([
392 (names::PREV.clone(), Object::Ref(ObjRef::new(3, 0))),
393 (names::LENGTH.clone(), Object::Ref(ObjRef::new(3, 0))),
394 ]);
395 assert_eq!(dict.direct_int(names::PREV), None);
398 assert_eq!(dict.int(names::LENGTH, &store), Some(99));
400 }
401
402 #[test]
403 fn resolution_stops_after_one_level() {
404 let store =
405 TestStore::from_pairs([(1, Object::Ref(ObjRef::new(2, 0))), (2, Object::Int(7))]);
406 let dict = Dict::from_pairs([(key("K"), Object::Ref(ObjRef::new(1, 0)))]);
407 assert_eq!(dict.int(&key("K"), &store), None);
409 assert_eq!(dict.number(&key("K"), &store), None);
410 assert_eq!(
412 dict.get(&key("K"), &store).as_deref(),
413 Some(&Object::Ref(ObjRef::new(2, 0)))
414 );
415 }
416
417 #[test]
418 fn dangling_references_read_as_absent() {
419 let store = TestStore::default();
420 let dict = Dict::from_pairs([(key("K"), Object::Ref(ObjRef::new(9, 0)))]);
421 assert!(dict.get(&key("K"), &store).is_none());
422 assert_eq!(dict.int(&key("K"), &store), None);
423 assert_eq!(dict.dict(&key("K"), &store), None);
424 }
425
426 #[test]
429 fn dict_accessor_accepts_a_stream() {
430 let inner = Dict::from_pairs([(names::LENGTH.clone(), Object::Int(3))]);
431 let stream = Stream::new(inner.clone(), b"abc".to_vec().into());
432 let store = TestStore::from_pairs([(5, Object::Stream(Box::new(stream)))]);
433 let dict = Dict::from_pairs([(key("S"), Object::Ref(ObjRef::new(5, 0)))]);
434
435 assert_eq!(dict.dict(&key("S"), &store), Some(inner));
436 assert!(dict.stream(&key("S"), &store).is_some());
437 assert_eq!(dict.array(&key("S"), &store), None);
438 }
439
440 #[test]
442 fn rect_and_matrix_need_exactly_the_right_element_count() {
443 let four = Object::Array(Array::of([
444 Object::Int(1),
445 Object::Int(2),
446 Object::Int(3),
447 Object::Int(4),
448 ]));
449 let three = Object::Array(Array::of([Object::Int(1), Object::Int(2), Object::Int(3)]));
450 let six = Object::Array(
451 (1..=6)
452 .map(|i| Object::Int(i64::from(i)))
453 .collect::<Array>(),
454 );
455
456 let dict = Dict::from_pairs([
457 (key("four"), four),
458 (key("three"), three),
459 (key("six"), six),
460 ]);
461
462 assert_eq!(
463 dict.rect(&key("four"), &NoResolve),
464 Rect::new(1.0, 2.0, 3.0, 4.0)
465 );
466 assert_eq!(
467 dict.rect(&key("three"), &NoResolve),
468 Rect::new(0.0, 0.0, 0.0, 0.0)
469 );
470 assert_eq!(
471 dict.rect(&key("missing"), &NoResolve),
472 Rect::new(0.0, 0.0, 0.0, 0.0)
473 );
474
475 assert_eq!(
476 dict.matrix(&key("six"), &NoResolve),
477 Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
478 );
479 assert_eq!(dict.matrix(&key("four"), &NoResolve), Affine::IDENTITY);
480 assert_eq!(dict.matrix(&key("missing"), &NoResolve), Affine::IDENTITY);
481 }
482
483 #[test]
484 fn missing_keys_read_as_their_fallbacks_everywhere() {
485 let dict = Dict::new();
486 let absent = key("nope");
487 assert!(dict.is_empty());
488 assert!(!dict.contains_key(&absent));
489 assert_eq!(dict.raw(&absent), None);
490 assert_eq!(dict.int(&absent, &NoResolve), None);
491 assert_eq!(dict.number(&absent, &NoResolve), None);
492 assert_eq!(dict.name(&absent), None);
493 assert_eq!(dict.bool(&absent), None);
494 assert_eq!(dict.string(&absent), None);
495 assert_eq!(dict.text(&absent, &NoResolve), None);
496 assert_eq!(dict.reference(&absent), None);
497 }
498
499 #[test]
500 fn parsed_nulls_are_stored_like_any_other_value() {
501 let dict = Dict::from_pairs([(key("K"), Object::Null)]);
502 assert!(dict.contains_key(&key("K")));
503 assert_eq!(dict.raw(&key("K")), Some(&Object::Null));
504 assert_eq!(dict.int(&key("K"), &NoResolve), None);
505 }
506
507 #[test]
508 fn insert_replaces_in_place_and_appends_when_new() {
509 let mut d = Dict::new();
510 d.push(Name::from("A"), Object::Int(1));
511 d.push(Name::from("B"), Object::Int(2));
512 d.insert(Name::from("B"), Object::Int(20));
513 assert_eq!(d.raw(&Name::from("B")), Some(&Object::Int(20)));
514 let keys: Vec<_> = d.iter().map(|(k, _)| k.clone()).collect();
515 assert_eq!(keys, vec![Name::from("A"), Name::from("B")]);
516 d.insert(Name::from("C"), Object::Int(3));
517 let keys: Vec<_> = d.iter().map(|(k, _)| k.clone()).collect();
518 assert_eq!(
519 keys,
520 vec![Name::from("A"), Name::from("B"), Name::from("C")]
521 );
522 }
523
524 #[test]
525 fn remove_returns_the_value_and_drops_the_key() {
526 let mut d = Dict::new();
527 d.push(Name::from("A"), Object::Int(1));
528 d.push(Name::from("B"), Object::Null);
529 assert_eq!(d.remove(&Name::from("A")), Some(Object::Int(1)));
530 assert_eq!(d.raw(&Name::from("A")), None);
531 assert_eq!(d.raw(&Name::from("B")), Some(&Object::Null));
532 assert_eq!(d.len(), 1);
533 assert_eq!(d.remove(&Name::from("A")), None);
534 }
535}