Progress on modularizing escpos.

This commit is contained in:
Jurn Wubben 2025-09-25 12:49:25 +02:00
parent 280d2fcbab
commit be3fd25fcf
5 changed files with 103 additions and 84 deletions

7
src/escpos/errors.rs Normal file
View file

@ -0,0 +1,7 @@
#[derive(Debug)]
pub enum EscPosError {
EmptyQueue,
InvalidQueueIndex,
InvalidBitmapMode,
InvalidBarcodeLength(String),
}

View file

@ -1,16 +1,5 @@
use crate::dithering::atkinson_mono; use crate::{dithering::atkinson_mono, escpos::errors::EscPosError};
use image::{DynamicImage, imageops::FilterType}; use image::{DynamicImage, imageops::FilterType};
use std::{collections::HashMap, io::Write, ops::AddAssign};
const MAX_WIDTH: u32 = 384;
#[derive(Debug)]
pub enum EscPosError {
EmptyQueue,
InvalidQueueIndex,
InvalidBitmapMode,
InvalidBarcodeLength(String),
}
#[repr(u8)] #[repr(u8)]
pub enum QREcc { pub enum QREcc {
@ -39,11 +28,9 @@ pub enum ImageOrientation {
} }
pub struct Job { pub struct Job {
content: Vec<u8>,
pub ready: bool, pub ready: bool,
} pub content: Vec<u8>,
pub struct Printer { max_width: u16,
pub queue: Vec<Job>,
} }
fn is_numeric(s: &[u8]) -> bool { fn is_numeric(s: &[u8]) -> bool {
@ -51,10 +38,11 @@ fn is_numeric(s: &[u8]) -> bool {
} }
impl Job { impl Job {
pub fn new() -> Self { pub fn new(max_width: u16) -> Self {
Job { Job {
content: vec![0x1B, b'@'], content: vec![0x1B, b'@'],
ready: false, ready: false,
max_width: max_width,
} }
} }
@ -71,18 +59,18 @@ impl Job {
let (w, h) = (img.width(), img.height()); let (w, h) = (img.width(), img.height());
let swap = matches!(orientation, Some(ImageOrientation::Largest)) let swap = matches!(orientation, Some(ImageOrientation::Largest))
&& h.min(MAX_WIDTH) * w > w.min(MAX_WIDTH) * h; && h.min(self.max_width as u32) * w > w.min(self.max_width as u32) * h;
let scale = if swap { let scale = if swap {
MAX_WIDTH as f32 / h as f32 self.max_width as f32 / h as f32
} else { } else {
MAX_WIDTH as f32 / w as f32 self.max_width as f32 / w as f32
}; };
let (w, h) = if swap { let (w, h) = if swap {
((h as f32 * scale) as u32, MAX_WIDTH) ((h as f32 * scale) as u32, self.max_width as u32)
} else { } else {
(MAX_WIDTH, (h as f32 * scale) as u32) (self.max_width as u32, (h as f32 * scale) as u32)
}; };
let img = let img =
@ -95,6 +83,8 @@ impl Job {
let buf = &mut self.content; let buf = &mut self.content;
buf.reserve(8 + mono.len()); buf.reserve(8 + mono.len());
buf.extend_from_slice(&[ buf.extend_from_slice(&[
0x1B,
b'@',
0x1D, 0x1D,
0x76, 0x76,
0x30, 0x30,
@ -124,6 +114,8 @@ impl Job {
let text = text.as_bytes(); let text = text.as_bytes();
let len = text.len() + 3; let len = text.len() + 3;
buf.extend_from_slice(&[ buf.extend_from_slice(&[
0x1B,
b'@',
// Size // Size
0x1D, 0x1D,
0x28, 0x28,
@ -186,6 +178,8 @@ impl Job {
let buf = &mut self.content; let buf = &mut self.content;
buf.extend([ buf.extend([
0x1B,
b'@',
0x1D, 0x1D,
0x68, 0x68,
height, height,
@ -205,42 +199,3 @@ impl Job {
Ok(()) Ok(())
} }
} }
impl Printer {
pub fn new() -> Self {
Printer { queue: Vec::new() }
}
pub fn new_job(&mut self) -> Result<&mut Job, EscPosError> {
self.queue.push(Job::new());
self.queue.last_mut().ok_or(EscPosError::InvalidQueueIndex)
}
pub fn print_job(&mut self, writer: &mut impl Write) -> Result<(), EscPosError> {
let page_feed: u8 = 0x0A;
let job = self
.queue
.extract_if(.., |j| j.ready)
.next()
.ok_or(EscPosError::EmptyQueue)?;
// writer.write(&[page_feed]).unwrap(); // FIXME: remove unwraps
writer.write(&job.content).unwrap();
// writer.write(&[page_feed]).unwrap();
Ok(())
}
pub fn export_job(&mut self) -> Result<Vec<u8>, EscPosError> {
let page_feed: u8 = 0x0A;
let job = self
.queue
.extract_if(.., |j| j.ready)
.next()
.ok_or(EscPosError::EmptyQueue)?;
let mut out = Vec::with_capacity(2 + job.content.len());
out.push(page_feed);
out.extend(job.content);
out.push(page_feed);
Ok(out)
}
}

3
src/escpos/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod errors;
pub mod job;
pub mod printer;

49
src/escpos/printer.rs Normal file
View file

@ -0,0 +1,49 @@
use crate::escpos::{errors::EscPosError, job::Job};
use std::io::Write;
pub struct Printer {
pub queue: Vec<Job>,
pub max_width: u16,
}
impl Printer {
pub fn new(max_width: u16) -> Self {
Printer {
queue: Vec::new(),
max_width: max_width,
}
}
pub fn new_job(&mut self) -> Result<&mut Job, EscPosError> {
self.queue.push(Job::new(self.max_width));
self.queue.last_mut().ok_or(EscPosError::InvalidQueueIndex)
}
pub fn print_job(&mut self, writer: &mut impl Write) -> Result<(), EscPosError> {
// let page_feed: u8 = 0x0A;
let job = self
.queue
.extract_if(.., |j| j.ready)
.next()
.ok_or(EscPosError::EmptyQueue)?;
// writer.write(&[page_feed]).unwrap(); // FIXME: remove unwraps
writer.write(&job.content).unwrap();
// writer.write(&[page_feed]).unwrap();
Ok(())
}
pub fn export_job(&mut self) -> Result<Vec<u8>, EscPosError> {
let page_feed: u8 = 0x0A;
let job = self
.queue
.extract_if(.., |j| j.ready)
.next()
.ok_or(EscPosError::EmptyQueue)?;
let mut out = Vec::with_capacity(2 + job.content.len());
out.push(page_feed);
out.extend(job.content);
out.push(page_feed);
Ok(out)
}
}

View file

@ -4,33 +4,38 @@ mod escpos;
use image::{ImageError, ImageReader}; use image::{ImageError, ImageReader};
use std::{env, process}; use std::{env, process};
use crate::escpos::Printer; use crate::escpos::{
job::{BARTextPosition, BARType},
printer::Printer,
};
fn main() { fn main() {
let args: Vec<String> = env::args().collect(); // let args: Vec<String> = env::args().collect();
//
// let len = args.len();
// if len < 2 || len > 2 {
// println!("Please provide a path to the image.");
// process::exit(1);
// }
//
// let img = ImageReader::open(&args[1])
// .map_err(|err| ImageError::IoError(err))
// .and_then(|v| v.decode())
// .unwrap();
let len = args.len(); let mut printer = Printer::new(384);
if len < 2 || len > 2 {
println!("Please provide a path to the image.");
process::exit(1);
}
let img = ImageReader::open(&args[1])
.map_err(|err| ImageError::IoError(err))
.and_then(|v| v.decode())
.unwrap();
let mut printer = Printer::new();
let job = printer.new_job().unwrap(); let job = printer.new_job().unwrap();
// job.write_qr("hi".to_string(), None, None).unwrap(); job.write_qr("https://pornhub.com".to_string(), None, None)
job.write_barcode( // .unwrap();
"hhhhhhhhhhh".to_string(), // job.write_barcode(
None, // "kleintje".to_string(),
None, // Some(10),
None, // None,
Some(escpos::BARType::UPCA), // Some(BARTextPosition::Both),
) // Some(BARType::CODE128),
// )
.unwrap(); .unwrap();
job.content.extend_from_slice(&[b'\n', b'\n']);
// job.write_bitmap(&img, None, None).unwrap(); // job.write_bitmap(&img, None, None).unwrap();
job.ready = true; job.ready = true;