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