escpos_rust/
printer.rs

1pub use self::printer_profile::{PrinterProfile, PrinterConnectionData, PrinterProfileBuilder};
2pub use self::printer_model::PrinterModel;
3use encoding::all::GB18030;
4use encoding::{EncoderTrap, Encoding};
5
6mod printer_profile;
7mod printer_model;
8
9use crate::{
10    Instruction,
11    PrintData,
12    EscposImage,
13    Error,
14    command::{Command, Font},
15    Formatter,
16    utils
17};
18
19extern crate codepage_437;
20extern crate log;
21
22use log::{warn};
23use rusb::{UsbContext, Context, DeviceHandle, TransferType, Direction};
24use codepage_437::{IntoCp437, CP437_CONTROL};
25
26/// Keeps the actual living connection to the device
27enum PrinterConnection {
28    Usb {
29        /// Bulk write endpoint
30        endpoint: u8,
31        /// Device handle
32        dh: DeviceHandle<Context>,
33        /// Time to wait before giving up writing to the bulk endpoint
34        timeout: std::time::Duration
35    },
36    #[allow(dead_code)]
37    Network,
38    Terminal
39}
40
41/// barcode position
42pub enum BarcodePostion {
43    None,
44    Top,
45    Bottom,
46    Both
47}
48
49/// Main escpos-rs structure
50///
51/// The printer represents the thermal printer connected to the computer.
52/// ```rust,no_run
53/// use escpos_rs::{Printer, PrinterModel};
54///
55/// let printer = match Printer::new(PrinterModel::TMT20.usb_profile()) {
56///     Ok(maybe_printer) => match maybe_printer {
57///         Some(printer) => printer,
58///         None => panic!("No printer was found :(")
59///     },
60///     Err(e) => panic!("Error: {}", e)
61/// };
62/// // Now we have a printer
63/// ```
64pub struct Printer {
65    printer_profile: PrinterProfile,
66    /// Actual connection to the printer
67    printer_connection: PrinterConnection,
68    /// Current font and width for printing text
69    font_and_width: (Font, u8),
70    /// The auxiliary formatter to print nicely
71    formatter: Formatter,
72    /// If words should be splitted or not
73    space_split: bool
74}
75
76impl Printer {
77    /// Creates a new printer
78    /// 
79    /// Creates the printer with the given details, from the printer details provided, and in the given USB context.
80    pub fn new(printer_profile: PrinterProfile) -> Result<Option<Printer>, Error> {
81        // Font and width, at least one required.
82        let font_and_width = if let Some(width) = printer_profile.columns_per_font.get(&Font::FontA) {
83            (Font::FontA, *width)
84        } else {
85            return Err(Error::NoFontFound);
86        };
87        let formatter = Formatter::new(font_and_width.1);
88        // Quick check for the profile containing at least one font
89        match printer_profile.printer_connection_data {
90            PrinterConnectionData::Usb{vendor_id, product_id, endpoint, timeout} => {
91                let context = Context::new().map_err(Error::RusbError)?;
92        
93                let devices = context.devices().map_err(Error::RusbError)?;
94                for device in devices.iter() {
95                    let s = device.device_descriptor().map_err(Error::RusbError)?;
96                    if s.vendor_id() == vendor_id && s.product_id() == product_id {
97                        // Before opening the device, we must find the bulk endpoint
98                        let config_descriptor = device.active_config_descriptor().map_err(Error::RusbError)?;
99                        let actual_endpoint = if let Some(endpoint) = endpoint {
100                            endpoint
101                        } else {
102                            let mut detected_endpoint: Option<u8> = None;
103                            // Horrible to have 3 nested for, but so be it
104                            for interface in config_descriptor.interfaces() {
105                                for descriptor in interface.descriptors() {
106                                    for endpoint in descriptor.endpoint_descriptors() {
107                                        if let (TransferType::Bulk, Direction::Out) = (endpoint.transfer_type(), endpoint.direction()) {
108                                            detected_endpoint = Some(endpoint.number());   
109                                        }
110                                    }
111                                }
112                            }
113            
114                            if let Some(detected_endpoint) = detected_endpoint {
115                                detected_endpoint
116                            } else {
117                                return Err(Error::NoBulkEndpoint);
118                            }
119                        };
120        
121                        // Now we continue opening the device
122        
123                        match device.open() {
124                            Ok(mut dh) => {
125                                if let Ok(active) = dh.kernel_driver_active(0) {
126                                    if active {
127                                        // The kernel is active, we have to detach it
128                                        match dh.detach_kernel_driver(0) {
129                                            Ok(_) => (),
130                                            Err(e) => return Err(Error::RusbError(e))
131                                        };
132                                    }
133                                } else {
134                                    warn!("Could not find out if kernel driver is active, might encounter a problem soon.");
135                                };
136                                // Now we claim the interface
137                                match dh.claim_interface(0) {
138                                    Ok(_) => (),
139                                    Err(e) => return Err(Error::RusbError(e))
140                                }
141                                return Ok(Some(Printer {
142                                    printer_connection: PrinterConnection::Usb {
143                                        endpoint: actual_endpoint,
144                                        dh,
145                                        timeout
146                                    },
147                                    printer_profile,
148                                    font_and_width,
149                                    formatter,
150                                    space_split: false
151                                }));
152                            },
153                            Err(e) => return Err(Error::RusbError(e))
154                        };
155                    }
156                }
157                // No printer was found with such vid and pid
158                Ok(None)
159            },
160            PrinterConnectionData::Network{..} => panic!("Unsupported!"),
161            PrinterConnectionData::Terminal => Ok(Some(Printer{
162                printer_connection: PrinterConnection::Terminal,
163                printer_profile,
164                font_and_width,
165                formatter,
166                space_split: false
167            }))
168        }
169    }
170
171    /// Guesses the printer, and connects to it (not meant for production)
172    ///
173    /// Might help to find which printer you have if you have only one connected. The function will try to connect to a printer, based on the common ones recognized by this library.
174    pub fn with_context_feeling_lucky() -> Result<Option<Printer>, Error> {
175        // Match to force update then a new model gets added, just as a reminder
176        /*****
177        IF YOU ARE READING THIS, AND YOU GOT AN  ERROR BECAUSE A PRINTER WAS MISSING,
178        UPDATE THE FOR FOLLOWING THE MATCH TO TRY ALL PRINTERS
179        *****/
180        match PrinterModel::TMT20 {
181            PrinterModel::TMT20 => (),
182            PrinterModel::ZKTeco => ()
183        }
184        // Keep up to date! All printers should appear here for the function to work
185        for printer_model in vec![PrinterModel::TMT20, PrinterModel::ZKTeco] {
186            let printer_profile = printer_model.usb_profile();
187            let candidate = Printer::new(printer_profile)?;
188            if candidate.is_some() {
189                return Ok(candidate)
190            }
191        }
192        // No printer was found
193        Ok(None)
194    }
195
196    /// Print an instruction
197    ///
198    /// You can pass optional printer data to the printer to fill in the dynamic parts of the instruction.
199    pub fn instruction(&self, instruction: &Instruction, print_data: Option<&PrintData>) -> Result<(), Error> {
200        let content = instruction.to_vec(&self.printer_profile, print_data)?;
201        self.raw(&content)
202    }
203    
204    /// Print some text.
205    ///
206    /// By default, lines will break when the text exceeds the current font's width. If you want to break lines with whitespaces, according to the width, you can use the [set_space_split](Printer::set_space_split) function.
207    pub fn print<T: Into<String>>(&self, content: T) -> Result<(), Error> {
208        let content = if self.space_split {
209            self.formatter.space_split(content.into())
210        } else {
211            content.into()
212        };
213        match self.printer_connection {
214            PrinterConnection::Usb{..} => {
215                let feed = content.into_cp437(&CP437_CONTROL).map_err(|e| Error::CP437Error(e.into_string()))?;
216                self.raw(&feed)
217            },
218            PrinterConnection::Network => panic!("Unimplemented!"),
219            PrinterConnection::Terminal => {
220                print!("{}", content);
221                Ok(())
222            }
223        }
224    }
225
226    /// Print gb18030 text.
227    /// 
228    /// Use encoding and send raw data
229    pub fn print_gb18030(&self, content: String) -> Result<(), Error> {
230        let encoded = GB18030.encode(&content, EncoderTrap::Strict).unwrap();
231        self.raw(encoded)
232    }
233
234    /// Print some text, with a newline at the end.
235    ///
236    /// By default, lines will break when the text exceeds the current font's width. If you want to break lines with whitespaces, according to the width, you can use the [set_space_split](Printer::set_space_split) function.
237    pub fn println<T: Into<String>>(&self, content: T) -> Result<(), Error> {
238        let feed = content.into() + "\n";
239        self.print(&feed)
240    }
241
242    /// Print barcode use type code128
243    ///
244    /// You also can set HRI code postion. None Top Bottom Both four postions.
245    pub fn print_barcode(&self, content: String, position: BarcodePostion) -> Result<(), Error> {
246        match self.set_barcode_position(position) {
247            Ok(_) => Ok({
248                let input = content.as_str();
249                let result = utils::Utils::separate_numbers_and_non_numbers(input);
250                let str_line = result[0].as_bytes().to_vec();
251                let number_line = result[1].as_bytes().to_vec();
252                let mut brcode_arr: Vec<u8> = vec![0x1d, 0x6b, 0x49, 0x0a, 0x7b, 0x42];
253                brcode_arr.extend(&str_line);
254                brcode_arr.push(0x7b);
255                brcode_arr.push(0x43);
256                brcode_arr.extend(&number_line);
257                match self.raw(brcode_arr) {
258                    Ok(_) => {},
259                    Err(_) => {}
260                };
261            }),
262            Err(_e) => return Err(Error::BarcodeError)
263        }
264    }
265
266    pub fn set_barcode_position(&self, position: BarcodePostion) -> Result<(), Error> {
267        match self.raw(&Command::Reset.as_bytes()) {
268            Ok(_) => {
269                match position.into() {
270                    BarcodePostion::None => {
271                        self.raw(&[0x1d, 0x48, 0x00])
272                    },
273                    BarcodePostion::Top => {
274                        self.raw(&[0x1d, 0x48, 0x01])
275                    },
276                    BarcodePostion::Bottom => {
277                        self.raw(&[0x1d, 0x48, 0x02])
278                    },
279                    BarcodePostion::Both => {
280                        self.raw(&[0x1d, 0x48, 0x03])
281                    }
282                }
283            },
284            Err(_e) => return Err(Error::BarcodeError)
285        }
286    }
287
288    /// Sets the current printing font.
289    ///
290    /// The function will return an error if the specified font does not exist in the printer profile.
291    pub fn set_font(&mut self, font: Font) -> Result<(), Error> {
292        if let Some(width) = self.printer_profile.columns_per_font.get(&font) {
293            self.font_and_width = (font, *width);
294            Ok(())
295        } else {
296            Err(Error::UnsupportedFont)
297        }
298    }
299
300    /// Enables or disables space splitting for long text printing.
301    ///
302    /// By default, the printer writes text in a single stream to the printer (which splits it wherever the maximum width is reached). To split by whitespaces, you can call this function with `true` as argument.
303    pub fn set_space_split(&mut self, state: bool) {
304        self.space_split = state;
305    }
306
307    /// Jumps _n_ number of lines (to leave whitespaces). Basically `n * '\n'` passed to `print`
308    pub fn jump(&self, n: u8) -> Result<(), Error> {
309        let feed = vec![b'\n', n];
310        self.raw(&feed)
311    }
312
313    /// Cuts the paper, in case the instruction is supported by the printer
314    pub fn cut(&self) -> Result<(), Error> {
315        self.raw(&Command::Cut.as_bytes())
316    }
317
318    /// Prints a table with two columns.
319    ///
320    /// For more details, check [Formatter](crate::Formatter)'s [duo_table](crate::Formatter::duo_table).
321    pub fn duo_table<A: Into<String>, B: Into<String>, C: IntoIterator<Item = (D, E)>, D: Into<String>, E: Into<String>>(&self, headers: (A, B), rows: C) -> Result<(), Error> {
322        let content = self.formatter.duo_table(headers, rows);
323        match &self.printer_connection {
324            PrinterConnection::Terminal => {
325                println!("{}", content);
326                Ok(())
327            },
328            _other => {
329                self.raw(&content)
330            }
331        }
332    }
333
334    /// Prints a table with three columns.
335    ///
336    /// For more details, check [Formatter](crate::Formatter)'s [trio_table](crate::Formatter::trio_table).
337    pub fn trio_table<A: Into<String>, B: Into<String>, C: Into<String>, D: IntoIterator<Item = (E, F, G)>, E: Into<String>, F: Into<String>, G: Into<String>>(&self, headers: (A, B, C), rows: D) -> Result<(), Error> {
338        let content = self.formatter.trio_table(headers, rows);
339        match &self.printer_connection {
340            PrinterConnection::Terminal => {
341                println!("{}", content);
342                Ok(())
343            },
344            _other => {
345                self.raw(&content)
346            }
347        }
348    }
349
350    pub fn image(&self, escpos_image: EscposImage) -> Result<(), Error> {
351        self.raw(&escpos_image.feed(self.printer_profile.width))
352    }
353
354    /// Sends raw information to the printer
355    ///
356    /// As simple as it sounds
357    /// ```rust,no_run
358    /// use escpos_rs::{Printer,PrinterProfile};
359    /// let printer_profile = PrinterProfile::usb_builder(0x0001, 0x0001).build();
360    /// let printer = Printer::new(printer_profile).unwrap().unwrap();
361    /// printer.raw(&[0x01, 0x02])?;
362    /// # Ok::<(), escpos_rs::Error>(())
363    /// ```
364    pub fn raw<A: AsRef<[u8]>>(&self, bytes: A) -> Result<(), Error> {
365        match &self.printer_connection {
366            PrinterConnection::Usb{endpoint, dh, timeout} => {
367                dh.write_bulk(
368                    *endpoint,
369                    bytes.as_ref(),
370                    *timeout
371                ).map_err(Error::RusbError)?;
372                Ok(())
373            },
374            _other => panic!("Unimplemented")
375        }
376    }
377}