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