1pub mod attachment;
5pub mod attachments;
6pub mod bookmark;
7pub mod bookmarks;
8pub mod fonts;
9pub mod form;
10pub mod metadata;
11pub mod page;
12pub mod pages;
13pub mod permissions;
14pub mod signature;
15pub mod signatures;
16
17use crate::bindgen::FPDF_DOCUMENT;
18use crate::error::PdfiumError;
19use crate::error::PdfiumInternalError;
20use crate::pdf::document::attachments::PdfAttachments;
21use crate::pdf::document::bookmarks::PdfBookmarks;
22use crate::pdf::document::fonts::PdfFonts;
23use crate::pdf::document::form::PdfForm;
24use crate::pdf::document::metadata::PdfMetadata;
25use crate::pdf::document::pages::PdfPages;
26use crate::pdf::document::permissions::PdfPermissions;
27use crate::pdf::document::signatures::PdfSignatures;
28use crate::pdfium::PdfiumLibraryBindingsAccessor;
29use crate::utils::files::get_pdfium_file_writer_from_writer;
30use crate::utils::files::FpdfFileAccessExt;
31use std::fmt::{Debug, Formatter};
32use std::io::Cursor;
33use std::io::Write;
34
35#[cfg(not(target_arch = "wasm32"))]
36use std::fs::File;
37
38use std::marker::PhantomData;
39#[cfg(not(target_arch = "wasm32"))]
40use std::path::Path;
41
42#[cfg(target_arch = "wasm32")]
43use js_sys::{Array, Uint8Array};
44
45#[cfg(target_arch = "wasm32")]
46use wasm_bindgen::JsValue;
47
48#[cfg(target_arch = "wasm32")]
49use web_sys::Blob;
50
51#[cfg(doc)]
56struct Blob;
57
58#[derive(Debug, Copy, Clone, PartialEq)]
62pub enum PdfDocumentVersion {
63 Unset,
66
67 Pdf1_0,
69
70 Pdf1_1,
72
73 Pdf1_2,
75
76 Pdf1_3,
78
79 Pdf1_4,
81
82 Pdf1_5,
84
85 Pdf1_6,
87
88 Pdf1_7,
93
94 Pdf2_0,
96
97 Other(i32),
102}
103
104impl PdfDocumentVersion {
105 pub const DEFAULT_VERSION: PdfDocumentVersion = PdfDocumentVersion::Pdf1_7;
107
108 #[inline]
109 pub(crate) fn from_pdfium(version: i32) -> Self {
110 match version {
111 10 => PdfDocumentVersion::Pdf1_0,
112 11 => PdfDocumentVersion::Pdf1_1,
113 12 => PdfDocumentVersion::Pdf1_2,
114 13 => PdfDocumentVersion::Pdf1_3,
115 14 => PdfDocumentVersion::Pdf1_4,
116 15 => PdfDocumentVersion::Pdf1_5,
117 16 => PdfDocumentVersion::Pdf1_6,
118 17 => PdfDocumentVersion::Pdf1_7,
119 20 => PdfDocumentVersion::Pdf2_0,
120 _ => PdfDocumentVersion::Other(version),
121 }
122 }
123
124 #[inline]
125 pub(crate) fn as_pdfium(&self) -> Option<i32> {
126 match self {
127 PdfDocumentVersion::Pdf1_0 => Some(10),
128 PdfDocumentVersion::Pdf1_1 => Some(11),
129 PdfDocumentVersion::Pdf1_2 => Some(12),
130 PdfDocumentVersion::Pdf1_3 => Some(13),
131 PdfDocumentVersion::Pdf1_4 => Some(14),
132 PdfDocumentVersion::Pdf1_5 => Some(15),
133 PdfDocumentVersion::Pdf1_6 => Some(16),
134 PdfDocumentVersion::Pdf1_7 => Some(17),
135 PdfDocumentVersion::Pdf2_0 => Some(20),
136 PdfDocumentVersion::Other(value) => Some(*value),
137 PdfDocumentVersion::Unset => None,
138 }
139 }
140}
141
142pub struct PdfDocument<'a> {
157 handle: FPDF_DOCUMENT,
158 output_version: Option<PdfDocumentVersion>,
159 attachments: PdfAttachments<'a>,
160 bookmarks: PdfBookmarks<'a>,
161 form: Option<PdfForm<'a>>,
162 fonts: PdfFonts<'a>,
163 metadata: PdfMetadata<'a>,
164 pages: PdfPages<'a>,
165 permissions: PdfPermissions<'a>,
166 signatures: PdfSignatures<'a>,
167 source_byte_buffer: Option<Vec<u8>>,
168
169 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
170 file_access_reader: Option<Box<FpdfFileAccessExt<'a>>>,
172
173 lifetime: PhantomData<&'a FPDF_DOCUMENT>,
174}
175
176impl<'a> PdfDocument<'a> {
177 #[inline]
178 pub(crate) fn from_pdfium(handle: FPDF_DOCUMENT) -> Self {
179 let form = PdfForm::from_pdfium(handle);
180
181 let pages = PdfPages::from_pdfium(handle, form.as_ref().map(|form| form.handle()));
182
183 PdfDocument {
184 handle,
185 output_version: None,
186 attachments: PdfAttachments::from_pdfium(handle),
187 bookmarks: PdfBookmarks::from_pdfium(handle),
188 form,
189 fonts: PdfFonts::from_pdfium(handle),
190 metadata: PdfMetadata::from_pdfium(handle),
191 pages,
192 permissions: PdfPermissions::from_pdfium(handle),
193 signatures: PdfSignatures::from_pdfium(handle),
194 source_byte_buffer: None,
195 file_access_reader: None,
196 lifetime: PhantomData,
197 }
198 }
199
200 #[inline]
202 pub(crate) fn handle(&self) -> FPDF_DOCUMENT {
203 self.handle
204 }
205
206 #[inline]
209 pub(crate) fn set_source_byte_buffer(&mut self, bytes: Vec<u8>) {
210 self.source_byte_buffer = Some(bytes);
211 }
212
213 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
216 #[inline]
218 pub(crate) fn set_file_access_reader(&mut self, reader: Box<FpdfFileAccessExt<'a>>) {
219 self.file_access_reader = Some(reader);
220 }
221
222 pub fn version(&self) -> PdfDocumentVersion {
224 let mut version = 0;
225
226 if unsafe {
227 self.bindings()
228 .FPDF_GetFileVersion(self.handle, &mut version)
229 } != 0
230 {
231 PdfDocumentVersion::from_pdfium(version)
232 } else {
233 PdfDocumentVersion::Unset
234 }
235 }
236
237 pub fn set_version(&mut self, version: PdfDocumentVersion) {
239 self.output_version = Some(version);
240 }
241
242 #[inline]
244 pub fn attachments(&self) -> &PdfAttachments<'_> {
245 &self.attachments
246 }
247
248 #[inline]
250 pub fn attachments_mut(&mut self) -> &mut PdfAttachments<'a> {
251 &mut self.attachments
252 }
253
254 #[inline]
256 pub fn bookmarks(&self) -> &PdfBookmarks<'_> {
257 &self.bookmarks
258 }
259
260 #[inline]
262 pub fn form(&self) -> Option<&PdfForm<'_>> {
263 self.form.as_ref()
264 }
265
266 #[inline]
268 pub fn fonts(&self) -> &PdfFonts<'_> {
269 &self.fonts
270 }
271
272 #[inline]
274 pub fn fonts_mut(&mut self) -> &mut PdfFonts<'a> {
275 &mut self.fonts
276 }
277
278 #[inline]
280 pub fn metadata(&self) -> &PdfMetadata<'_> {
281 &self.metadata
282 }
283
284 #[inline]
286 pub fn pages(&self) -> &PdfPages<'a> {
287 &self.pages
288 }
289
290 #[inline]
292 pub fn pages_mut(&mut self) -> &mut PdfPages<'a> {
293 &mut self.pages
294 }
295
296 #[inline]
298 pub fn permissions(&self) -> &PdfPermissions<'_> {
299 &self.permissions
300 }
301
302 #[inline]
304 pub fn signatures(&self) -> &PdfSignatures<'_> {
305 &self.signatures
306 }
307
308 pub fn save_to_writer<W: Write + 'static>(&self, writer: &mut W) -> Result<(), PdfiumError> {
310 let flags = 0;
316
317 let mut pdfium_file_writer = get_pdfium_file_writer_from_writer(writer);
318
319 let result = match self.output_version {
320 Some(version) => unsafe {
321 self.bindings().FPDF_SaveWithVersion(
322 self.handle,
323 pdfium_file_writer.as_fpdf_file_write_mut_ptr(),
324 flags,
325 version.as_pdfium().unwrap_or_else(|| {
326 PdfDocumentVersion::DEFAULT_VERSION.as_pdfium().unwrap()
327 }),
328 )
329 },
330 None => unsafe {
331 self.bindings().FPDF_SaveAsCopy(
332 self.handle,
333 pdfium_file_writer.as_fpdf_file_write_mut_ptr(),
334 flags,
335 )
336 },
337 };
338
339 match self.bindings().is_true(result) {
340 true => {
341 pdfium_file_writer.flush().map_err(PdfiumError::IoError)
344 }
345 false => {
346 Err(PdfiumError::PdfiumLibraryInternalError(
349 PdfiumInternalError::Unknown,
350 ))
351 }
352 }
353 }
354
355 #[cfg(not(target_arch = "wasm32"))]
364 pub fn save_to_file(&self, path: &(impl AsRef<Path> + ?Sized)) -> Result<(), PdfiumError> {
365 self.save_to_writer(&mut File::create(path).map_err(PdfiumError::IoError)?)
366 }
367
368 pub fn save_to_bytes(&self) -> Result<Vec<u8>, PdfiumError> {
370 let mut cursor = Cursor::new(Vec::new());
371
372 self.save_to_writer(&mut cursor)?;
373
374 Ok(cursor.into_inner())
375 }
376
377 #[cfg(any(doc, target_arch = "wasm32"))]
381 pub fn save_to_blob(&self) -> Result<Blob, PdfiumError> {
382 let bytes = self.save_to_bytes()?;
383
384 let array = Uint8Array::new_with_length(bytes.len() as u32);
385
386 array.copy_from(bytes.as_slice());
387
388 let blob =
389 Blob::new_with_u8_array_sequence(&JsValue::from(Array::of1(&JsValue::from(array))))
390 .map_err(|_| PdfiumError::JsSysErrorConstructingBlobFromBytes)?;
391
392 Ok(blob)
393 }
394}
395
396impl<'a> Drop for PdfDocument<'a> {
397 #[inline]
400 fn drop(&mut self) {
401 self.form = None;
406 unsafe {
407 self.bindings().FPDF_CloseDocument(self.handle);
408 }
409 }
410}
411
412impl<'a> Debug for PdfDocument<'a> {
413 #[inline]
414 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
415 f.debug_struct("PdfDocument")
416 .field("FPDF_DOCUMENT", &format!("{:?}", self.handle))
417 .finish()
418 }
419}
420
421impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfDocument<'a> {}
422
423#[cfg(feature = "thread_safe")]
424unsafe impl<'a> Send for PdfDocument<'a> {}
425
426#[cfg(feature = "thread_safe")]
427unsafe impl<'a> Sync for PdfDocument<'a> {}