winprint_ext/printer/
win_pdf.rs1use super::DxgiPrintContext;
2use super::DxgiPrintContextError;
3use crate::printer::FilePrinter;
4use crate::printer::PrinterDevice;
5use crate::ticket::PrintTicket;
6use std::path::Path;
7use thiserror::Error;
8use windows::core::HSTRING;
9use windows::Data::Pdf::PdfDocument;
10use windows::Storage::StorageFile;
11use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_F;
12use windows::Win32::System::WinRT::Pdf::PdfCreateRenderer;
13use windows::Win32::System::WinRT::Pdf::PDF_RENDER_PARAMS;
14
15#[derive(Error, Debug)]
16pub enum WinPdfPrinterError {
18 #[error("DXGI print context error")]
20 DxgiPrintContextError(#[from] DxgiPrintContextError),
21 #[error("Invalid path")]
23 InvalidPath(#[source] std::io::Error),
24 #[error("Failed to open the document")]
26 FailedToOpenDocument(#[source] windows::core::Error),
27 #[error("Render error")]
29 RenderError(#[source] windows::core::Error),
30}
31
32pub struct WinPdfPrinter {
34 printer: PrinterDevice,
35}
36
37impl WinPdfPrinter {
38 pub fn new(printer: PrinterDevice) -> Self {
40 Self { printer }
41 }
42}
43
44impl FilePrinter for WinPdfPrinter {
45 type Options = PrintTicket;
46 type Error = WinPdfPrinterError;
47 fn print(
48 &self,
49 path: &Path,
50 options: PrintTicket,
51 ) -> std::result::Result<(), WinPdfPrinterError> {
52 let context = DxgiPrintContext::new(
53 &self.printer,
54 &options,
55 path.file_name().unwrap_or(path.as_ref()),
56 )?;
57 let dxgi_device = &context.dxgi_device;
58 let print_control = &context.print_control;
59 let d2d_context = &context.d2d_context;
60 unsafe {
61 let absolute_path =
62 std::path::absolute(path).map_err(WinPdfPrinterError::InvalidPath)?;
63 let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(absolute_path.as_path()))
64 .map_err(WinPdfPrinterError::FailedToOpenDocument)?
65 .join()
66 .map_err(WinPdfPrinterError::FailedToOpenDocument)?;
67 let pdf_document = PdfDocument::LoadFromFileAsync(&file)
68 .map_err(WinPdfPrinterError::FailedToOpenDocument)?
69 .join()
70 .map_err(WinPdfPrinterError::FailedToOpenDocument)?;
71
72 let pdf_renderer =
73 PdfCreateRenderer(dxgi_device).map_err(WinPdfPrinterError::RenderError)?;
74 let pdf_render_options = PDF_RENDER_PARAMS {
75 IgnoreHighContrast: true.into(),
76 ..Default::default()
77 };
78 let page_count = pdf_document
79 .PageCount()
80 .map_err(WinPdfPrinterError::RenderError)?;
81 for i in 0..page_count {
82 let pdf_page = pdf_document
83 .GetPage(i)
84 .map_err(WinPdfPrinterError::RenderError)?;
85
86 let command_list = d2d_context
87 .CreateCommandList()
88 .map_err(WinPdfPrinterError::RenderError)?;
89 d2d_context.SetTarget(&command_list);
90 d2d_context.BeginDraw();
91 pdf_renderer
92 .RenderPageToDeviceContext(&pdf_page, d2d_context, Some(&pdf_render_options))
93 .map_err(WinPdfPrinterError::RenderError)?;
94 d2d_context
95 .EndDraw(None, None)
96 .map_err(WinPdfPrinterError::RenderError)?;
97 command_list
98 .Close()
99 .map_err(WinPdfPrinterError::RenderError)?;
100
101 let pdf_size = pdf_page.Size().map_err(WinPdfPrinterError::RenderError)?;
102 let dx_size = D2D_SIZE_F {
103 width: pdf_size.Width as f32,
104 height: pdf_size.Height as f32,
105 };
106 print_control
107 .AddPage(&command_list, dx_size, None, None, None)
108 .map_err(WinPdfPrinterError::RenderError)?;
109 }
110 }
111 context.close_and_wait()?;
112 Ok(())
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::WinPdfPrinter;
119 use crate::{printer::FilePrinter, test_utils::null_device};
120 use std::path::Path;
121
122 #[test]
123 fn print_simple_pdf_document() {
124 let device = null_device::thread_local();
125 let pdf = WinPdfPrinter::new(device);
126 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.pdf");
127 pdf.print(path.as_path(), Default::default()).unwrap();
128 }
129}