raw_printer/
lib.rs

1#[cfg(target_os = "windows")]
2use windows::Win32::Foundation::HANDLE;
3
4#[cfg(test)]
5mod tests {
6    use super::*;
7
8    #[test]
9    #[cfg(target_os = "linux")]
10    fn test_write_to_device_linux() {
11        let payload = b"^FDhello world";
12        let result = write_to_device("/dev/usb/lp0", payload, Some("Test Document"));
13        assert!(result.is_ok());
14    }
15
16    #[test]
17    #[cfg(target_os = "windows")]
18    fn test_write_to_device_windows() {
19        let payload = b"^FDhello world";
20        let result = write_to_device("ZDesigner ZD220-203dpi ZPL",
21            payload,
22            Some("Test Document"),
23        );
24        assert!(result.is_ok());
25    }
26}
27
28/// # Platform-specific Behavior
29///
30/// This function returns a result containing the size of bytes written on success or an error.
31///
32/// - On Linux and Windows, the result type is `Result<usize, Error>`.
33/// - Note: On Windows, the original bytes written are u32 but cast to usize.
34///
35/// # Examples
36///
37/// ```
38/// let zpl = "^FDhello world";
39/// let printer = "/dev/usb/lp0";
40/// let result = raw_printer::write_to_device(printer, zpl, Some("My Custom Document"));
41///
42/// assert!(result.is_ok());
43///
44/// ```
45#[cfg(target_os = "linux")]
46pub fn write_to_device(
47    printer: &str,
48    payload: &[u8],
49    _document_name: Option<&str>,
50) -> Result<usize, std::io::Error> {
51    use std::fs::OpenOptions;
52    use std::io::Write;
53
54    let mut device = OpenOptions::new().write(true).open(printer)?;
55    let bytes_written = device.write(payload)?;
56    device.flush()?; // Ensure data is written
57    Ok(bytes_written)
58}
59
60#[cfg(target_os = "windows")]
61pub fn write_to_device(
62    printer: &str,
63    payload: &[u8],
64    document_name: Option<&str>,
65) -> Result<usize, std::io::Error> {
66    use std::ffi::CString;
67    use std::ptr;
68    use windows::core::PCSTR;
69    use windows::Win32::Graphics::Printing::{
70        EndDocPrinter, EndPagePrinter, OpenPrinterA, StartDocPrinterA, StartPagePrinter,
71        WritePrinter, DOC_INFO_1A, PRINTER_ACCESS_USE, PRINTER_DEFAULTSA,
72    };
73
74    let printer_name = CString::new(printer)
75        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
76
77    let mut printer_handle: HANDLE = HANDLE(std::ptr::null_mut());
78
79    // Open the printer
80    unsafe {
81        let pd = PRINTER_DEFAULTSA {
82            pDatatype: windows::core::PSTR(ptr::null_mut()),
83            pDevMode: ptr::null_mut(),
84            DesiredAccess: PRINTER_ACCESS_USE,
85        };
86
87        if OpenPrinterA(
88            PCSTR(printer_name.as_ptr() as *const u8),
89            &mut printer_handle,
90            Some(&pd),
91        )
92        .is_err()
93        {
94            return Err(std::io::Error::new(
95                std::io::ErrorKind::NotFound,
96                format!("Failed to open printer: {}", printer),
97            ));
98        }
99
100        // Ensure proper cleanup with RAII-style wrapper
101        let _cleanup = PrinterGuard::new(printer_handle);
102
103        let doc_name = document_name.unwrap_or("RAW_Print");
104        let doc_name_cstring = CString::new(doc_name)
105            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
106
107        let datatype_cstring = CString::new("RAW").unwrap();
108
109        let doc_info = DOC_INFO_1A {
110            pDocName: windows::core::PSTR(doc_name_cstring.as_ptr() as *mut u8),
111            pOutputFile: windows::core::PSTR::null(),
112            pDatatype: windows::core::PSTR(datatype_cstring.as_ptr() as *mut u8),
113        };
114
115        // Start the document
116        let job_id = StartDocPrinterA(printer_handle, 1, &doc_info as *const _ as _);
117        if job_id == 0 {
118            return Err(std::io::Error::new(
119                std::io::ErrorKind::Other,
120                "Failed to start document",
121            ));
122        }
123
124        // Start the page
125        if !StartPagePrinter(printer_handle).as_bool() {
126            let _ = EndDocPrinter(printer_handle);
127            return Err(std::io::Error::new(
128                std::io::ErrorKind::Other,
129                "Failed to start page",
130            ));
131        }
132
133        let buffer = payload;
134        let mut bytes_written: u32 = 0;
135
136        let write_result = WritePrinter(
137            printer_handle,
138            buffer.as_ptr() as _,
139            buffer.len() as u32,
140            &mut bytes_written,
141        );
142
143        // Always end the page and document, regardless of write success
144        let _ = EndPagePrinter(printer_handle);
145        let _ = EndDocPrinter(printer_handle);
146
147        if !write_result.as_bool() {
148            return Err(std::io::Error::new(
149                std::io::ErrorKind::Other,
150                "Failed to write to printer",
151            ));
152        }
153
154        Ok(bytes_written as usize)
155    }
156}
157
158#[cfg(target_os = "windows")]
159struct PrinterGuard {
160    handle: HANDLE,
161}
162
163#[cfg(target_os = "windows")]
164impl PrinterGuard {
165    fn new(handle: HANDLE) -> Self {
166        Self { handle }
167    }
168}
169
170#[cfg(target_os = "windows")]
171impl Drop for PrinterGuard {
172    fn drop(&mut self) {
173        unsafe {
174            use windows::Win32::Graphics::Printing::ClosePrinter;
175            let _ = ClosePrinter(self.handle);
176        }
177    }
178}