use crate::bindgen::FPDF_DOCUMENT;
use crate::error::{PdfiumError, PdfiumInternalError};
use crate::pdf::document::attachment::PdfAttachment;
use crate::pdfium::PdfiumLibraryBindingsAccessor;
use std::io::Read;
use std::marker::PhantomData;
use std::ops::{Range, RangeInclusive};
use std::os::raw::{c_int, c_ulong, c_void};
#[cfg(not(target_arch = "wasm32"))]
use {std::fs::File, std::path::Path};
#[cfg(target_arch = "wasm32")]
use {
js_sys::{ArrayBuffer, Uint8Array},
wasm_bindgen::JsCast,
wasm_bindgen_futures::JsFuture,
web_sys::{window, Blob, Response},
};
#[cfg(doc)]
struct Blob;
#[cfg(doc)]
use crate::pdf::document::PdfDocument;
pub type PdfAttachmentIndex = u16;
pub struct PdfAttachments<'a> {
document_handle: FPDF_DOCUMENT,
lifetime: PhantomData<&'a FPDF_DOCUMENT>,
}
impl<'a> PdfAttachments<'a> {
#[inline]
pub(crate) fn from_pdfium(document_handle: FPDF_DOCUMENT) -> Self {
PdfAttachments {
document_handle,
lifetime: PhantomData,
}
}
pub fn len(&self) -> PdfAttachmentIndex {
(unsafe {
self.bindings()
.FPDFDoc_GetAttachmentCount(self.document_handle)
}) as PdfAttachmentIndex
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn as_range(&self) -> Range<PdfAttachmentIndex> {
0..self.len()
}
#[inline]
pub fn as_range_inclusive(&self) -> RangeInclusive<PdfAttachmentIndex> {
if self.is_empty() {
0..=0
} else {
0..=(self.len() - 1)
}
}
pub fn get(&self, index: PdfAttachmentIndex) -> Result<PdfAttachment<'a>, PdfiumError> {
if index >= self.len() {
return Err(PdfiumError::AttachmentIndexOutOfBounds);
}
let handle = unsafe {
self.bindings()
.FPDFDoc_GetAttachment(self.document_handle, index as c_int)
};
if handle.is_null() {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
} else {
Ok(PdfAttachment::from_pdfium(handle))
}
}
pub fn create_attachment_from_bytes(
&mut self,
name: &str,
bytes: &[u8],
) -> Result<PdfAttachment<'_>, PdfiumError> {
let handle = unsafe {
self.bindings()
.FPDFDoc_AddAttachment_str(self.document_handle, name)
};
if handle.is_null() {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
} else {
if self.bindings().is_true(unsafe {
self.bindings().FPDFAttachment_SetFile(
handle,
self.document_handle,
bytes.as_ptr() as *const c_void,
bytes.len() as c_ulong,
)
}) {
Ok(PdfAttachment::from_pdfium(handle))
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn create_attachment_from_file(
&mut self,
name: &str,
path: &(impl AsRef<Path> + ?Sized),
) -> Result<PdfAttachment<'_>, PdfiumError> {
self.create_attachment_from_reader(name, File::open(path).map_err(PdfiumError::IoError)?)
}
pub fn create_attachment_from_reader<R: Read>(
&mut self,
name: &str,
mut reader: R,
) -> Result<PdfAttachment<'_>, PdfiumError> {
let mut bytes = Vec::new();
reader
.read_to_end(&mut bytes)
.map_err(PdfiumError::IoError)?;
self.create_attachment_from_bytes(name, bytes.as_slice())
}
#[cfg(any(doc, target_arch = "wasm32"))]
pub async fn create_attachment_from_fetch(
&'a mut self,
name: &str,
url: impl ToString,
) -> Result<PdfAttachment<'a>, PdfiumError> {
if let Some(window) = window() {
let fetch_result = JsFuture::from(window.fetch_with_str(url.to_string().as_str()))
.await
.map_err(PdfiumError::WebSysFetchError)?;
debug_assert!(fetch_result.is_instance_of::<Response>());
let response: Response = fetch_result
.dyn_into()
.map_err(|_| PdfiumError::WebSysInvalidResponseError)?;
let blob: Blob =
JsFuture::from(response.blob().map_err(PdfiumError::WebSysFetchError)?)
.await
.map_err(PdfiumError::WebSysFetchError)?
.into();
self.create_attachment_from_blob(name, blob).await
} else {
Err(PdfiumError::WebSysWindowObjectNotAvailable)
}
}
#[cfg(any(doc, target_arch = "wasm32"))]
pub async fn create_attachment_from_blob(
&'a mut self,
name: &str,
blob: Blob,
) -> Result<PdfAttachment<'a>, PdfiumError> {
let array_buffer: ArrayBuffer = JsFuture::from(blob.array_buffer())
.await
.map_err(PdfiumError::WebSysFetchError)?
.into();
let u8_array: Uint8Array = Uint8Array::new(&array_buffer);
let bytes: Vec<u8> = u8_array.to_vec();
self.create_attachment_from_bytes(name, bytes.as_slice())
}
pub fn delete_at_index(&mut self, index: PdfAttachmentIndex) -> Result<(), PdfiumError> {
if index >= self.len() {
return Err(PdfiumError::AttachmentIndexOutOfBounds);
}
if self.bindings().is_true(unsafe {
self.bindings()
.FPDFDoc_DeleteAttachment(self.document_handle, index as c_int)
}) {
Ok(())
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
}
}
#[inline]
pub fn iter(&self) -> PdfAttachmentsIterator<'_> {
PdfAttachmentsIterator::new(self)
}
}
impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfAttachments<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Send for PdfAttachments<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Sync for PdfAttachments<'a> {}
pub struct PdfAttachmentsIterator<'a> {
attachments: &'a PdfAttachments<'a>,
next_index: PdfAttachmentIndex,
}
impl<'a> PdfAttachmentsIterator<'a> {
#[inline]
pub(crate) fn new(signatures: &'a PdfAttachments<'a>) -> Self {
PdfAttachmentsIterator {
attachments: signatures,
next_index: 0,
}
}
}
impl<'a> Iterator for PdfAttachmentsIterator<'a> {
type Item = PdfAttachment<'a>;
fn next(&mut self) -> Option<Self::Item> {
let next = self.attachments.get(self.next_index);
self.next_index += 1;
next.ok()
}
}