Skip to main content

winprint_ext/printer/
dxgi.rs

1use crate::printer::PrinterDevice;
2use crate::ticket::PrintTicket;
3use crate::ticket::ToDevModeError;
4use crate::utils::com::ComInitializer;
5use crate::utils::print_completion_source::PrintCompletionSource;
6use crate::utils::print_completion_source::PrintCompletionWaiter;
7use crate::utils::wchar;
8use std::cmp::max;
9use std::ffi::OsStr;
10use std::ptr;
11use thiserror::Error;
12use windows::core::Interface;
13use windows::core::PCWSTR;
14use windows::Win32::Foundation::E_UNEXPECTED;
15use windows::Win32::Foundation::HMODULE;
16use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
17use windows::Win32::Graphics::Direct2D::ID2D1Device;
18use windows::Win32::Graphics::Direct2D::ID2D1DeviceContext;
19use windows::Win32::Graphics::Direct2D::ID2D1Factory1;
20use windows::Win32::Graphics::Direct2D::ID2D1PrintControl;
21use windows::Win32::Graphics::Direct2D::D2D1_DEVICE_CONTEXT_OPTIONS_NONE;
22use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_OPTIONS;
23use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_SINGLE_THREADED;
24use windows::Win32::Graphics::Direct2D::D2D1_PRINT_CONTROL_PROPERTIES;
25use windows::Win32::Graphics::Direct3D::D3D_DRIVER_TYPE_HARDWARE;
26use windows::Win32::Graphics::Direct3D11::D3D11CreateDevice;
27use windows::Win32::Graphics::Direct3D11::ID3D11Device;
28use windows::Win32::Graphics::Direct3D11::ID3D11DeviceContext;
29use windows::Win32::Graphics::Direct3D11::D3D11_CREATE_DEVICE_BGRA_SUPPORT;
30use windows::Win32::Graphics::Direct3D11::D3D11_SDK_VERSION;
31use windows::Win32::Graphics::Dxgi::IDXGIDevice;
32use windows::Win32::Graphics::Gdi::DEVMODEW;
33use windows::Win32::Graphics::Imaging::CLSID_WICImagingFactory2;
34use windows::Win32::Graphics::Imaging::D2D::IWICImagingFactory2;
35use windows::Win32::Storage::Xps::Printing::IPrintDocumentPackageStatusEvent;
36use windows::Win32::Storage::Xps::Printing::IPrintDocumentPackageTarget;
37use windows::Win32::Storage::Xps::Printing::IPrintDocumentPackageTargetFactory;
38use windows::Win32::Storage::Xps::Printing::PrintDocumentPackageTargetFactory;
39use windows::Win32::System::Com::CoCreateInstance;
40use windows::Win32::System::Com::IConnectionPointContainer;
41use windows::Win32::System::Com::CLSCTX_ALL;
42use windows::Win32::UI::Shell::SHCreateMemStream;
43
44#[derive(Error, Debug)]
45/// Represents an error occurred while printing via DXGI.
46pub enum DxgiPrintContextError {
47    /// Print ticket error.
48    #[error("Print Ticker Error")]
49    PrintTicketError(#[source] ToDevModeError),
50    /// Failed to create event.
51    #[error("Failed to create event")]
52    FailedToCreateEvent(#[source] windows::core::Error),
53    /// Failed to initialize DirectX.
54    #[error("Failed to initialize DirectX")]
55    InitializeDirectX(#[source] windows::core::Error),
56    /// Failed to start job.
57    #[error("Failed to start job")]
58    FailedToStartJob(#[source] windows::core::Error),
59    /// Stream not allocated.
60    #[error("Stream not allocated")]
61    StreamNotAllocated,
62    /// Failed to close print control.
63    #[error("Failed to close print control")]
64    FailedToClosePrintControl(#[source] windows::core::Error),
65}
66
67const DEFAULT_DPI: i16 = 300;
68
69/// Represents a print context during printing via DXGI.
70#[allow(missing_docs)]
71pub struct DxgiPrintContext {
72    pub d3d_device: ID3D11Device,
73    pub d3d_context: ID3D11DeviceContext,
74    pub dxgi_device: IDXGIDevice,
75    pub d2d_factory: ID2D1Factory1,
76    pub d2d_device: ID2D1Device,
77    pub d2d_context: ID2D1DeviceContext,
78    pub wic_factory: IWICImagingFactory2,
79    pub document_target_factory: IPrintDocumentPackageTargetFactory,
80    pub document_target: IPrintDocumentPackageTarget,
81    pub print_control: ID2D1PrintControl,
82    pub completion_waiter: PrintCompletionWaiter,
83    // The last field will be dropped last, according to the drop order.
84    _com_initializer: ComInitializer,
85}
86
87impl Drop for DxgiPrintContext {
88    fn drop(&mut self) {
89        // Ensure the print control is closed even on early error paths,
90        // otherwise the spooler keeps an unfinished job.
91        unsafe {
92            let _ = self.print_control.Close();
93        }
94    }
95}
96
97impl DxgiPrintContext {
98    /// Create a new print context, used for printing via DXGI.
99    pub fn new(
100        device: &PrinterDevice,
101        options: &PrintTicket,
102        job_name: &OsStr,
103    ) -> Result<Self, DxgiPrintContextError> {
104        // Initialize COM before calling any COM functions.
105        let _com_initializer = ComInitializer::new();
106        unsafe {
107            let dev_mode = options
108                .to_dev_mode(device)
109                .map_err(DxgiPrintContextError::PrintTicketError)?;
110            let raster_dpi = {
111                let dev_mode = &*(dev_mode.as_ptr() as *const DEVMODEW);
112                let dpi_x = if (dev_mode.dmFields & windows::Win32::Graphics::Gdi::DM_PRINTQUALITY)
113                    .0
114                    != 0
115                {
116                    Some(dev_mode.Anonymous1.Anonymous1.dmPrintQuality)
117                } else {
118                    None
119                };
120                let dpi_y =
121                    if (dev_mode.dmFields & windows::Win32::Graphics::Gdi::DM_YRESOLUTION).0 != 0 {
122                        Some(dev_mode.dmYResolution)
123                    } else {
124                        None
125                    };
126                match (dpi_x, dpi_y) {
127                    (Some(x), Some(y)) => max(x, y),
128                    (Some(x), None) => x,
129                    (None, Some(y)) => y,
130                    (None, None) => DEFAULT_DPI,
131                }
132            };
133            let mut d3d_device: Option<ID3D11Device> = None;
134            let mut d3d_context: Option<ID3D11DeviceContext> = None;
135            D3D11CreateDevice(
136                None,
137                D3D_DRIVER_TYPE_HARDWARE,
138                HMODULE::default(),
139                D3D11_CREATE_DEVICE_BGRA_SUPPORT,
140                None,
141                D3D11_SDK_VERSION,
142                Some(ptr::addr_of_mut!(d3d_device)),
143                None,
144                Some(ptr::addr_of_mut!(d3d_context)),
145            )
146            .map_err(DxgiPrintContextError::InitializeDirectX)?;
147            let d3d_device = d3d_device.ok_or(DxgiPrintContextError::InitializeDirectX(
148                windows::core::Error::from_hresult(E_UNEXPECTED),
149            ))?;
150            let d3d_context = d3d_context.ok_or(DxgiPrintContextError::InitializeDirectX(
151                windows::core::Error::from_hresult(E_UNEXPECTED),
152            ))?;
153            let dxgi_device = d3d_device
154                .cast::<IDXGIDevice>()
155                .map_err(DxgiPrintContextError::InitializeDirectX)?;
156
157            let d2d_factory_options = D2D1_FACTORY_OPTIONS::default();
158            let d2d_factory: ID2D1Factory1 = D2D1CreateFactory(
159                D2D1_FACTORY_TYPE_SINGLE_THREADED,
160                Some(&d2d_factory_options),
161            )
162            .map_err(DxgiPrintContextError::InitializeDirectX)?;
163
164            let d2d_device = d2d_factory
165                .CreateDevice(&dxgi_device)
166                .map_err(DxgiPrintContextError::InitializeDirectX)?;
167            let d2d_context = d2d_device
168                .CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE)
169                .map_err(DxgiPrintContextError::InitializeDirectX)?;
170
171            let wic_factory: IWICImagingFactory2 =
172                CoCreateInstance(&CLSID_WICImagingFactory2, None, CLSCTX_ALL)
173                    .map_err(DxgiPrintContextError::InitializeDirectX)?;
174
175            let document_target_factory: IPrintDocumentPackageTargetFactory =
176                CoCreateInstance(&PrintDocumentPackageTargetFactory, None, CLSCTX_ALL)
177                    .map_err(DxgiPrintContextError::FailedToStartJob)?;
178            let print_ticket_stream = SHCreateMemStream(Some(options.get_xml()))
179                .ok_or(DxgiPrintContextError::StreamNotAllocated)?;
180            let document_target = document_target_factory
181                .CreateDocumentPackageTargetForPrintJob(
182                    PCWSTR(wchar::to_wide_chars(device.os_name()).as_ptr()),
183                    PCWSTR(wchar::to_wide_chars(job_name).as_ptr()),
184                    None,
185                    &print_ticket_stream,
186                )
187                .map_err(DxgiPrintContextError::FailedToStartJob)?;
188            let print_control_options = D2D1_PRINT_CONTROL_PROPERTIES {
189                rasterDPI: raster_dpi as f32,
190                ..Default::default()
191            };
192            let print_control = d2d_device
193                .CreatePrintControl(
194                    &wic_factory,
195                    &document_target,
196                    Some(ptr::addr_of!(print_control_options)),
197                )
198                .map_err(DxgiPrintContextError::FailedToStartJob)?;
199
200            let completion_source =
201                PrintCompletionSource::new().map_err(DxgiPrintContextError::FailedToCreateEvent)?;
202            let completion_waiter = completion_source.waiter();
203            let completion_source: IPrintDocumentPackageStatusEvent = completion_source.into();
204            let cpc: IConnectionPointContainer = document_target
205                .cast()
206                .map_err(DxgiPrintContextError::FailedToCreateEvent)?;
207            cpc.FindConnectionPoint(&IPrintDocumentPackageStatusEvent::IID)
208                .map_err(DxgiPrintContextError::FailedToCreateEvent)?
209                .Advise(&completion_source)
210                .map_err(DxgiPrintContextError::FailedToCreateEvent)?;
211
212            Ok(Self {
213                d3d_device,
214                d3d_context,
215                dxgi_device,
216                d2d_factory,
217                d2d_device,
218                d2d_context,
219                wic_factory,
220                document_target_factory,
221                document_target,
222                print_control,
223                completion_waiter,
224                _com_initializer,
225            })
226        }
227    }
228
229    /// Close the print context and wait for the completion of the print job.
230    pub fn close_and_wait(self) -> Result<(), DxgiPrintContextError> {
231        unsafe {
232            self.print_control
233                .Close()
234                .map(|_| ())
235                .map_err(DxgiPrintContextError::FailedToClosePrintControl)?;
236        }
237        self.completion_waiter.wait();
238        Ok(())
239    }
240}