1use super::DxgiPrintContext;
2use super::DxgiPrintContextError;
3use crate::printer::FilePrinter;
4use crate::printer::PrinterDevice;
5use crate::ticket::PrintTicket;
6use crate::ticket::ToDevModeError;
7use crate::utils::wchar;
8use std::path::Path;
9use thiserror::Error;
10use windows::core::PCWSTR;
11use windows::Win32::Foundation::GENERIC_READ;
12use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
13use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_F;
14use windows::Win32::Graphics::Direct2D::D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC;
15use windows::Win32::Graphics::Imaging::GUID_WICPixelFormat32bppPBGRA;
16use windows::Win32::Graphics::Imaging::WICBitmapDitherTypeNone;
17use windows::Win32::Graphics::Imaging::WICBitmapPaletteTypeMedianCut;
18use windows::Win32::Graphics::Imaging::WICDecodeMetadataCacheOnDemand;
19use windows::Win32::Graphics::Gdi::{
20 CreateDCW, DeleteDC, GetDeviceCaps, LOGPIXELSX,
21 PHYSICALHEIGHT, PHYSICALOFFSETX, PHYSICALOFFSETY, PHYSICALWIDTH,
22};
23use windows_numerics::Matrix3x2;
24
25#[derive(Error, Debug)]
26pub enum ImagePrinterError {
28 #[error("DXGI print context error")]
30 DxgiPrintContextError(#[from] DxgiPrintContextError),
31 #[error("Print ticket error")]
33 PrintTicketError(#[from] ToDevModeError),
34 #[error("Invalid path")]
36 InvalidPath(#[source] std::io::Error),
37 #[error("Failed to open the document")]
39 FailedToOpenDocument(#[source] windows::core::Error),
40 #[error("Render error")]
42 RenderError(#[source] windows::core::Error),
43}
44
45pub struct ImagePrinter {
47 printer: PrinterDevice,
48}
49
50impl ImagePrinter {
51 pub fn new(printer: PrinterDevice) -> Self {
53 Self { printer }
54 }
55
56 pub fn print_with_options(
63 &self,
64 path: &Path,
65 options: PrintTicket,
66 auto_rotate: bool,
67 ) -> std::result::Result<(), ImagePrinterError> {
68 let context = DxgiPrintContext::new(
69 &self.printer,
70 &options,
71 path.file_name().unwrap_or(path.as_ref()),
72 )?;
73 let wic_factory = &context.wic_factory;
74 let print_control = &context.print_control;
75 let d2d_context = &context.d2d_context;
76 unsafe {
77 let absolute_path =
78 std::path::absolute(path).map_err(ImagePrinterError::InvalidPath)?;
79 let image_decoder = wic_factory
80 .CreateDecoderFromFilename(
81 PCWSTR(wchar::to_wide_chars(absolute_path.as_os_str()).as_ptr()),
82 None,
83 GENERIC_READ,
84 WICDecodeMetadataCacheOnDemand,
85 )
86 .map_err(ImagePrinterError::FailedToOpenDocument)?;
87
88 let page_count = image_decoder
89 .GetFrameCount()
90 .map_err(ImagePrinterError::RenderError)?;
91 for i in 0..page_count {
92 let frame = image_decoder
93 .GetFrame(i)
94 .map_err(ImagePrinterError::RenderError)?;
95
96 let mut image_width = 0;
97 let mut image_height = 0;
98 frame
99 .GetSize(&mut image_width, &mut image_height)
100 .map_err(ImagePrinterError::RenderError)?;
101 let mut image_dpi_x = 0.0;
102 let mut image_dpi_y = 0.0;
103 frame
104 .GetResolution(&mut image_dpi_x, &mut image_dpi_y)
105 .map_err(ImagePrinterError::RenderError)?;
106
107 let dpi_x = if image_dpi_x > 0.0 { image_dpi_x } else { 96.0 };
109 let dpi_y = if image_dpi_y > 0.0 { image_dpi_y } else { 96.0 };
110
111 let natural_page_size = D2D_SIZE_F {
112 width: (image_width as f64 * 96.0 / dpi_x) as f32,
113 height: (image_height as f64 * 96.0 / dpi_y) as f32,
114 };
115
116 let need_rotate = auto_rotate
118 && natural_page_size.width > natural_page_size.height;
119
120 let dev_mode_for_dc = options
122 .to_dev_mode(&self.printer)
123 .map_err(ImagePrinterError::PrintTicketError)?;
124 let print_driver = PCWSTR(
125 ['W' as u16, 'I' as u16, 'N' as u16, 'S' as u16, 'P' as u16,
126 'O' as u16, 'O' as u16, 'L' as u16, 0].as_ptr(),
127 );
128 let hdc = CreateDCW(
129 print_driver,
130 PCWSTR(wchar::to_wide_chars(self.printer.os_name()).as_ptr()),
131 None,
132 Some(dev_mode_for_dc.as_ptr() as *const _),
133 );
134 let (page_size, translate_x, translate_y, dest_w, dest_h, scale, actual_rotate) =
135 if hdc.is_invalid() {
136 (natural_page_size, 0.0f64, 0.0f64,
137 natural_page_size.width as f64, natural_page_size.height as f64,
138 1.0f64, false)
139 } else {
140 let paper_w = GetDeviceCaps(Some(hdc), PHYSICALWIDTH);
141 let paper_h = GetDeviceCaps(Some(hdc), PHYSICALHEIGHT);
142 let offset_x = GetDeviceCaps(Some(hdc), PHYSICALOFFSETX);
143 let offset_y = GetDeviceCaps(Some(hdc), PHYSICALOFFSETY);
144 let dpi_x = GetDeviceCaps(Some(hdc), LOGPIXELSX);
145 let _ = DeleteDC(hdc);
146
147 let paper_w_dips = paper_w as f64 * 96.0 / dpi_x as f64;
149 let paper_h_dips = paper_h as f64 * 96.0 / dpi_x as f64;
150 let offset_x_dips = offset_x as f64 * 96.0 / dpi_x as f64;
151 let offset_y_dips = offset_y as f64 * 96.0 / dpi_x as f64;
152
153 let paper_landscape = paper_w_dips > paper_h_dips;
155 let should_rotate = need_rotate && paper_landscape != (natural_page_size.width > natural_page_size.height);
156
157 let (eff_w, eff_h) = if should_rotate {
158 (natural_page_size.height as f64, natural_page_size.width as f64)
159 } else {
160 (natural_page_size.width as f64, natural_page_size.height as f64)
161 };
162
163 let scale = f64::min(paper_w_dips / eff_w, paper_h_dips / eff_h);
164 let scaled_w = eff_w * scale;
165 let scaled_h = eff_h * scale;
166
167 let tx = -offset_x_dips + (paper_w_dips - scaled_w) / 2.0;
169 let ty = -offset_y_dips + (paper_h_dips - scaled_h) / 2.0;
170
171 let page_size = D2D_SIZE_F {
172 width: paper_w_dips as f32,
173 height: paper_h_dips as f32,
174 };
175 (page_size, tx, ty,
178 natural_page_size.width as f64, natural_page_size.height as f64,
179 scale, should_rotate)
180 };
181
182 let format_converter = wic_factory
183 .CreateFormatConverter()
184 .map_err(ImagePrinterError::RenderError)?;
185 format_converter
186 .Initialize(
187 &frame,
188 &GUID_WICPixelFormat32bppPBGRA,
189 WICBitmapDitherTypeNone,
190 None,
191 0.0,
192 WICBitmapPaletteTypeMedianCut,
193 )
194 .map_err(ImagePrinterError::RenderError)?;
195 let bitmap = d2d_context
196 .CreateBitmapFromWicBitmap(&format_converter, None)
197 .map_err(ImagePrinterError::RenderError)?;
198
199 let command_list = d2d_context
200 .CreateCommandList()
201 .map_err(ImagePrinterError::RenderError)?;
202 d2d_context.SetTarget(&command_list);
203
204 d2d_context.BeginDraw();
205
206 let s = scale as f32;
212 let matrix = if actual_rotate {
213 Matrix3x2 {
214 M11: 0.0, M12: s,
215 M21: s, M22: 0.0,
216 M31: translate_x as f32, M32: translate_y as f32,
217 }
218 } else {
219 Matrix3x2 {
220 M11: s, M12: 0.0,
221 M21: 0.0, M22: s,
222 M31: translate_x as f32, M32: translate_y as f32,
223 }
224 };
225 d2d_context.SetTransform(&matrix);
226
227 d2d_context.DrawBitmap(
228 &bitmap,
229 Some(&D2D_RECT_F {
230 left: 0.0,
231 top: 0.0,
232 right: dest_w as f32,
233 bottom: dest_h as f32,
234 }),
235 1.0,
236 D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC,
237 None,
238 None,
239 );
240 d2d_context
241 .EndDraw(None, None)
242 .map_err(ImagePrinterError::RenderError)?;
243 command_list
244 .Close()
245 .map_err(ImagePrinterError::RenderError)?;
246 print_control
247 .AddPage(&command_list, page_size, None, None, None)
248 .map_err(ImagePrinterError::RenderError)?;
249 }
250 }
251 context.close_and_wait()?;
252 Ok(())
253 }
254}
255
256impl FilePrinter for ImagePrinter {
257 type Options = PrintTicket;
258 type Error = ImagePrinterError;
259 fn print(
260 &self,
261 path: &Path,
262 options: PrintTicket,
263 ) -> std::result::Result<(), ImagePrinterError> {
264 self.print_with_options(path, options, false)
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::ImagePrinter;
271 use crate::{printer::FilePrinter, test_utils::null_device};
272 use std::path::Path;
273
274 #[test]
275 fn print_simple_tiff_document() {
276 let device = null_device::thread_local();
277 let image = ImagePrinter::new(device);
278 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.tiff");
279 image.print(path.as_path(), Default::default()).unwrap();
280 }
281}