#![cfg(target_os = "windows")]
use dunce::canonicalize;
use std::path::Path;
use thiserror::Error;
use windows::{
core::{GUID, HSTRING},
Data::Pdf::{PdfDocument, PdfPage, PdfPageRenderOptions},
Foundation::{self, IAsyncAction, IAsyncOperation},
Storage::{
StorageFile,
Streams::{DataReader, DataWriter, InMemoryRandomAccessStream},
},
};
mod guid;
use guid::*;
#[derive(Debug, Error)]
pub enum PdfThumbError {
#[error("io error")]
Io(#[from] std::io::Error),
#[error("windows error")]
Windows(#[from] windows::core::Error),
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rect {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl From<Rect> for Foundation::Rect {
fn from(r: Rect) -> Self {
Self {
X: r.x as _,
Y: r.y as _,
Width: r.width as _,
Height: r.height as _,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Options {
pub width: u32,
pub height: u32,
pub rect: Rect,
pub page: u32,
pub format: ImageFormat,
}
impl TryFrom<Options> for PdfPageRenderOptions {
type Error = PdfThumbError;
fn try_from(options: Options) -> Result<Self, Self::Error> {
let op = PdfPageRenderOptions::new()?;
if options.width > 0 {
op.SetDestinationWidth(options.width)?;
}
if options.height > 0 {
op.SetDestinationHeight(options.height)?;
}
if options.rect.ne(&Rect::default()) {
op.SetSourceRect(options.rect.into())?;
}
op.SetBitmapEncoderId(options.format.guid())?;
Ok(op)
}
}
#[derive(Debug, Clone, Copy)]
pub enum ImageFormat {
Png,
Bmp,
Jpeg,
Tiff,
Gif,
}
impl Default for ImageFormat {
fn default() -> Self {
Self::Png
}
}
impl ImageFormat {
const fn guid(&self) -> GUID {
use ImageFormat::*;
match self {
Png => PNG_ENCORDER_ID,
Bmp => BITMAP_ENCODER_ID,
Jpeg => JPEG_ENCORDER_ID,
Tiff => TIFF_ENCODER_ID,
Gif => GIF_ENCODER_ID,
}
}
}
#[derive(Debug)]
pub struct PdfDoc {
doc: PdfDocument,
}
impl PdfDoc {
pub fn load(pdf: &[u8]) -> Result<Self, PdfThumbError> {
let stream = InMemoryRandomAccessStream::new()?;
let writer = DataWriter::CreateDataWriter(&stream)?;
writer.WriteBytes(pdf)?;
writer.StoreAsync()?.get()?;
writer.FlushAsync()?.get()?;
writer.DetachStream()?;
let doc = PdfDocument::LoadFromStreamAsync(&stream)?.get()?;
Ok(Self { doc })
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, PdfThumbError> {
let file = get_file(path)?.get()?;
let doc = open(&file)?.get()?;
Ok(Self { doc })
}
pub async fn open_async<P: AsRef<Path>>(path: P) -> Result<Self, PdfThumbError> {
let file = get_file(path)?.await?;
let doc = open(&file)?.await?;
Ok(Self { doc })
}
pub fn page_count(&self) -> Result<u32, PdfThumbError> {
Ok(self.doc.PageCount()?)
}
pub fn thumb(&self) -> Result<Vec<u8>, PdfThumbError> {
let options = Options::default();
self.thumb_with_options(options)
}
pub async fn thumb_async(&self) -> Result<Vec<u8>, PdfThumbError> {
let options = Options::default();
self.thumb_with_options_async(options).await
}
pub fn thumb_with_options(&self, options: Options) -> Result<Vec<u8>, PdfThumbError> {
let page = self.doc.GetPage(options.page)?;
let output = InMemoryRandomAccessStream::new()?;
render(page, &output, options)?.get()?;
read_bytes(output)
}
pub async fn thumb_with_options_async(
&self,
options: Options,
) -> Result<Vec<u8>, PdfThumbError> {
let page = self.doc.GetPage(options.page)?;
let output = InMemoryRandomAccessStream::new()?;
render(page, &output, options)?.await?;
read_bytes(output)
}
}
fn get_file<P: AsRef<Path>>(path: P) -> Result<IAsyncOperation<StorageFile>, PdfThumbError> {
let path = HSTRING::from(canonicalize(path)?.as_path());
StorageFile::GetFileFromPathAsync(&path).map_err(Into::into)
}
fn open(file: &StorageFile) -> Result<IAsyncOperation<PdfDocument>, PdfThumbError> {
PdfDocument::LoadFromFileAsync(file).map_err(Into::into)
}
fn render(
page: PdfPage,
output: &InMemoryRandomAccessStream,
options: Options,
) -> Result<IAsyncAction, PdfThumbError> {
page.RenderWithOptionsToStreamAsync(output, options.try_into().as_ref().ok())
.map_err(Into::into)
}
fn read_bytes(output: InMemoryRandomAccessStream) -> Result<Vec<u8>, PdfThumbError> {
let input = output.GetInputStreamAt(0)?;
let reader = DataReader::CreateDataReader(&input)?;
let size = output.Size()?;
reader.LoadAsync(size as u32)?.get()?;
let mut buf = vec![0; size as usize];
reader.ReadBytes(&mut buf)?;
Ok(buf)
}