1use crate::bindings::pdfium::*;
2use crate::printer::FilePrinter;
3use crate::printer::PrinterDevice;
4use crate::ticket::PrintTicket;
5use crate::ticket::ToDevModeError;
6use crate::utils::emf::Emf;
7use crate::utils::pdfium::PdfiumCustomDocument;
8use crate::utils::pdfium::PdfiumGuard;
9use crate::utils::wchar;
10use scopeguard::defer;
11use std::cell::Cell;
12use std::path::Path;
13use std::ptr;
14use std::{fs::File, mem};
15use thiserror::Error;
16use windows::Win32::Foundation::RECT;
17use windows::{
18 core::PCWSTR,
19 Win32::{
20 Graphics::Gdi::{
21 CreateDCW, DeleteDC, GetDeviceCaps, SetBrushOrgEx, SetGraphicsMode, SetStretchBltMode,
22 GET_DEVICE_CAPS_INDEX, GM_ADVANCED, HALFTONE, LOGPIXELSX, LOGPIXELSY, PHYSICALHEIGHT,
23 PHYSICALOFFSETX, PHYSICALOFFSETY, PHYSICALWIDTH,
24 },
25 Storage::Xps::{AbortDoc, EndDoc, EndPage, StartDocW, StartPage, DOCINFOW},
26 },
27};
28
29#[derive(Error, Debug)]
30pub enum PdfiumPrinterError {
32 #[error("Failed to open printer")]
34 FailedToOpenPrinter,
35 #[error("File I/O error")]
37 FileIOError(#[source] std::io::Error),
38 #[error("Print Ticker Error")]
40 PrintTicketError(#[source] ToDevModeError),
41 #[error("StartDocW failed (returned {0})")]
43 StartDocFailed(i32),
44 #[error("StartPage failed for page {0} (returned {1})")]
46 StartPageFailed(i32, i32),
47 #[error("EndPage failed for page {0} (returned {1})")]
49 EndPageFailed(i32, i32),
50 #[error("EndDoc failed (returned {0})")]
52 EndDocFailed(i32),
53 #[error("PDFium failed to load the document (error {0})")]
55 PdfiumLoadFailed(u32),
56 #[error("Failed to create EMF for page {0}")]
58 EmfCreateFailed(i32),
59 #[error("Failed to playback EMF for page {0}")]
61 EmfPlaybackFailed(i32),
62}
63
64pub struct PdfiumPrinter {
66 printer: PrinterDevice,
67}
68
69impl PdfiumPrinter {
70 pub fn new(printer: PrinterDevice) -> Self {
72 Self { printer }
73 }
74}
75
76const PRINT_DRIVER: PCWSTR = PCWSTR(
77 [
78 'W' as u16, 'I' as u16, 'N' as u16, 'S' as u16, 'P' as u16, 'O' as u16, 'O' as u16,
79 'L' as u16, 0,
80 ]
81 .as_ptr(),
82);
83
84impl FilePrinter for PdfiumPrinter {
85 type Options = PrintTicket;
86 type Error = PdfiumPrinterError;
87
88 fn print(
89 &self,
90 path: &Path,
91 options: PrintTicket,
92 ) -> std::result::Result<(), PdfiumPrinterError> {
93 unsafe {
94 let dev_mode = options
95 .to_dev_mode(&self.printer)
96 .map_err(PdfiumPrinterError::PrintTicketError)?;
97 let hdc_print = CreateDCW(
106 PRINT_DRIVER,
107 PCWSTR(wchar::to_wide_chars(self.printer.os_name()).as_ptr()),
108 None,
109 Some(dev_mode.as_ptr() as *const _),
110 );
111 if hdc_print.is_invalid() {
112 return Err(PdfiumPrinterError::FailedToOpenPrinter);
113 }
114 defer! {
115 let _ = DeleteDC(hdc_print);
116 }
117
118 SetGraphicsMode(hdc_print, GM_ADVANCED);
119 SetStretchBltMode(hdc_print, HALFTONE);
120 let _ = SetBrushOrgEx(hdc_print, 0, 0, None);
124
125 let mut doc_name = wchar::to_wide_chars(path.file_name().unwrap_or(path.as_ref()));
126 let doc_info = DOCINFOW {
127 cbSize: mem::size_of::<DOCINFOW>() as i32,
128 fwType: 0,
129 lpszDocName: PCWSTR(doc_name.as_mut_ptr()),
130 lpszOutput: PCWSTR::null(),
131 lpszDatatype: PCWSTR::null(),
132 };
133
134 let start_doc_ret = StartDocW(hdc_print, &doc_info);
135 if start_doc_ret <= 0 {
136 return Err(PdfiumPrinterError::StartDocFailed(start_doc_ret));
137 }
138
139 let document_completed = Cell::new(false);
140 defer! {
141 if !document_completed.get() {
142 let _ = AbortDoc(hdc_print);
143 }
144 }
145
146 let _pdfium_guard = PdfiumGuard::guard();
147 let mut file = File::open(path).map_err(PdfiumPrinterError::FileIOError)?;
148 let mut file_delegation =
149 PdfiumCustomDocument::new(&mut file).map_err(PdfiumPrinterError::FileIOError)?;
150 let document = FPDF_LoadCustomDocument(file_delegation.as_mut(), ptr::null());
151 if document.is_null() {
152 return Err(PdfiumPrinterError::PdfiumLoadFailed(FPDF_GetLastError()));
153 }
154 defer! {
155 FPDF_CloseDocument(document);
156 }
157
158 let get_attr =
159 |kind: GET_DEVICE_CAPS_INDEX| -> i32 { GetDeviceCaps(Some(hdc_print), kind) };
160 let page_count = FPDF_GetPageCount(document);
161 for page_index in 0..page_count {
162 let page = FPDF_LoadPage(document, page_index);
163 defer! {
164 FPDF_ClosePage(page);
165 }
166
167 let start_page_ret = StartPage(hdc_print);
168 if start_page_ret <= 0 {
169 return Err(PdfiumPrinterError::StartPageFailed(
170 page_index,
171 start_page_ret,
172 ));
173 }
174 let dpi_x = get_attr(LOGPIXELSX);
175 let dpi_y = get_attr(LOGPIXELSY);
176 let page_std_width = FPDF_GetPageWidth(page);
177 let page_std_height = FPDF_GetPageHeight(page);
178 let page_width = (page_std_width * dpi_x as f64 / 72.0).round() as i32;
179 let page_height = (page_std_height * dpi_y as f64 / 72.0).round() as i32;
180 let emf = Emf::new(
181 hdc_print,
182 (page_std_width * 2540.0 / 72.0).round() as i32,
183 (page_std_height * 2540.0 / 72.0).round() as i32,
184 |hdc_emf| {
185 SetGraphicsMode(hdc_emf, GM_ADVANCED);
186 FPDF_RenderPage(
187 hdc_emf,
188 page,
189 0,
190 0,
191 page_width,
192 page_height,
193 0,
194 FPDF_PRINTING,
195 );
196 true
197 },
198 )
199 .map_err(|_| PdfiumPrinterError::EmfCreateFailed(page_index))?;
200
201 let paper_width = get_attr(PHYSICALWIDTH);
202 let paper_height = get_attr(PHYSICALHEIGHT);
203 let scale = f64::min(
204 paper_width as f64 / page_width as f64,
205 paper_height as f64 / page_height as f64,
206 );
207 let actual_width = (page_width as f64 * scale).round() as i32;
208 let actual_height = (page_height as f64 * scale).round() as i32;
209 let left = -get_attr(PHYSICALOFFSETX) + (paper_width - actual_width) / 2;
210 let top = -get_attr(PHYSICALOFFSETY) + (paper_height - actual_height) / 2;
211 let target_rect = RECT {
212 left,
213 top,
214 right: actual_width + left,
215 bottom: actual_height + top,
216 };
217 let page_result = emf
218 .playback(hdc_print, target_rect)
219 .map_err(|_| PdfiumPrinterError::EmfPlaybackFailed(page_index));
220 let end_page_ret = EndPage(hdc_print);
221 page_result?;
222 if end_page_ret <= 0 {
223 return Err(PdfiumPrinterError::EndPageFailed(page_index, end_page_ret));
224 }
225 }
226
227 let end_doc_ret = EndDoc(hdc_print);
228 if end_doc_ret <= 0 {
229 return Err(PdfiumPrinterError::EndDocFailed(end_doc_ret));
230 }
231 document_completed.set(true);
232 }
233 Ok(())
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::PdfiumPrinter;
240 use crate::{printer::FilePrinter, test_utils::null_device};
241 use std::path::Path;
242
243 #[test]
244 fn print_simple_pdf_document() {
245 let device = null_device::thread_local();
246 let pdf = PdfiumPrinter::new(device);
247 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.pdf");
248 pdf.print(path.as_path(), Default::default()).unwrap();
249 }
250}