Skip to main content

pdfkit/
document.rs

1use std::path::Path;
2use std::ptr;
3
4use crate::document_delegate::PdfDocumentDelegateHandle;
5use crate::error::{PdfKitError, Result};
6use crate::ffi;
7use crate::handle::ObjectHandle;
8use crate::outline::PdfOutline;
9use crate::page::PdfPage;
10use crate::selection::PdfSelection;
11use crate::types::{
12    PdfDocumentAttributes, PdfDocumentInfo, PdfDocumentWriteOptions, PdfPoint,
13    PdfSelectionGranularity,
14};
15use crate::util::{c_string, parse_json, path_to_c_string, required_handle, take_string};
16
17#[derive(Debug, Clone)]
18pub struct PdfDocument {
19    handle: ObjectHandle,
20}
21
22impl PdfDocument {
23    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
24        Self { handle }
25    }
26
27    pub fn new() -> Result<Self> {
28        let mut out_document = ptr::null_mut();
29        let mut out_error = ptr::null_mut();
30        let status = unsafe { ffi::pdf_document_new(&mut out_document, &mut out_error) };
31        crate::util::status_result(status, out_error)?;
32        Ok(Self::from_handle(required_handle(
33            out_document,
34            "PDFDocument",
35        )?))
36    }
37
38    pub fn from_url(path: impl AsRef<Path>) -> Result<Self> {
39        let path = path_to_c_string(path.as_ref())?;
40        let mut out_document = ptr::null_mut();
41        let mut out_error = ptr::null_mut();
42        let status = unsafe {
43            ffi::pdf_document_new_with_url(path.as_ptr(), &mut out_document, &mut out_error)
44        };
45        crate::util::status_result(status, out_error)?;
46        Ok(Self::from_handle(required_handle(
47            out_document,
48            "PDFDocument",
49        )?))
50    }
51
52    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
53        let mut out_document = ptr::null_mut();
54        let mut out_error = ptr::null_mut();
55        let status = unsafe {
56            ffi::pdf_document_new_with_data(
57                bytes.as_ptr(),
58                bytes.len(),
59                &mut out_document,
60                &mut out_error,
61            )
62        };
63        crate::util::status_result(status, out_error)?;
64        Ok(Self::from_handle(required_handle(
65            out_document,
66            "PDFDocument",
67        )?))
68    }
69
70    pub fn info(&self) -> Result<PdfDocumentInfo> {
71        parse_json(
72            unsafe { ffi::pdf_document_info_json(self.handle.as_ptr()) },
73            "PDFDocument",
74        )
75    }
76
77    pub fn attributes(&self) -> Result<PdfDocumentAttributes> {
78        parse_json(
79            unsafe { ffi::pdf_document_attributes_json(self.handle.as_ptr()) },
80            "PDFDocument attributes",
81        )
82    }
83
84    #[must_use]
85    pub fn string(&self) -> Option<String> {
86        take_string(unsafe { ffi::pdf_document_string(self.handle.as_ptr()) })
87    }
88
89    #[must_use]
90    pub fn page_count(&self) -> usize {
91        unsafe { ffi::pdf_document_page_count(self.handle.as_ptr()) as usize }
92    }
93
94    #[must_use]
95    pub fn page(&self, index: usize) -> Option<PdfPage> {
96        let ptr = unsafe { ffi::pdf_document_page_at(self.handle.as_ptr(), index as u64) };
97        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(PdfPage::from_handle)
98    }
99
100    #[must_use]
101    pub fn pages(&self) -> Vec<PdfPage> {
102        (0..self.page_count())
103            .filter_map(|index| self.page(index))
104            .collect()
105    }
106
107    #[must_use]
108    pub fn page_index(&self, page: &PdfPage) -> Option<usize> {
109        let index =
110            unsafe { ffi::pdf_document_index_for_page(self.handle.as_ptr(), page.as_handle_ptr()) };
111        (index != u64::MAX).then_some(index as usize)
112    }
113
114    #[must_use]
115    pub fn outline_root(&self) -> Option<PdfOutline> {
116        let ptr = unsafe { ffi::pdf_document_outline_root(self.handle.as_ptr()) };
117        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(PdfOutline::from_handle)
118    }
119
120    pub fn set_outline_root(&self, outline: Option<&PdfOutline>) -> Result<()> {
121        let mut out_error = ptr::null_mut();
122        let status = unsafe {
123            ffi::pdf_document_set_outline_root(
124                self.handle.as_ptr(),
125                outline.map_or(ptr::null_mut(), PdfOutline::as_handle_ptr),
126                &mut out_error,
127            )
128        };
129        crate::util::status_result(status, out_error)
130    }
131
132    #[must_use]
133    pub fn outline_item_for_selection(&self, selection: &PdfSelection) -> Option<PdfOutline> {
134        let ptr = unsafe {
135            ffi::pdf_document_outline_item_for_selection(
136                self.handle.as_ptr(),
137                selection.as_handle_ptr(),
138            )
139        };
140        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(PdfOutline::from_handle)
141    }
142
143    #[must_use]
144    pub fn selection_for_entire_document(&self) -> Option<PdfSelection> {
145        let ptr = unsafe { ffi::pdf_document_selection_for_entire_document(self.handle.as_ptr()) };
146        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(PdfSelection::from_handle)
147    }
148
149    #[must_use]
150    pub fn selection_from_page_points(
151        &self,
152        start_page: &PdfPage,
153        start_point: PdfPoint,
154        end_page: &PdfPage,
155        end_point: PdfPoint,
156    ) -> Option<PdfSelection> {
157        let ptr = unsafe {
158            ffi::pdf_document_selection_from_pages_points(
159                self.handle.as_ptr(),
160                start_page.as_handle_ptr(),
161                start_point.x,
162                start_point.y,
163                end_page.as_handle_ptr(),
164                end_point.x,
165                end_point.y,
166            )
167        };
168        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(PdfSelection::from_handle)
169    }
170
171    #[must_use]
172    pub fn selection_from_page_points_with_granularity(
173        &self,
174        start_page: &PdfPage,
175        start_point: PdfPoint,
176        end_page: &PdfPage,
177        end_point: PdfPoint,
178        granularity: PdfSelectionGranularity,
179    ) -> Option<PdfSelection> {
180        let ptr = unsafe {
181            ffi::pdf_document_selection_from_pages_points_with_granularity(
182                self.handle.as_ptr(),
183                start_page.as_handle_ptr(),
184                start_point.x,
185                start_point.y,
186                end_page.as_handle_ptr(),
187                end_point.x,
188                end_point.y,
189                granularity.as_raw(),
190            )
191        };
192        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(PdfSelection::from_handle)
193    }
194
195    #[must_use]
196    pub fn selection_from_page_characters(
197        &self,
198        start_page: &PdfPage,
199        start_character: usize,
200        end_page: &PdfPage,
201        end_character: usize,
202    ) -> Option<PdfSelection> {
203        let ptr = unsafe {
204            ffi::pdf_document_selection_from_pages_characters(
205                self.handle.as_ptr(),
206                start_page.as_handle_ptr(),
207                start_character as u64,
208                end_page.as_handle_ptr(),
209                end_character as u64,
210            )
211        };
212        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(PdfSelection::from_handle)
213    }
214
215    pub fn unlock(&self, password: &str) -> Result<bool> {
216        let password = c_string(password)?;
217        Ok(unsafe { ffi::pdf_document_unlock(self.handle.as_ptr(), password.as_ptr()) != 0 })
218    }
219
220    pub fn set_delegate(&self, delegate: Option<&PdfDocumentDelegateHandle>) -> Result<()> {
221        let mut out_error = ptr::null_mut();
222        let status = unsafe {
223            ffi::pdf_document_set_delegate(
224                self.handle.as_ptr(),
225                delegate.map_or(ptr::null_mut(), PdfDocumentDelegateHandle::as_handle_ptr),
226                &mut out_error,
227            )
228        };
229        crate::util::status_result(status, out_error)
230    }
231
232    pub fn write_to_url(&self, path: impl AsRef<Path>) -> Result<()> {
233        let path = path_to_c_string(path.as_ref())?;
234        let mut out_error = ptr::null_mut();
235        let status = unsafe {
236            ffi::pdf_document_write_to_url(self.handle.as_ptr(), path.as_ptr(), &mut out_error)
237        };
238        crate::util::status_result(status, out_error)
239    }
240
241    pub fn write_to_url_with_options(
242        &self,
243        path: impl AsRef<Path>,
244        options: &PdfDocumentWriteOptions,
245    ) -> Result<()> {
246        let path = path_to_c_string(path.as_ref())?;
247        let options_json = serde_json::to_string(options).map_err(|error| {
248            PdfKitError::new(
249                ffi::status::FRAMEWORK,
250                format!("failed to encode PDFDocument write options: {error}"),
251            )
252        })?;
253        let options_json = c_string(&options_json)?;
254        let mut out_error = ptr::null_mut();
255        let status = unsafe {
256            ffi::pdf_document_write_to_url_with_options(
257                self.handle.as_ptr(),
258                path.as_ptr(),
259                options_json.as_ptr(),
260                &mut out_error,
261            )
262        };
263        crate::util::status_result(status, out_error)
264    }
265
266    pub fn insert_page(&self, page: &PdfPage, index: usize) -> Result<()> {
267        let mut out_error = ptr::null_mut();
268        let status = unsafe {
269            ffi::pdf_document_insert_page(
270                self.handle.as_ptr(),
271                page.as_handle_ptr(),
272                index as u64,
273                &mut out_error,
274            )
275        };
276        crate::util::status_result(status, out_error)
277    }
278
279    pub fn remove_page(&self, index: usize) -> Result<()> {
280        let mut out_error = ptr::null_mut();
281        let status = unsafe {
282            ffi::pdf_document_remove_page_at(self.handle.as_ptr(), index as u64, &mut out_error)
283        };
284        crate::util::status_result(status, out_error)
285    }
286
287    pub fn exchange_pages(&self, index_a: usize, index_b: usize) -> Result<()> {
288        let mut out_error = ptr::null_mut();
289        let status = unsafe {
290            ffi::pdf_document_exchange_pages(
291                self.handle.as_ptr(),
292                index_a as u64,
293                index_b as u64,
294                &mut out_error,
295            )
296        };
297        crate::util::status_result(status, out_error)
298    }
299
300    pub(crate) fn as_handle_ptr(&self) -> *mut core::ffi::c_void {
301        self.handle.as_ptr()
302    }
303}