Skip to main content

rust_pdf_box/pd/
catalog.rs

1use crate::cos::document::COSDocument;
2use crate::cos::object::{COSArray, COSDictionary, COSName, ObjectId};
3use crate::error::{PdfError, PdfResult};
4
5/// The Document Catalog dictionary (/Root).
6#[derive(Debug, Clone)]
7pub struct PDDocumentCatalog {
8    pub catalog_id: ObjectId,
9    pub pages_id: ObjectId,
10}
11
12impl PDDocumentCatalog {
13    /// Creates a new catalog and page tree root in the given COSDocument.
14    pub fn new(cos_doc: &mut COSDocument) -> Self {
15        let pages_id = cos_doc.allocate_id();
16        let mut pages_dict = COSDictionary::new();
17        pages_dict.set("Type", COSName::new("Pages"));
18        pages_dict.set("Kids", COSArray::new());
19        pages_dict.set("Count", 0);
20        cos_doc.set_object(pages_id, pages_dict);
21
22        let catalog_id = cos_doc.allocate_id();
23        let mut cat_dict = COSDictionary::new();
24        cat_dict.set("Type", COSName::new("Catalog"));
25        cat_dict.set("Pages", pages_id);
26        cos_doc.set_object(catalog_id, cat_dict);
27
28        cos_doc.trailer.set("Root", catalog_id);
29
30        Self {
31            catalog_id,
32            pages_id,
33        }
34    }
35
36    /// Loads the catalog and resolves the pages root from an existing document.
37    pub fn from_doc(cos_doc: &COSDocument) -> PdfResult<Self> {
38        let catalog_id = cos_doc
39            .trailer
40            .get_reference("Root")
41            .ok_or_else(|| PdfError::MissingEntry("Trailer missing /Root".to_string()))?;
42
43        let cat_dict = cos_doc
44            .get_catalog()
45            .ok_or_else(|| PdfError::MissingEntry("Root is not a dictionary".to_string()))?;
46
47        let pages_id = cat_dict.get_reference("Pages").ok_or_else(|| {
48            PdfError::MissingEntry("Catalog missing /Pages reference".to_string())
49        })?;
50
51        Ok(Self {
52            catalog_id,
53            pages_id,
54        })
55    }
56
57    /// Traverses the page tree and collects all page object IDs in sequence.
58    pub fn collect_page_ids(&self, cos_doc: &COSDocument) -> Vec<ObjectId> {
59        let mut page_ids = Vec::new();
60        self.collect_pages_recursive(cos_doc, self.pages_id, &mut page_ids);
61        page_ids
62    }
63
64    fn collect_pages_recursive(
65        &self,
66        cos_doc: &COSDocument,
67        node_id: ObjectId,
68        page_ids: &mut Vec<ObjectId>,
69    ) {
70        if let Some(obj) = cos_doc.get_object(node_id) {
71            if let Some(dict) = obj.as_dict() {
72                match dict.get_name("Type") {
73                    Some("Page") => {
74                        page_ids.push(node_id);
75                    }
76                    Some("Pages") => {
77                        if let Some(kids) = dict.get_array("Kids") {
78                            for kid in kids.iter() {
79                                if let Some(kid_id) = kid.as_reference() {
80                                    self.collect_pages_recursive(cos_doc, kid_id, page_ids);
81                                }
82                            }
83                        }
84                    }
85                    _ => {}
86                }
87            }
88        }
89    }
90}