1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
use crate::{
    Instruction,
    PrintData,
    EscposImage,
    Error,
    command::Command
};

extern crate libusb;
extern crate codepage_437;
extern crate log;

use log::{warn};
use libusb::{Context, DeviceHandle};
use codepage_437::{IntoCp437, CP437_CONTROL};
use super::{PrinterProfile, PrinterModel};

/// Main escpos-rs structure
///
/// The printer represents the thermal printer connected to the computer.
/// ```rust,no_run
/// use escpos_rs::{Printer, PrinterModel};
/// use libusb::{Context};
///
/// fn main() {
///     // We create a usb contest for the printer
///     let context = Context::new().unwrap();
///     // We pass it to the printer
///     let printer = match Printer::with_context(&context, PrinterModel::TMT20.profile()) {
///         Ok(maybe_printer) => match maybe_printer {
///             Some(printer) => printer,
///             None => panic!("No printer was found :(")
///         },
///         Err(e) => panic!("Error: {}", e)
///     };
///     // Now we have a printer
/// }
/// ```
pub struct Printer<'a> {
    printer_profile: PrinterProfile,
    /// Bulk write endpoint
    endpoint: u8,
    dh: DeviceHandle<'a>
}

impl<'a> Printer<'a> {
    /// Creates a new printer
    /// 
    /// Creates the printer with the given details, from the printer details provided, and in the given USB context.
    pub fn with_context(context: &'a Context, printer_profile: PrinterProfile) -> Result<Option<Printer<'a>>, Error> {
        let (vendor_id, product_id) = (printer_profile.vendor_id, printer_profile.product_id);
        let devices = context.devices().map_err(|e| Error::LibusbError(e))?;
        for device in devices.iter() {
            let s = device.device_descriptor().map_err(|e| Error::LibusbError(e))?;
            if s.vendor_id() == vendor_id && s.product_id() == product_id {
                // Before opening the device, we must find the bulk endpoint
                let config_descriptor = device.active_config_descriptor().map_err(|e| Error::LibusbError(e))?;
                let endpoint = if let Some(endpoint) = printer_profile.endpoint {
                    endpoint
                } else {
                    let mut detected_endpoint: Option<u8> = None;
                    // Horrible to have 3 nested for, but so be it
                    for interface in config_descriptor.interfaces() {
                        for descriptor in interface.descriptors() {
                            for endpoint in descriptor.endpoint_descriptors() {
                                match (endpoint.transfer_type(), endpoint.direction()) {
                                    (libusb::TransferType::Bulk, libusb::Direction::Out) => if detected_endpoint.is_none() {
                                        detected_endpoint = Some(endpoint.number());
                                    },
                                    _ => ()
                                };
                            }
                        }
                    }
    
                    if let Some(endpoint) = detected_endpoint {
                        endpoint
                    } else {
                        return Err(Error::NoBulkEndpoint);
                    }
                };

                // Now we continue opening the device

                match device.open() {
                    Ok(mut dh) => {
                        if let Ok(active) = dh.kernel_driver_active(0) {
                            if active {
                                // The kernel is active, we have to detach it
                                match dh.detach_kernel_driver(0) {
                                    Ok(_) => (),
                                    Err(e) => return Err(Error::LibusbError(e))
                                };
                            }
                        } else {
                            warn!("Could not find out if kernel driver is active, might encounter a problem soon.");
                        };
                        // Now we claim the interface
                        match dh.claim_interface(0) {
                            Ok(_) => (),
                            Err(e) => return Err(Error::LibusbError(e))
                        }
                        return Ok(Some(Printer {
                            printer_profile,
                            endpoint,
                            dh
                        }));
                    },
                    Err(e) => return Err(Error::LibusbError(e))
                };
            }
        }
        // No printer was found with such vid and pid
        Ok(None)
    }

    /// Guesses the printer, and connects to it (not meant for production)
    ///
    /// 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.
    pub fn with_context_feeling_lucky(context: &'a Context) -> Result<Option<Printer<'a>>, Error> {
        // Match to force update then a new model gets added, just as a reminder
        /*****
        IF YOU ARE READING THIS, AND YOU GOT AN  ERROR BECAUSE A PRINTER WAS MISSING,
        UPDATE THE FOR FOLLOWING THE MATCH TO TRY ALL PRINTERS
        *****/
        match PrinterModel::TMT20 {
            PrinterModel::TMT20 => (),
            PrinterModel::ZKTeco => ()
        }
        // Keep up to date! All printers should appear here for the function to work
        for printer_model in vec![PrinterModel::TMT20, PrinterModel::ZKTeco] {
            let printer_profile = printer_model.profile();
            let candidate = Printer::with_context(context, printer_profile)?;
            if candidate.is_some() {
                return Ok(candidate)
            }
        }
        // No printer was found
        Ok(None)
    }

    /// Print an instruction
    ///
    /// You can pass optional printer data to the printer to fill in the dynamic parts of the instruction.
    pub fn instruction(&self, instruction: &Instruction, print_data: Option<&PrintData>) -> Result<(), Error> {
        let content = instruction.to_vec(&self.printer_profile, print_data)?;
        self.raw(&content)
    }
    
    /// Print some text. By default, there is line skipping with spaces as newlines (when the text does not fit). At the end, no newline is added.
    pub fn print<T: AsRef<str>>(&self, content: T) -> Result<(), Error> {
        let feed = String::from(content.as_ref()).into_cp437(&CP437_CONTROL).map_err(|e| Error::CP437Error(e.into_string()))?;
        self.raw(&feed)
    }

    /// Print some text.
    ///
    /// By default, there is line skipping with spaces as newlines (when the text does not fit, assuming a width for at least one font was provided, else the text will split exacty where the line is full). At the end, no newline is added.
    pub fn println<T: AsRef<str>>(&self, content: T) -> Result<(), Error> {
        let feed = String::from(content.as_ref()) + "\n";
        self.print(&feed)
    }

    /// Jumps _n_ number of lines (to leave whitespaces). Basically `n * '\n'` passed to `print`
    pub fn jump(&self, n: u8) -> Result<(), Error> {
        let feed = vec!['\n' as u8, n];
        self.raw(&feed)
    }

    /// Cuts the paper, in case the instruction is supported by the printer
    pub fn cut(&self) -> Result<(), Error> {
        self.raw(&Command::Cut.as_bytes())
    }

    /// Prints a table with two columns. The sum of lengths of both strings
    pub fn table_2(&self, rows: Vec<(String, String)>) -> Result<(), Error> {
        let mut feed = Vec::new();
        for pair in rows {
            let len1 = pair.0.len();
            let len2 = pair.1.len();
            let num_spaces = 30 - len1 - len2;
            feed.append(&mut pair.0.as_bytes().to_vec());
            for _ in 0..num_spaces {
                feed.push(' ' as u8);
            }
            feed.append(&mut pair.1.as_bytes().to_vec());
            feed.push('\n' as u8);
        }
        self.raw(&feed)
    }

    pub fn image(&self, escpos_image: EscposImage) -> Result<(), Error> {
        self.raw(&escpos_image.feed(self.printer_profile.width))
    }

    /// Sends raw information to the printer
    ///
    /// As simple as it sounds
    /// ```rust,no_run
    /// # use libusb::Context;
    /// # use escpos_rs::{Printer,PrinterProfile};
    /// # let context = Context::new().unwrap();
    /// # let printer_profile = PrinterProfile::builder(0x0001, 0x0001).build();
    /// # let printer = Printer::with_context(&context, printer_profile).unwrap().unwrap();
    /// printer.raw(&[0x01, 0x02])?;
    /// # Ok::<(), escpos_rs::Error>(())
    /// ```
    pub fn raw<A: AsRef<[u8]>>(&self, bytes: A) -> Result<(), Error> {
        self.dh.write_bulk(
            self.endpoint,
            bytes.as_ref(),
            self.printer_profile.timeout
        ).map_err(|e| Error::LibusbError(e))?;
        Ok(())
    }
}