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