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> {}