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::Decryptor;
12use crate::elements::Span;
13use crate::error::{Error, Result};
14use crate::filters;
15use crate::geom::Rect;
16use crate::object::{decode_text_string, Dict, ObjRef, Object, Stream};
17use crate::objstm;
18use crate::parser::{Parser, Resolve};
19use crate::source::{block_on, AsyncObjectSource, Immediate};
20use crate::xref::{load_xref, Xref, XrefEntry};
21
22const MAX_TREE_DEPTH: usize = 256;
24
25pub struct Document {
27 data: Arc<Vec<u8>>,
28 version: (u8, u8),
29 xref: Arc<Xref>,
30 cache: RefCell<FastMap<(u32, u16), Rc<Object>>>,
32 loading: RefCell<FastSet<u32>>,
35 objstms: RefCell<FastMap<u32, Rc<objstm::ObjStm>>>,
39 decryptor: Option<Decryptor>,
43 pages: OnceCell<Arc<Vec<PageRec>>>,
48}
49
50pub struct DocumentSeed {
58 data: Arc<Vec<u8>>,
59 version: (u8, u8),
60 xref: Arc<Xref>,
61 decryptor: Option<Decryptor>,
62 pages: Arc<Vec<PageRec>>,
63}
64
65impl Clone for DocumentSeed {
66 fn clone(&self) -> DocumentSeed {
67 DocumentSeed {
68 data: Arc::clone(&self.data),
69 version: self.version,
70 xref: Arc::clone(&self.xref),
71 decryptor: self.decryptor.clone(),
72 pages: Arc::clone(&self.pages),
73 }
74 }
75}
76
77struct PageRec {
79 obj_ref: Option<ObjRef>,
80 media_box: Rect,
81 crop_box: Rect,
82 bleed_box: Rect,
83 trim_box: Rect,
84 art_box: Rect,
85 rotate: i32,
86 resources: Dict,
87 dict: Dict,
88}
89
90#[derive(Clone, Default)]
92struct Inherited {
93 resources: Option<Dict>,
94 media_box: Option<Rect>,
95 crop_box: Option<Rect>,
96 rotate: Option<i32>,
97}
98
99fn parse_version(data: &[u8]) -> (u8, u8) {
102 try_parse_version(data).unwrap_or((1, 4))
103}
104
105fn try_parse_version(data: &[u8]) -> Option<(u8, u8)> {
106 let window = &data[..data.len().min(1024)];
107 let pos = memchr::memmem::find(window, b"%PDF-")?;
108 let rest = &window[pos + 5..];
109 let (major, used) = read_version_component(rest)?;
110 if rest.get(used) != Some(&b'.') {
111 return None;
112 }
113 let (minor, _) = read_version_component(&rest[used + 1..])?;
114 Some((major, minor))
115}
116
117fn read_version_component(bytes: &[u8]) -> Option<(u8, usize)> {
120 let end = bytes
121 .iter()
122 .position(|b| !b.is_ascii_digit())
123 .unwrap_or(bytes.len());
124 if end == 0 || end > 3 {
125 return None;
126 }
127 let value = std::str::from_utf8(&bytes[..end]).ok()?.parse().ok()?;
128 Some((value, end))
129}
130
131fn normalize_rotation(deg: i32) -> i32 {
134 let r = deg.rem_euclid(360);
135 if r % 90 == 0 {
136 r
137 } else {
138 0
139 }
140}
141
142impl Document {
143 pub fn load(data: Vec<u8>) -> Result<Document> {
153 Document::load_with_password(data, "")
154 }
155
156 pub fn load_with_password(data: Vec<u8>, password: &str) -> Result<Document> {
164 let version = parse_version(&data);
165 let xref = load_xref(&data)?;
166 let mut doc = Document {
167 data: Arc::new(data),
168 version,
169 xref: Arc::new(xref),
170 cache: RefCell::new(FastMap::default()),
171 loading: RefCell::new(FastSet::default()),
172 objstms: RefCell::new(FastMap::default()),
173 decryptor: None,
174 pages: OnceCell::new(),
175 };
176 if doc
177 .xref
178 .trailer
179 .get("Encrypt")
180 .is_some_and(|o| !o.is_null())
181 {
182 doc.setup_decryption(password)?;
183 }
184 Ok(doc)
185 }
186
187 pub fn open_with_password(path: impl AsRef<Path>, password: &str) -> Result<Document> {
190 Document::load_with_password(std::fs::read(path)?, password)
191 }
192
193 fn setup_decryption(&mut self, password: &str) -> Result<()> {
201 let enc_obj = self
202 .xref
203 .trailer
204 .get("Encrypt")
205 .cloned()
206 .unwrap_or(Object::Null);
207 let enc = self.resolve(&enc_obj)?;
208 let enc_dict = enc.as_dict().ok_or(Error::Encrypted)?;
209 let id0: Vec<u8> = self
210 .xref
211 .trailer
212 .get("ID")
213 .and_then(Object::as_array)
214 .and_then(<[Object]>::first)
215 .and_then(Object::as_str_bytes)
216 .unwrap_or(&[])
217 .to_vec();
218 match Decryptor::from_standard_with_password_str(enc_dict, &id0, password) {
219 Some(dec) => {
220 self.decryptor = Some(dec);
221 self.cache.borrow_mut().clear();
225 Ok(())
226 }
227 None => Err(Error::Encrypted),
228 }
229 }
230
231 pub fn open(path: impl AsRef<Path>) -> Result<Document> {
233 Document::load(std::fs::read(path)?)
234 }
235
236 pub fn version(&self) -> (u8, u8) {
238 self.version
239 }
240
241 pub fn bytes(&self) -> &[u8] {
243 &self.data
244 }
245
246 pub fn xref(&self) -> &Xref {
248 &self.xref
249 }
250
251 pub fn is_encrypted(&self) -> bool {
254 self.xref()
255 .trailer
256 .get("Encrypt")
257 .is_some_and(|o| !o.is_null())
258 }
259
260 pub fn is_locked(&self) -> bool {
265 self.is_encrypted() && self.decryptor.is_none()
266 }
267
268 pub fn get(&self, r: ObjRef) -> Result<Object> {
272 if let Some(cached) = self.cache.borrow().get(&(r.num, r.gen)) {
273 return Ok((**cached).clone());
274 }
275 if !self.loading.borrow_mut().insert(r.num) {
276 return Err(Error::CircularReference(r.num));
277 }
278 let result = self.load_object(r);
279 self.loading.borrow_mut().remove(&r.num);
280 let object = result?;
281 self.cache
282 .borrow_mut()
283 .insert((r.num, r.gen), Rc::new(object.clone()));
284 Ok(object)
285 }
286
287 fn load_object(&self, r: ObjRef) -> Result<Object> {
290 match self.xref.get(r.num) {
291 None | Some(XrefEntry::Free) => Err(Error::ObjectNotFound(r.num, r.gen)),
292 Some(XrefEntry::InFile { offset, .. }) => {
293 let offset = usize::try_from(offset)
294 .ok()
295 .filter(|&o| o < self.data.len())
296 .ok_or(Error::ObjectNotFound(r.num, r.gen))?;
297 self.object_at_spanned(offset).map(|parsed| parsed.1)
298 }
299 Some(XrefEntry::InStream { stream_num, index }) => {
300 self.load_from_object_stream(stream_num, index)
301 }
302 }
303 }
304
305 pub(crate) fn object_at_spanned(&self, offset: usize) -> Result<(ObjRef, Object, Span)> {
308 let mut parser = Parser::at(&self.data, offset);
309 let (r, mut object) = parser.parse_indirect(self)?;
310 if let Some(dec) = &self.decryptor {
314 dec.decrypt_object(&mut object, r.num, r.gen);
315 }
316 Ok((r, object, Span::new(offset as u64, parser.pos() as u64)))
317 }
318
319 pub(crate) fn objstm_handle(&self, stream_num: u32) -> Result<Rc<objstm::ObjStm>> {
322 if let Some(stm) = self.objstms.borrow().get(&stream_num) {
323 return Ok(Rc::clone(stm));
324 }
325 let container = self.get(ObjRef {
326 num: stream_num,
327 gen: 0,
328 })?;
329 let stream = container.as_stream().ok_or_else(|| Error::TypeMismatch {
330 expected: "stream",
331 found: type_name(&container),
332 })?;
333 let n = self
334 .resolve(stream.dict.get("N").unwrap_or(&Object::Null))?
335 .as_int()
336 .and_then(|v| usize::try_from(v).ok())
337 .ok_or(Error::MissingKey("N"))?;
338 let first = self
339 .resolve(stream.dict.get("First").unwrap_or(&Object::Null))?
340 .as_int()
341 .and_then(|v| usize::try_from(v).ok())
342 .ok_or(Error::MissingKey("First"))?;
343 let decoded = self.stream_data(stream)?;
344 let stm = Rc::new(objstm::ObjStm::parse(decoded, n, first)?);
345 self.objstms
346 .borrow_mut()
347 .insert(stream_num, Rc::clone(&stm));
348 Ok(stm)
349 }
350
351 fn load_from_object_stream(&self, stream_num: u32, index: u32) -> Result<Object> {
353 self.objstm_handle(stream_num)?.object(index)
354 }
355
356 pub fn resolve(&self, o: &Object) -> Result<Object> {
367 crate::source::resolve_sync_with(self, o)
368 }
369
370 pub fn stream_data(&self, s: &Stream) -> Result<Vec<u8>> {
373 filters::decode_stream(s, self)
374 }
375
376 fn pages(&self) -> &[PageRec] {
378 self.pages.get_or_init(|| Arc::new(self.flatten_pages()))
379 }
380
381 pub fn seed(&self) -> DocumentSeed {
385 self.pages();
386 DocumentSeed {
387 data: Arc::clone(&self.data),
388 version: self.version,
389 xref: Arc::clone(&self.xref),
390 decryptor: self.decryptor.clone(),
391 pages: Arc::clone(self.pages.get().expect("pages() was just forced")),
392 }
393 }
394
395 pub fn from_seed(seed: DocumentSeed) -> Document {
409 Document {
410 data: seed.data,
411 version: seed.version,
412 xref: seed.xref,
413 cache: RefCell::new(FastMap::default()),
414 loading: RefCell::new(FastSet::default()),
415 objstms: RefCell::new(FastMap::default()),
416 decryptor: seed.decryptor,
417 pages: OnceCell::from(seed.pages),
418 }
419 }
420
421 pub fn fork(&self) -> Document {
424 Document::from_seed(self.seed())
425 }
426
427 pub fn page_count(&self) -> usize {
436 if let Some(pages) = self.pages.get() {
437 return pages.len();
438 }
439 if let Some(count) = self.declared_page_count() {
440 return count;
441 }
442 self.pages().len()
443 }
444
445 fn declared_page_count(&self) -> Option<usize> {
451 let root = self.xref.trailer.get("Root")?;
452 let catalog = self.resolve(root).ok()?;
453 let pages = self.resolve(catalog.as_dict()?.get("Pages")?).ok()?;
454 let count = usize::try_from(self.int_value(pages.as_dict()?, "Count")?).ok()?;
455 (count <= self.data.len()).then_some(count)
458 }
459
460 pub fn page(&self, index: usize) -> Result<Page> {
462 let pages = self.pages();
463 let rec = pages
464 .get(index)
465 .ok_or(Error::PageNotFound(index, pages.len()))?;
466 Ok(Page {
467 index,
468 media_box: rec.media_box,
469 crop_box: rec.crop_box,
470 bleed_box: rec.bleed_box,
471 trim_box: rec.trim_box,
472 art_box: rec.art_box,
473 rotate: rec.rotate,
474 resources: rec.resources.clone(),
475 dict: rec.dict.clone(),
476 obj_ref: rec.obj_ref,
477 })
478 }
479
480 pub fn metadata(&self) -> Metadata {
483 let mut meta = Metadata::default();
484 let Some(info) = self.xref.trailer.get("Info") else {
485 return meta;
486 };
487 let Ok(info) = self.resolve(info) else {
488 return meta;
489 };
490 let Some(dict) = info.as_dict() else {
491 return meta;
492 };
493 meta.title = self.meta_string(dict, "Title");
494 meta.author = self.meta_string(dict, "Author");
495 meta.subject = self.meta_string(dict, "Subject");
496 meta.keywords = self.meta_string(dict, "Keywords");
497 meta.creator = self.meta_string(dict, "Creator");
498 meta.producer = self.meta_string(dict, "Producer");
499 meta.creation_date = self.meta_string(dict, "CreationDate");
500 meta.mod_date = self.meta_string(dict, "ModDate");
501 meta
502 }
503
504 pub fn oc_state(&self) -> Option<crate::oc::OcState> {
509 block_on(crate::oc::OcState::load_with(
510 &Immediate(self),
511 &self.xref.trailer,
512 ))
513 }
514
515 pub fn structure_tree(&self) -> Option<crate::structure::StructureTree> {
519 block_on(crate::structure::StructureTree::load_with(
520 &Immediate(self),
521 &self.xref.trailer,
522 ))
523 }
524
525 fn meta_string(&self, dict: &Dict, key: &str) -> Option<String> {
527 let value = self.resolve(dict.get(key)?).ok()?;
528 Some(decode_text_string(value.as_str_bytes()?))
529 }
530
531 fn flatten_pages(&self) -> Vec<PageRec> {
536 let mut pages = Vec::new();
537 let Some(root) = self.xref.trailer.get("Root") else {
538 return pages;
539 };
540 let Ok(catalog) = self.resolve(root) else {
541 return pages;
542 };
543 let Some(tree_root) = catalog.as_dict().and_then(|d| d.get("Pages")) else {
544 return pages;
545 };
546 let mut visited: FastSet<ObjRef> = FastSet::default();
547 let mut stack: Vec<(Object, Inherited, usize)> =
548 vec![(tree_root.clone(), Inherited::default(), 0)];
549 while let Some((node, mut inherited, depth)) = stack.pop() {
550 if depth > MAX_TREE_DEPTH {
551 continue;
552 }
553 let node_ref = if let Object::Ref(r) = node {
554 Some(r)
555 } else {
556 None
557 };
558 if let Some(r) = node_ref {
559 if !visited.insert(r) {
560 continue; }
562 }
563 let Ok(resolved) = self.resolve(&node) else {
564 continue;
565 };
566 let Some(dict) = resolved.as_dict() else {
567 continue;
568 };
569 if let Some(res) = self.dict_value(dict, "Resources") {
570 inherited.resources = Some(res);
571 }
572 if let Some(mb) = self.rect_value(dict, "MediaBox") {
573 inherited.media_box = Some(mb);
574 }
575 if let Some(cb) = self.rect_value(dict, "CropBox") {
576 inherited.crop_box = Some(cb);
577 }
578 if let Some(rot) = self.int_value(dict, "Rotate") {
579 inherited.rotate = Some(rot);
580 }
581 let is_page = dict.get_name("Type").is_some_and(|n| n.0 == "Page");
582 let kids = if is_page {
583 None
584 } else {
585 self.array_value(dict, "Kids")
586 };
587 match kids {
588 Some(kids) => {
589 for kid in kids.iter().rev() {
591 stack.push((kid.clone(), inherited.clone(), depth + 1));
592 }
593 }
594 None => {
595 let bleed = self.rect_value(dict, "BleedBox");
599 let trim = self.rect_value(dict, "TrimBox");
600 let art = self.rect_value(dict, "ArtBox");
601 pages.push(make_page_rec(
602 node_ref,
603 dict.clone(),
604 &inherited,
605 bleed,
606 trim,
607 art,
608 ));
609 }
610 }
611 }
612 pages
613 }
614
615 fn dict_value(&self, dict: &Dict, key: &str) -> Option<Dict> {
617 self.resolve(dict.get(key)?).ok()?.as_dict().cloned()
618 }
619
620 fn array_value(&self, dict: &Dict, key: &str) -> Option<Vec<Object>> {
622 match self.resolve(dict.get(key)?).ok()? {
623 Object::Array(items) => Some(items),
624 _ => None,
625 }
626 }
627
628 fn int_value(&self, dict: &Dict, key: &str) -> Option<i32> {
630 let v = self.resolve(dict.get(key)?).ok()?.as_f64()?;
631 if v.is_finite() {
632 Some(v as i32)
633 } else {
634 None
635 }
636 }
637
638 fn rect_value(&self, dict: &Dict, key: &str) -> Option<Rect> {
641 let items = self.array_value(dict, key)?;
642 if items.len() != 4 {
643 return None;
644 }
645 let mut coords = [0.0f32; 4];
646 for (slot, item) in coords.iter_mut().zip(&items) {
647 let n = self.resolve(item).ok()?.as_f64()?;
648 if !n.is_finite() {
649 return None;
650 }
651 *slot = n as f32;
652 }
653 Some(Rect::new(coords[0], coords[1], coords[2], coords[3]).normalize())
654 }
655}
656
657pub fn map_pages<T, F>(doc: &Document, work: F) -> Vec<Result<T>>
676where
677 T: Send + Sync,
678 F: Fn(&Document, &Page) -> Result<T> + Send + Sync,
679{
680 let count = doc.pages().len();
681 let workers = std::thread::available_parallelism()
682 .map(std::num::NonZeroUsize::get)
683 .unwrap_or(1)
684 .min(count);
685 if workers <= 1 {
686 return (0..count)
687 .map(|i| doc.page(i).and_then(|page| work(doc, &page)))
688 .collect();
689 }
690
691 let seed = doc.seed();
692 let next = std::sync::atomic::AtomicUsize::new(0);
693 let slots: Vec<std::sync::OnceLock<Result<T>>> =
694 (0..count).map(|_| std::sync::OnceLock::new()).collect();
695 std::thread::scope(|scope| {
696 for _ in 0..workers {
697 let seed = seed.clone();
700 let next = &next;
701 let slots = &slots;
702 let work = &work;
703 scope.spawn(move || {
704 let worker = Document::from_seed(seed);
705 loop {
706 let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
707 if i >= count {
708 break;
709 }
710 let outcome = worker.page(i).and_then(|page| work(&worker, &page));
711 if slots[i].set(outcome).is_err() {
716 unreachable!("page index {i} was dispatched twice");
717 }
718 }
719 });
720 }
721 });
722 slots
723 .into_iter()
724 .map(|slot| {
725 slot.into_inner()
726 .expect("every page index below the count was dispatched")
727 })
728 .collect()
729}
730
731fn make_page_rec(
736 obj_ref: Option<ObjRef>,
737 dict: Dict,
738 inherited: &Inherited,
739 bleed_box: Option<Rect>,
740 trim_box: Option<Rect>,
741 art_box: Option<Rect>,
742) -> PageRec {
743 let page = Page::from_tree_attrs(
744 0,
745 inherited.resources.clone(),
746 inherited.media_box,
747 inherited.crop_box,
748 bleed_box,
749 trim_box,
750 art_box,
751 inherited.rotate,
752 dict,
753 obj_ref,
754 );
755 PageRec {
756 obj_ref: page.obj_ref,
757 media_box: page.media_box,
758 crop_box: page.crop_box,
759 bleed_box: page.bleed_box,
760 trim_box: page.trim_box,
761 art_box: page.art_box,
762 rotate: page.rotate,
763 resources: page.resources,
764 dict: page.dict,
765 }
766}
767
768fn type_name(o: &Object) -> &'static str {
770 match o {
771 Object::Null => "null",
772 Object::Bool(_) => "boolean",
773 Object::Int(_) => "integer",
774 Object::Real(_) => "real",
775 Object::String(_) => "string",
776 Object::Name(_) => "name",
777 Object::Array(_) => "array",
778 Object::Dict(_) => "dictionary",
779 Object::Stream(_) => "stream",
780 Object::Ref(_) => "reference",
781 }
782}
783
784impl Resolve for Document {
785 fn resolve_ref(&self, r: ObjRef) -> Option<Object> {
786 self.get(r).ok()
787 }
788}
789
790impl crate::source::ObjectSource for Document {
798 fn get(&self, r: ObjRef) -> Result<Object> {
799 Document::get(self, r)
800 }
801
802 fn stream_data(&self, s: &Stream) -> Result<Vec<u8>> {
803 Document::stream_data(self, s)
804 }
805
806 fn resolve(&self, o: &Object) -> Result<Object> {
807 Document::resolve(self, o)
808 }
809}
810
811#[derive(Debug, Clone, Default, PartialEq, Eq)]
814pub struct Metadata {
815 pub title: Option<String>,
816 pub author: Option<String>,
817 pub subject: Option<String>,
818 pub keywords: Option<String>,
819 pub creator: Option<String>,
820 pub producer: Option<String>,
821 pub creation_date: Option<String>,
822 pub mod_date: Option<String>,
823}
824
825#[derive(Clone)]
840pub struct Page {
841 pub index: usize,
843 pub media_box: Rect,
844 pub crop_box: Rect,
845 pub bleed_box: Rect,
846 pub trim_box: Rect,
847 pub art_box: Rect,
848 pub rotate: i32,
849 pub resources: Dict,
851 dict: Dict,
852 obj_ref: Option<ObjRef>,
853}
854
855impl Page {
856 pub fn from_parts(
874 index: usize,
875 media_box: Rect,
876 crop_box: Rect,
877 rotate: i32,
878 resources: Dict,
879 dict: Dict,
880 obj_ref: Option<ObjRef>,
881 ) -> Page {
882 Page {
883 index,
884 media_box,
885 crop_box,
886 bleed_box: crop_box,
887 trim_box: crop_box,
888 art_box: crop_box,
889 rotate: normalize_rotation(rotate),
890 resources,
891 dict,
892 obj_ref,
893 }
894 }
895
896 pub const US_LETTER: Rect = Rect::new(0.0, 0.0, 612.0, 792.0);
901
902 #[allow(clippy::too_many_arguments)]
917 pub fn from_tree_attrs(
918 index: usize,
919 resources: Option<Dict>,
920 media_box: Option<Rect>,
921 crop_box: Option<Rect>,
922 bleed_box: Option<Rect>,
923 trim_box: Option<Rect>,
924 art_box: Option<Rect>,
925 rotate: Option<i32>,
926 dict: Dict,
927 obj_ref: Option<ObjRef>,
928 ) -> Page {
929 let media_box = media_box
930 .filter(|r| r.width() > 0.0 && r.height() > 0.0)
931 .unwrap_or(Page::US_LETTER);
932 let crop_box = crop_box
933 .and_then(|c| c.intersect(media_box))
934 .filter(|r| r.width() > 0.0 && r.height() > 0.0)
935 .unwrap_or(media_box);
936 let clip = |declared: Option<Rect>| {
937 declared
938 .and_then(|b| b.intersect(media_box))
939 .filter(|r| r.width() > 0.0 && r.height() > 0.0)
940 .unwrap_or(crop_box)
941 };
942 Page {
943 index,
944 media_box,
945 crop_box,
946 bleed_box: clip(bleed_box),
947 trim_box: clip(trim_box),
948 art_box: clip(art_box),
949 rotate: normalize_rotation(rotate.unwrap_or(0)),
950 resources: resources.unwrap_or_default(),
951 dict,
952 obj_ref,
953 }
954 }
955
956 pub fn object_ref(&self) -> Option<ObjRef> {
960 self.obj_ref
961 }
962
963 pub fn content(&self, doc: &Document) -> Result<Vec<u8>> {
972 block_on(page_content_with(Immediate(doc), self))
973 }
974
975 pub fn size(&self) -> (f32, f32) {
984 let (w, h) = (self.crop_box.width(), self.crop_box.height());
985 let turn = self.rotate.rem_euclid(360);
986 if turn == 90 || turn == 270 {
987 (h, w)
988 } else {
989 (w, h)
990 }
991 }
992
993 pub fn dict(&self) -> &Dict {
995 &self.dict
996 }
997}
998
999pub async fn page_content_with<S: AsyncObjectSource>(src: S, page: &Page) -> Result<Vec<u8>> {
1032 let Some(contents) = page.dict.get("Contents") else {
1033 return Ok(Vec::new());
1034 };
1035 match src.resolve(contents).await? {
1036 Object::Stream(ref s) => content_stream_data_with(&src, s).await,
1037 Object::Array(items) => {
1038 let mut out = Vec::new();
1039 let mut first = true;
1040 for item in &items {
1041 let part = src.resolve(item).await?;
1042 let Some(stream) = part.as_stream() else {
1043 continue; };
1045 if !first {
1046 out.push(b'\n');
1047 }
1048 out.extend_from_slice(&content_stream_data_with(&src, stream).await?);
1049 first = false;
1050 }
1051 Ok(out)
1052 }
1053 _ => Ok(Vec::new()),
1054 }
1055}
1056
1057pub async fn decoded_stream_data_with<S: AsyncObjectSource>(src: S, s: &Stream) -> Result<Vec<u8>> {
1090 if let Some(name) = filters::trailing_filter_checked_with(&src, &s.dict).await? {
1091 if filters::is_image_codec(&name.0) {
1092 return Err(Error::UnsupportedFilter(name.0));
1093 }
1094 }
1095 src.stream_data(s).await
1096}
1097
1098pub async fn content_stream_data_with<S: AsyncObjectSource>(src: S, s: &Stream) -> Result<Vec<u8>> {
1108 decoded_stream_data_with(src, s).await
1109}
1110
1111#[cfg(test)]
1112mod tests {
1113 use super::*;
1114 use crate::object::Name;
1115 use crate::parser::{NoResolve, Parser};
1116 use crate::xref::XrefEntry;
1117 use pdfboss_testkit::{multi_page_doc, objstm_doc, objstm_payload, simple_doc, PdfBuilder};
1118
1119 #[test]
1123 fn async_page_content_matches_the_sync_accessor() {
1124 let doc = Document::load(multi_page_doc(&["one", "two", "three"])).expect("load");
1125 let mut saw_content = false;
1126 for index in 0..3 {
1127 let page = doc.page(index).expect("page");
1128 let direct = page.content(&doc).expect("sync content");
1129 let awaited =
1130 block_on(page_content_with(Immediate(&doc), &page)).expect("async content");
1131 assert_eq!(direct, awaited, "page {index}");
1132 saw_content |= !direct.is_empty();
1133 }
1134 assert!(
1136 saw_content,
1137 "fixture must give at least one page real content"
1138 );
1139 }
1140
1141 #[test]
1149 fn image_codec_page_contents_are_refused_not_returned() {
1150 for codec in ["JPXDecode", "DCTDecode"] {
1151 let mut b = PdfBuilder::new();
1152 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1153 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1154 b.object(
1155 3,
1156 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents 4 0 R >>",
1157 );
1158 b.stream(
1159 4,
1160 &format!("/Filter /{codec}"),
1161 b"1 0 0 rg 0 0 100 100 re f",
1162 );
1163 let doc = Document::load(b.build(1)).expect("load");
1164 let page = doc.page(0).expect("page");
1165 match page.content(&doc) {
1166 Err(Error::UnsupportedFilter(n)) => assert_eq!(n, codec),
1167 other => panic!("{codec} content must be refused, got {other:?}"),
1168 }
1169 }
1170 }
1171
1172 #[test]
1179 fn an_unreadable_filter_refuses_the_content_not_returns_it_raw() {
1180 let mut b = PdfBuilder::new();
1181 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1182 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1183 b.object(
1184 3,
1185 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Contents 4 0 R >>",
1186 );
1187 b.stream(4, "/Filter 6 0 R", b"1 0 0 rg 0 0 100 100 re f");
1188 b.object(6, "7 0 R");
1189 b.object(7, "6 0 R");
1190 let doc = Document::load(b.build(1)).expect("load");
1191 let page = doc.page(0).expect("page");
1192 assert!(
1193 page.content(&doc).is_err(),
1194 "an unreadable /Filter must refuse, not pass stored bytes"
1195 );
1196 }
1197
1198 #[test]
1203 fn a_shared_helper_is_reachable_from_a_caller_that_owns_its_source() {
1204 let doc = Document::load(simple_doc("Hello")).expect("load");
1205 let page = doc.page(0).expect("page");
1206
1207 let owner = Immediate(&doc);
1208 let through_reference = block_on(page_content_with(&owner, &page)).expect("async content");
1209
1210 assert_eq!(through_reference, page.content(&doc).expect("sync content"));
1211 assert!(!through_reference.is_empty());
1212 }
1213
1214 #[test]
1218 fn the_seed_is_shareable_across_threads() {
1219 fn assert_send_sync<T: Send + Sync>() {}
1220 assert_send_sync::<DocumentSeed>();
1221 }
1222
1223 #[test]
1227 fn a_fork_reads_exactly_what_its_parent_reads() {
1228 let doc = Document::load(pdfboss_testkit::encrypted_rc4_doc("forked secret")).unwrap();
1229 let fork = doc.fork();
1230 assert_eq!(fork.page_count(), doc.page_count());
1231 let (a, b) = (doc.page(0).unwrap(), fork.page(0).unwrap());
1232 assert_eq!(a.media_box, b.media_box);
1233 assert_eq!(
1234 a.content(&doc).expect("parent content"),
1235 b.content(&fork).expect("fork content"),
1236 "decrypted content agrees"
1237 );
1238 }
1239
1240 #[test]
1244 fn map_pages_preserves_page_order() {
1245 let doc = Document::load(multi_page_doc(&["one", "two", "three"])).unwrap();
1246 let contents = map_pages(&doc, |doc, page| page.content(doc));
1247 assert_eq!(contents.len(), 3);
1248 let texts: Vec<String> = contents
1249 .into_iter()
1250 .map(|c| String::from_utf8_lossy(&c.unwrap()).into_owned())
1251 .collect();
1252 assert!(texts[0].contains("(one)"), "{}", texts[0]);
1253 assert!(texts[1].contains("(two)"), "{}", texts[1]);
1254 assert!(texts[2].contains("(three)"), "{}", texts[2]);
1255 }
1256
1257 fn replace_once(data: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
1260 let pos = memchr::memmem::find(data, from).expect("pattern present in fixture");
1261 let mut out = Vec::with_capacity(data.len() - from.len() + to.len());
1262 out.extend_from_slice(&data[..pos]);
1263 out.extend_from_slice(to);
1264 out.extend_from_slice(&data[pos + from.len()..]);
1265 out
1266 }
1267
1268 fn contains(haystack: &[u8], needle: &[u8]) -> bool {
1269 memchr::memmem::find(haystack, needle).is_some()
1270 }
1271
1272 const FONT: &str = "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>";
1273
1274 #[test]
1275 fn loads_simple_doc() {
1276 let doc = Document::load(simple_doc("Greetings, cosmos!")).unwrap();
1277 assert_eq!(doc.version(), (1, 7));
1278 assert_eq!(doc.page_count(), 1);
1279 let page = doc.page(0).unwrap();
1280 assert_eq!(page.index, 0);
1281 assert_eq!(page.media_box, Rect::new(0.0, 0.0, 612.0, 792.0));
1282 assert_eq!(page.crop_box, page.media_box);
1283 assert_eq!(page.rotate, 0);
1284 assert_eq!(page.size(), (612.0, 792.0));
1285 assert!(page.resources.get("Font").is_some());
1286 let content = page.content(&doc).unwrap();
1287 assert!(contains(&content, b"Greetings, cosmos!"));
1288 }
1289
1290 #[test]
1291 fn multi_page_ordering() {
1292 let doc = Document::load(multi_page_doc(&["alpha", "beta", "gamma"])).unwrap();
1293 assert_eq!(doc.page_count(), 3);
1294 for (i, text) in ["alpha", "beta", "gamma"].iter().enumerate() {
1295 let content = doc.page(i).unwrap().content(&doc).unwrap();
1296 assert!(
1297 contains(&content, text.as_bytes()),
1298 "page {i} should show {text}"
1299 );
1300 }
1301 }
1302
1303 #[test]
1304 fn page_index_out_of_bounds() {
1305 let doc = Document::load(simple_doc("x")).unwrap();
1306 assert!(matches!(doc.page(5), Err(Error::PageNotFound(5, 1))));
1307 }
1308
1309 #[test]
1310 fn open_reads_from_disk() {
1311 let dir = std::env::temp_dir();
1312 let path = dir.join(format!("pdfboss-doc-test-{}.pdf", std::process::id()));
1313 std::fs::write(&path, simple_doc("from disk")).unwrap();
1314 let doc = Document::open(&path).unwrap();
1315 std::fs::remove_file(&path).ok();
1316 assert_eq!(doc.page_count(), 1);
1317 let content = doc.page(0).unwrap().content(&doc).unwrap();
1318 assert!(contains(&content, b"from disk"));
1319 assert!(matches!(
1320 Document::open(dir.join("pdfboss-doc-test-missing.pdf")),
1321 Err(Error::Io(_))
1322 ));
1323 }
1324
1325 #[test]
1326 fn encrypt_in_trailer_is_rejected() {
1327 let data = replace_once(
1328 &simple_doc("secret"),
1329 b"trailer\n<< /Size",
1330 b"trailer\n<< /Encrypt 9 0 R /Size",
1331 );
1332 assert!(matches!(Document::load(data), Err(Error::Encrypted)));
1333 }
1334
1335 #[test]
1336 fn metadata_utf16be_round_trip() {
1337 let mut b = PdfBuilder::new();
1338 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1339 b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1340 b.object(6, "<< /Title <FEFF00480151> /Author (plain author) >>");
1342 let data = replace_once(&b.build(1), b"<< /Size", b"<< /Info 6 0 R /Size");
1343 let doc = Document::load(data).unwrap();
1344 let meta = doc.metadata();
1345 assert_eq!(meta.title.as_deref(), Some("H\u{151}"));
1346 assert_eq!(meta.author.as_deref(), Some("plain author"));
1347 assert_eq!(meta.subject, None);
1348 assert_eq!(meta.keywords, None);
1349 assert_eq!(meta.creation_date, None);
1350 }
1351
1352 #[test]
1353 fn metadata_without_info_is_all_none() {
1354 let doc = Document::load(simple_doc("x")).unwrap();
1355 assert_eq!(doc.metadata(), Metadata::default());
1356 }
1357
1358 #[test]
1359 fn missing_object_resolves_to_null() {
1360 let doc = Document::load(simple_doc("x")).unwrap();
1361 let missing = Object::Ref(ObjRef { num: 99, gen: 0 });
1362 assert_eq!(doc.resolve(&missing).unwrap(), Object::Null);
1363 assert!(matches!(
1364 doc.get(ObjRef { num: 99, gen: 0 }),
1365 Err(Error::ObjectNotFound(99, 0))
1366 ));
1367 }
1368
1369 #[test]
1370 fn self_reference_is_circular() {
1371 let mut b = PdfBuilder::new();
1372 b.object(1, "<< /Type /Catalog >>");
1373 b.object(6, "6 0 R");
1374 let doc = Document::load(b.build(1)).unwrap();
1375 let loops = Object::Ref(ObjRef { num: 6, gen: 0 });
1376 assert!(matches!(
1377 doc.resolve(&loops),
1378 Err(Error::CircularReference(6))
1379 ));
1380 }
1381
1382 #[test]
1383 fn generation_mismatch_is_tolerated() {
1384 let doc = Document::load(simple_doc("x")).unwrap();
1385 let catalog = doc.get(ObjRef { num: 1, gen: 7 }).unwrap();
1386 let dict = catalog.as_dict().unwrap();
1387 assert_eq!(dict.get_name("Type").map(|n| n.0.as_str()), Some("Catalog"));
1388 }
1389
1390 #[test]
1391 fn objects_in_object_streams_are_fetched() {
1392 let mut b = PdfBuilder::new();
1393 let (dict, payload) =
1394 objstm_payload(&[(1, "<< /Type /Catalog /Pages 2 0 R >>"), (5, FONT)]);
1395 b.stream(6, &dict, &payload);
1396 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1397 b.object(
1398 3,
1399 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] \
1400 /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>",
1401 );
1402 b.stream(4, "", b"BT /F1 12 Tf (compressed hello) Tj ET");
1403 let doc = Document::load(b.build_xref_stream(1)).unwrap();
1404 assert_eq!(doc.page_count(), 1);
1405 let page = doc.page(0).unwrap();
1406 assert!(contains(&page.content(&doc).unwrap(), b"compressed hello"));
1407 let font = doc.get(ObjRef { num: 5, gen: 0 }).unwrap();
1408 assert_eq!(
1409 font.as_dict()
1410 .and_then(|d| d.get_name("BaseFont"))
1411 .map(|n| n.0.as_str()),
1412 Some("Helvetica")
1413 );
1414 }
1415
1416 #[test]
1417 fn contents_array_is_joined_with_newlines() {
1418 let mut b = PdfBuilder::new();
1419 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1420 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1421 b.object(
1422 3,
1423 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] \
1424 /Contents [4 0 R null 5 0 R] >>",
1425 );
1426 b.stream(4, "", b"q");
1427 b.stream(5, "", b"Q");
1428 let doc = Document::load(b.build(1)).unwrap();
1429 let content = doc.page(0).unwrap().content(&doc).unwrap();
1430 assert_eq!(content, b"q\nQ", "streams joined by \\n, null skipped");
1431 }
1432
1433 #[test]
1434 fn inheritance_from_pages_node_and_rotate_swap() {
1435 let mut b = PdfBuilder::new();
1436 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1437 b.object(
1438 2,
1439 "<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 \
1440 /Resources << /Font << /F1 5 0 R >> >> /MediaBox [0 0 400 600] >>",
1441 );
1442 b.object(3, "<< /Type /Page /Parent 2 0 R >>");
1443 b.object(4, "<< /Type /Page /Parent 2 0 R /Rotate 270 >>");
1444 b.object(5, FONT);
1445 let doc = Document::load(b.build(1)).unwrap();
1446 assert_eq!(doc.page_count(), 2);
1447
1448 let first = doc.page(0).unwrap();
1449 assert_eq!(first.media_box, Rect::new(0.0, 0.0, 400.0, 600.0));
1450 assert_eq!(first.crop_box, first.media_box);
1451 assert!(first.resources.get("Font").is_some(), "inherited resources");
1452 assert_eq!(first.rotate, 0);
1453 assert!(
1454 first.content(&doc).unwrap().is_empty(),
1455 "no /Contents means empty content"
1456 );
1457 assert_eq!(first.size(), (400.0, 600.0));
1458
1459 let second = doc.page(1).unwrap();
1460 assert_eq!(second.rotate, 270);
1461 assert_eq!(second.size(), (600.0, 400.0), "rotate 270 swaps w/h");
1462 }
1463
1464 #[test]
1465 fn crop_box_intersected_and_rotate_normalized() {
1466 let mut b = PdfBuilder::new();
1467 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1468 b.object(2, "<< /Type /Pages /Kids [3 0 R 4 0 R 5 0 R] /Count 3 >>");
1469 b.object(
1470 3,
1471 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] \
1472 /CropBox [100 100 400 400] /Rotate 450 >>",
1473 );
1474 b.object(4, "<< /Type /Page /Parent 2 0 R /Rotate -90 >>");
1475 b.object(
1476 5,
1477 "<< /Type /Page /Parent 2 0 R /Rotate 45 /MediaBox [0 0 0 0] >>",
1478 );
1479 let doc = Document::load(b.build(1)).unwrap();
1480
1481 let clipped = doc.page(0).unwrap();
1482 assert_eq!(clipped.crop_box, Rect::new(100.0, 100.0, 200.0, 200.0));
1483 assert_eq!(clipped.rotate, 90, "450 normalizes to 90");
1484 assert_eq!(clipped.size(), (100.0, 100.0));
1485
1486 assert_eq!(doc.page(1).unwrap().rotate, 270, "-90 normalizes to 270");
1487 let odd = doc.page(2).unwrap();
1488 assert_eq!(odd.rotate, 0, "non-multiple of 90 falls back to 0");
1489 assert_eq!(
1490 odd.media_box,
1491 Page::US_LETTER,
1492 "degenerate media box defaults"
1493 );
1494 }
1495
1496 #[test]
1497 fn page_boxes_declared_and_defaulted() {
1498 let mut b = PdfBuilder::new();
1499 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1500 b.object(2, "<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>");
1501 b.object(
1502 3,
1503 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 600 800] \
1504 /CropBox [50 50 550 750] /BleedBox [40 40 560 760] \
1505 /TrimBox [5 0 R 6 0 R 7 0 R 8 0 R] >>",
1506 );
1507 b.object(4, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 600 800] >>");
1508 b.object(5, "60");
1509 b.object(6, "70");
1510 b.object(7, "540");
1511 b.object(8, "730");
1512 let doc = Document::load(b.build(1)).unwrap();
1513
1514 let page = doc.page(0).unwrap();
1515 assert_eq!(page.bleed_box, Rect::new(40.0, 40.0, 560.0, 760.0));
1516 assert_eq!(
1517 page.trim_box,
1518 Rect::new(60.0, 70.0, 540.0, 730.0),
1519 "indirect array elements resolve"
1520 );
1521 assert_eq!(
1522 page.art_box, page.crop_box,
1523 "undeclared box is the crop box"
1524 );
1525
1526 let bare = doc.page(1).unwrap();
1527 assert_eq!(bare.bleed_box, bare.crop_box);
1528 assert_eq!(bare.trim_box, bare.crop_box);
1529 assert_eq!(bare.art_box, bare.crop_box);
1530 }
1531
1532 #[test]
1533 fn page_boxes_clip_to_media_and_fall_back() {
1534 let mut b = PdfBuilder::new();
1535 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1536 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1537 b.object(
1538 3,
1539 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] \
1540 /CropBox [10 10 190 190] /TrimBox [100 100 400 400] \
1541 /BleedBox [0 0 0 0] /ArtBox [300 300 400 400] >>",
1542 );
1543 let doc = Document::load(b.build(1)).unwrap();
1544
1545 let page = doc.page(0).unwrap();
1546 assert_eq!(
1547 page.trim_box,
1548 Rect::new(100.0, 100.0, 200.0, 200.0),
1549 "a trim box past the media box clips to it"
1550 );
1551 assert_eq!(
1552 page.bleed_box, page.crop_box,
1553 "a degenerate bleed box falls back to the crop box"
1554 );
1555 assert_eq!(
1556 page.art_box, page.crop_box,
1557 "an art box disjoint from the media box falls back to the crop box"
1558 );
1559 }
1560
1561 #[test]
1565 fn page_boxes_are_not_inherited() {
1566 let mut b = PdfBuilder::new();
1567 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1568 b.object(
1569 2,
1570 "<< /Type /Pages /Kids [3 0 R] /Count 1 \
1571 /TrimBox [10 10 90 90] /BleedBox [5 5 95 95] /ArtBox [20 20 80 80] >>",
1572 );
1573 b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
1574 let doc = Document::load(b.build(1)).unwrap();
1575
1576 let page = doc.page(0).unwrap();
1577 assert_eq!(page.trim_box, page.crop_box);
1578 assert_eq!(page.bleed_box, page.crop_box);
1579 assert_eq!(page.art_box, page.crop_box);
1580 }
1581
1582 #[test]
1583 fn kids_cycle_truncates_without_hanging() {
1584 let mut b = PdfBuilder::new();
1585 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1586 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1588 b.object(3, "<< /Type /Pages /Kids [4 0 R 2 0 R] /Count 1 >>");
1589 b.object(
1590 4,
1591 "<< /Type /Page /Parent 3 0 R /MediaBox [0 0 100 100] /Contents 5 0 R >>",
1592 );
1593 b.stream(5, "", b"0 0 50 50 re f");
1594 let doc = Document::load(b.build(1)).unwrap();
1595 assert_eq!(doc.page_count(), 1, "cycle back-edge yields no extra pages");
1596 assert!(contains(
1597 &doc.page(0).unwrap().content(&doc).unwrap(),
1598 b"re f"
1599 ));
1600 }
1601
1602 #[test]
1603 fn tree_depth_is_capped() {
1604 let mut b = PdfBuilder::new();
1605 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1606 let last = 302u32;
1608 for num in 2..last {
1609 b.object(
1610 num,
1611 &format!("<< /Type /Pages /Kids [{} 0 R] /Count 1 >>", num + 1),
1612 );
1613 }
1614 b.object(last, "<< /Type /Page >>");
1615 let doc = Document::load(b.build(1)).unwrap();
1616 assert_eq!(doc.page_count(), 1, "declared /Count is reported cheaply");
1621 assert!(
1622 matches!(doc.page(0), Err(Error::PageNotFound(0, 0))),
1623 "leaf beyond the depth cap cannot be materialized"
1624 );
1625 }
1626
1627 #[test]
1628 fn page_count_reports_declared_count_cheaply() {
1629 let mut b = PdfBuilder::new();
1633 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1634 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 5 >>");
1635 b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
1636 let doc = Document::load(b.build(1)).unwrap();
1637 assert_eq!(doc.page_count(), 5, "declared /Count reported verbatim");
1638 assert!(doc.page(0).is_ok(), "the one real page materializes");
1639 assert!(
1640 matches!(doc.page(1), Err(Error::PageNotFound(1, 1))),
1641 "access past the real pages fails with the true length"
1642 );
1643 }
1644
1645 #[test]
1646 fn page_count_falls_back_to_walk_when_count_absent() {
1647 let mut b = PdfBuilder::new();
1648 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1649 b.object(2, "<< /Type /Pages /Kids [3 0 R] >>"); b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
1651 let doc = Document::load(b.build(1)).unwrap();
1652 assert_eq!(
1653 doc.page_count(),
1654 1,
1655 "missing /Count is recovered by walking"
1656 );
1657 }
1658
1659 #[test]
1660 fn page_count_ignores_corrupt_oversized_count() {
1661 let mut b = PdfBuilder::new();
1664 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1665 b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 999999999 >>");
1666 b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
1667 let doc = Document::load(b.build(1)).unwrap();
1668 assert_eq!(doc.page_count(), 1, "implausible /Count is rejected");
1669 }
1670
1671 #[test]
1672 fn version_scan_and_default() {
1673 let mut b = PdfBuilder::new().version(2, 0);
1674 b.object(1, "<< /Type /Catalog >>");
1675 assert_eq!(Document::load(b.build(1)).unwrap().version(), (2, 0));
1676 let data = replace_once(&simple_doc("v"), b"%PDF-", b"%QQQ-");
1678 assert_eq!(Document::load(data).unwrap().version(), (1, 4));
1679 }
1680
1681 #[test]
1682 fn deeply_nested_root_object_does_not_overflow_the_stack() {
1683 let mut data = b"%PDF-1.7\n1 0 obj\n".to_vec();
1688 data.extend(std::iter::repeat_n(b'[', 50_000));
1689 data.extend(std::iter::repeat_n(b']', 50_000));
1690 data.extend_from_slice(b"\nendobj\ntrailer\n<</Root 1 0 R>>\n%%EOF\n");
1691 let outcome = std::thread::Builder::new()
1692 .stack_size(1024 * 1024)
1693 .spawn(move || Document::load(data).map(|doc| doc.page_count()))
1694 .expect("spawn test thread")
1695 .join()
1696 .expect("Document::load must not overflow the stack");
1697 assert!(matches!(outcome, Ok(0) | Err(_)));
1700 }
1701
1702 #[test]
1703 fn bytes_and_xref_accessors() {
1704 let data = simple_doc("accessors");
1705 let doc = Document::load(data.clone()).unwrap();
1706 assert_eq!(doc.bytes(), &data[..]);
1707 assert!(!doc.xref().is_empty());
1708 assert!(doc.xref().trailer.get("Root").is_some());
1709 }
1710
1711 #[test]
1712 fn object_at_spanned_reparses_identically() {
1713 let data = simple_doc("spanned");
1714 let doc = Document::load(data).unwrap();
1715 for (num, entry) in doc.xref().iter() {
1716 let XrefEntry::InFile { offset, gen } = entry else {
1717 continue;
1718 };
1719 let (r, object, span) = doc.object_at_spanned(offset as usize).unwrap();
1720 assert_eq!(r.num, num);
1721 assert_eq!(r.gen, gen);
1722 assert_eq!(span.start, offset);
1723 assert!(span.end as usize <= doc.bytes().len());
1724 let slice = &doc.bytes()[span.start as usize..span.end as usize];
1726 let (r2, object2) = Parser::new(slice).parse_indirect(&NoResolve).unwrap();
1727 assert_eq!(r2, r);
1728 assert_eq!(object2, object);
1729 }
1730 }
1731
1732 #[test]
1733 fn page_object_ref_points_at_a_page_dict() {
1734 let doc = Document::load(multi_page_doc(&["one", "two"])).unwrap();
1735 for index in 0..doc.page_count() {
1736 let page = doc.page(index).unwrap();
1737 let r = page.object_ref().expect("builder pages are indirect");
1738 let resolved = doc.get(r).unwrap();
1739 assert_eq!(
1740 resolved
1741 .as_dict()
1742 .unwrap()
1743 .get_name("Type")
1744 .map(|n| n.0.as_str()),
1745 Some("Page")
1746 );
1747 }
1748 }
1749
1750 const RC4_FIXTURE_KEY_LEN: usize = 16; const RC4_FIXTURE_P: i32 = -44;
1760 const RC4_FIXTURE_ID0: &[u8] = b"0123456789abcdef";
1761 const RC4_FIXTURE_PAD: [u8; 32] = [
1762 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01,
1763 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53,
1764 0x69, 0x7A,
1765 ];
1766
1767 #[rustfmt::skip]
1768 const RC4_FIXTURE_MD5_S: [u32; 64] = [
1769 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
1770 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
1771 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
1772 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
1773 ];
1774 #[rustfmt::skip]
1775 const RC4_FIXTURE_MD5_K: [u32; 64] = [
1776 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
1777 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
1778 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
1779 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
1780 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
1781 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
1782 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
1783 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
1784 ];
1785
1786 fn rc4_fixture_md5(input: &[u8]) -> [u8; 16] {
1787 let (mut a0, mut b0, mut c0, mut d0) = (
1788 0x6745_2301u32,
1789 0xefcd_ab89u32,
1790 0x98ba_dcfeu32,
1791 0x1032_5476u32,
1792 );
1793 let mut msg = input.to_vec();
1794 let bitlen = (input.len() as u64).wrapping_mul(8);
1795 msg.push(0x80);
1796 while msg.len() % 64 != 56 {
1797 msg.push(0);
1798 }
1799 msg.extend_from_slice(&bitlen.to_le_bytes());
1800 for chunk in msg.as_chunks::<64>().0 {
1801 let mut m = [0u32; 16];
1802 for (word, bytes) in m.iter_mut().zip(chunk.as_chunks::<4>().0) {
1803 *word = u32::from_le_bytes(*bytes);
1804 }
1805 let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
1806 for i in 0..64 {
1807 let (f, g) = match i {
1808 0..=15 => ((b & c) | (!b & d), i),
1809 16..=31 => ((d & b) | (!d & c), (5 * i + 1) % 16),
1810 32..=47 => (b ^ c ^ d, (3 * i + 5) % 16),
1811 _ => (c ^ (b | !d), (7 * i) % 16),
1812 };
1813 let f = f
1814 .wrapping_add(a)
1815 .wrapping_add(RC4_FIXTURE_MD5_K[i])
1816 .wrapping_add(m[g]);
1817 a = d;
1818 d = c;
1819 c = b;
1820 b = b.wrapping_add(f.rotate_left(RC4_FIXTURE_MD5_S[i]));
1821 }
1822 a0 = a0.wrapping_add(a);
1823 b0 = b0.wrapping_add(b);
1824 c0 = c0.wrapping_add(c);
1825 d0 = d0.wrapping_add(d);
1826 }
1827 let mut out = [0u8; 16];
1828 out[0..4].copy_from_slice(&a0.to_le_bytes());
1829 out[4..8].copy_from_slice(&b0.to_le_bytes());
1830 out[8..12].copy_from_slice(&c0.to_le_bytes());
1831 out[12..16].copy_from_slice(&d0.to_le_bytes());
1832 out
1833 }
1834
1835 fn rc4_fixture_rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
1836 let mut s: [u8; 256] = core::array::from_fn(|i| i as u8);
1837 let mut j = 0u8;
1838 for i in 0..256 {
1839 j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
1840 s.swap(i, j as usize);
1841 }
1842 let mut out = Vec::with_capacity(data.len());
1843 let (mut i, mut j) = (0u8, 0u8);
1844 for &byte in data {
1845 i = i.wrapping_add(1);
1846 j = j.wrapping_add(s[i as usize]);
1847 s.swap(i as usize, j as usize);
1848 let k = s[s[i as usize].wrapping_add(s[j as usize]) as usize];
1849 out.push(byte ^ k);
1850 }
1851 out
1852 }
1853
1854 fn rc4_fixture_owner_entry() -> Vec<u8> {
1856 let mut d = rc4_fixture_md5(&RC4_FIXTURE_PAD);
1857 for _ in 0..50 {
1858 d = rc4_fixture_md5(&d[..RC4_FIXTURE_KEY_LEN]);
1859 }
1860 let rc4key = d[..RC4_FIXTURE_KEY_LEN].to_vec();
1861 let mut o = rc4_fixture_rc4(&rc4key, &RC4_FIXTURE_PAD);
1862 for i in 1u8..=19 {
1863 let k: Vec<u8> = rc4key.iter().map(|b| b ^ i).collect();
1864 o = rc4_fixture_rc4(&k, &o);
1865 }
1866 o
1867 }
1868
1869 fn rc4_fixture_file_key(o: &[u8]) -> Vec<u8> {
1871 let mut input = Vec::new();
1872 input.extend_from_slice(&RC4_FIXTURE_PAD);
1873 input.extend_from_slice(o);
1874 input.extend_from_slice(&(RC4_FIXTURE_P as u32).to_le_bytes());
1875 input.extend_from_slice(RC4_FIXTURE_ID0);
1876 let mut d = rc4_fixture_md5(&input);
1877 for _ in 0..50 {
1878 d = rc4_fixture_md5(&d[..RC4_FIXTURE_KEY_LEN]);
1879 }
1880 d[..RC4_FIXTURE_KEY_LEN].to_vec()
1881 }
1882
1883 fn rc4_fixture_user_entry(key: &[u8]) -> Vec<u8> {
1885 let mut input = Vec::new();
1886 input.extend_from_slice(&RC4_FIXTURE_PAD);
1887 input.extend_from_slice(RC4_FIXTURE_ID0);
1888 let mut x = rc4_fixture_md5(&input).to_vec();
1889 x = rc4_fixture_rc4(key, &x);
1890 for i in 1u8..=19 {
1891 let k: Vec<u8> = key.iter().map(|b| b ^ i).collect();
1892 x = rc4_fixture_rc4(&k, &x);
1893 }
1894 x.resize(32, 0); x
1896 }
1897
1898 fn rc4_fixture_obj_key(key: &[u8], num: u32, gen: u16) -> Vec<u8> {
1899 let mut input = key.to_vec();
1900 input.extend_from_slice(&num.to_le_bytes()[..3]);
1901 input.extend_from_slice(&gen.to_le_bytes()[..2]);
1902 rc4_fixture_md5(&input)[..(key.len() + 5).min(16)].to_vec()
1903 }
1904
1905 fn rc4_fixture_hexstr(b: &[u8]) -> String {
1906 let mut s = String::from("<");
1907 for x in b {
1908 s.push_str(&format!("{x:02x}"));
1909 }
1910 s.push('>');
1911 s
1912 }
1913
1914 fn rc4_encrypted_fixture() -> Vec<u8> {
1918 let o = rc4_fixture_owner_entry();
1919 let key = rc4_fixture_file_key(&o);
1920 let u = rc4_fixture_user_entry(&key);
1921 let msg = rc4_fixture_rc4(&rc4_fixture_obj_key(&key, 3, 0), b"Top secret message");
1922
1923 let mut b = PdfBuilder::new().version(1, 4);
1924 b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1925 b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1926 b.object(3, &format!("<< /Msg {} >>", rc4_fixture_hexstr(&msg)));
1927 b.object(
1928 9,
1929 &format!(
1930 "<< /Filter /Standard /V 2 /R 3 /Length 128 /P {} /O {} /U {} >>",
1931 RC4_FIXTURE_P,
1932 rc4_fixture_hexstr(&o),
1933 rc4_fixture_hexstr(&u)
1934 ),
1935 );
1936 let trailer = format!(
1937 "/Encrypt 9 0 R /ID [{}{}]",
1938 rc4_fixture_hexstr(RC4_FIXTURE_ID0),
1939 rc4_fixture_hexstr(RC4_FIXTURE_ID0)
1940 );
1941 b.trailer_extra(&trailer).build(1)
1942 }
1943
1944 #[test]
1945 fn encrypted_generation_mismatch_still_decrypts() {
1946 let doc = Document::load(rc4_encrypted_fixture()).expect("empty password opens the file");
1956 let obj3 = doc.get(ObjRef { num: 3, gen: 7 }).unwrap();
1957 let msg = obj3
1958 .as_dict()
1959 .unwrap()
1960 .get("Msg")
1961 .unwrap()
1962 .as_str_bytes()
1963 .unwrap();
1964 assert_eq!(
1965 msg, b"Top secret message",
1966 "decrypted using the file's real gen (0), not the mismatched request (7)"
1967 );
1968 }
1969
1970 #[test]
1971 fn objstm_doc_fixture_loads_and_resolves_members() {
1972 let data = objstm_doc(&[(7, "<< /Marker (inside) >>")]);
1973 let doc = Document::load(data).unwrap();
1974 assert_eq!(doc.page_count(), 1);
1975 let member = doc.get(ObjRef { num: 7, gen: 0 }).unwrap();
1976 let text = member.as_dict().unwrap().get("Marker").unwrap();
1977 assert_eq!(text.as_str_bytes(), Some(&b"inside"[..]));
1978 assert!(matches!(
1980 doc.xref().get(7),
1981 Some(XrefEntry::InStream { stream_num: 4, .. })
1982 ));
1983 }
1984
1985 #[test]
1988 fn page_from_parts_exposes_its_accessors() {
1989 let mut dict = Dict::default();
1990 dict.insert(Name("Type".into()), Object::Name(Name("Page".into())));
1991 let media = Rect::new(0.0, 0.0, 200.0, 400.0);
1992 let obj_ref = ObjRef { num: 7, gen: 0 };
1993
1994 let page = Page::from_parts(
1995 3,
1996 media,
1997 media,
1998 90,
1999 Dict::default(),
2000 dict.clone(),
2001 Some(obj_ref),
2002 );
2003
2004 assert_eq!(page.index, 3);
2005 assert_eq!(page.object_ref(), Some(obj_ref));
2006 assert_eq!(page.dict(), &dict);
2007 assert_eq!(page.size(), (400.0, 200.0));
2009 assert_eq!(page.bleed_box, page.crop_box);
2011 assert_eq!(page.trim_box, page.crop_box);
2012 assert_eq!(page.art_box, page.crop_box);
2013 }
2014
2015 #[test]
2019 fn page_from_parts_normalizes_rotation() {
2020 let media = Rect::new(0.0, 0.0, 200.0, 400.0);
2021 let build = |rotate: i32| {
2022 Page::from_parts(
2023 0,
2024 media,
2025 media,
2026 rotate,
2027 Dict::default(),
2028 Dict::default(),
2029 None,
2030 )
2031 };
2032
2033 for (given, canonical) in [(-90, 270), (450, 90), (540, 180), (720, 0), (-360, 0)] {
2034 let page = build(given);
2035 assert_eq!(
2036 page.rotate, canonical,
2037 "from_parts must normalize /Rotate {given} to {canonical}"
2038 );
2039 assert_eq!(
2040 page.size(),
2041 build(canonical).size(),
2042 "/Rotate {given} must report the same size as {canonical}"
2043 );
2044 }
2045 }
2046
2047 #[test]
2052 fn page_size_handles_unnormalized_rotation() {
2053 let media = Rect::new(0.0, 0.0, 200.0, 400.0);
2054 let mut page = Page::from_parts(0, media, media, 0, Dict::default(), Dict::default(), None);
2055
2056 for rotate in [90, 270, -90, -270, 450, 630] {
2057 page.rotate = rotate;
2058 assert_eq!(
2059 page.size(),
2060 (400.0, 200.0),
2061 "/Rotate {rotate} is a quarter turn and must swap the size"
2062 );
2063 }
2064 for rotate in [0, 180, -180, 360, 540, 720] {
2065 page.rotate = rotate;
2066 assert_eq!(
2067 page.size(),
2068 (200.0, 400.0),
2069 "/Rotate {rotate} is a half turn and must leave the size alone"
2070 );
2071 }
2072 }
2073}