diff --git a/src/escpos.rs b/src/escpos.rs index d5c1b67..240afd6 100644 --- a/src/escpos.rs +++ b/src/escpos.rs @@ -1,46 +1,130 @@ use crate::dithering::atkinson_mono; use image::{DynamicImage, imageops::FilterType}; +use std::{ + collections::{HashMap, VecDeque}, + ops::{Add, AddAssign}, +}; const MAX_WIDTH: u32 = 384; +pub enum EscPosError { + InvalidBitmapMode, + InvalidQueueId, +} pub enum ImageOrientation { Preserve, Largest, } -pub fn escpos_raster( - img: &DynamicImage, - orientation: Option, - mode: Option, -) -> Vec { - let mode = mode.unwrap_or(0); - assert!(mode <= 3 || (48..=51).contains(&mode)); - - let (w, h) = (img.width(), img.height()); - let swap = matches!(orientation, Some(ImageOrientation::Largest)) - && h.min(MAX_WIDTH) * w > w.min(MAX_WIDTH) * h; - - let (w, h) = if swap { (h, w) } else { (w, h) }; - let (w, h) = (MAX_WIDTH, h.saturating_mul(MAX_WIDTH).div_ceil(w)); - - let img = if swap { &img.rotate90() } else { &img }; - let img = img.resize(w, h, FilterType::Triangle); - - let mono = atkinson_mono(&img); - let (width_px, height_px) = (img.width() as u16, img.height() as u16); - let width_bytes = ((width_px + 7) >> 3) as u16; - - let mut out = Vec::with_capacity(8 + mono.len()); - out.extend_from_slice(&[ - 0x1D, - 0x76, - 0x30, - mode, - width_bytes as u8, - (width_bytes >> 8) as u8, - height_px as u8, - (height_px >> 8) as u8, - ]); - out.extend_from_slice(&mono); - out +pub struct EscPosPrinter { + increment: u16, + queue: HashMap>, +} +pub trait EscPosPrinterTrait { + fn new() -> EscPosPrinter; + fn new_job(&mut self) -> u16; + + fn bitmap( + &mut self, + queue_id: u16, + img: &DynamicImage, + orientation: Option, + mode: Option, + ) -> Result<(), EscPosError>; + fn qr(&mut self, queue_id: u16, text: String) -> Result<(), EscPosError>; +} + +impl EscPosPrinterTrait for EscPosPrinter { + fn new() -> EscPosPrinter { + EscPosPrinter { + increment: 0, + queue: HashMap::new(), + } + } + + fn new_job(&mut self) -> u16 { + let job: Vec = Vec::new(); + + self.increment.add_assign(1); + self.queue.insert(self.increment, job); + self.increment + } + fn print_job(&mut self, queue_id: u16) -> Result<(), EscPosError> { + let page_feed: u8 = 0x0A; + + io::stdout().write(&page_feed); + io::stdout().write(&escpos).unwrap(); + io::stdout().write(&page_feed).unwrap(); + + Ok(()) + } + + fn bitmap( + &mut self, + queue_id: u16, + img: &DynamicImage, + orientation: Option, + mode: Option, + ) -> Result<(), EscPosError> { + let mode = mode.unwrap_or(0); + if !(mode <= 3 || (48..=51).contains(&mode)) { + return Err(EscPosError::InvalidBitmapMode); + } + + let buf = self + .queue + .get_mut(&queue_id) + .ok_or(EscPosError::InvalidQueueId)?; + + let (w, h) = (img.width(), img.height()); + let swap = matches!(orientation, Some(ImageOrientation::Largest)) + && h.min(MAX_WIDTH) * w > w.min(MAX_WIDTH) * h; + + let scale = if swap { + MAX_WIDTH as f32 / h as f32 + } else { + MAX_WIDTH as f32 / w as f32 + }; + + let (w, h) = if swap { + ((h as f32 * scale) as u32, MAX_WIDTH) + } else { + (MAX_WIDTH, (h as f32 * scale) as u32) + }; + + let img = + DynamicImage::ImageRgba8(image::imageops::resize(img, w, h, FilterType::Triangle)); + + let img = if swap { img.rotate90() } else { img }; + let mono = atkinson_mono(&img); + let width_bytes = ((w + 7) >> 3) as u16; + + buf.reserve(8 + mono.len()); + buf.extend_from_slice(&[ + 0x1D, + 0x76, + 0x30, + mode, + width_bytes as u8, + (width_bytes >> 8) as u8, + h as u8, + (h >> 8) as u8, + ]); + buf.extend_from_slice(&mono); + + Ok(()) + } + fn qr(&mut self, queue_id: u16, text: String) -> Result<(), EscPosError> { + let buf = self + .queue + .get_mut(&queue_id) + .ok_or(EscPosError::InvalidQueueId)?; + + let text = text.as_bytes(); + buf.extend_from_slice(&[0x1C, 0x7D, 0x25, text.len() as u8]); + buf.extend(&text[..255]); + buf.extend(&[0x0A]); + + return Ok(()); + } }