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