Skip to main content

pdfium_render/pdf/document/
attachment.rs

1//! Defines the [PdfAttachment] struct, exposing functionality related to a single
2//! attachment in a `PdfAttachments` collection.
3
4use crate::bindgen::{FPDF_ATTACHMENT, FPDF_WCHAR};
5use crate::error::PdfiumError;
6use crate::pdfium::PdfiumLibraryBindingsAccessor;
7use crate::utils::mem::create_byte_buffer;
8use crate::utils::utf16le::get_string_from_pdfium_utf16le_bytes;
9use std::io::Write;
10use std::marker::PhantomData;
11use std::os::raw::{c_ulong, c_void};
12
13#[cfg(not(target_arch = "wasm32"))]
14use {std::fs::File, std::path::Path};
15
16#[cfg(target_arch = "wasm32")]
17use {
18    js_sys::{Array, Uint8Array},
19    wasm_bindgen::JsValue,
20    web_sys::Blob,
21};
22
23// The following dummy declaration is used only when running cargo doc.
24// It allows documentation of WASM-specific functionality to be included
25// in documentation generated on non-WASM targets.
26
27#[cfg(doc)]
28struct Blob;
29
30#[cfg(doc)]
31use crate::pdf::document::PdfDocument;
32
33/// A single attached data file embedded in a [PdfDocument].
34pub struct PdfAttachment<'a> {
35    handle: FPDF_ATTACHMENT,
36    lifetime: PhantomData<&'a FPDF_ATTACHMENT>,
37}
38
39impl<'a> PdfAttachment<'a> {
40    #[inline]
41    pub(crate) fn from_pdfium(handle: FPDF_ATTACHMENT) -> Self {
42        PdfAttachment {
43            handle,
44            lifetime: PhantomData,
45        }
46    }
47
48    /// Returns the name of this [PdfAttachment].
49    pub fn name(&self) -> String {
50        // Retrieving the attachment name from Pdfium is a two-step operation. First, we call
51        // FPDFAttachment_GetName() with a null buffer; this will retrieve the length of
52        // the name in bytes. If the length is zero, then there is no name associated
53        // with this attachment.
54
55        // If the length is non-zero, then we reserve a byte buffer of the given
56        // length and call FPDFAttachment_GetName() again with a pointer to the buffer;
57        // this will write the name to the buffer in UTF16-LE format.
58
59        let buffer_length = unsafe {
60            self.bindings()
61                .FPDFAttachment_GetName(self.handle, std::ptr::null_mut(), 0)
62        };
63
64        if buffer_length == 0 {
65            // There is no name given for this attachment.
66
67            return String::new();
68        }
69
70        let mut buffer = create_byte_buffer(buffer_length as usize);
71
72        let result = unsafe {
73            self.bindings().FPDFAttachment_GetName(
74                self.handle,
75                buffer.as_mut_ptr() as *mut FPDF_WCHAR,
76                buffer_length,
77            )
78        };
79
80        assert_eq!(result, buffer_length);
81
82        get_string_from_pdfium_utf16le_bytes(buffer).unwrap_or_default()
83    }
84
85    /// Returns the size of this [PdfAttachment] in bytes.
86    pub fn len(&self) -> usize {
87        // Calling FPDFAttachment_GetFile() with a null buffer will retrieve the length of the
88        // data in bytes without allocating any additional memory.
89
90        let mut out_buflen: c_ulong = 0;
91
92        if self.bindings().is_true(unsafe {
93            self.bindings().FPDFAttachment_GetFile(
94                self.handle,
95                std::ptr::null_mut(),
96                0,
97                &mut out_buflen,
98            )
99        }) {
100            out_buflen as usize
101        } else {
102            0
103        }
104    }
105
106    /// Returns `true` if there is no byte data associated with this [PdfAttachment].
107    #[inline]
108    pub fn is_empty(&self) -> bool {
109        self.len() == 0
110    }
111
112    /// Writes this [PdfAttachment] to a new byte buffer, returning the byte buffer.
113    pub fn save_to_bytes(&self) -> Result<Vec<u8>, PdfiumError> {
114        // Retrieving the attachment data from Pdfium is a two-step operation. First, we call
115        // FPDFAttachment_GetFile() with a null buffer; this will retrieve the length of
116        // the data in bytes. If the length is zero, then there is no data associated
117        // with this attachment. (This can be the case if the attachment is newly created,
118        // and data for the attachment is yet to be embedded in the containing document.)
119
120        // If the length is non-zero, then we reserve a byte buffer of the given
121        // length and call FPDFAttachment_GetFile() again with a pointer to the buffer;
122        // this will write the file data to the buffer.
123
124        let mut out_buflen: c_ulong = 0;
125
126        if self.bindings().is_true(unsafe {
127            self.bindings().FPDFAttachment_GetFile(
128                self.handle,
129                std::ptr::null_mut(),
130                0,
131                &mut out_buflen,
132            )
133        }) {
134            // out_buflen now contains the length of the file data.
135
136            let buffer_length = out_buflen;
137
138            let mut buffer = create_byte_buffer(buffer_length as usize);
139
140            let result = unsafe {
141                self.bindings().FPDFAttachment_GetFile(
142                    self.handle,
143                    buffer.as_mut_ptr() as *mut c_void,
144                    buffer_length,
145                    &mut out_buflen,
146                )
147            };
148
149            assert!(self.bindings().is_true(result));
150            assert_eq!(buffer_length, out_buflen);
151
152            Ok(buffer)
153        } else {
154            Err(PdfiumError::NoDataInAttachment)
155        }
156    }
157
158    /// Writes this [PdfAttachment] to the given writer.
159    pub fn save_to_writer<W: Write>(&self, writer: &mut W) -> Result<(), PdfiumError> {
160        self.save_to_bytes().and_then(|bytes| {
161            writer
162                .write_all(bytes.as_slice())
163                .map_err(PdfiumError::IoError)
164        })
165    }
166
167    /// Writes this [PdfAttachment] to the file at the given path.
168    ///
169    /// This function is not available when compiling to WASM. You have several options for
170    /// saving attachment data in WASM:
171    /// * Use either the [PdfAttachment::save_to_writer()] or the [PdfAttachment::save_to_bytes()] functions,
172    ///   both of which are available when compiling to WASM.
173    /// * Use the [PdfAttachment::save_to_blob()] function to save attachment data directly into a new
174    ///   Javascript `Blob` object. This function is only available when compiling to WASM.
175    #[cfg(not(target_arch = "wasm32"))]
176    pub fn save_to_file(&self, path: &(impl AsRef<Path> + ?Sized)) -> Result<(), PdfiumError> {
177        self.save_to_writer(&mut File::create(path).map_err(PdfiumError::IoError)?)
178    }
179
180    /// Writes this [PdfAttachment] to a new `Blob`, returning the `Blob`.
181    ///
182    /// This function is only available when compiling to WASM.
183    #[cfg(any(doc, target_arch = "wasm32"))]
184    pub fn save_to_blob(&self) -> Result<Blob, PdfiumError> {
185        let bytes = self.save_to_bytes()?;
186
187        let array = Uint8Array::new_with_length(bytes.len() as u32);
188
189        array.copy_from(bytes.as_slice());
190
191        let blob =
192            Blob::new_with_u8_array_sequence(&JsValue::from(Array::of1(&JsValue::from(array))))
193                .map_err(|_| PdfiumError::JsSysErrorConstructingBlobFromBytes)?;
194
195        Ok(blob)
196    }
197}
198
199impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfAttachment<'a> {}
200
201#[cfg(feature = "thread_safe")]
202unsafe impl<'a> Send for PdfAttachment<'a> {}
203
204#[cfg(feature = "thread_safe")]
205unsafe impl<'a> Sync for PdfAttachment<'a> {}