feat(print): Windows Native Raw Print Spooler (GDI/ICM Bypass) (#26) #29

Merged
gronod merged 4 commits from feature/issue-26-windows-raw-print into development 2026-08-23 14:47:02 +01:00
7 changed files with 556 additions and 16 deletions
+7 -2
View File
@@ -14,6 +14,10 @@ jobs:
build-linux:
name: Build Linux
runs-on: ubuntu-latest
env:
APPIMAGE_EXTRACT_AND_RUN: 1
NO_STRIP: "true"
RUST_BACKTRACE: "1"
steps:
- name: Checkout repository
@@ -37,7 +41,8 @@ jobs:
- name: Install Tauri Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev patchelf xdg-utils
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev patchelf xdg-utils squashfs-tools zsync libxss-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxcursor-dev libxi-dev
sudo apt-get install -y libfuse2 || sudo apt-get install -y libfuse2t64 || true
- name: Install Node dependencies
run: npm ci
@@ -56,7 +61,7 @@ jobs:
- name: Build Tauri App
env:
APPIMAGE_EXTRACT_AND_RUN: 1
run: npm run tauri build
run: npm run tauri build -- -v
- name: Upload Debian Package
uses: actions/upload-artifact@v4
+202 -14
View File
@@ -334,6 +334,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.12.1"
@@ -567,6 +573,12 @@ version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
version = "0.1.7"
@@ -965,6 +977,12 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "fax"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
[[package]]
name = "fdeflate"
version = "0.3.7"
@@ -1423,6 +1441,17 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -1580,6 +1609,7 @@ name = "iccery"
version = "0.1.1"
dependencies = [
"base64 0.22.1",
"image",
"serde",
"serde_json",
"tauri",
@@ -1587,6 +1617,7 @@ dependencies = [
"tauri-plugin-dialog",
"tauri-plugin-opener",
"tokio",
"windows 0.58.0",
]
[[package]]
@@ -1709,6 +1740,22 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"moxcms",
"num-traits",
"png 0.18.1",
"tiff",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -2074,6 +2121,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "moxcms"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
dependencies = [
"num-traits",
"pxfm",
]
[[package]]
name = "muda"
version = "0.19.3"
@@ -2678,6 +2735,18 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pxfm"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.41.0"
@@ -3384,7 +3453,7 @@ dependencies = [
"tao-macros",
"unicode-segmentation",
"url",
"windows",
"windows 0.61.3",
"windows-core 0.61.2",
"windows-version",
"x11-dl",
@@ -3455,7 +3524,7 @@ dependencies = [
"webkit2gtk",
"webview2-com",
"window-vibrancy",
"windows",
"windows 0.61.3",
]
[[package]]
@@ -3596,7 +3665,7 @@ dependencies = [
"tauri-plugin",
"thiserror 2.0.20",
"url",
"windows",
"windows 0.61.3",
"zbus",
]
@@ -3622,7 +3691,7 @@ dependencies = [
"url",
"webkit2gtk",
"webview2-com",
"windows",
"windows 0.61.3",
]
[[package]]
@@ -3647,7 +3716,7 @@ dependencies = [
"url",
"webkit2gtk",
"webview2-com",
"windows",
"windows 0.61.3",
"wry",
]
@@ -3762,6 +3831,20 @@ dependencies = [
"syn 3.0.3",
]
[[package]]
name = "tiff"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
dependencies = [
"fax",
"flate2",
"half",
"quick-error",
"weezl",
"zune-jpeg",
]
[[package]]
name = "time"
version = "0.3.55"
@@ -4409,10 +4492,10 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
dependencies = [
"webview2-com-macros",
"webview2-com-sys",
"windows",
"windows 0.61.3",
"windows-core 0.61.2",
"windows-implement",
"windows-interface",
"windows-implement 0.60.2",
"windows-interface 0.59.3",
]
[[package]]
@@ -4433,10 +4516,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
dependencies = [
"thiserror 2.0.20",
"windows",
"windows 0.61.3",
"windows-core 0.61.2",
]
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "winapi"
version = "0.3.9"
@@ -4483,6 +4572,16 @@ dependencies = [
"windows-version",
]
[[package]]
name = "windows"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
dependencies = [
"windows-core 0.58.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows"
version = "0.61.3"
@@ -4505,14 +4604,27 @@ dependencies = [
"windows-core 0.61.2",
]
[[package]]
name = "windows-core"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
"windows-implement 0.58.0",
"windows-interface 0.58.0",
"windows-result 0.2.0",
"windows-strings 0.1.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-implement 0.60.2",
"windows-interface 0.59.3",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-strings 0.4.2",
@@ -4524,8 +4636,8 @@ version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-implement 0.60.2",
"windows-interface 0.59.3",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
@@ -4542,6 +4654,17 @@ dependencies = [
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
@@ -4553,6 +4676,17 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "windows-interface"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
@@ -4586,6 +4720,15 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.3.4"
@@ -4604,6 +4747,16 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result 0.2.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
@@ -4946,7 +5099,7 @@ dependencies = [
"webkit2gtk",
"webkit2gtk-sys",
"webview2-com",
"windows",
"windows 0.61.3",
"windows-core 0.61.2",
"windows-version",
"x11-dl",
@@ -5066,6 +5219,26 @@ dependencies = [
"serde",
]
[[package]]
name = "zerocopy"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "zerofrom"
version = "0.1.8"
@@ -5126,6 +5299,21 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]]
name = "zune-core"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b"
[[package]]
name = "zune-jpeg"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core",
]
[[package]]
name = "zvariant"
version = "5.15.0"
+9
View File
@@ -25,4 +25,13 @@ serde_json = "1"
tokio = { version = "1", features = ["full"] }
tauri-plugin-dialog = "2.7.2"
base64 = "0.22"
image = { version = "0.25", default-features = false, features = ["tiff", "png", "jpeg"] }
[target.'cfg(windows)'.dependencies]
windows = { version = "0.58", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_Graphics_Printing",
"Win32_Storage_FileSystem",
"Win32_UI_WindowsAndMessaging",
] }
+10
View File
@@ -342,6 +342,16 @@ pub async fn run_profcheck(
state.spawn(app, id, binary, args, cwd).await
}
#[tauri::command]
pub async fn get_windows_printers() -> Result<Vec<String>, String> {
crate::print::get_printers()
}
#[tauri::command]
pub async fn print_target_windows(printer_name: String, tiff_path: String) -> Result<(), String> {
crate::print::print_target(&printer_name, &tiff_path)
}
#[cfg(test)]
mod tests {
use super::*;
+3
View File
@@ -1,5 +1,6 @@
mod commands;
mod events;
mod print;
mod process_manager;
mod settings;
@@ -23,6 +24,8 @@ pub fn run() {
commands::run_chartread,
commands::run_colprof,
commands::run_profcheck,
commands::get_windows_printers,
commands::print_target_windows,
settings::load_settings,
settings::save_settings,
])
+16
View File
@@ -0,0 +1,16 @@
#[cfg(windows)]
pub mod windows;
#[cfg(windows)]
pub use windows::*;
#[cfg(not(windows))]
pub fn get_printers() -> Result<Vec<String>, String> {
Err("Direct native printer querying is only supported on Windows in this build.".to_string())
}
#[cfg(not(windows))]
pub fn print_target(printer_name: &str, tiff_path: &str) -> Result<(), String> {
let _ = (printer_name, tiff_path);
Err("Direct native raw printing is currently implemented for Windows. macOS/Linux CUPS support is planned for the next sub-task.".to_string())
}
+309
View File
@@ -0,0 +1,309 @@
use std::ffi::{c_void, OsStr};
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use windows::core::{PCWSTR, PWSTR};
use windows::Win32::Foundation::{HANDLE, HWND};
use windows::Win32::Graphics::Gdi::{
CreateDCW, DeleteDC, GetDeviceCaps, SetICMMode, StretchDIBits,
BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, HDC, HORZRES,
ICM_OFF, LOGPIXELSX, LOGPIXELSY, SRCCOPY, VERTRES,
};
use windows::Win32::Graphics::Printing::{
ClosePrinter, DocumentPropertiesW, EndDoc, EndPage, EnumPrintersW,
OpenPrinterW, StartDocW, StartPage, DEVMODEW, DMICMMETHOD_NONE,
DM_ICMMETHOD, DM_OUT_BUFFER, DOCINFOW, PRINTER_ENUM_CONNECTIONS,
PRINTER_ENUM_LOCAL, PRINTER_INFO_2W, PRINTER_INFO_4W,
};
fn to_wide(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain(std::iter::once(0)).collect()
}
/// Enumerate available printers on the system
pub fn get_printers() -> Result<Vec<String>, String> {
unsafe {
let flags = PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS;
let mut bytes_needed = 0u32;
let mut count = 0u32;
// Query buffer size using Level 4 (fast local & network connections)
let _ = EnumPrintersW(
flags,
PCWSTR::null(),
4,
None,
0,
&mut bytes_needed,
&mut count,
);
if bytes_needed == 0 {
// Fallback to Level 2
let _ = EnumPrintersW(
flags,
PCWSTR::null(),
2,
None,
0,
&mut bytes_needed,
&mut count,
);
if bytes_needed == 0 {
return Ok(Vec::new());
}
let mut buffer = vec![0u8; bytes_needed as usize];
let success = EnumPrintersW(
flags,
PCWSTR::null(),
2,
Some(buffer.as_mut_ptr()),
bytes_needed,
&mut bytes_needed,
&mut count,
);
if !success.as_bool() {
return Err("Failed to enumerate printers (Level 2)".to_string());
}
let p_info = buffer.as_ptr() as *const PRINTER_INFO_2W;
let mut printers = Vec::new();
for i in 0..count as usize {
let info = *p_info.add(i);
if !info.pPrinterName.is_null() {
let name = info.pPrinterName.to_string().map_err(|e| e.to_string())?;
printers.push(name);
}
}
return Ok(printers);
}
let mut buffer = vec![0u8; bytes_needed as usize];
let success = EnumPrintersW(
flags,
PCWSTR::null(),
4,
Some(buffer.as_mut_ptr()),
bytes_needed,
&mut bytes_needed,
&mut count,
);
if !success.as_bool() {
return Err("Failed to enumerate printers (Level 4)".to_string());
}
let p_info = buffer.as_ptr() as *const PRINTER_INFO_4W;
let mut printers = Vec::new();
for i in 0..count as usize {
let info = *p_info.add(i);
if !info.pPrinterName.is_null() {
let name = info.pPrinterName.to_string().map_err(|e| e.to_string())?;
printers.push(name);
}
}
Ok(printers)
}
}
/// Print target TIFF file to printer with GDI ICM bypass and DIB drawing
pub fn print_target(printer_name: &str, tiff_path: &str) -> Result<(), String> {
let path = Path::new(tiff_path);
if !path.exists() {
return Err(format!("Target TIFF file not found: {}", tiff_path));
}
// Load and convert the TIFF image
let dyn_img = image::open(path).map_err(|e| format!("Failed to open TIFF image: {}", e))?;
let rgb_img = dyn_img.to_rgb8();
let img_width = rgb_img.width();
let img_height = rgb_img.height();
if img_width == 0 || img_height == 0 {
return Err("Image has zero dimensions".to_string());
}
// Prepare 24-bit BGR DIB buffer with 4-byte row stride alignment
let row_stride = ((img_width as usize * 3 + 3) / 4) * 4;
let mut dib_bytes = vec![0u8; row_stride * img_height as usize];
for y in 0..img_height as usize {
for x in 0..img_width as usize {
let pixel = rgb_img.get_pixel(x as u32, y as u32);
let dst_idx = y * row_stride + x * 3;
dib_bytes[dst_idx] = pixel[2]; // Blue
dib_bytes[dst_idx + 1] = pixel[1]; // Green
dib_bytes[dst_idx + 2] = pixel[0]; // Red
}
}
let printer_wide = to_wide(printer_name);
let doc_name = format!("ICCery Target - {}", path.file_name().and_then(|n| n.to_str()).unwrap_or("Profiling Target"));
let doc_name_wide = to_wide(&doc_name);
unsafe {
// Open printer handle
let mut h_printer = HANDLE::default();
let open_res = OpenPrinterW(PCWSTR(printer_wide.as_ptr()), &mut h_printer, None);
if !open_res.as_bool() || h_printer.is_invalid() {
return Err(format!("Failed to open printer '{}'", printer_name));
}
// Configure DEVMODE if possible
let devmode_size = DocumentPropertiesW(
HWND::default(),
h_printer,
PCWSTR(printer_wide.as_ptr()),
None,
None,
0,
);
let mut devmode_buf = if devmode_size > 0 {
let mut buf = vec![0u8; devmode_size as usize];
let res = DocumentPropertiesW(
HWND::default(),
h_printer,
PCWSTR(printer_wide.as_ptr()),
Some(buf.as_mut_ptr() as *mut DEVMODEW),
None,
DM_OUT_BUFFER,
);
if res >= 0 {
let p_devmode = buf.as_mut_ptr() as *mut DEVMODEW;
// Attempt to explicitly disable driver ICM if supported in DEVMODE
(*p_devmode).dmFields |= DM_ICMMETHOD;
(*p_devmode).dmICMMethod = DMICMMETHOD_NONE as i32;
Some(buf)
} else {
None
}
} else {
None
};
let p_devmode_ptr = devmode_buf
.as_mut()
.map(|b| b.as_mut_ptr() as *const DEVMODEW);
// Create printer Device Context (DC)
let hdc = CreateDCW(
PCWSTR::null(),
PCWSTR(printer_wide.as_ptr()),
PCWSTR::null(),
p_devmode_ptr,
);
if hdc.is_invalid() {
let _ = ClosePrinter(h_printer);
return Err(format!("Failed to create Device Context for printer '{}'", printer_name));
}
// STRICT ICM BYPASS: Turn off Windows GDI Image Color Management
let prev_icm = SetICMMode(hdc, ICM_OFF as i32);
if prev_icm == 0 {
// Note: Some printer drivers report 0 if ICM is unsupported or already off
}
// Start Document
let doc_info = DOCINFOW {
cbSize: std::mem::size_of::<DOCINFOW>() as i32,
lpszDocName: PCWSTR(doc_name_wide.as_ptr()),
lpszOutput: PCWSTR::null(),
lpszDatatype: PCWSTR::null(),
fwType: 0,
};
let start_doc_res = StartDocW(hdc, &doc_info);
if start_doc_res <= 0 {
let _ = DeleteDC(hdc);
let _ = ClosePrinter(h_printer);
return Err("Failed to start print document (StartDocW)".to_string());
}
// Start Page
let start_page_res = StartPage(hdc);
if start_page_res <= 0 {
let _ = EndDoc(hdc);
let _ = DeleteDC(hdc);
let _ = ClosePrinter(h_printer);
return Err("Failed to start print page (StartPage)".to_string());
}
// Query DC dimensions and DPI
let dpi_x = GetDeviceCaps(hdc, LOGPIXELSX);
let dpi_y = GetDeviceCaps(hdc, LOGPIXELSY);
let page_width = GetDeviceCaps(hdc, HORZRES);
let page_height = GetDeviceCaps(hdc, VERTRES);
// Calculate destination dimensions assuming standard 300 DPI target
let target_dpi = 300.0f64;
let mut dest_width = ((img_width as f64 / target_dpi) * dpi_x as f64).round() as i32;
let mut dest_height = ((img_height as f64 / target_dpi) * dpi_y as f64).round() as i32;
// If target exceeds page bounds, scale proportionally to fit printable area
if dest_width > page_width || dest_height > page_height {
let scale_x = page_width as f64 / dest_width as f64;
let scale_y = page_height as f64 / dest_height as f64;
let scale = scale_x.min(scale_y);
dest_width = (dest_width as f64 * scale).round() as i32;
dest_height = (dest_height as f64 * scale).round() as i32;
}
let dest_x = (page_width - dest_width).max(0) / 2;
let dest_y = (page_height - dest_height).max(0) / 2;
// Build BITMAPINFO structure for top-down 24-bit DIB
let mut bmi = BITMAPINFO {
bmiHeader: BITMAPINFOHEADER {
biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
biWidth: img_width as i32,
biHeight: -(img_height as i32), // Negative height denotes top-down DIB
biPlanes: 1,
biBitCount: 24,
biCompression: BI_RGB.0,
biSizeImage: dib_bytes.len() as u32,
biXPelsPerMeter: ((dpi_x as f64 / 0.0254).round()) as i32,
biYPelsPerMeter: ((dpi_y as f64 / 0.0254).round()) as i32,
biClrUsed: 0,
biClrImportant: 0,
},
bmiColors: [windows::Win32::Graphics::Gdi::RGBQUAD::default()],
};
// Draw unmanaged DIB to printer device context
let lines_drawn = StretchDIBits(
hdc,
dest_x,
dest_y,
dest_width,
dest_height,
0,
0,
img_width as i32,
img_height as i32,
Some(dib_bytes.as_ptr() as *const c_void),
&bmi,
DIB_RGB_COLORS,
SRCCOPY,
);
if lines_drawn <= 0 {
let _ = EndPage(hdc);
let _ = EndDoc(hdc);
let _ = DeleteDC(hdc);
let _ = ClosePrinter(h_printer);
return Err("Failed to draw DIB bitmap to printer DC (StretchDIBits)".to_string());
}
// Finish Page and Document
let _ = EndPage(hdc);
let _ = EndDoc(hdc);
let _ = DeleteDC(hdc);
let _ = ClosePrinter(h_printer);
Ok(())
}
}