Skip to main content

winprint_ext/printer/
win_pdf.rs

1use 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)]
16/// Represents an error from [`WinPdfPrinter`].
17pub enum WinPdfPrinterError {
18    /// DXGI print context error.
19    #[error("DXGI print context error")]
20    DxgiPrintContextError(#[from] DxgiPrintContextError),
21    /// Invalid path.
22    #[error("Invalid path")]
23    InvalidPath(#[source] std::io::Error),
24    /// Failed to open the document.
25    #[error("Failed to open the document")]
26    FailedToOpenDocument(#[source] windows::core::Error),
27    /// Render error.
28    #[error("Render error")]
29    RenderError(#[source] windows::core::Error),
30}
31
32/// A printer that uses [`Windows.Data.Pdf`](https://learn.microsoft.com/en-us/uwp/api/windows.data.pdf?view=winrt-26100) to to print PDF documents.
33pub struct WinPdfPrinter {
34    printer: PrinterDevice,
35}
36
37impl WinPdfPrinter {
38    /// Create a new [`WinPdfPrinter`] for the given printer device.
39    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(all(test, feature = "test-utils"))]
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}