1use crate::error::PdfError;
8use crate::objects::{PdfDict, PdfObj};
9use crate::resolver::Resolver;
10use std::collections::HashSet;
11
12const MAX_PAGE_TREE_DEPTH: u32 = 256;
16
17#[derive(Debug, Clone)]
19pub struct PageInfo {
20 pub obj_num: u32,
22 pub media_box: [f64; 4],
24 pub crop_box: [f64; 4],
26 pub rotate: i32,
28 pub resources: PdfDict,
30 pub contents: Vec<(u32, u16)>,
32 pub annots: Vec<(u32, u16)>,
34}
35
36#[derive(Clone, Default)]
38struct Inherited {
39 media_box: Option<[f64; 4]>,
40 crop_box: Option<[f64; 4]>,
41 rotate: Option<i32>,
42 resources: Option<PdfDict>,
43}
44
45pub fn collect_pages(resolver: &Resolver) -> Result<Vec<PageInfo>, PdfError> {
47 let catalog_owned;
49 let catalog_dict = if let Some(root_ref) = resolver.trailer().get_ref(b"Root") {
50 let catalog = match resolver.resolve(root_ref.0, root_ref.1) {
51 Ok(c) => c,
52 Err(_) => return collect_pages_by_scan(resolver),
53 };
54 catalog_owned = catalog;
55 match catalog_owned.as_dict() {
56 Some(d) => d,
57 None => return collect_pages_by_scan(resolver),
58 }
59 } else if let Some(root_obj) = resolver.trailer().get(b"Root") {
60 match root_obj.as_dict() {
61 Some(d) => d,
62 None => return collect_pages_by_scan(resolver),
63 }
64 } else {
65 return collect_pages_by_scan(resolver);
66 };
67
68 let pages_ref = match catalog_dict.get(b"Pages") {
74 Some(r) => r,
75 None => return collect_pages_via_catalog_scan(resolver),
76 };
77 let pages_obj = match resolver.deref(pages_ref) {
78 Ok(obj) => obj,
79 Err(_) => return collect_pages_by_scan(resolver),
80 };
81 let pages_dict = match pages_obj.as_dict() {
82 Some(d) => d,
83 None => return collect_pages_by_scan(resolver),
84 };
85
86 let mut pages = Vec::new();
87 let inherited = Inherited::default();
88 let mut visited: HashSet<u32> = HashSet::new();
89 collect_pages_recursive(
90 resolver,
91 pages_dict,
92 0,
93 &inherited,
94 &mut pages,
95 &mut visited,
96 0,
97 )?;
98
99 Ok(pages)
100}
101
102fn collect_pages_via_catalog_scan(resolver: &Resolver) -> Result<Vec<PageInfo>, PdfError> {
105 let xref_len = resolver.xref_len();
106 for obj_num in 0..xref_len as u32 {
107 if let Ok(obj) = resolver.resolve(obj_num, 0) {
108 if let Some(dict) = obj.as_dict() {
109 if dict.get_name(b"Type") == Some(b"Catalog") {
110 if let Some(pages_ref) = dict.get(b"Pages") {
111 if let Ok(pages_obj) = resolver.deref(pages_ref) {
112 if let Some(pages_dict) = pages_obj.as_dict() {
113 let mut pages = Vec::new();
114 let inherited = Inherited::default();
115 let mut visited: HashSet<u32> = HashSet::new();
116 if collect_pages_recursive(
117 resolver,
118 pages_dict,
119 0,
120 &inherited,
121 &mut pages,
122 &mut visited,
123 0,
124 )
125 .is_ok()
126 && !pages.is_empty()
127 {
128 return Ok(pages);
129 }
130 }
131 }
132 }
133 }
134 }
135 }
136 }
137 collect_pages_by_scan(resolver)
138}
139
140fn collect_pages_by_scan(resolver: &Resolver) -> Result<Vec<PageInfo>, PdfError> {
143 let mut pages = Vec::new();
144 let xref_len = resolver.xref_len();
145
146 for obj_num in 0..xref_len as u32 {
147 if let Ok(obj) = resolver.resolve(obj_num, 0) {
148 if let Some(dict) = obj.as_dict() {
149 if dict.get_name(b"Type") == Some(b"Page") && dict.get(b"Kids").is_none() {
150 let media_box =
151 parse_rect(dict, b"MediaBox", resolver).unwrap_or([0.0, 0.0, 612.0, 792.0]);
152 let crop_box = clamp_box_to_media(
153 &parse_rect(dict, b"CropBox", resolver).unwrap_or(media_box),
154 &media_box,
155 );
156 let rotate = dict.get_int(b"Rotate").unwrap_or(0) as i32;
157 let resources = resolve_resources_inherited(dict, resolver);
158 let contents = parse_contents(dict, resolver)?;
159 let annots = parse_annots(dict, resolver);
160
161 pages.push(PageInfo {
162 obj_num,
163 media_box,
164 crop_box,
165 rotate,
166 resources,
167 contents,
168 annots,
169 });
170 }
171 }
172 }
173 }
174
175 if pages.is_empty() {
176 return Err(PdfError::MissingKey("Pages"));
177 }
178
179 Ok(pages)
180}
181
182fn resolve_resources_inherited(dict: &PdfDict, resolver: &Resolver) -> PdfDict {
184 let res = resolve_resources(dict, resolver);
185 if !res.is_empty() {
186 return res;
187 }
188 let mut current = dict.clone();
190 for _ in 0..10 {
191 if let Some(&PdfObj::Ref(n, g)) = current.get(b"Parent") {
192 if let Ok(parent_obj) = resolver.resolve(n, g) {
193 if let Some(parent_dict) = parent_obj.as_dict() {
194 let res = resolve_resources(parent_dict, resolver);
195 if !res.is_empty() {
196 return res;
197 }
198 current = parent_dict.clone();
199 continue;
200 }
201 }
202 }
203 break;
204 }
205 PdfDict::default()
206}
207
208fn resolve_resources(dict: &PdfDict, resolver: &Resolver) -> PdfDict {
210 if let Some(res) = dict.get_dict(b"Resources") {
211 return res.clone();
212 }
213 if let Some(PdfObj::Ref(n, g)) = dict.get(b"Resources") {
214 if let Ok(resolved) = resolver.resolve(*n, *g) {
215 if let Some(d) = resolved.as_dict() {
216 return d.clone();
217 }
218 }
219 }
220 PdfDict::default()
221}
222
223fn collect_pages_recursive(
224 resolver: &Resolver,
225 node_dict: &PdfDict,
226 obj_num: u32,
227 parent_inherited: &Inherited,
228 pages: &mut Vec<PageInfo>,
229 visited: &mut HashSet<u32>,
230 depth: u32,
231) -> Result<(), PdfError> {
232 if depth >= MAX_PAGE_TREE_DEPTH {
236 return Ok(());
237 }
238 if obj_num != 0 && !visited.insert(obj_num) {
239 return Ok(());
240 }
241 let mut inherited = parent_inherited.clone();
243 if let Some(mb) = parse_rect(node_dict, b"MediaBox", resolver) {
244 inherited.media_box = Some(mb);
245 }
246 if let Some(cb) = parse_rect(node_dict, b"CropBox", resolver) {
247 inherited.crop_box = Some(cb);
248 }
249 if let Some(r) = node_dict.get_int(b"Rotate") {
250 inherited.rotate = Some(r as i32);
251 }
252 if let Some(res) = node_dict.get_dict(b"Resources") {
253 inherited.resources = Some(res.clone());
254 } else if let Some(PdfObj::Ref(n, g)) = node_dict.get(b"Resources") {
255 if let Ok(resolved) = resolver.resolve(*n, *g)
257 && let Some(d) = resolved.as_dict()
258 {
259 inherited.resources = Some(d.clone());
260 }
261 }
262
263 let has_kids = node_dict.get(b"Kids").is_some();
269 let type_name = node_dict.get_name(b"Type");
270
271 if !has_kids && (matches!(type_name, Some(b"Page")) || (type_name.is_none() && !has_kids)) {
272 let media_box = inherited.media_box.unwrap_or([0.0, 0.0, 612.0, 792.0]); let crop_box = clamp_box_to_media(&inherited.crop_box.unwrap_or(media_box), &media_box);
277 let rotate = inherited.rotate.unwrap_or(0);
278 let resources = inherited.resources.clone().unwrap_or_default();
279
280 let contents = parse_contents(node_dict, resolver)?;
282
283 let annots = parse_annots(node_dict, resolver);
285
286 pages.push(PageInfo {
287 obj_num,
288 media_box,
289 crop_box,
290 rotate,
291 resources,
292 contents,
293 annots,
294 });
295 } else {
296 let kids_owned;
299 let kids: &[PdfObj] = if let Some(arr) = node_dict.get_array(b"Kids") {
300 arr
301 } else if let Some(PdfObj::Ref(n, g)) = node_dict.get(b"Kids") {
302 kids_owned = match resolver.resolve(*n, *g) {
303 Ok(PdfObj::Array(arr)) => arr,
304 _ => Vec::new(),
305 };
306 &kids_owned
307 } else {
308 &[]
309 };
310 for kid in kids {
311 match kid {
312 PdfObj::Ref(n, g) => {
313 let child = resolver.resolve(*n, *g)?;
314 if let Some(child_dict) = child.as_dict() {
315 collect_pages_recursive(
316 resolver,
317 child_dict,
318 *n,
319 &inherited,
320 pages,
321 visited,
322 depth + 1,
323 )?;
324 }
325 }
326 _ => {
327 if let Some(child_dict) = kid.as_dict() {
329 collect_pages_recursive(
330 resolver,
331 child_dict,
332 0,
333 &inherited,
334 pages,
335 visited,
336 depth + 1,
337 )?;
338 }
339 }
340 }
341 }
342 }
343
344 Ok(())
345}
346
347fn clamp_box_to_media(crop: &[f64; 4], media: &[f64; 4]) -> [f64; 4] {
349 let (c_llx, c_urx) = (crop[0].min(crop[2]), crop[0].max(crop[2]));
351 let (c_lly, c_ury) = (crop[1].min(crop[3]), crop[1].max(crop[3]));
352 let (m_llx, m_urx) = (media[0].min(media[2]), media[0].max(media[2]));
353 let (m_lly, m_ury) = (media[1].min(media[3]), media[1].max(media[3]));
354 [
355 c_llx.max(m_llx),
356 c_lly.max(m_lly),
357 c_urx.min(m_urx),
358 c_ury.min(m_ury),
359 ]
360}
361
362fn arr_to_rect(arr: &[PdfObj], resolver: &Resolver) -> Option<[f64; 4]> {
364 if arr.len() >= 4 {
365 let resolve = |obj: &PdfObj| -> Option<f64> {
367 obj.as_f64()
368 .or_else(|| resolver.deref(obj).ok().and_then(|r| r.as_f64()))
369 };
370 Some([
371 resolve(&arr[0])?,
372 resolve(&arr[1])?,
373 resolve(&arr[2])?,
374 resolve(&arr[3])?,
375 ])
376 } else {
377 None
378 }
379}
380
381fn parse_rect(dict: &PdfDict, key: &[u8], resolver: &Resolver) -> Option<[f64; 4]> {
384 match dict.get(key)? {
385 PdfObj::Array(a) => arr_to_rect(a, resolver),
386 PdfObj::Ref(n, g) => match resolver.resolve(*n, *g).ok()? {
387 PdfObj::Array(a) => arr_to_rect(&a, resolver),
388 _ => None,
389 },
390 _ => None,
391 }
392}
393
394fn parse_contents(dict: &PdfDict, resolver: &Resolver) -> Result<Vec<(u32, u16)>, PdfError> {
399 match dict.get(b"Contents") {
400 None => Ok(Vec::new()), Some(PdfObj::Ref(n, g)) => {
402 if let Ok(resolved) = resolver.resolve(*n, *g)
405 && let PdfObj::Array(arr) = &resolved
406 {
407 return collect_refs_from_array(arr);
408 }
409 Ok(vec![(*n, *g)])
411 }
412 Some(PdfObj::Array(arr)) => collect_refs_from_array(arr),
413 _ => Ok(Vec::new()),
414 }
415}
416
417fn parse_annots(dict: &PdfDict, resolver: &Resolver) -> Vec<(u32, u16)> {
419 let annot_arr = match dict.get(b"Annots") {
420 Some(PdfObj::Array(arr)) => arr.clone(),
421 Some(PdfObj::Ref(n, g)) => {
422 if let Ok(PdfObj::Array(arr)) = resolver.resolve(*n, *g) {
424 arr
425 } else {
426 return Vec::new();
427 }
428 }
429 _ => return Vec::new(),
430 };
431
432 let mut refs = Vec::new();
433 for obj in &annot_arr {
434 if let PdfObj::Ref(n, g) = obj {
435 refs.push((*n, *g));
436 }
437 }
438 refs
439}
440
441fn collect_refs_from_array(arr: &[PdfObj]) -> Result<Vec<(u32, u16)>, PdfError> {
443 let mut refs = Vec::new();
444 for obj in arr {
445 if let PdfObj::Ref(n, g) = obj {
446 refs.push((*n, *g));
447 }
448 }
449 Ok(refs)
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455 use crate::xref::XrefTable;
456
457 fn dummy_resolver() -> Resolver<'static> {
459 static EMPTY: &[u8] = b"";
460 Resolver::new(EMPTY, &XrefTable::empty())
461 }
462
463 #[test]
464 fn arr_to_rect_valid() {
465 let r = dummy_resolver();
466 let arr = vec![
467 PdfObj::Int(0),
468 PdfObj::Int(0),
469 PdfObj::Real(612.0),
470 PdfObj::Real(792.0),
471 ];
472 let rect = arr_to_rect(&arr, &r).unwrap();
473 assert_eq!(rect, [0.0, 0.0, 612.0, 792.0]);
474 }
475
476 #[test]
477 fn arr_to_rect_too_short() {
478 let r = dummy_resolver();
479 let arr = vec![PdfObj::Int(0), PdfObj::Int(0)];
480 assert!(arr_to_rect(&arr, &r).is_none());
481 }
482
483 #[test]
484 fn collect_refs_from_array_basic() {
485 let arr = vec![PdfObj::Ref(5, 0), PdfObj::Ref(6, 0)];
486 let refs = collect_refs_from_array(&arr).unwrap();
487 assert_eq!(refs, vec![(5, 0), (6, 0)]);
488 }
489
490 #[test]
491 fn collect_refs_from_array_empty() {
492 let refs = collect_refs_from_array(&[]).unwrap();
493 assert!(refs.is_empty());
494 }
495}