1use crate::hash::{FastMap, FastSet};
6use std::cell::{OnceCell, RefCell};
7use std::path::Path;
8use std::rc::Rc;
9use std::sync::Arc;
10
11use crate::crypt::{direct_crypt_filters, Decryptor};
12use crate::date::Date;
13use crate::elements::Span;
14use crate::error::{Error, Result};
15use crate::filters;
16use crate::geom::Rect;
17use crate::object::{Dict, ObjRef, Object, Stream};
18use crate::objstm;
19use crate::parser::{Parser, Resolve};
20use crate::source::{block_on, AsyncObjectSource, Immediate};
21use crate::tree::Entries;
22use crate::xref::{load_xref, Xref, XrefEntry};
23
24const MAX_TREE_DEPTH: usize = 256;
26
27pub struct Document {
29 data: Arc<Vec<u8>>,
30 version: (u8, u8),
31 xref: Arc<Xref>,
32 cache: RefCell<FastMap<(u32, u16), Rc<Object>>>,
34 loading: RefCell<FastSet<u32>>,
37 objstms: RefCell<FastMap<u32, Rc<objstm::ObjStm>>>,
41 decryptor: Option<Decryptor>,
45 pages: OnceCell<Arc<Vec<PageRec>>>,
50}
51
52pub struct DocumentSeed {
60 data: Arc<Vec<u8>>,
61 version: (u8, u8),
62 xref: Arc<Xref>,
63 decryptor: Option<Decryptor>,
64 pages: Arc<Vec<PageRec>>,
65}
66
67impl Clone for DocumentSeed {
68 fn clone(&self) -> DocumentSeed {
69 DocumentSeed {
70 data: Arc::clone(&self.data),
71 version: self.version,
72 xref: Arc::clone(&self.xref),
73 decryptor: self.decryptor.clone(),
74 pages: Arc::clone(&self.pages),
75 }
76 }
77}
78
79impl DocumentSeed {
80 pub fn page_count(&self) -> usize {
83 self.pages.len()
84 }
85}
86
87struct PageRec {
91 obj_ref: Option<ObjRef>,
92 media_box: Rect,
93 crop_box: Rect,
94 bleed_box: Rect,
95 trim_box: Rect,
96 art_box: Rect,
97 rotate: i32,
98 resources: Dict,
99 dict: Dict,
100}
101
102#[derive(Clone, Default)]
104struct Inherited {
105 resources: Option<Dict>,
106 media_box: Option<Rect>,
107 crop_box: Option<Rect>,
108 rotate: Option<i32>,
109}
110
111fn parse_version(data: &[u8]) -> (u8, u8) {
116 try_parse_version(data).unwrap_or((1, 4))
117}
118
119fn try_parse_version(data: &[u8]) -> Option<(u8, u8)> {
121 let window = &data[..data.len().min(1024)];
122 let pos = memchr::memmem::find(window, b"%PDF-")?;
123 let rest = &window[pos + 5..];
124 let (major, used) = read_version_component(rest)?;
125 if rest.get(used) != Some(&b'.') {
126 return None;
127 }
128 let (minor, _) = read_version_component(&rest[used + 1..])?;
129 Some((major, minor))
130}
131
132fn read_version_component(bytes: &[u8]) -> Option<(u8, usize)> {
135 let end = bytes
136 .iter()
137 .position(|b| !b.is_ascii_digit())
138 .unwrap_or(bytes.len());
139 if end == 0 || end > 3 {
140 return None;
141 }
142 let value = std::str::from_utf8(&bytes[..end]).ok()?.parse().ok()?;
143 Some((value, end))
144}
145
146fn normalize_rotation(deg: i32) -> i32 {
149 let r = deg.rem_euclid(360);
150 if r % 90 == 0 {
151 r
152 } else {
153 0
154 }
155}
156
157impl Document {
158 pub fn load(data: Vec<u8>) -> Result<Document> {
168 Document::load_with_password(data, "")
169 }
170
171 pub fn load_with_password(data: Vec<u8>, password: &str) -> Result<Document> {
179 let version = parse_version(&data);
180 let xref = load_xref(&data)?;
181 let mut doc = Document {
182 data: Arc::new(data),
183 version,
184 xref: Arc::new(xref),
185 cache: RefCell::new(FastMap::default()),
186 loading: RefCell::new(FastSet::default()),
187 objstms: RefCell::new(FastMap::default()),
188 decryptor: None,
189 pages: OnceCell::new(),
190 };
191 if doc
192 .xref
193 .trailer
194 .get("Encrypt")
195 .is_some_and(|o| !o.is_null())
196 {
197 doc.setup_decryption(password)?;
198 }
199 Ok(doc)
200 }
201
202 pub fn open_with_password(path: impl AsRef<Path>, password: &str) -> Result<Document> {
205 Document::load_with_password(std::fs::read(path)?, password)
206 }
207
208 fn setup_decryption(&mut self, password: &str) -> Result<()> {
216 let enc_obj = self
217 .xref
218 .trailer
219 .get("Encrypt")
220 .cloned()
221 .unwrap_or(Object::Null);
222 let enc = self.resolve(&enc_obj)?;
223 let enc_dict = enc.as_dict().ok_or(Error::Encrypted)?;
224 let enc_dict = direct_crypt_filters(enc_dict, |r| self.get(r).ok());
227 let id0: Vec<u8> = self
228 .xref
229 .trailer
230 .get("ID")
231 .and_then(Object::as_array)
232 .and_then(<[Object]>::first)
233 .and_then(Object::as_str_bytes)
234 .unwrap_or(&[])
235 .to_vec();
236 match Decryptor::from_standard_with_password_str(&enc_dict, &id0, password) {
237 Some(dec) => {
238 self.decryptor = Some(dec);
239 self.cache.borrow_mut().clear();
243 Ok(())
244 }
245 None => Err(Error::Encrypted),
246 }
247 }
248
249 pub fn open(path: impl AsRef<Path>) -> Result<Document> {
251 Document::load(std::fs::read(path)?)
252 }
253
254 pub fn version(&self) -> (u8, u8) {
256 self.version
257 }
258
259 pub fn bytes(&self) -> &[u8] {
261 &self.data
262 }
263
264 pub fn xref(&self) -> &Xref {
266 &self.xref
267 }
268
269 pub fn is_encrypted(&self) -> bool {
272 self.xref()
273 .trailer
274 .get("Encrypt")
275 .is_some_and(|o| !o.is_null())
276 }
277
278 pub fn is_locked(&self) -> bool {
283 self.is_encrypted() && self.decryptor.is_none()
284 }
285
286 pub fn get(&self, r: ObjRef) -> Result<Object> {
290 if let Some(cached) = self.cache.borrow().get(&(r.num, r.gen)) {
291 return Ok((**cached).clone());
292 }
293 if !self.loading.borrow_mut().insert(r.num) {
294 return Err(Error::CircularReference(r.num));
295 }
296 let result = self.load_object(r);
297 self.loading.borrow_mut().remove(&r.num);
298 let object = result?;
299 self.cache
300 .borrow_mut()
301 .insert((r.num, r.gen), Rc::new(object.clone()));
302 Ok(object)
303 }
304
305 fn load_object(&self, r: ObjRef) -> Result<Object> {
310 match self.xref.get(r.num) {
311 None | Some(XrefEntry::Free) => Err(Error::ObjectNotFound(r.num, r.gen)),
312 Some(XrefEntry::InFile { offset, .. }) => {
313 let offset = usize::try_from(offset)
314 .ok()
315 .filter(|&o| o < self.data.len())
316 .ok_or(Error::ObjectNotFound(r.num, r.gen))?;
317 self.object_at_spanned(offset).map(|parsed| parsed.1)
318 }
319 Some(XrefEntry::InStream { stream_num, index }) => {
320 self.load_from_object_stream(stream_num, index)
321 }
322 }
323 }
324
325 pub(crate) fn object_at_spanned(&self, offset: usize) -> Result<(ObjRef, Object, Span)> {
328 let mut parser = Parser::at(&self.data, offset);
329 let (r, mut object) = parser.parse_indirect(self)?;
330 if let Some(dec) = &self.decryptor {
334 dec.decrypt_object(&mut object, r.num, r.gen);
335 }
336 Ok((r, object, Span::new(offset as u64, parser.pos() as u64)))
337 }
338
339 pub(crate) fn objstm_handle(&self, stream_num: u32) -> Result<Rc<objstm::ObjStm>> {
342 if let Some(stm) = self.objstms.borrow().get(&stream_num) {
343 return Ok(Rc::clone(stm));
344 }
345 let container = self.get(ObjRef {
346 num: stream_num,
347 gen: 0,
348 })?;
349 let stream = container.as_stream().ok_or_else(|| Error::TypeMismatch {
350 expected: "stream",
351 found: type_name(&container),
352 })?;
353 let n = self
354 .resolve(stream.dict.get("N").unwrap_or(&Object::Null))?
355 .as_int()
356 .and_then(|v| usize::try_from(v).ok())
357 .ok_or(Error::MissingKey("N"))?;
358 let first = self
359 .resolve(stream.dict.get("First").unwrap_or(&Object::Null))?
360 .as_int()
361 .and_then(|v| usize::try_from(v).ok())
362 .ok_or(Error::MissingKey("First"))?;
363 let decoded = self.stream_data(stream)?;
364 let stm = Rc::new(objstm::ObjStm::parse(decoded, n, first)?);
365 self.objstms
366 .borrow_mut()
367 .insert(stream_num, Rc::clone(&stm));
368 Ok(stm)
369 }
370
371 fn load_from_object_stream(&self, stream_num: u32, index: u32) -> Result<Object> {
373 self.objstm_handle(stream_num)?.object(index)
374 }
375
376 pub fn resolve(&self, o: &Object) -> Result<Object> {
387 crate::source::resolve_sync_with(self, o)
388 }
389
390 pub fn stream_data(&self, s: &Stream) -> Result<Vec<u8>> {
393 filters::decode_stream(s, self)
394 }
395
396 fn pages(&self) -> &[PageRec] {
398 self.pages.get_or_init(|| Arc::new(self.flatten_pages()))
399 }
400
401 pub fn seed(&self) -> DocumentSeed {
405 DocumentSeed {
406 data: Arc::clone(&self.data),
407 version: self.version,
408 xref: Arc::clone(&self.xref),
409 decryptor: self.decryptor.clone(),
410 pages: Arc::clone(self.pages.get_or_init(|| Arc::new(self.flatten_pages()))),
411 }
412 }
413
414 pub fn from_seed(seed: DocumentSeed) -> Document {
428 Document {
429 data: seed.data,
430 version: seed.version,
431 xref: seed.xref,
432 cache: RefCell::new(FastMap::default()),
433 loading: RefCell::new(FastSet::default()),
434 objstms: RefCell::new(FastMap::default()),
435 decryptor: seed.decryptor,
436 pages: OnceCell::from(seed.pages),
437 }
438 }
439
440 pub fn fork(&self) -> Document {
443 Document::from_seed(self.seed())
444 }
445
446 pub fn page_count(&self) -> usize {
455 if let Some(pages) = self.pages.get() {
456 return pages.len();
457 }
458 if let Some(count) = self.declared_page_count() {
459 return count;
460 }
461 self.pages().len()
462 }
463
464 pub fn flattened_page_count(&self) -> usize {
470 self.pages().len()
471 }
472
473 fn declared_page_count(&self) -> Option<usize> {
481 let root = self.xref.trailer.get("Root")?;
482 let catalog = self.resolve(root).ok()?;
483 let pages = self.resolve(catalog.as_dict()?.get("Pages")?).ok()?;
484 let count = usize::try_from(self.int_value(pages.as_dict()?, "Count")?).ok()?;
485 (count <= self.data.len()).then_some(count)
488 }
489
490 pub fn page(&self, index: usize) -> Result<Page> {
492 let pages = self.pages();
493 let rec = pages
494 .get(index)
495 .ok_or(Error::PageNotFound(index, pages.len()))?;
496 Ok(Page {
497 index,
498 media_box: rec.media_box,
499 crop_box: rec.crop_box,
500 bleed_box: rec.bleed_box,
501 trim_box: rec.trim_box,
502 art_box: rec.art_box,
503 rotate: rec.rotate,
504 resources: rec.resources.clone(),
505 dict: rec.dict.clone(),
506 obj_ref: rec.obj_ref,
507 })
508 }
509
510 pub fn metadata(&self) -> Metadata {
515 let meta = Metadata::default();
516 let Some(info) = self.xref.trailer.get("Info") else {
517 return meta;
518 };
519 let Ok(info) = self.resolve(info) else {
520 return meta;
521 };
522 let Some(dict) = info.as_dict() else {
523 return meta;
524 };
525 block_on(metadata_with(&Immediate(self), dict))
526 }
527
528 pub fn oc_state(&self) -> Option<crate::oc::OcState> {
536 block_on(crate::oc::OcState::load_with(
537 &Immediate(self),
538 &self.xref.trailer,
539 ))
540 }
541
542 pub fn oc_state_for(&self, event: Option<crate::oc::OcEvent>) -> Option<crate::oc::OcState> {
549 block_on(crate::oc::OcState::load_for_with(
550 &Immediate(self),
551 &self.xref.trailer,
552 event,
553 ))
554 }
555
556 pub fn optional_content_groups(
562 &self,
563 event: Option<crate::oc::OcEvent>,
564 ) -> Vec<crate::oc::OcGroup> {
565 block_on(crate::oc::optional_content_groups_with(
566 &Immediate(self),
567 &self.xref.trailer,
568 event,
569 ))
570 }
571
572 pub fn structure_tree(&self) -> Option<crate::structure::StructureTree> {
579 block_on(crate::structure::StructureTree::load_with(
580 &Immediate(self),
581 &self.xref.trailer,
582 ))
583 }
584
585 pub fn content_items(&self, page: &Page) -> Vec<crate::structure::PlacedItem> {
592 let Some(tree) = self.structure_tree() else {
593 return Vec::new();
594 };
595 block_on(tree.content_items_with(&Immediate(self), page))
596 }
597
598 pub fn named(&self, tree: crate::names::NameTree, key: &[u8]) -> Option<Object> {
603 block_on(crate::names::named_with(
604 &Immediate(self),
605 &self.xref.trailer,
606 tree,
607 key,
608 ))
609 }
610
611 pub fn names(&self, tree: crate::names::NameTree) -> Vec<(Vec<u8>, Object)> {
614 block_on(crate::names::names_with(
615 &Immediate(self),
616 &self.xref.trailer,
617 tree,
618 ))
619 }
620
621 pub fn named_destination(&self, name: &[u8]) -> Option<crate::destination::Destination> {
624 block_on(crate::destination::named_destination_with(
625 &Immediate(self),
626 &self.xref.trailer,
627 name,
628 ))
629 }
630
631 pub fn named_destinations(&self) -> Vec<(Vec<u8>, crate::destination::Destination)> {
633 block_on(crate::destination::named_destinations_with(
634 &Immediate(self),
635 &self.xref.trailer,
636 ))
637 }
638
639 pub fn destination(&self, value: &Object) -> Option<crate::destination::Destination> {
643 block_on(crate::destination::destination_value_with(
644 &Immediate(self),
645 &self.xref.trailer,
646 value,
647 ))
648 }
649
650 pub fn outline(&self) -> Vec<crate::outline::OutlineItem> {
653 block_on(crate::outline::outline_with(
654 &Immediate(self),
655 &self.xref.trailer,
656 ))
657 }
658
659 pub fn page_labels(&self) -> Option<Vec<crate::page_label::PageLabel>> {
662 block_on(crate::page_label::page_labels_with(
663 &Immediate(self),
664 &self.xref.trailer,
665 ))
666 }
667
668 pub fn language(&self) -> Option<String> {
671 block_on(crate::language::language_with(
672 &Immediate(self),
673 &self.xref.trailer,
674 ))
675 }
676
677 pub fn language_tag(&self) -> Option<crate::language::LanguageTag> {
681 crate::language::LanguageTag::parse(&self.language()?)
682 }
683
684 pub fn extensions(&self) -> Vec<crate::extension::DeveloperExtension> {
687 block_on(crate::extension::extensions_with(
688 &Immediate(self),
689 &self.xref.trailer,
690 ))
691 }
692
693 pub fn viewer_preferences(&self) -> Option<crate::viewer_preferences::ViewerPreferences> {
696 block_on(crate::viewer_preferences::viewer_preferences_with(
697 &Immediate(self),
698 &self.xref.trailer,
699 ))
700 }
701
702 pub fn linearization(&self) -> Option<crate::linearization::Linearization> {
706 crate::linearization::linearization_dictionary(&self.data)
707 }
708
709 pub fn is_linearized(&self) -> bool {
715 self.linearization()
716 .is_some_and(|record| record.is_current(self.data.len() as u64))
717 }
718
719 pub fn output_intents(&self) -> Vec<crate::output_intent::OutputIntent> {
722 block_on(crate::output_intent::output_intents_with(
723 &Immediate(self),
724 &self.xref.trailer,
725 ))
726 }
727
728 pub fn piece_info(&self) -> Vec<crate::piece_info::PagePiece> {
731 block_on(crate::piece_info::document_piece_info_with(
732 &Immediate(self),
733 &self.xref.trailer,
734 ))
735 }
736
737 pub fn page_piece_info(&self, page: &Page) -> Vec<crate::piece_info::PagePiece> {
740 block_on(crate::piece_info::piece_info_with(
741 &Immediate(self),
742 page.dict(),
743 ))
744 }
745
746 pub fn thumbnail(&self, page: &Page) -> Option<crate::thumbnail::Thumbnail> {
749 block_on(crate::thumbnail::thumbnail_with(&Immediate(self), page))
750 }
751
752 pub fn articles(&self) -> Vec<crate::article::ArticleThread> {
755 block_on(crate::article::articles_with(
756 &Immediate(self),
757 &self.xref.trailer,
758 ))
759 }
760
761 pub fn page_beads(&self, page: &Page) -> Vec<ObjRef> {
764 block_on(crate::article::page_beads_with(&Immediate(self), page))
765 }
766
767 pub fn presentation(&self, page: &Page) -> Option<crate::presentation::Presentation> {
770 block_on(crate::presentation::presentation_with(
771 &Immediate(self),
772 page,
773 ))
774 }
775
776 pub fn viewports(&self, page: &Page) -> Vec<crate::measure::Viewport> {
779 block_on(crate::measure::viewports_with(&Immediate(self), page))
780 }
781
782 pub fn separation_info(&self, page: &Page) -> Option<crate::separation_info::SeparationInfo> {
785 block_on(crate::separation_info::separation_info_with(
786 &Immediate(self),
787 page,
788 ))
789 }
790
791 pub fn annotations(&self, page: &Page) -> Vec<crate::annotation::Annotation> {
794 block_on(crate::annotation::annotations_with(
795 &Immediate(self),
796 &self.xref.trailer,
797 page,
798 ))
799 }
800
801 pub fn page_additional_actions(&self, page: &Page) -> Vec<crate::annotation::TriggeredAction> {
804 block_on(crate::annotation::page_additional_actions_with(
805 &Immediate(self),
806 &self.xref.trailer,
807 page,
808 ))
809 }
810
811 pub fn additional_actions(&self) -> Vec<crate::annotation::TriggeredAction> {
814 block_on(crate::annotation::document_additional_actions_with(
815 &Immediate(self),
816 &self.xref.trailer,
817 ))
818 }
819
820 pub fn action(&self, value: &Object) -> Option<crate::annotation::Action> {
824 block_on(crate::annotation::action_with(
825 &Immediate(self),
826 &self.xref.trailer,
827 value,
828 ))
829 }
830
831 pub fn permission_handlers(&self) -> Option<crate::permission::PermissionHandlers> {
834 block_on(crate::permission::permission_handlers_with(
835 &Immediate(self),
836 &self.xref.trailer,
837 ))
838 }
839
840 pub fn requirements(&self) -> Vec<crate::requirement::Requirement> {
843 block_on(crate::requirement::requirements_with(
844 &Immediate(self),
845 &self.xref.trailer,
846 ))
847 }
848
849 pub fn legal_attestation(&self) -> Option<crate::legal::LegalAttestation> {
852 block_on(crate::legal::legal_attestation_with(
853 &Immediate(self),
854 &self.xref.trailer,
855 ))
856 }
857
858 pub fn interactive_form(&self) -> Option<crate::form::InteractiveForm> {
861 block_on(crate::form::interactive_form_with(
862 &Immediate(self),
863 &self.xref.trailer,
864 ))
865 }
866
867 pub fn form_fields(&self) -> Vec<crate::form::FormField> {
870 block_on(crate::form::form_fields_with(
871 &Immediate(self),
872 &self.xref.trailer,
873 ))
874 }
875
876 pub fn page_label(&self, index: usize) -> Option<String> {
879 if index >= self.page_count() {
880 return None;
881 }
882 let ranges = self.page_labels()?;
883 Some(crate::page_label::page_label(&ranges, index))
884 }
885
886 pub fn embedded_files(&self) -> Vec<crate::embedded_file::EmbeddedFile> {
889 block_on(crate::embedded_file::embedded_files_with(
890 &Immediate(self),
891 &self.xref.trailer,
892 ))
893 }
894
895 pub fn embedded_file_data(&self, file: &crate::embedded_file::EmbeddedFile) -> Result<Vec<u8>> {
902 block_on(crate::embedded_file::embedded_file_data_with(
903 &Immediate(self),
904 file,
905 ))
906 }
907
908 pub fn file_spec_data(&self, spec: &crate::embedded_file::FileSpec) -> Result<Vec<u8>> {
916 block_on(crate::embedded_file::file_spec_data_with(
917 &Immediate(self),
918 spec,
919 ))
920 }
921
922 fn flatten_pages(&self) -> Vec<PageRec> {
929 let mut pages = Vec::new();
930 let Some(root) = self.xref.trailer.get("Root") else {
931 return pages;
932 };
933 let Ok(catalog) = self.resolve(root) else {
934 return pages;
935 };
936 let Some(tree_root) = catalog.as_dict().and_then(|d| d.get("Pages")) else {
937 return pages;
938 };
939 let mut visited: FastSet<ObjRef> = FastSet::default();
940 let mut stack: Vec<(Object, Inherited, usize)> =
941 vec![(tree_root.clone(), Inherited::default(), 0)];
942 while let Some((node, mut inherited, depth)) = stack.pop() {
943 if depth > MAX_TREE_DEPTH {
944 continue;
945 }
946 let node_ref = if let Object::Ref(r) = node {
947 Some(r)
948 } else {
949 None
950 };
951 if let Some(r) = node_ref {
952 if !visited.insert(r) {
953 continue; }
955 }
956 let Ok(resolved) = self.resolve(&node) else {
957 continue;
958 };
959 let Some(dict) = resolved.as_dict() else {
960 continue;
961 };
962 if let Some(res) = self.dict_value(dict, "Resources") {
963 inherited.resources = Some(res);
964 }
965 if let Some(mb) = self.rect_value(dict, "MediaBox") {
966 inherited.media_box = Some(mb);
967 }
968 if let Some(cb) = self.rect_value(dict, "CropBox") {
969 inherited.crop_box = Some(cb);
970 }
971 if let Some(rot) = self.int_value(dict, "Rotate") {
972 inherited.rotate = Some(rot);
973 }
974 let is_page = dict.get_name("Type").is_some_and(|n| n.0 == "Page");
975 let kids = if is_page {
976 None
977 } else {
978 self.array_value(dict, "Kids")
979 };
980 match kids {
981 Some(kids) => {
982 for kid in kids.iter().rev() {
984 stack.push((kid.clone(), inherited.clone(), depth + 1));
985 }
986 }
987 None => {
988 let bleed = self.rect_value(dict, "BleedBox");
992 let trim = self.rect_value(dict, "TrimBox");
993 let art = self.rect_value(dict, "ArtBox");
994 pages.push(make_page_rec(
995 node_ref,
996 dict.clone(),
997 &inherited,
998 bleed,
999 trim,
1000 art,
1001 ));
1002 }
1003 }
1004 }
1005 pages
1006 }
1007
1008 fn dict_value(&self, dict: &Dict, key: &str) -> Option<Dict> {
1010 self.resolve(dict.get(key)?).ok()?.as_dict().cloned()
1011 }
1012
1013 fn array_value(&self, dict: &Dict, key: &str) -> Option<Vec<Object>> {
1015 match self.resolve(dict.get(key)?).ok()? {
1016 Object::Array(items) => Some(items),
1017 _ => None,
1018 }
1019 }
1020
1021 fn int_value(&self, dict: &Dict, key: &str) -> Option<i32> {
1023 let v = self.resolve(dict.get(key)?).ok()?.as_f64()?;
1024 if v.is_finite() {
1025 Some(v as i32)
1026 } else {
1027 None
1028 }
1029 }
1030
1031 fn rect_value(&self, dict: &Dict, key: &str) -> Option<Rect> {
1036 let items = self.array_value(dict, key)?;
1037 if items.len() != 4 {
1038 return None;
1039 }
1040 let mut coords = [0.0f32; 4];
1041 for (slot, item) in coords.iter_mut().zip(&items) {
1042 let n = self.resolve(item).ok()?.as_f64()?;
1043 if !n.is_finite() {
1044 return None;
1045 }
1046 *slot = n as f32;
1047 }
1048 Some(Rect::new(coords[0], coords[1], coords[2], coords[3]).normalize())
1049 }
1050}
1051
1052pub fn map_pages<T, F>(doc: &Document, work: F) -> Vec<Result<T>>
1071where
1072 T: Send + Sync,
1073 F: Fn(&Document, &Page) -> Result<T> + Send + Sync,
1074{
1075 map_pages_forking(doc, || (), |(), doc, page| work(doc, page))
1076}
1077
1078pub fn map_pages_forking<W, T, K, F>(doc: &Document, fork: K, work: F) -> Vec<Result<T>>
1086where
1087 T: Send + Sync,
1088 K: Fn() -> W + Sync,
1089 F: Fn(&W, &Document, &Page) -> Result<T> + Send + Sync,
1090{
1091 let count = doc.pages().len();
1092 let workers = std::thread::available_parallelism()
1093 .map(std::num::NonZeroUsize::get)
1094 .unwrap_or(1)
1095 .min(count);
1096 if workers <= 1 {
1097 let state = fork();
1098 return (0..count)
1099 .map(|i| doc.page(i).and_then(|page| work(&state, doc, &page)))
1100 .collect();
1101 }
1102
1103 let seed = doc.seed();
1104 let next = std::sync::atomic::AtomicUsize::new(0);
1105 let slots: Vec<std::sync::OnceLock<Result<T>>> =
1106 (0..count).map(|_| std::sync::OnceLock::new()).collect();
1107 std::thread::scope(|scope| {
1108 for _ in 0..workers {
1109 let seed = seed.clone();
1113 let next = &next;
1114 let slots = &slots;
1115 let fork = ⋔
1116 let work = &work;
1117 scope.spawn(move || {
1118 let worker = Document::from_seed(seed);
1119 let state = fork();
1120 loop {
1121 let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1122 if i >= count {
1123 break;
1124 }
1125 let outcome = worker.page(i).and_then(|page| work(&state, &worker, &page));
1126 let _ = slots[i].set(outcome);
1127 }
1128 });
1129 }
1130 });
1131 slots
1132 .into_iter()
1133 .map(|slot| {
1134 slot.into_inner()
1135 .unwrap_or_else(|| Err(Error::Other("page index was never dispatched".to_owned())))
1136 })
1137 .collect()
1138}
1139
1140fn make_page_rec(
1147 obj_ref: Option<ObjRef>,
1148 dict: Dict,
1149 inherited: &Inherited,
1150 bleed_box: Option<Rect>,
1151 trim_box: Option<Rect>,
1152 art_box: Option<Rect>,
1153) -> PageRec {
1154 let page = Page::from_tree_attrs(
1155 0,
1156 inherited.resources.clone(),
1157 inherited.media_box,
1158 inherited.crop_box,
1159 bleed_box,
1160 trim_box,
1161 art_box,
1162 inherited.rotate,
1163 dict,
1164 obj_ref,
1165 );
1166 PageRec {
1167 obj_ref: page.obj_ref,
1168 media_box: page.media_box,
1169 crop_box: page.crop_box,
1170 bleed_box: page.bleed_box,
1171 trim_box: page.trim_box,
1172 art_box: page.art_box,
1173 rotate: page.rotate,
1174 resources: page.resources,
1175 dict: page.dict,
1176 }
1177}
1178
1179pub(crate) fn type_name(o: &Object) -> &'static str {
1181 match o {
1182 Object::Null => "null",
1183 Object::Bool(_) => "boolean",
1184 Object::Int(_) => "integer",
1185 Object::Real(_) => "real",
1186 Object::String(_) => "string",
1187 Object::Name(_) => "name",
1188 Object::Array(_) => "array",
1189 Object::Dict(_) => "dictionary",
1190 Object::Stream(_) => "stream",
1191 Object::Ref(_) => "reference",
1192 }
1193}
1194
1195impl Resolve for Document {
1196 fn resolve_ref(&self, r: ObjRef) -> Option<Object> {
1197 self.get(r).ok()
1198 }
1199}
1200
1201impl crate::source::ObjectSource for Document {
1209 fn get(&self, r: ObjRef) -> Result<Object> {
1210 Document::get(self, r)
1211 }
1212
1213 fn stream_data(&self, s: &Stream) -> Result<Vec<u8>> {
1214 Document::stream_data(self, s)
1215 }
1216
1217 fn resolve(&self, o: &Object) -> Result<Object> {
1218 Document::resolve(self, o)
1219 }
1220}
1221
1222#[derive(Debug, Clone, Default, PartialEq, Eq)]
1227pub struct Metadata {
1228 pub title: Option<String>,
1229 pub author: Option<String>,
1230 pub subject: Option<String>,
1231 pub keywords: Option<String>,
1232 pub creator: Option<String>,
1233 pub producer: Option<String>,
1234 pub creation_date: Option<String>,
1235 pub mod_date: Option<String>,
1236}
1237
1238pub async fn metadata_with<S: AsyncObjectSource>(src: &S, dict: &Dict) -> Metadata {
1244 let entries = Entries { src, dict };
1245 Metadata {
1246 title: entries.text("Title").await,
1247 author: entries.text("Author").await,
1248 subject: entries.text("Subject").await,
1249 keywords: entries.text("Keywords").await,
1250 creator: entries.text("Creator").await,
1251 producer: entries.text("Producer").await,
1252 creation_date: entries.text("CreationDate").await,
1253 mod_date: entries.text("ModDate").await,
1254 }
1255}
1256
1257impl Metadata {
1258 pub fn creation_date_parsed(&self) -> Option<Date> {
1263 Date::parse_pdf(self.creation_date.as_deref()?)
1264 }
1265
1266 pub fn mod_date_parsed(&self) -> Option<Date> {
1270 Date::parse_pdf(self.mod_date.as_deref()?)
1271 }
1272}
1273
1274#[derive(Clone)]
1289pub struct Page {
1290 pub index: usize,
1292 pub media_box: Rect,
1293 pub crop_box: Rect,
1294 pub bleed_box: Rect,
1295 pub trim_box: Rect,
1296 pub art_box: Rect,
1297 pub rotate: i32,
1298 pub resources: Dict,
1300 dict: Dict,
1301 obj_ref: Option<ObjRef>,
1302}
1303
1304impl Page {
1305 pub fn from_parts(
1325 index: usize,
1326 media_box: Rect,
1327 crop_box: Rect,
1328 rotate: i32,
1329 resources: Dict,
1330 dict: Dict,
1331 obj_ref: Option<ObjRef>,
1332 ) -> Page {
1333 Page {
1334 index,
1335 media_box,
1336 crop_box,
1337 bleed_box: crop_box,
1338 trim_box: crop_box,
1339 art_box: crop_box,
1340 rotate: normalize_rotation(rotate),
1341 resources,
1342 dict,
1343 obj_ref,
1344 }
1345 }
1346
1347 pub const US_LETTER: Rect = Rect::new(0.0, 0.0, 612.0, 792.0);
1352
1353 #[allow(clippy::too_many_arguments)]
1368 pub fn from_tree_attrs(
1369 index: usize,
1370 resources: Option<Dict>,
1371 media_box: Option<Rect>,
1372 crop_box: Option<Rect>,
1373 bleed_box: Option<Rect>,
1374 trim_box: Option<Rect>,
1375 art_box: Option<Rect>,
1376 rotate: Option<i32>,
1377 dict: Dict,
1378 obj_ref: Option<ObjRef>,
1379 ) -> Page {
1380 let media_box = media_box
1381 .filter(|r| r.width() > 0.0 && r.height() > 0.0)
1382 .unwrap_or(Page::US_LETTER);
1383 let crop_box = crop_box
1384 .and_then(|c| c.intersect(media_box))
1385 .filter(|r| r.width() > 0.0 && r.height() > 0.0)
1386 .unwrap_or(media_box);
1387 let clip = |declared: Option<Rect>| {
1388 declared
1389 .and_then(|b| b.intersect(media_box))
1390 .filter(|r| r.width() > 0.0 && r.height() > 0.0)
1391 .unwrap_or(crop_box)
1392 };
1393 Page {
1394 index,
1395 media_box,
1396 crop_box,
1397 bleed_box: clip(bleed_box),
1398 trim_box: clip(trim_box),
1399 art_box: clip(art_box),
1400 rotate: normalize_rotation(rotate.unwrap_or(0)),
1401 resources: resources.unwrap_or_default(),
1402 dict,
1403 obj_ref,
1404 }
1405 }
1406
1407 pub fn object_ref(&self) -> Option<ObjRef> {
1411 self.obj_ref
1412 }
1413
1414 pub fn content(&self, doc: &Document) -> Result<Vec<u8>> {
1423 block_on(page_content_with(Immediate(doc), self))
1424 }
1425
1426 pub fn size(&self) -> (f32, f32) {
1435 let (w, h) = (self.crop_box.width(), self.crop_box.height());
1436 let turn = self.rotate.rem_euclid(360);
1437 if turn == 90 || turn == 270 {
1438 (h, w)
1439 } else {
1440 (w, h)
1441 }
1442 }
1443
1444 pub fn dict(&self) -> &Dict {
1446 &self.dict
1447 }
1448}
1449
1450pub async fn page_content_with<S: AsyncObjectSource>(src: S, page: &Page) -> Result<Vec<u8>> {
1483 let Some(contents) = page.dict.get("Contents") else {
1484 return Ok(Vec::new());
1485 };
1486 match src.resolve(contents).await? {
1487 Object::Stream(ref s) => content_stream_data_with(&src, s).await,
1488 Object::Array(items) => {
1489 let mut out = Vec::new();
1490 let mut first = true;
1491 for item in &items {
1492 let part = src.resolve(item).await?;
1493 let Some(stream) = part.as_stream() else {
1494 continue; };
1496 if !first {
1497 out.push(b'\n');
1498 }
1499 out.extend_from_slice(&content_stream_data_with(&src, stream).await?);
1500 first = false;
1501 }
1502 Ok(out)
1503 }
1504 _ => Ok(Vec::new()),
1505 }
1506}
1507
1508pub async fn decoded_stream_data_with<S: AsyncObjectSource>(src: S, s: &Stream) -> Result<Vec<u8>> {
1541 if let Some(name) = filters::trailing_filter_checked_with(&src, &s.dict).await? {
1542 if filters::is_image_codec(&name.0) {
1543 return Err(Error::UnsupportedFilter(name.0));
1544 }
1545 }
1546 src.stream_data(s).await
1547}
1548
1549pub async fn content_stream_data_with<S: AsyncObjectSource>(src: S, s: &Stream) -> Result<Vec<u8>> {
1559 decoded_stream_data_with(src, s).await
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564 use super::*;
1565 use crate::object::Name;
1566 use crate::parser::{NoResolve, Parser};
1567 use crate::xref::XrefEntry;
1568 use pdfboss_testkit::{multi_page_doc, objstm_doc, objstm_payload, simple_doc, PdfBuilder};
1569
1570 #[test]
1574 fn async_page_content_matches_the_sync_accessor() {
1575 let doc = Document::load(multi_page_doc(&["one", "two", "three"])).expect("load");
1576 let mut saw_content = false;
1577 for index in 0..3 {
1578 let page = doc.page(index).expect("page");
1579 let direct = page.content(&doc).expect("sync content");
1580 let awaited =
1581 block_on(page_content_with(Immediate(&doc), &page)).expect("async content");
1582 assert_eq!(direct, awaited, "page {index}");
1583 saw_content |= !direct.is_empty();
1584 }
1585 assert!(
1587 saw_content,
1588 "fixture must give at least one page real content"
1589 );
1590 }
1591
1592 #[test]
1600 fn image_codec_page_contents_are_refused_not_returned() {
1601 for codec in ["JPXDecode", "DCTDecode"] {
1602 let mut b = PdfBuilder::new();
1603 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1604 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1605 b.object(
1606 3,
1607 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents 4 0 R >>",
1608 );
1609 b.stream(
1610 4,
1611 &format!("/Filter /{codec}"),
1612 b"1 0 0 rg 0 0 100 100 re f",
1613 );
1614 let doc = Document::load(b.build(1)).expect("load");
1615 let page = doc.page(0).expect("page");
1616 match page.content(&doc) {
1617 Err(Error::UnsupportedFilter(n)) => assert_eq!(n, codec),
1618 other => panic!("{codec} content must be refused, got {other:?}"),
1619 }
1620 }
1621 }
1622
1623 #[test]
1630 fn an_unreadable_filter_refuses_the_content_not_returns_it_raw() {
1631 let mut b = PdfBuilder::new();
1632 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1633 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1634 b.object(
1635 3,
1636 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents 4 0 R >>",
1637 );
1638 b.stream(4, "/Filter 6 0 R", b"1 0 0 rg 0 0 100 100 re f");
1639 b.object(6, "7 0 R");
1640 b.object(7, "6 0 R");
1641 let doc = Document::load(b.build(1)).expect("load");
1642 let page = doc.page(0).expect("page");
1643 assert!(
1644 page.content(&doc).is_err(),
1645 "an unreadable /Filter must refuse, not pass stored bytes"
1646 );
1647 }
1648
1649 #[test]
1654 fn a_shared_helper_is_reachable_from_a_caller_that_owns_its_source() {
1655 let doc = Document::load(simple_doc("Hello")).expect("load");
1656 let page = doc.page(0).expect("page");
1657
1658 let owner = Immediate(&doc);
1659 let through_reference = block_on(page_content_with(&owner, &page)).expect("async content");
1660
1661 assert_eq!(through_reference, page.content(&doc).expect("sync content"));
1662 assert!(!through_reference.is_empty());
1663 }
1664
1665 #[test]
1669 fn the_seed_is_shareable_across_threads() {
1670 fn assert_send_sync<T: Send + Sync>() {}
1671 assert_send_sync::<DocumentSeed>();
1672 }
1673
1674 #[test]
1678 fn a_fork_reads_exactly_what_its_parent_reads() {
1679 let doc = Document::load(pdfboss_testkit::encrypted_rc4_doc("forked secret")).unwrap();
1680 let fork = doc.fork();
1681 assert_eq!(fork.page_count(), doc.page_count());
1682 let (a, b) = (doc.page(0).unwrap(), fork.page(0).unwrap());
1683 assert_eq!(a.media_box, b.media_box);
1684 assert_eq!(
1685 a.content(&doc).expect("parent content"),
1686 b.content(&fork).expect("fork content"),
1687 "decrypted content agrees"
1688 );
1689 }
1690
1691 #[test]
1694 fn the_seed_reports_the_flattened_page_count() {
1695 let doc = Document::load(multi_page_doc(&["one", "two", "three"])).unwrap();
1696 let seed = doc.seed();
1697 assert_eq!(seed.page_count(), 3);
1698 assert_eq!(Document::from_seed(seed).page_count(), 3);
1699 }
1700
1701 #[test]
1705 fn map_pages_preserves_page_order() {
1706 let doc = Document::load(multi_page_doc(&["one", "two", "three"])).unwrap();
1707 let contents = map_pages(&doc, |doc, page| page.content(doc));
1708 assert_eq!(contents.len(), 3);
1709 let texts: Vec<String> = contents
1710 .into_iter()
1711 .map(|c| String::from_utf8_lossy(&c.unwrap()).into_owned())
1712 .collect();
1713 assert!(texts[0].contains("(one)"), "{}", texts[0]);
1714 assert!(texts[1].contains("(two)"), "{}", texts[1]);
1715 assert!(texts[2].contains("(three)"), "{}", texts[2]);
1716 }
1717
1718 #[test]
1722 fn map_pages_forking_forks_the_state_once_per_worker() {
1723 let doc = Document::load(multi_page_doc(&["one", "two", "three"])).unwrap();
1724 let forks = std::sync::atomic::AtomicUsize::new(0);
1725 let tags = map_pages_forking(
1726 &doc,
1727 || forks.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1,
1728 |fork, doc, page| Ok((*fork, page.content(doc)?.len())),
1729 );
1730 let forked = forks.load(std::sync::atomic::Ordering::Relaxed);
1731 assert!((1..=3).contains(&forked), "{forked} forks for three pages");
1732 for outcome in tags {
1733 let (fork, len) = outcome.unwrap();
1734 assert!((1..=forked).contains(&fork));
1735 assert!(len > 0);
1736 }
1737 }
1738
1739 fn replace_once(data: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
1742 let pos = memchr::memmem::find(data, from).expect("pattern present in fixture");
1743 let mut out = Vec::with_capacity(data.len() - from.len() + to.len());
1744 out.extend_from_slice(&data[..pos]);
1745 out.extend_from_slice(to);
1746 out.extend_from_slice(&data[pos + from.len()..]);
1747 out
1748 }
1749
1750 fn contains(haystack: &[u8], needle: &[u8]) -> bool {
1751 memchr::memmem::find(haystack, needle).is_some()
1752 }
1753
1754 const FONT: &str = "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>";
1755
1756 #[test]
1758 fn loads_simple_doc() {
1759 let doc = Document::load(simple_doc("Greetings, cosmos!")).unwrap();
1760 assert_eq!(doc.version(), (1, 7));
1761 assert_eq!(doc.page_count(), 1);
1762 let page = doc.page(0).unwrap();
1763 assert_eq!(page.index, 0);
1764 assert_eq!(page.media_box, Rect::new(0.0, 0.0, 612.0, 792.0));
1765 assert_eq!(page.crop_box, page.media_box);
1766 assert_eq!(page.rotate, 0);
1767 assert_eq!(page.size(), (612.0, 792.0));
1768 assert!(page.resources.get("Font").is_some());
1769 let content = page.content(&doc).unwrap();
1770 assert!(contains(&content, b"Greetings, cosmos!"));
1771 }
1772
1773 #[test]
1775 fn multi_page_ordering() {
1776 let doc = Document::load(multi_page_doc(&["alpha", "beta", "gamma"])).unwrap();
1777 assert_eq!(doc.page_count(), 3);
1778 for (i, text) in ["alpha", "beta", "gamma"].iter().enumerate() {
1779 let content = doc.page(i).unwrap().content(&doc).unwrap();
1780 assert!(
1781 contains(&content, text.as_bytes()),
1782 "page {i} should show {text}"
1783 );
1784 }
1785 }
1786
1787 #[test]
1788 fn page_index_out_of_bounds() {
1789 let doc = Document::load(simple_doc("x")).unwrap();
1790 assert!(matches!(doc.page(5), Err(Error::PageNotFound(5, 1))));
1791 }
1792
1793 #[test]
1794 fn open_reads_from_disk() {
1795 let dir = std::env::temp_dir();
1796 let path = dir.join(format!("pdfboss-doc-test-{}.pdf", std::process::id()));
1797 std::fs::write(&path, simple_doc("from disk")).unwrap();
1798 let doc = Document::open(&path).unwrap();
1799 std::fs::remove_file(&path).ok();
1800 assert_eq!(doc.page_count(), 1);
1801 let content = doc.page(0).unwrap().content(&doc).unwrap();
1802 assert!(contains(&content, b"from disk"));
1803 assert!(matches!(
1804 Document::open(dir.join("pdfboss-doc-test-missing.pdf")),
1805 Err(Error::Io(_))
1806 ));
1807 }
1808
1809 #[test]
1811 fn encrypt_in_trailer_is_rejected() {
1812 let data = replace_once(
1813 &simple_doc("secret"),
1814 b"trailer\n<< /Size",
1815 b"trailer\n<< /Encrypt 9 0 R /Size",
1816 );
1817 assert!(matches!(Document::load(data), Err(Error::Encrypted)));
1818 }
1819
1820 #[test]
1822 fn metadata_utf16be_round_trip() {
1823 let mut b = PdfBuilder::new();
1824 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1825 b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1826 b.object(6, "<< /Title <FEFF00480151> /Author (plain author) >>");
1828 let data = replace_once(&b.build(1), b"<< /Size", b"<< /Info 6 0 R /Size");
1829 let doc = Document::load(data).unwrap();
1830 let meta = doc.metadata();
1831 assert_eq!(meta.title.as_deref(), Some("H\u{151}"));
1832 assert_eq!(meta.author.as_deref(), Some("plain author"));
1833 assert_eq!(meta.subject, None);
1834 assert_eq!(meta.keywords, None);
1835 assert_eq!(meta.creation_date, None);
1836 }
1837
1838 #[test]
1841 fn metadata_dates_parse_per_the_date_clause() {
1842 let mut b = PdfBuilder::new();
1843 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1844 b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1845 b.object(
1847 6,
1848 "<< /CreationDate (D:199812231952-08'00) /ModDate (D:191020717014604) >>",
1849 );
1850 let data = replace_once(&b.build(1), b"<< /Size", b"<< /Info 6 0 R /Size");
1851 let doc = Document::load(data).unwrap();
1852 let meta = doc.metadata();
1853 assert_eq!(meta.creation_date.as_deref(), Some("D:199812231952-08'00"));
1854 assert_eq!(
1855 meta.creation_date_parsed(),
1856 Some(Date {
1857 year: 1998,
1858 month: 12,
1859 day: 23,
1860 hour: 19,
1861 minute: 52,
1862 second: 0,
1863 utc_offset_minutes: -480,
1864 })
1865 );
1866 assert_eq!(meta.mod_date.as_deref(), Some("D:191020717014604"));
1867 assert_eq!(meta.mod_date_parsed(), None, "month 20 is not a date");
1868 }
1869
1870 #[test]
1871 fn metadata_without_info_is_all_none() {
1872 let doc = Document::load(simple_doc("x")).unwrap();
1873 assert_eq!(doc.metadata(), Metadata::default());
1874 }
1875
1876 #[test]
1878 fn missing_object_resolves_to_null() {
1879 let doc = Document::load(simple_doc("x")).unwrap();
1880 let missing = Object::Ref(ObjRef { num: 99, gen: 0 });
1881 assert_eq!(doc.resolve(&missing).unwrap(), Object::Null);
1882 assert!(matches!(
1883 doc.get(ObjRef { num: 99, gen: 0 }),
1884 Err(Error::ObjectNotFound(99, 0))
1885 ));
1886 }
1887
1888 #[test]
1889 fn self_reference_is_circular() {
1890 let mut b = PdfBuilder::new();
1891 b.object(1, "<< /Type /Catalog >>");
1892 b.object(6, "6 0 R");
1893 let doc = Document::load(b.build(1)).unwrap();
1894 let loops = Object::Ref(ObjRef { num: 6, gen: 0 });
1895 assert!(matches!(
1896 doc.resolve(&loops),
1897 Err(Error::CircularReference(6))
1898 ));
1899 }
1900
1901 #[test]
1903 fn generation_mismatch_is_tolerated() {
1904 let doc = Document::load(simple_doc("x")).unwrap();
1905 let catalog = doc.get(ObjRef { num: 1, gen: 7 }).unwrap();
1906 let dict = catalog.as_dict().unwrap();
1907 assert_eq!(dict.get_name("Type").map(|n| n.0.as_str()), Some("Catalog"));
1908 }
1909
1910 #[test]
1912 fn objects_in_object_streams_are_fetched() {
1913 let mut b = PdfBuilder::new();
1914 let (dict, payload) =
1915 objstm_payload(&[(1, "<< /Type /Catalog /Pages 2 0 R >>"), (5, FONT)]);
1916 b.stream(6, &dict, &payload);
1917 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1918 b.object(
1919 3,
1920 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] \
1921 /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>",
1922 );
1923 b.stream(4, "", b"BT /F1 12 Tf (compressed hello) Tj ET");
1924 let doc = Document::load(b.build_xref_stream(1)).unwrap();
1925 assert_eq!(doc.page_count(), 1);
1926 let page = doc.page(0).unwrap();
1927 assert!(contains(&page.content(&doc).unwrap(), b"compressed hello"));
1928 let font = doc.get(ObjRef { num: 5, gen: 0 }).unwrap();
1929 assert_eq!(
1930 font.as_dict()
1931 .and_then(|d| d.get_name("BaseFont"))
1932 .map(|n| n.0.as_str()),
1933 Some("Helvetica")
1934 );
1935 }
1936
1937 #[test]
1939 fn contents_array_is_joined_with_newlines() {
1940 let mut b = PdfBuilder::new();
1941 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1942 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1943 b.object(
1944 3,
1945 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] \
1946 /Contents [4 0 R null 5 0 R] >>",
1947 );
1948 b.stream(4, "", b"q");
1949 b.stream(5, "", b"Q");
1950 let doc = Document::load(b.build(1)).unwrap();
1951 let content = doc.page(0).unwrap().content(&doc).unwrap();
1952 assert_eq!(content, b"q\nQ", "streams joined by \\n, null skipped");
1953 }
1954
1955 #[test]
1957 fn inheritance_from_pages_node_and_rotate_swap() {
1958 let mut b = PdfBuilder::new();
1959 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1960 b.object(
1961 2,
1962 "<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 \
1963 /Resources << /Font << /F1 5 0 R >> >> /MediaBox [0 0 400 600] >>",
1964 );
1965 b.object(3, "<< /Type /Page /Parent 2 0 R >>");
1966 b.object(4, "<< /Type /Page /Parent 2 0 R /Rotate 270 >>");
1967 b.object(5, FONT);
1968 let doc = Document::load(b.build(1)).unwrap();
1969 assert_eq!(doc.page_count(), 2);
1970
1971 let first = doc.page(0).unwrap();
1972 assert_eq!(first.media_box, Rect::new(0.0, 0.0, 400.0, 600.0));
1973 assert_eq!(first.crop_box, first.media_box);
1974 assert!(first.resources.get("Font").is_some(), "inherited resources");
1975 assert_eq!(first.rotate, 0);
1976 assert!(
1977 first.content(&doc).unwrap().is_empty(),
1978 "no /Contents means empty content"
1979 );
1980 assert_eq!(first.size(), (400.0, 600.0));
1981
1982 let second = doc.page(1).unwrap();
1983 assert_eq!(second.rotate, 270);
1984 assert_eq!(second.size(), (600.0, 400.0), "rotate 270 swaps w/h");
1985 }
1986
1987 #[test]
1989 fn crop_box_intersected_and_rotate_normalized() {
1990 let mut b = PdfBuilder::new();
1991 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1992 b.object(2, "<< /Type /Pages /Kids [3 0 R 4 0 R 5 0 R] /Count 3 >>");
1993 b.object(
1994 3,
1995 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] \
1996 /CropBox [100 100 400 400] /Rotate 450 >>",
1997 );
1998 b.object(4, "<< /Type /Page /Parent 2 0 R /Rotate -90 >>");
1999 b.object(
2000 5,
2001 "<< /Type /Page /Parent 2 0 R /Rotate 45 /MediaBox [0 0 0 0] >>",
2002 );
2003 let doc = Document::load(b.build(1)).unwrap();
2004
2005 let clipped = doc.page(0).unwrap();
2006 assert_eq!(clipped.crop_box, Rect::new(100.0, 100.0, 200.0, 200.0));
2007 assert_eq!(clipped.rotate, 90, "450 normalizes to 90");
2008 assert_eq!(clipped.size(), (100.0, 100.0));
2009
2010 assert_eq!(doc.page(1).unwrap().rotate, 270, "-90 normalizes to 270");
2011 let odd = doc.page(2).unwrap();
2012 assert_eq!(odd.rotate, 0, "non-multiple of 90 falls back to 0");
2013 assert_eq!(
2014 odd.media_box,
2015 Page::US_LETTER,
2016 "degenerate media box defaults"
2017 );
2018 }
2019
2020 #[test]
2022 fn page_boxes_declared_and_defaulted() {
2023 let mut b = PdfBuilder::new();
2024 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2025 b.object(2, "<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>");
2026 b.object(
2027 3,
2028 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 600 800] \
2029 /CropBox [50 50 550 750] /BleedBox [40 40 560 760] \
2030 /TrimBox [5 0 R 6 0 R 7 0 R 8 0 R] >>",
2031 );
2032 b.object(4, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 600 800] >>");
2033 b.object(5, "60");
2034 b.object(6, "70");
2035 b.object(7, "540");
2036 b.object(8, "730");
2037 let doc = Document::load(b.build(1)).unwrap();
2038
2039 let page = doc.page(0).unwrap();
2040 assert_eq!(page.bleed_box, Rect::new(40.0, 40.0, 560.0, 760.0));
2041 assert_eq!(
2042 page.trim_box,
2043 Rect::new(60.0, 70.0, 540.0, 730.0),
2044 "indirect array elements resolve"
2045 );
2046 assert_eq!(
2047 page.art_box, page.crop_box,
2048 "undeclared box is the crop box"
2049 );
2050
2051 let bare = doc.page(1).unwrap();
2052 assert_eq!(bare.bleed_box, bare.crop_box);
2053 assert_eq!(bare.trim_box, bare.crop_box);
2054 assert_eq!(bare.art_box, bare.crop_box);
2055 }
2056
2057 #[test]
2059 fn page_boxes_clip_to_media_and_fall_back() {
2060 let mut b = PdfBuilder::new();
2061 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2062 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
2063 b.object(
2064 3,
2065 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] \
2066 /CropBox [10 10 190 190] /TrimBox [100 100 400 400] \
2067 /BleedBox [0 0 0 0] /ArtBox [300 300 400 400] >>",
2068 );
2069 let doc = Document::load(b.build(1)).unwrap();
2070
2071 let page = doc.page(0).unwrap();
2072 assert_eq!(
2073 page.trim_box,
2074 Rect::new(100.0, 100.0, 200.0, 200.0),
2075 "a trim box past the media box clips to it"
2076 );
2077 assert_eq!(
2078 page.bleed_box, page.crop_box,
2079 "a degenerate bleed box falls back to the crop box"
2080 );
2081 assert_eq!(
2082 page.art_box, page.crop_box,
2083 "an art box disjoint from the media box falls back to the crop box"
2084 );
2085 }
2086
2087 #[test]
2092 fn page_boxes_are_not_inherited() {
2093 let mut b = PdfBuilder::new();
2094 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2095 b.object(
2096 2,
2097 "<< /Type /Pages /Kids [3 0 R] /Count 1 \
2098 /TrimBox [10 10 90 90] /BleedBox [5 5 95 95] /ArtBox [20 20 80 80] >>",
2099 );
2100 b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
2101 let doc = Document::load(b.build(1)).unwrap();
2102
2103 let page = doc.page(0).unwrap();
2104 assert_eq!(page.trim_box, page.crop_box);
2105 assert_eq!(page.bleed_box, page.crop_box);
2106 assert_eq!(page.art_box, page.crop_box);
2107 }
2108
2109 #[test]
2111 fn kids_cycle_truncates_without_hanging() {
2112 let mut b = PdfBuilder::new();
2113 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2114 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
2116 b.object(3, "<< /Type /Pages /Kids [4 0 R 2 0 R] /Count 1 >>");
2117 b.object(
2118 4,
2119 "<< /Type /Page /Parent 3 0 R /MediaBox [0 0 100 100] /Contents 5 0 R >>",
2120 );
2121 b.stream(5, "", b"0 0 50 50 re f");
2122 let doc = Document::load(b.build(1)).unwrap();
2123 assert_eq!(doc.page_count(), 1, "cycle back-edge yields no extra pages");
2124 assert!(contains(
2125 &doc.page(0).unwrap().content(&doc).unwrap(),
2126 b"re f"
2127 ));
2128 }
2129
2130 #[test]
2132 fn tree_depth_is_capped() {
2133 let mut b = PdfBuilder::new();
2134 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2135 let last = 302u32;
2137 for num in 2..last {
2138 b.object(
2139 num,
2140 &format!("<< /Type /Pages /Kids [{} 0 R] /Count 1 >>", num + 1),
2141 );
2142 }
2143 b.object(last, "<< /Type /Page >>");
2144 let doc = Document::load(b.build(1)).unwrap();
2145 assert_eq!(doc.page_count(), 1, "declared /Count is reported cheaply");
2150 assert!(
2151 matches!(doc.page(0), Err(Error::PageNotFound(0, 0))),
2152 "leaf beyond the depth cap cannot be materialized"
2153 );
2154 }
2155
2156 #[test]
2158 fn page_count_reports_declared_count_cheaply() {
2159 let mut b = PdfBuilder::new();
2163 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2164 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 5 >>");
2165 b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
2166 let doc = Document::load(b.build(1)).unwrap();
2167 assert_eq!(doc.page_count(), 5, "declared /Count reported verbatim");
2168 assert!(doc.page(0).is_ok(), "the one real page materializes");
2169 assert!(
2170 matches!(doc.page(1), Err(Error::PageNotFound(1, 1))),
2171 "access past the real pages fails with the true length"
2172 );
2173 }
2174
2175 #[test]
2177 fn page_count_falls_back_to_walk_when_count_absent() {
2178 let mut b = PdfBuilder::new();
2179 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2180 b.object(2, "<< /Type /Pages /Kids [3 0 R] >>"); b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
2182 let doc = Document::load(b.build(1)).unwrap();
2183 assert_eq!(
2184 doc.page_count(),
2185 1,
2186 "missing /Count is recovered by walking"
2187 );
2188 }
2189
2190 #[test]
2192 fn page_count_ignores_corrupt_oversized_count() {
2193 let mut b = PdfBuilder::new();
2196 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2197 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 999999999 >>");
2198 b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
2199 let doc = Document::load(b.build(1)).unwrap();
2200 assert_eq!(doc.page_count(), 1, "implausible /Count is rejected");
2201 }
2202
2203 #[test]
2205 fn version_scan_and_default() {
2206 let mut b = PdfBuilder::new().version(2, 0);
2207 b.object(1, "<< /Type /Catalog >>");
2208 assert_eq!(Document::load(b.build(1)).unwrap().version(), (2, 0));
2209 let data = replace_once(&simple_doc("v"), b"%PDF-", b"%QQQ-");
2211 assert_eq!(Document::load(data).unwrap().version(), (1, 4));
2212 }
2213
2214 #[test]
2216 fn deeply_nested_root_object_does_not_overflow_the_stack() {
2217 let mut data = b"%PDF-1.7\n1 0 obj\n".to_vec();
2222 data.extend(std::iter::repeat_n(b'[', 50_000));
2223 data.extend(std::iter::repeat_n(b']', 50_000));
2224 data.extend_from_slice(b"\nendobj\ntrailer\n<</Root 1 0 R>>\n%%EOF\n");
2225 let outcome = std::thread::Builder::new()
2226 .stack_size(1024 * 1024)
2227 .spawn(move || Document::load(data).map(|doc| doc.page_count()))
2228 .expect("spawn test thread")
2229 .join()
2230 .expect("Document::load must not overflow the stack");
2231 assert!(matches!(outcome, Ok(0) | Err(_)));
2234 }
2235
2236 #[test]
2237 fn bytes_and_xref_accessors() {
2238 let data = simple_doc("accessors");
2239 let doc = Document::load(data.clone()).unwrap();
2240 assert_eq!(doc.bytes(), &data[..]);
2241 assert!(!doc.xref().is_empty());
2242 assert!(doc.xref().trailer.get("Root").is_some());
2243 }
2244
2245 #[test]
2246 fn object_at_spanned_reparses_identically() {
2247 let data = simple_doc("spanned");
2248 let doc = Document::load(data).unwrap();
2249 for (num, entry) in doc.xref().iter() {
2250 let XrefEntry::InFile { offset, gen } = entry else {
2251 continue;
2252 };
2253 let (r, object, span) = doc.object_at_spanned(offset as usize).unwrap();
2254 assert_eq!(r.num, num);
2255 assert_eq!(r.gen, gen);
2256 assert_eq!(span.start, offset);
2257 assert!(span.end as usize <= doc.bytes().len());
2258 let slice = &doc.bytes()[span.start as usize..span.end as usize];
2260 let (r2, object2) = Parser::new(slice).parse_indirect(&NoResolve).unwrap();
2261 assert_eq!(r2, r);
2262 assert_eq!(object2, object);
2263 }
2264 }
2265
2266 #[test]
2267 fn page_object_ref_points_at_a_page_dict() {
2268 let doc = Document::load(multi_page_doc(&["one", "two"])).unwrap();
2269 for index in 0..doc.page_count() {
2270 let page = doc.page(index).unwrap();
2271 let r = page.object_ref().expect("builder pages are indirect");
2272 let resolved = doc.get(r).unwrap();
2273 assert_eq!(
2274 resolved
2275 .as_dict()
2276 .unwrap()
2277 .get_name("Type")
2278 .map(|n| n.0.as_str()),
2279 Some("Page")
2280 );
2281 }
2282 }
2283
2284 const RC4_FIXTURE_KEY_LEN: usize = 16; const RC4_FIXTURE_P: i32 = -44;
2294 const RC4_FIXTURE_ID0: &[u8] = b"0123456789abcdef";
2295 const RC4_FIXTURE_PAD: [u8; 32] = [
2296 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01,
2297 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53,
2298 0x69, 0x7A,
2299 ];
2300
2301 #[rustfmt::skip]
2302 const RC4_FIXTURE_MD5_S: [u32; 64] = [
2303 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
2304 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
2305 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
2306 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
2307 ];
2308 #[rustfmt::skip]
2309 const RC4_FIXTURE_MD5_K: [u32; 64] = [
2310 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
2311 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
2312 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
2313 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
2314 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
2315 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
2316 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
2317 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
2318 ];
2319
2320 fn rc4_fixture_md5(input: &[u8]) -> [u8; 16] {
2321 let (mut a0, mut b0, mut c0, mut d0) = (
2322 0x6745_2301u32,
2323 0xefcd_ab89u32,
2324 0x98ba_dcfeu32,
2325 0x1032_5476u32,
2326 );
2327 let mut msg = input.to_vec();
2328 let bitlen = (input.len() as u64).wrapping_mul(8);
2329 msg.push(0x80);
2330 while msg.len() % 64 != 56 {
2331 msg.push(0);
2332 }
2333 msg.extend_from_slice(&bitlen.to_le_bytes());
2334 for chunk in msg.as_chunks::<64>().0 {
2335 let mut m = [0u32; 16];
2336 for (word, bytes) in m.iter_mut().zip(chunk.as_chunks::<4>().0) {
2337 *word = u32::from_le_bytes(*bytes);
2338 }
2339 let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
2340 for i in 0..64 {
2341 let (f, g) = match i {
2342 0..=15 => ((b & c) | (!b & d), i),
2343 16..=31 => ((d & b) | (!d & c), (5 * i + 1) % 16),
2344 32..=47 => (b ^ c ^ d, (3 * i + 5) % 16),
2345 _ => (c ^ (b | !d), (7 * i) % 16),
2346 };
2347 let f = f
2348 .wrapping_add(a)
2349 .wrapping_add(RC4_FIXTURE_MD5_K[i])
2350 .wrapping_add(m[g]);
2351 a = d;
2352 d = c;
2353 c = b;
2354 b = b.wrapping_add(f.rotate_left(RC4_FIXTURE_MD5_S[i]));
2355 }
2356 a0 = a0.wrapping_add(a);
2357 b0 = b0.wrapping_add(b);
2358 c0 = c0.wrapping_add(c);
2359 d0 = d0.wrapping_add(d);
2360 }
2361 let mut out = [0u8; 16];
2362 out[0..4].copy_from_slice(&a0.to_le_bytes());
2363 out[4..8].copy_from_slice(&b0.to_le_bytes());
2364 out[8..12].copy_from_slice(&c0.to_le_bytes());
2365 out[12..16].copy_from_slice(&d0.to_le_bytes());
2366 out
2367 }
2368
2369 fn rc4_fixture_rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
2370 let mut s: [u8; 256] = core::array::from_fn(|i| i as u8);
2371 let mut j = 0u8;
2372 for i in 0..256 {
2373 j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
2374 s.swap(i, j as usize);
2375 }
2376 let mut out = Vec::with_capacity(data.len());
2377 let (mut i, mut j) = (0u8, 0u8);
2378 for &byte in data {
2379 i = i.wrapping_add(1);
2380 j = j.wrapping_add(s[i as usize]);
2381 s.swap(i as usize, j as usize);
2382 let k = s[s[i as usize].wrapping_add(s[j as usize]) as usize];
2383 out.push(byte ^ k);
2384 }
2385 out
2386 }
2387
2388 fn rc4_fixture_owner_entry() -> Vec<u8> {
2390 let mut d = rc4_fixture_md5(&RC4_FIXTURE_PAD);
2391 for _ in 0..50 {
2392 d = rc4_fixture_md5(&d[..RC4_FIXTURE_KEY_LEN]);
2393 }
2394 let rc4key = d[..RC4_FIXTURE_KEY_LEN].to_vec();
2395 let mut o = rc4_fixture_rc4(&rc4key, &RC4_FIXTURE_PAD);
2396 for i in 1u8..=19 {
2397 let k: Vec<u8> = rc4key.iter().map(|b| b ^ i).collect();
2398 o = rc4_fixture_rc4(&k, &o);
2399 }
2400 o
2401 }
2402
2403 fn rc4_fixture_file_key(o: &[u8]) -> Vec<u8> {
2405 let mut input = Vec::new();
2406 input.extend_from_slice(&RC4_FIXTURE_PAD);
2407 input.extend_from_slice(o);
2408 input.extend_from_slice(&(RC4_FIXTURE_P as u32).to_le_bytes());
2409 input.extend_from_slice(RC4_FIXTURE_ID0);
2410 let mut d = rc4_fixture_md5(&input);
2411 for _ in 0..50 {
2412 d = rc4_fixture_md5(&d[..RC4_FIXTURE_KEY_LEN]);
2413 }
2414 d[..RC4_FIXTURE_KEY_LEN].to_vec()
2415 }
2416
2417 fn rc4_fixture_user_entry(key: &[u8]) -> Vec<u8> {
2419 let mut input = Vec::new();
2420 input.extend_from_slice(&RC4_FIXTURE_PAD);
2421 input.extend_from_slice(RC4_FIXTURE_ID0);
2422 let mut x = rc4_fixture_md5(&input).to_vec();
2423 x = rc4_fixture_rc4(key, &x);
2424 for i in 1u8..=19 {
2425 let k: Vec<u8> = key.iter().map(|b| b ^ i).collect();
2426 x = rc4_fixture_rc4(&k, &x);
2427 }
2428 x.resize(32, 0); x
2430 }
2431
2432 fn rc4_fixture_obj_key(key: &[u8], num: u32, gen: u16) -> Vec<u8> {
2433 let mut input = key.to_vec();
2434 input.extend_from_slice(&num.to_le_bytes()[..3]);
2435 input.extend_from_slice(&gen.to_le_bytes()[..2]);
2436 rc4_fixture_md5(&input)[..(key.len() + 5).min(16)].to_vec()
2437 }
2438
2439 fn rc4_fixture_hexstr(b: &[u8]) -> String {
2440 let mut s = String::from("<");
2441 for x in b {
2442 s.push_str(&format!("{x:02x}"));
2443 }
2444 s.push('>');
2445 s
2446 }
2447
2448 fn rc4_encrypted_fixture() -> Vec<u8> {
2452 let o = rc4_fixture_owner_entry();
2453 let key = rc4_fixture_file_key(&o);
2454 let u = rc4_fixture_user_entry(&key);
2455 let msg = rc4_fixture_rc4(&rc4_fixture_obj_key(&key, 3, 0), b"Top secret message");
2456
2457 let mut b = PdfBuilder::new().version(1, 4);
2458 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2459 b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
2460 b.object(3, &format!("<< /Msg {} >>", rc4_fixture_hexstr(&msg)));
2461 b.object(
2462 9,
2463 &format!(
2464 "<< /Filter /Standard /V 2 /R 3 /Length 128 /P {} /O {} /U {} >>",
2465 RC4_FIXTURE_P,
2466 rc4_fixture_hexstr(&o),
2467 rc4_fixture_hexstr(&u)
2468 ),
2469 );
2470 let trailer = format!(
2471 "/Encrypt 9 0 R /ID [{}{}]",
2472 rc4_fixture_hexstr(RC4_FIXTURE_ID0),
2473 rc4_fixture_hexstr(RC4_FIXTURE_ID0)
2474 );
2475 b.trailer_extra(&trailer).build(1)
2476 }
2477
2478 #[test]
2480 fn encrypted_generation_mismatch_still_decrypts() {
2481 let doc = Document::load(rc4_encrypted_fixture()).expect("empty password opens the file");
2491 let obj3 = doc.get(ObjRef { num: 3, gen: 7 }).unwrap();
2492 let msg = obj3
2493 .as_dict()
2494 .unwrap()
2495 .get("Msg")
2496 .unwrap()
2497 .as_str_bytes()
2498 .unwrap();
2499 assert_eq!(
2500 msg, b"Top secret message",
2501 "decrypted using the file's real gen (0), not the mismatched request (7)"
2502 );
2503 }
2504
2505 #[test]
2507 fn objstm_doc_fixture_loads_and_resolves_members() {
2508 let data = objstm_doc(&[(7, "<< /Marker (inside) >>")]);
2509 let doc = Document::load(data).unwrap();
2510 assert_eq!(doc.page_count(), 1);
2511 let member = doc.get(ObjRef { num: 7, gen: 0 }).unwrap();
2512 let text = member.as_dict().unwrap().get("Marker").unwrap();
2513 assert_eq!(text.as_str_bytes(), Some(&b"inside"[..]));
2514 assert!(matches!(
2516 doc.xref().get(7),
2517 Some(XrefEntry::InStream { stream_num: 4, .. })
2518 ));
2519 }
2520
2521 #[test]
2524 fn page_from_parts_exposes_its_accessors() {
2525 let mut dict = Dict::default();
2526 dict.insert(Name("Type".into()), Object::Name(Name("Page".into())));
2527 let media = Rect::new(0.0, 0.0, 200.0, 400.0);
2528 let obj_ref = ObjRef { num: 7, gen: 0 };
2529
2530 let page = Page::from_parts(
2531 3,
2532 media,
2533 media,
2534 90,
2535 Dict::default(),
2536 dict.clone(),
2537 Some(obj_ref),
2538 );
2539
2540 assert_eq!(page.index, 3);
2541 assert_eq!(page.object_ref(), Some(obj_ref));
2542 assert_eq!(page.dict(), &dict);
2543 assert_eq!(page.size(), (400.0, 200.0));
2545 assert_eq!(page.bleed_box, page.crop_box);
2547 assert_eq!(page.trim_box, page.crop_box);
2548 assert_eq!(page.art_box, page.crop_box);
2549 }
2550
2551 #[test]
2555 fn page_from_parts_normalizes_rotation() {
2556 let media = Rect::new(0.0, 0.0, 200.0, 400.0);
2557 let build = |rotate: i32| {
2558 Page::from_parts(
2559 0,
2560 media,
2561 media,
2562 rotate,
2563 Dict::default(),
2564 Dict::default(),
2565 None,
2566 )
2567 };
2568
2569 for (given, canonical) in [(-90, 270), (450, 90), (540, 180), (720, 0), (-360, 0)] {
2570 let page = build(given);
2571 assert_eq!(
2572 page.rotate, canonical,
2573 "from_parts must normalize /Rotate {given} to {canonical}"
2574 );
2575 assert_eq!(
2576 page.size(),
2577 build(canonical).size(),
2578 "/Rotate {given} must report the same size as {canonical}"
2579 );
2580 }
2581 }
2582
2583 #[test]
2588 fn page_size_handles_unnormalized_rotation() {
2589 let media = Rect::new(0.0, 0.0, 200.0, 400.0);
2590 let mut page = Page::from_parts(0, media, media, 0, Dict::default(), Dict::default(), None);
2591
2592 for rotate in [90, 270, -90, -270, 450, 630] {
2593 page.rotate = rotate;
2594 assert_eq!(
2595 page.size(),
2596 (400.0, 200.0),
2597 "/Rotate {rotate} is a quarter turn and must swap the size"
2598 );
2599 }
2600 for rotate in [0, 180, -180, 360, 540, 720] {
2601 page.rotate = rotate;
2602 assert_eq!(
2603 page.size(),
2604 (200.0, 400.0),
2605 "/Rotate {rotate} is a half turn and must leave the size alone"
2606 );
2607 }
2608 }
2609}