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 natural_page_size = D2D_SIZE_F {
108 width: (image_width as f64 * 96.0 / image_dpi_x) as f32,
109 height: (image_height as f64 * 96.0 / image_dpi_y) as f32,
110 };
111
112 let need_rotate = auto_rotate
114 && natural_page_size.width > natural_page_size.height;
115
116 let dev_mode_for_dc = options
118 .to_dev_mode(&self.printer)
119 .map_err(ImagePrinterError::PrintTicketError)?;
120 let print_driver = PCWSTR(
121 ['W' as u16, 'I' as u16, 'N' as u16, 'S' as u16, 'P' as u16,
122 'O' as u16, 'O' as u16, 'L' as u16, 0].as_ptr(),
123 );
124 let hdc = CreateDCW(
125 print_driver,
126 PCWSTR(wchar::to_wide_chars(self.printer.os_name()).as_ptr()),
127 None,
128 Some(dev_mode_for_dc.as_ptr() as *const _),
129 );
130 let (page_size, translate_x, translate_y, dest_w, dest_h, scale, actual_rotate) =
131 if hdc.is_invalid() {
132 (natural_page_size, 0.0f64, 0.0f64,
133 natural_page_size.width as f64, natural_page_size.height as f64,
134 1.0f64, false)
135 } else {
136 let paper_w = GetDeviceCaps(Some(hdc), PHYSICALWIDTH);
137 let paper_h = GetDeviceCaps(Some(hdc), PHYSICALHEIGHT);
138 let offset_x = GetDeviceCaps(Some(hdc), PHYSICALOFFSETX);
139 let offset_y = GetDeviceCaps(Some(hdc), PHYSICALOFFSETY);
140 let dpi_x = GetDeviceCaps(Some(hdc), LOGPIXELSX);
141 let _ = DeleteDC(hdc);
142
143 let paper_w_dips = paper_w as f64 * 96.0 / dpi_x as f64;
145 let paper_h_dips = paper_h as f64 * 96.0 / dpi_x as f64;
146 let offset_x_dips = offset_x as f64 * 96.0 / dpi_x as f64;
147 let offset_y_dips = offset_y as f64 * 96.0 / dpi_x as f64;
148
149 let paper_landscape = paper_w_dips > paper_h_dips;
151 let should_rotate = need_rotate && paper_landscape != (natural_page_size.width > natural_page_size.height);
152
153 let (eff_w, eff_h) = if should_rotate {
154 (natural_page_size.height as f64, natural_page_size.width as f64)
155 } else {
156 (natural_page_size.width as f64, natural_page_size.height as f64)
157 };
158
159 let scale = f64::min(paper_w_dips / eff_w, paper_h_dips / eff_h);
160 let scaled_w = eff_w * scale;
161 let scaled_h = eff_h * scale;
162
163 let tx = -offset_x_dips + (paper_w_dips - scaled_w) / 2.0;
165 let ty = -offset_y_dips + (paper_h_dips - scaled_h) / 2.0;
166
167 let page_size = D2D_SIZE_F {
168 width: paper_w_dips as f32,
169 height: paper_h_dips as f32,
170 };
171 (page_size, tx, ty,
174 natural_page_size.width as f64, natural_page_size.height as f64,
175 scale, should_rotate)
176 };
177
178 let format_converter = wic_factory
179 .CreateFormatConverter()
180 .map_err(ImagePrinterError::RenderError)?;
181 format_converter
182 .Initialize(
183 &frame,
184 &GUID_WICPixelFormat32bppPBGRA,
185 WICBitmapDitherTypeNone,
186 None,
187 0.0,
188 WICBitmapPaletteTypeMedianCut,
189 )
190 .map_err(ImagePrinterError::RenderError)?;
191 let bitmap = d2d_context
192 .CreateBitmapFromWicBitmap(&format_converter, None)
193 .map_err(ImagePrinterError::RenderError)?;
194
195 let command_list = d2d_context
196 .CreateCommandList()
197 .map_err(ImagePrinterError::RenderError)?;
198 d2d_context.SetTarget(&command_list);
199
200 d2d_context.BeginDraw();
201
202 let s = scale as f32;
208 let matrix = if actual_rotate {
209 Matrix3x2 {
210 M11: 0.0, M12: s,
211 M21: s, M22: 0.0,
212 M31: translate_x as f32, M32: translate_y as f32,
213 }
214 } else {
215 Matrix3x2 {
216 M11: s, M12: 0.0,
217 M21: 0.0, M22: s,
218 M31: translate_x as f32, M32: translate_y as f32,
219 }
220 };
221 d2d_context.SetTransform(&matrix);
222
223 d2d_context.DrawBitmap(
224 &bitmap,
225 Some(&D2D_RECT_F {
226 left: 0.0,
227 top: 0.0,
228 right: dest_w as f32,
229 bottom: dest_h as f32,
230 }),
231 1.0,
232 D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC,
233 None,
234 None,
235 );
236 d2d_context
237 .EndDraw(None, None)
238 .map_err(ImagePrinterError::RenderError)?;
239 command_list
240 .Close()
241 .map_err(ImagePrinterError::RenderError)?;
242 print_control
243 .AddPage(&command_list, page_size, None, None, None)
244 .map_err(ImagePrinterError::RenderError)?;
245 }
246 }
247 context.close_and_wait()?;
248 Ok(())
249 }
250}
251
252impl FilePrinter for ImagePrinter {
253 type Options = PrintTicket;
254 type Error = ImagePrinterError;
255 fn print(
256 &self,
257 path: &Path,
258 options: PrintTicket,
259 ) -> std::result::Result<(), ImagePrinterError> {
260 self.print_with_options(path, options, false)
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::ImagePrinter;
267 use crate::{printer::FilePrinter, test_utils::null_device};
268 use std::path::Path;
269
270 #[test]
271 fn print_simple_tiff_document() {
272 let device = null_device::thread_local();
273 let image = ImagePrinter::new(device);
274 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.tiff");
275 image.print(path.as_path(), Default::default()).unwrap();
276 }
277}