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}