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 if page_std_width <= 0.0 || page_std_height <= 0.0 {
179 return Err(PdfiumPrinterError::EmfCreateFailed(page_index));
180 }
181 let page_width = (page_std_width * dpi_x as f64 / 72.0).round() as i32;
182 let page_height = (page_std_height * dpi_y as f64 / 72.0).round() as i32;
183 let emf = Emf::new(
184 hdc_print,
185 (page_std_width * 2540.0 / 72.0).round() as i32,
186 (page_std_height * 2540.0 / 72.0).round() as i32,
187 |hdc_emf| {
188 SetGraphicsMode(hdc_emf, GM_ADVANCED);
189 FPDF_RenderPage(
190 hdc_emf,
191 page,
192 0,
193 0,
194 page_width,
195 page_height,
196 0,
197 FPDF_PRINTING,
198 );
199 true
200 },
201 )
202 .map_err(|_| PdfiumPrinterError::EmfCreateFailed(page_index))?;
203
204 let paper_width = get_attr(PHYSICALWIDTH);
205 let paper_height = get_attr(PHYSICALHEIGHT);
206 let scale = f64::min(
207 paper_width as f64 / page_width as f64,
208 paper_height as f64 / page_height as f64,
209 );
210 let actual_width = (page_width as f64 * scale).round() as i32;
211 let actual_height = (page_height as f64 * scale).round() as i32;
212 let left = -get_attr(PHYSICALOFFSETX) + (paper_width - actual_width) / 2;
213 let top = -get_attr(PHYSICALOFFSETY) + (paper_height - actual_height) / 2;
214 let target_rect = RECT {
215 left,
216 top,
217 right: actual_width + left,
218 bottom: actual_height + top,
219 };
220 let page_result = emf
221 .playback(hdc_print, target_rect)
222 .map_err(|_| PdfiumPrinterError::EmfPlaybackFailed(page_index));
223 let end_page_ret = EndPage(hdc_print);
224 page_result?;
225 if end_page_ret <= 0 {
226 return Err(PdfiumPrinterError::EndPageFailed(page_index, end_page_ret));
227 }
228 }
229
230 let end_doc_ret = EndDoc(hdc_print);
231 if end_doc_ret <= 0 {
232 return Err(PdfiumPrinterError::EndDocFailed(end_doc_ret));
233 }
234 document_completed.set(true);
235 }
236 Ok(())
237 }
238}
239
240#[cfg(all(test, feature = "test-utils"))]
241mod tests {
242 use super::PdfiumPrinter;
243 use crate::{printer::FilePrinter, test_utils::null_device};
244 use std::path::Path;
245
246 #[test]
247 fn print_simple_pdf_document() {
248 let device = null_device::thread_local();
249 let pdf = PdfiumPrinter::new(device);
250 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.pdf");
251 pdf.print(path.as_path(), Default::default()).unwrap();
252 }
253}