1#![cfg(target_os = "windows")]
41
42use dunce::canonicalize;
43use std::path::Path;
44use thiserror::Error;
45use windows::{
46 core::{GUID, HSTRING},
47 Data::Pdf::{PdfDocument, PdfPage, PdfPageRenderOptions},
48 Foundation::{self, IAsyncAction, IAsyncOperation},
49 Storage::{
50 StorageFile,
51 Streams::{DataReader, DataWriter, InMemoryRandomAccessStream},
52 },
53};
54
55mod guid;
56use guid::*;
57
58#[derive(Debug, Error)]
59pub enum PdfThumbError {
60 #[error("io error")]
61 Io(#[from] std::io::Error),
62 #[error("windows error")]
63 Windows(#[from] windows::core::Error),
64}
65
66#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
67pub struct Rect {
68 pub x: u32,
69 pub y: u32,
70 pub width: u32,
71 pub height: u32,
72}
73
74impl From<Rect> for Foundation::Rect {
75 fn from(r: Rect) -> Self {
76 Self {
77 X: r.x as _,
78 Y: r.y as _,
79 Width: r.width as _,
80 Height: r.height as _,
81 }
82 }
83}
84
85#[derive(Debug, Default, Clone, Copy)]
86pub struct Options {
87 pub width: u32,
89 pub height: u32,
91 pub rect: Rect,
93 pub page: u32,
95 pub format: ImageFormat,
97}
98
99impl TryFrom<Options> for PdfPageRenderOptions {
100 type Error = PdfThumbError;
101 fn try_from(options: Options) -> Result<Self, Self::Error> {
102 let op = PdfPageRenderOptions::new()?;
103 if options.width > 0 {
104 op.SetDestinationWidth(options.width)?;
105 }
106 if options.height > 0 {
107 op.SetDestinationHeight(options.height)?;
108 }
109 if options.rect.ne(&Rect::default()) {
110 op.SetSourceRect(options.rect.into())?;
111 }
112 op.SetBitmapEncoderId(options.format.guid())?;
113 Ok(op)
114 }
115}
116
117#[derive(Debug, Clone, Copy)]
118pub enum ImageFormat {
119 Png,
120 Bmp,
121 Jpeg,
122 Tiff,
123 Gif,
124}
125
126impl Default for ImageFormat {
127 fn default() -> Self {
128 Self::Png
129 }
130}
131
132impl ImageFormat {
133 const fn guid(&self) -> GUID {
134 use ImageFormat::*;
135 match self {
136 Png => PNG_ENCORDER_ID,
137 Bmp => BITMAP_ENCODER_ID,
138 Jpeg => JPEG_ENCORDER_ID,
139 Tiff => TIFF_ENCODER_ID,
140 Gif => GIF_ENCODER_ID,
141 }
142 }
143}
144
145#[derive(Debug)]
146pub struct PdfDoc {
147 doc: PdfDocument,
148}
149
150impl PdfDoc {
151 pub fn load(pdf: &[u8]) -> Result<Self, PdfThumbError> {
153 let stream = InMemoryRandomAccessStream::new()?;
154 let writer = DataWriter::CreateDataWriter(&stream)?;
155 writer.WriteBytes(pdf)?;
156 writer.StoreAsync()?.get()?;
157 writer.FlushAsync()?.get()?;
158 writer.DetachStream()?;
159 let doc = PdfDocument::LoadFromStreamAsync(&stream)?.get()?;
160 Ok(Self { doc })
161 }
162
163 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, PdfThumbError> {
165 let file = get_file(path)?.get()?;
166 let doc = open(&file)?.get()?;
167 Ok(Self { doc })
168 }
169
170 pub async fn open_async<P: AsRef<Path>>(path: P) -> Result<Self, PdfThumbError> {
172 let file = get_file(path)?.await?;
173 let doc = open(&file)?.await?;
174 Ok(Self { doc })
175 }
176
177 pub fn page_count(&self) -> Result<u32, PdfThumbError> {
179 Ok(self.doc.PageCount()?)
180 }
181
182 pub fn thumb(&self) -> Result<Vec<u8>, PdfThumbError> {
184 let options = Options::default();
185 self.thumb_with_options(options)
186 }
187
188 pub async fn thumb_async(&self) -> Result<Vec<u8>, PdfThumbError> {
190 let options = Options::default();
191 self.thumb_with_options_async(options).await
192 }
193
194 pub fn thumb_with_options(&self, options: Options) -> Result<Vec<u8>, PdfThumbError> {
196 let page = self.doc.GetPage(options.page)?;
197 let output = InMemoryRandomAccessStream::new()?;
198 render(page, &output, options)?.get()?;
199 read_bytes(output)
200 }
201
202 pub async fn thumb_with_options_async(
204 &self,
205 options: Options,
206 ) -> Result<Vec<u8>, PdfThumbError> {
207 let page = self.doc.GetPage(options.page)?;
208 let output = InMemoryRandomAccessStream::new()?;
209 render(page, &output, options)?.await?;
210 read_bytes(output)
211 }
212}
213
214fn get_file<P: AsRef<Path>>(path: P) -> Result<IAsyncOperation<StorageFile>, PdfThumbError> {
215 let path = HSTRING::from(canonicalize(path)?.as_path());
216 StorageFile::GetFileFromPathAsync(&path).map_err(Into::into)
217}
218
219fn open(file: &StorageFile) -> Result<IAsyncOperation<PdfDocument>, PdfThumbError> {
220 PdfDocument::LoadFromFileAsync(file).map_err(Into::into)
221}
222
223fn render(
224 page: PdfPage,
225 output: &InMemoryRandomAccessStream,
226 options: Options,
227) -> Result<IAsyncAction, PdfThumbError> {
228 page.RenderWithOptionsToStreamAsync(output, options.try_into().as_ref().ok())
229 .map_err(Into::into)
230}
231
232fn read_bytes(output: InMemoryRandomAccessStream) -> Result<Vec<u8>, PdfThumbError> {
233 let input = output.GetInputStreamAt(0)?;
234 let reader = DataReader::CreateDataReader(&input)?;
235 let size = output.Size()?;
236 reader.LoadAsync(size as u32)?.get()?;
237 let mut buf = vec![0; size as usize];
238 reader.ReadBytes(&mut buf)?;
239 Ok(buf)
240}