Skip to main content

rust_pdf_box/pd/
document.rs

1use crate::cos::document::COSDocument;
2use crate::cos::object::{COSArray, ObjectId};
3use crate::cos::parser::Parser;
4use crate::cos::writer::Writer;
5use crate::error::{PdfError, PdfResult};
6use crate::pd::catalog::PDDocumentCatalog;
7use crate::pd::info::PDDocumentInformation;
8use crate::pd::page::PDPage;
9use std::fs::File;
10use std::io::{Read, Write};
11use std::path::Path;
12
13/// High-level PDF document representing pages, metadata, catalog and content streams.
14#[derive(Debug, Clone)]
15pub struct PDDocument {
16    pub cos_doc: COSDocument,
17    pub catalog: PDDocumentCatalog,
18    pub pages: Vec<PDPage>,
19    pub info: PDDocumentInformation,
20}
21
22impl Default for PDDocument {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl PDDocument {
29    /// Creates a new, empty PDF document with catalog and info dictionaries.
30    pub fn new() -> Self {
31        let mut cos_doc = COSDocument::new();
32        let catalog = PDDocumentCatalog::new(&mut cos_doc);
33
34        let info = PDDocumentInformation::new();
35        let info_id = cos_doc.add_object(info.dict.clone());
36        cos_doc.trailer.set("Info", info_id);
37
38        Self {
39            cos_doc,
40            catalog,
41            pages: Vec::new(),
42            info,
43        }
44    }
45
46    /// Loads a PDF document from raw byte slice.
47    pub fn load_from_bytes(bytes: &[u8]) -> PdfResult<Self> {
48        let parser = Parser::new(bytes);
49        let cos_doc = parser.parse()?;
50        let catalog = PDDocumentCatalog::from_doc(&cos_doc)?;
51
52        // Collect and load all pages
53        let page_ids = catalog.collect_page_ids(&cos_doc);
54        let mut pages = Vec::with_capacity(page_ids.len());
55
56        for id in page_ids {
57            if let Some(obj) = cos_doc.get_object(id) {
58                if let Some(dict) = obj.as_dict() {
59                    pages.push(PDPage::from_dict(dict.clone(), id));
60                }
61            }
62        }
63
64        let info = if let Some(info_dict) = cos_doc.get_info() {
65            PDDocumentInformation::from_dict(info_dict.clone())
66        } else {
67            PDDocumentInformation::new()
68        };
69
70        Ok(Self {
71            cos_doc,
72            catalog,
73            pages,
74            info,
75        })
76    }
77
78    /// Loads a PDF document from a file path.
79    pub fn load_from_file(path: impl AsRef<Path>) -> PdfResult<Self> {
80        let mut file = File::open(path)?;
81        let mut bytes = Vec::new();
82        file.read_to_end(&mut bytes)?;
83        Self::load_from_bytes(&bytes)
84    }
85
86    /// Number of pages in the document.
87    pub fn page_count(&self) -> usize {
88        self.pages.len()
89    }
90
91    /// Returns a slice of all pages.
92    pub fn pages(&self) -> &[PDPage] {
93        &self.pages
94    }
95
96    /// Returns a mutable slice of all pages.
97    pub fn pages_mut(&mut self) -> &mut [PDPage] {
98        &mut self.pages
99    }
100
101    /// Gets a page reference by 0-based index.
102    pub fn get_page(&self, index: usize) -> PdfResult<&PDPage> {
103        self.pages.get(index).ok_or_else(|| {
104            PdfError::PageOperation(format!(
105                "Page index {index} out of bounds (count {})",
106                self.pages.len()
107            ))
108        })
109    }
110
111    /// Gets a mutable page reference by 0-based index.
112    pub fn get_page_mut(&mut self, index: usize) -> PdfResult<&mut PDPage> {
113        let count = self.pages.len();
114        self.pages.get_mut(index).ok_or_else(|| {
115            PdfError::PageOperation(format!("Page index {index} out of bounds (count {count})"))
116        })
117    }
118
119    /// Appends a new page to the document.
120    pub fn add_page(&mut self, mut page: PDPage) -> ObjectId {
121        page.sync_resources();
122        let page_id = if let Some(id) = page.page_id {
123            self.cos_doc.set_object(id, page.dict.clone());
124            id
125        } else {
126            let id = self.cos_doc.allocate_id();
127            page.page_id = Some(id);
128            page.dict.set("Parent", self.catalog.pages_id);
129            self.cos_doc.set_object(id, page.dict.clone());
130            id
131        };
132
133        self.pages.push(page);
134        self.update_page_tree();
135        page_id
136    }
137
138    /// Inserts a page at a given 0-based index.
139    pub fn insert_page(&mut self, index: usize, mut page: PDPage) -> ObjectId {
140        page.sync_resources();
141        let page_id = if let Some(id) = page.page_id {
142            self.cos_doc.set_object(id, page.dict.clone());
143            id
144        } else {
145            let id = self.cos_doc.allocate_id();
146            page.page_id = Some(id);
147            page.dict.set("Parent", self.catalog.pages_id);
148            self.cos_doc.set_object(id, page.dict.clone());
149            id
150        };
151
152        if index >= self.pages.len() {
153            self.pages.push(page);
154        } else {
155            self.pages.insert(index, page);
156        }
157
158        self.update_page_tree();
159        page_id
160    }
161
162    /// Removes a page by 0-based index.
163    pub fn remove_page(&mut self, index: usize) -> PdfResult<()> {
164        if index >= self.pages.len() {
165            return Err(PdfError::PageOperation(format!(
166                "Cannot remove page at index {index}, total pages {}",
167                self.pages.len()
168            )));
169        }
170        let removed = self.pages.remove(index);
171        if let Some(id) = removed.page_id {
172            self.cos_doc.objects.remove(&id);
173            self.cos_doc.dirty_objects.remove(&id);
174        }
175        self.update_page_tree();
176        Ok(())
177    }
178
179    /// Updates the `/Pages` dictionary with the current list of page IDs and count.
180    fn update_page_tree(&mut self) {
181        let mut kids = COSArray::new();
182        for p in &self.pages {
183            if let Some(id) = p.page_id {
184                kids.push(id);
185            }
186        }
187
188        if let Some(pages_obj) = self.cos_doc.get_object_mut(self.catalog.pages_id) {
189            if let Some(dict) = pages_obj.as_dict_mut() {
190                dict.set("Kids", kids);
191                dict.set("Count", self.pages.len() as i64);
192            }
193        }
194    }
195
196    /// Synchronizes all modified pages, resources, and info metadata back to the COSDocument.
197    pub fn sync(&mut self) {
198        for page in &mut self.pages {
199            page.sync_resources();
200            if let Some(id) = page.page_id {
201                self.cos_doc.set_object(id, page.dict.clone());
202            }
203        }
204
205        let info_id = if let Some(id) = self.cos_doc.trailer.get_reference("Info") {
206            id
207        } else {
208            let id = self.cos_doc.allocate_id();
209            self.cos_doc.trailer.set("Info", id);
210            id
211        };
212        self.cos_doc.set_object(info_id, self.info.dict.clone());
213        self.update_page_tree();
214    }
215
216    /// Saves the document to any writer implementing `Write`.
217    pub fn save_to_writer<W: Write>(&mut self, writer: W) -> PdfResult<()> {
218        self.sync();
219        let mut cos_writer = Writer::new(writer);
220        cos_writer.write_document(&self.cos_doc)
221    }
222
223    /// Saves the document to a file.
224    pub fn save_to_file(&mut self, path: impl AsRef<Path>) -> PdfResult<()> {
225        let file = File::create(path)?;
226        self.save_to_writer(file)
227    }
228
229    /// Appends an incremental update to original bytes and writes the result.
230    pub fn save_incremental<W: Write>(
231        &mut self,
232        prev_bytes: &[u8],
233        mut writer: W,
234    ) -> PdfResult<()> {
235        self.sync();
236        writer.write_all(prev_bytes)?;
237
238        // Find prev startxref offset in original bytes
239        let parser = Parser::new(prev_bytes);
240        let prev_startxref = parser.find_startxref().unwrap_or(0);
241
242        let mut cos_writer = Writer::with_offset(writer, prev_bytes.len());
243        cos_writer.write_incremental_update(&self.cos_doc, prev_startxref)
244    }
245
246    /// Creates a fluent Stamper for watermarking, headers/footers, and rubber stamps.
247    pub fn stamp(&self) -> crate::stamp::Stamper {
248        crate::stamp::Stamper::new()
249    }
250
251    /// Digitally signs the document using the given Signer and returns the signed PDF bytes.
252    pub fn sign(&mut self, signer: crate::sign::Signer) -> PdfResult<Vec<u8>> {
253        signer.sign_document(self)
254    }
255}