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