1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![warn(clippy::indexing_slicing)]
6
7mod array;
8mod dict;
9mod error;
10mod name;
11pub mod names;
12mod number;
13mod object;
14mod resolve;
15mod stream;
16mod string;
17
18#[cfg(test)]
19mod test_resolve;
20
21pub use array::Array;
22pub use dict::Dict;
23pub use error::Error;
24pub use name::{Name, name_decode, name_encode};
25pub use number::{
26 INT_RANGE, fmt_int, fmt_number, narrow_to_signed32, truncate_to_signed32, widen_to_f32,
27};
28pub use object::{ObjRef, Object};
29pub use resolve::{NoResolve, Resolve, Resolved};
30pub use stream::{ByteSpan, Stream};
31pub use string::{
32 PDF_DOC_ENCODING, PdfString, StringSyntax, decode_text, encode_string_hex,
33 encode_string_literal, encode_text,
34};
35
36#[cfg(test)]
37mod tests {
38 use crate::test_resolve::TestStore;
39 use crate::{Array, ByteSpan, Dict, Name, NoResolve, ObjRef, Object, PdfString, Stream, names};
40
41 fn every_variant() -> Dict {
42 Dict::from_pairs([
43 (Name::from("null"), Object::Null),
44 (Name::from("bool"), Object::Bool(true)),
45 (Name::from("int"), Object::Int(1245)),
46 (Name::from("real"), Object::Real(9.003_45)),
47 (
48 Name::from("str"),
49 Object::Str(PdfString::literal(b"A simple test")),
50 ),
51 (
52 Name::from("hexstr"),
53 Object::Str(PdfString::hex(b"\x12\xAC")),
54 ),
55 (Name::from("name"), Object::Name(Name::from("space"))),
56 (
57 Name::from("array"),
58 Object::Array(Array::of([
59 Object::Int(8902),
60 Object::Name(Name::from("address")),
61 ])),
62 ),
63 (
64 Name::from("dict"),
65 Object::Dict(Dict::from_pairs([(Name::from("k"), Object::Int(1))])),
66 ),
67 (Name::from("ref"), Object::Ref(ObjRef::new(7, 0))),
68 ])
69 }
70
71 #[test]
73 fn byte_string_spelling_per_type() {
74 let dict = every_variant();
75 let spell = |k: &str| {
76 dict.byte_string(&Name::from(k), &NoResolve)
77 .unwrap_or_default()
78 };
79 assert_eq!(spell("bool"), b"true");
80 assert_eq!(spell("int"), b"1245");
81 assert_eq!(spell("real"), b"9.00345");
82 assert_eq!(spell("str"), b"A simple test");
83 assert_eq!(spell("name"), b"space");
84 assert_eq!(spell("null"), b"");
86 assert_eq!(spell("array"), b"");
87 assert_eq!(spell("dict"), b"");
88 }
89
90 #[test]
92 fn text_reading_per_type() {
93 let dict = every_variant();
94 let text = |k: &str| dict.text(&Name::from(k), &NoResolve).unwrap_or_default();
95 assert_eq!(text("str"), "A simple test");
96 assert_eq!(text("name"), "space");
97 assert_eq!(text("bool"), "");
98 assert_eq!(text("int"), "");
99 assert_eq!(text("real"), "");
100 assert_eq!(text("array"), "");
101 assert_eq!(text("dict"), "");
102 assert_eq!(text("null"), "");
103 }
104
105 #[test]
107 fn numeric_readings_per_type() {
108 let dict = every_variant();
109 let num = |k: &str| dict.number(&Name::from(k), &NoResolve);
110 let int = |k: &str| dict.int(&Name::from(k), &NoResolve);
111
112 assert_eq!(num("int"), Some(1245.0));
113 assert_eq!(num("real"), Some(9.003_45));
114 assert_eq!(num("bool"), None);
116 assert_eq!(num("str"), None);
117 assert_eq!(num("name"), None);
118 assert_eq!(num("null"), None);
119
120 assert_eq!(int("bool"), Some(1));
122 assert_eq!(int("int"), Some(1245));
123 assert_eq!(int("real"), Some(9));
124 assert_eq!(int("str"), None);
125 assert_eq!(int("name"), None);
126 assert_eq!(int("null"), None);
127 }
128
129 #[test]
130 fn stream_reads_as_its_own_dictionary_and_keeps_its_bytes() {
131 let dict = Dict::from_pairs([(names::LENGTH.clone(), Object::Int(3))]);
132 let stream = Object::Stream(Box::new(Stream::new(
133 dict.clone(),
134 ByteSpan::from(b"abc".to_vec()),
135 )));
136 assert_eq!(stream.as_dict(), Some(&dict));
137 assert_eq!(stream.as_stream().map(|s| &*s.data), Some(&b"abc"[..]));
138 assert_eq!(stream.to_byte_string(), b"");
139 assert_eq!(stream.number(), None);
140 }
141
142 #[test]
145 fn clone_direct_flattens_references() {
146 let store = TestStore::from_pairs([(7, Object::Int(42))]);
147 let obj = Object::Array(Array::of([Object::Ref(ObjRef::new(7, 0)), Object::Int(1)]));
148 assert_eq!(
149 obj.clone_direct(&store),
150 Object::Array(Array::of([Object::Int(42), Object::Int(1)]))
151 );
152 }
153
154 #[test]
155 fn clone_direct_cuts_self_referential_edges() {
156 let store = TestStore::from_pairs([(
158 1,
159 Object::Array(Array::of([Object::Ref(ObjRef::new(1, 0))])),
160 )]);
161 let root = Object::Ref(ObjRef::new(1, 0));
162 assert_eq!(root.clone_direct(&store), Object::Array(Array::new()));
164 }
165
166 #[test]
167 fn clone_direct_cuts_a_dictionary_stream_loop() {
168 let inner = Dict::from_pairs([(Name::from("Self"), Object::Ref(ObjRef::new(2, 0)))]);
170 let store = TestStore::from_pairs([(
171 2,
172 Object::Stream(Box::new(Stream::new(
173 inner,
174 ByteSpan::from(b"data".to_vec()),
175 ))),
176 )]);
177 let cloned = Object::Ref(ObjRef::new(2, 0)).clone_direct(&store);
178 let stream = cloned.as_stream().expect("still a stream");
179 assert!(stream.dict.is_empty(), "the looping key was dropped");
180 assert_eq!(&*stream.data, b"data");
181 }
182
183 #[test]
184 fn clone_direct_keeps_shared_substructure_for_siblings() {
185 let store = TestStore::from_pairs([(3, Object::Int(5))]);
187 let obj = Object::Array(Array::of([
188 Object::Ref(ObjRef::new(3, 0)),
189 Object::Ref(ObjRef::new(3, 0)),
190 ]));
191 assert_eq!(
192 obj.clone_direct(&store),
193 Object::Array(Array::of([Object::Int(5), Object::Int(5)]))
194 );
195 }
196
197 #[test]
198 fn clone_direct_drops_dangling_references() {
199 let store = TestStore::default();
200 let obj = Object::Dict(Dict::from_pairs([
201 (Name::from("gone"), Object::Ref(ObjRef::new(9, 0))),
202 (Name::from("here"), Object::Int(1)),
203 ]));
204 assert_eq!(
205 obj.clone_direct(&store),
206 Object::Dict(Dict::from_pairs([(Name::from("here"), Object::Int(1))]))
207 );
208 }
209
210 #[test]
217 fn clone_direct_stores_a_resolved_stream_as_a_direct_dict_value() {
218 let image = Stream::new(
219 Dict::from_pairs([
220 (names::TYPE.clone(), Object::Name(Name::from("XObject"))),
221 (names::SUBTYPE.clone(), Object::Name(Name::from("Image"))),
222 (names::LENGTH.clone(), Object::Int(4)),
223 ]),
224 ByteSpan::from(b"\xDE\xAD\xBE\xEF".to_vec()),
225 );
226 let store = TestStore::from_pairs([(9, Object::Stream(Box::new(image.clone())))]);
227
228 let resources = Object::Dict(Dict::from_pairs([(
229 Name::from("XObject"),
230 Object::Dict(Dict::from_pairs([(
231 Name::from("Image9"),
232 Object::Ref(ObjRef::new(9, 0)),
233 )])),
234 )]));
235
236 let cloned = resources.clone_direct(&store);
237 let xobject = cloned
238 .as_dict()
239 .and_then(|d| d.raw(&Name::from("XObject")))
240 .and_then(Object::as_dict)
241 .expect("the /XObject sub-dictionary survives");
242 assert_eq!(
244 xobject.raw(&Name::from("Image9")),
245 Some(&Object::Stream(Box::new(image.clone())))
246 );
247 assert_eq!(
249 xobject.stream(&Name::from("Image9"), &NoResolve),
250 Some(image)
251 );
252 }
253
254 #[test]
255 fn clone_direct_stores_a_resolved_stream_as_a_direct_array_element() {
256 let stream = Stream::new(
257 Dict::from_pairs([(names::LENGTH.clone(), Object::Int(2))]),
258 ByteSpan::from(b"hi".to_vec()),
259 );
260 let store = TestStore::from_pairs([(4, Object::Stream(Box::new(stream.clone())))]);
261 let array = Object::Array(Array::of([Object::Ref(ObjRef::new(4, 0))]));
262
263 let cloned = array.clone_direct(&store);
264 let cloned = cloned.as_array().expect("still an array");
265 assert_eq!(
266 cloned.raw_at(0),
267 Some(&Object::Stream(Box::new(stream.clone())))
268 );
269 assert_eq!(cloned.stream_at(0, &NoResolve), Some(stream));
270 }
271
272 #[test]
276 fn clone_direct_keeps_a_streams_raw_bytes_and_filter() {
277 let stream = Stream::new(
278 Dict::from_pairs([
279 (
280 names::FILTER.clone(),
281 Object::Name(Name::from("FlateDecode")),
282 ),
283 (names::LENGTH.clone(), Object::Int(3)),
284 ]),
285 ByteSpan::from(b"\x78\x9C\x03".to_vec()),
286 );
287 let store = TestStore::from_pairs([(1, Object::Stream(Box::new(stream)))]);
288
289 let cloned = Object::Ref(ObjRef::new(1, 0)).clone_direct(&store);
290 let cloned = cloned.as_stream().expect("a stream");
291 assert_eq!(&*cloned.data, b"\x78\x9C\x03");
292 assert_eq!(
293 cloned.dict.name(names::FILTER).map(Name::as_str),
294 Some(Some("FlateDecode"))
295 );
296 }
297
298 #[test]
300 fn clone_direct_clones_a_shared_stream_down_both_sibling_paths() {
301 let stream = Stream::new(Dict::new(), ByteSpan::from(b"xy".to_vec()));
302 let store = TestStore::from_pairs([(6, Object::Stream(Box::new(stream.clone())))]);
303 let dict = Object::Dict(Dict::from_pairs([
304 (Name::from("a"), Object::Ref(ObjRef::new(6, 0))),
305 (Name::from("b"), Object::Ref(ObjRef::new(6, 0))),
306 ]));
307
308 assert_eq!(
309 dict.clone_direct(&store),
310 Object::Dict(Dict::from_pairs([
311 (Name::from("a"), Object::Stream(Box::new(stream.clone()))),
312 (Name::from("b"), Object::Stream(Box::new(stream))),
313 ]))
314 );
315 }
316
317 #[test]
320 fn clone_direct_cuts_only_the_cycle_edge_of_an_inlined_stream() {
321 let inner = Stream::new(
322 Dict::from_pairs([(Name::from("Up"), Object::Ref(ObjRef::new(1, 0)))]),
323 ByteSpan::from(b"body".to_vec()),
324 );
325 let store = TestStore::from_pairs([
326 (
327 1,
328 Object::Dict(Dict::from_pairs([(
329 Name::from("Down"),
330 Object::Ref(ObjRef::new(2, 0)),
331 )])),
332 ),
333 (2, Object::Stream(Box::new(inner))),
334 ]);
335
336 let cloned = Object::Ref(ObjRef::new(1, 0)).clone_direct(&store);
337 let down = cloned
338 .as_dict()
339 .and_then(|d| d.raw(&Name::from("Down")))
340 .and_then(Object::as_stream)
341 .expect("the stream is stored directly under /Down");
342 assert_eq!(&*down.data, b"body");
343 assert!(down.dict.is_empty(), "the /Up cycle edge was cut");
344 }
345
346 #[test]
349 fn containers_accept_a_stream_value_in_memory() {
350 let stream = Object::Stream(Box::new(Stream::new(
351 Dict::new(),
352 ByteSpan::from(b"z".to_vec()),
353 )));
354 let mut dict = Dict::new();
355 dict.push(Name::from("S"), stream.clone());
356 assert!(dict.stream(&Name::from("S"), &NoResolve).is_some());
357
358 let mut array = Array::new();
359 array.push(stream);
360 assert!(array.stream_at(0, &NoResolve).is_some());
361 }
362
363 #[test]
364 fn plain_clone_keeps_references_as_references() {
365 let obj = every_variant();
366 let copy = obj.clone();
367 assert_eq!(obj, copy);
368 assert_eq!(copy.reference(&Name::from("ref")), Some(ObjRef::new(7, 0)));
369 }
370
371 #[test]
372 fn objects_are_send_and_sync() {
373 const fn assert_send_sync<T: Send + Sync>() {}
374 assert_send_sync::<Object>();
375 assert_send_sync::<Dict>();
376 assert_send_sync::<Array>();
377 assert_send_sync::<Stream>();
378 assert_send_sync::<ByteSpan>();
379 assert_send_sync::<Name>();
380 assert_send_sync::<PdfString>();
381 }
382
383 #[test]
384 fn reference_identity_is_by_number_and_generation() {
385 assert_eq!(ObjRef::new(1, 0), ObjRef::new(1, 0));
386 assert_ne!(ObjRef::new(1, 0), ObjRef::new(1, 1));
387 assert!(ObjRef::new(0xFFFF_FFFF, 0).is_invalid());
391 assert!(!ObjRef::new(1, 0).is_invalid());
392 }
393
394 #[test]
395 fn debug_dump_of_a_composite_tree_is_readable() {
396 let dump = format!("{:?}", every_variant());
400 for expected in [
401 "/null",
402 "Null",
403 "Bool(true)",
404 "Int(1245)",
405 "Real(9.00345)",
406 "(A simple test)",
407 "<12AC>",
408 "Name(/space)",
409 "Ref(ObjRef { num: 7, generation: 0 })",
410 ] {
411 assert!(dump.contains(expected), "missing {expected} in {dump}");
412 }
413 let stream = Stream::new(Dict::new(), ByteSpan::from(vec![0u8; 4096]));
414 let stream_dump = format!("{stream:?}");
415 assert!(stream_dump.contains("len: 4096"), "{stream_dump}");
416 assert!(stream_dump.len() < 200, "a stream dump stays short");
417 }
418}