From 280d2fcbabdbf839b9f3f8c92f037a0824488566 Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Wed, 24 Sep 2025 17:32:07 +0200 Subject: [PATCH 1/8] Implemented: Queues, QR Codes, BARCodes Implemented QR and barcode --- src/escpos.rs | 270 +++++++++++++++++++++++++++++++++++++++++++------- src/main.rs | 26 +++-- 2 files changed, 255 insertions(+), 41 deletions(-) diff --git a/src/escpos.rs b/src/escpos.rs index d5c1b67..13d33f8 100644 --- a/src/escpos.rs +++ b/src/escpos.rs @@ -1,46 +1,246 @@ use crate::dithering::atkinson_mono; 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)] +pub enum QREcc { + Low = 48, + Medium = 49, + Quartile = 50, + High = 51, +} +#[repr(u8)] +pub enum BARTextPosition { + Hidden = 0, + Above = 1, + Below = 2, + Both = 3, +} +#[repr(u8)] +pub enum BARType { + CODE128 = 0x49, + EAN13 = 0x43, + UPCA = 0x41, +} + 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 Job { + content: Vec, + pub ready: bool, +} +pub struct Printer { + pub queue: Vec, +} + +fn is_numeric(s: &[u8]) -> bool { + s.iter().all(|&b| b.is_ascii_digit()) +} + +impl Job { + pub fn new() -> Self { + Job { + content: vec![0x1B, b'@'], + ready: false, + } + } + + pub fn write_bitmap( + &mut self, + 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 (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; + + let buf = &mut self.content; + 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(()) + } + pub fn write_qr( + &mut self, + text: String, + size: Option, + ecc: Option, + ) -> Result<(), EscPosError> { + let buf = &mut self.content; + let ecc = ecc.unwrap_or(QREcc::Medium); + let size = match size { + Some(v) => v.clamp(6, 15), + None => 10, + }; + + let text = text.as_bytes(); + let len = text.len() + 3; + buf.extend_from_slice(&[ + // Size + 0x1D, + 0x28, + 0x6B, + 0x03, + 0x00, + 0x31, + 0x43, + size, + // Error correction level + 0x1D, + 0x28, + 0x6B, + 0x03, + 0x00, + 0x31, + 0x45, + ecc as u8, + // Storing data... + 0x1D, + 0x28, + 0x6B, + (len & 0xFF) as u8, + ((len >> 8) & 0xFF) as u8, + 0x31, + 0x50, + 0x30, + ]); + buf.extend(text); + buf.extend(&[0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30, 0x0A]); // Print! + + return Ok(()); + } + pub fn write_barcode( + &mut self, + text: String, + height: Option, + mod_width: Option, + text_position: Option, + bar_type: Option, + ) -> Result<(), EscPosError> { + let height: u8 = height.unwrap_or(80); + let mod_width: u8 = mod_width.unwrap_or(2); + let text_position = text_position.unwrap_or(BARTextPosition::Below) as u8; + let bar_type = bar_type.unwrap_or(BARType::CODE128); + + let text = text.as_bytes(); + let len = text.len() as u8; + let is_num = is_numeric(text); + match bar_type { + BARType::UPCA if len != 11 || !is_num => Err(EscPosError::InvalidBarcodeLength( + "UCPA Requires 11 numbers.".to_string(), + )), + + BARType::EAN13 if len != 12 || !is_num => Err(EscPosError::InvalidBarcodeLength( + "EAN13 Requires 12 numbers.".to_string(), + )), + _ => Ok(()), + }?; + + let buf = &mut self.content; + buf.extend([ + 0x1D, + 0x68, + height, + 0x1D, + 0x77, + mod_width, + 0x1d, + 0x48, + text_position, + 0x1D, + 0x6B, + bar_type as u8, + len, + ]); + buf.extend(text); + + 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, 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) + } } diff --git a/src/main.rs b/src/main.rs index 9de8881..740f049 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,9 @@ mod dithering; mod escpos; use image::{ImageError, ImageReader}; -use std::{env, fs, io, io::Write, process}; +use std::{env, process}; + +use crate::escpos::Printer; fn main() { let args: Vec = env::args().collect(); @@ -18,10 +20,22 @@ fn main() { .and_then(|v| v.decode()) .unwrap(); - let mut escpos = escpos::escpos_raster(&img, Some(escpos::ImageOrientation::Largest), Some(0)); - for _ in 0..2 { - escpos.push('\n' as u8); - } + let mut printer = Printer::new(); + let job = printer.new_job().unwrap(); + // job.write_qr("hi".to_string(), None, None).unwrap(); + job.write_barcode( + "hhhhhhhhhhh".to_string(), + None, + None, + None, + Some(escpos::BARType::UPCA), + ) + .unwrap(); + // job.write_bitmap(&img, None, None).unwrap(); + job.ready = true; - io::stdout().write(&escpos).unwrap(); + println!("{}", printer.queue.len()); + + let mut out = std::fs::File::create("/dev/usb/lp0").unwrap(); + printer.print_job(&mut out).unwrap(); } From be3fd25fcfbf067bde4dba4fcddd2ed5a01abf06 Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Thu, 25 Sep 2025 12:49:25 +0200 Subject: [PATCH 2/8] Progress on modularizing escpos. --- src/escpos/errors.rs | 7 +++ src/{escpos.rs => escpos/job.rs} | 77 +++++++------------------------- src/escpos/mod.rs | 3 ++ src/escpos/printer.rs | 49 ++++++++++++++++++++ src/main.rs | 51 +++++++++++---------- 5 files changed, 103 insertions(+), 84 deletions(-) create mode 100644 src/escpos/errors.rs rename src/{escpos.rs => escpos/job.rs} (71%) create mode 100644 src/escpos/mod.rs create mode 100644 src/escpos/printer.rs diff --git a/src/escpos/errors.rs b/src/escpos/errors.rs new file mode 100644 index 0000000..d40865f --- /dev/null +++ b/src/escpos/errors.rs @@ -0,0 +1,7 @@ +#[derive(Debug)] +pub enum EscPosError { + EmptyQueue, + InvalidQueueIndex, + InvalidBitmapMode, + InvalidBarcodeLength(String), +} diff --git a/src/escpos.rs b/src/escpos/job.rs similarity index 71% rename from src/escpos.rs rename to src/escpos/job.rs index 13d33f8..5a0a75f 100644 --- a/src/escpos.rs +++ b/src/escpos/job.rs @@ -1,16 +1,5 @@ -use crate::dithering::atkinson_mono; +use crate::{dithering::atkinson_mono, escpos::errors::EscPosError}; 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)] pub enum QREcc { @@ -39,11 +28,9 @@ pub enum ImageOrientation { } pub struct Job { - content: Vec, pub ready: bool, -} -pub struct Printer { - pub queue: Vec, + pub content: Vec, + max_width: u16, } fn is_numeric(s: &[u8]) -> bool { @@ -51,10 +38,11 @@ fn is_numeric(s: &[u8]) -> bool { } impl Job { - pub fn new() -> Self { + pub fn new(max_width: u16) -> Self { Job { content: vec![0x1B, b'@'], ready: false, + max_width: max_width, } } @@ -71,18 +59,18 @@ impl Job { let (w, h) = (img.width(), img.height()); 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 { - MAX_WIDTH as f32 / h as f32 + self.max_width as f32 / h as f32 } else { - MAX_WIDTH as f32 / w as f32 + self.max_width as f32 / w as f32 }; 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 { - (MAX_WIDTH, (h as f32 * scale) as u32) + (self.max_width as u32, (h as f32 * scale) as u32) }; let img = @@ -95,6 +83,8 @@ impl Job { let buf = &mut self.content; buf.reserve(8 + mono.len()); buf.extend_from_slice(&[ + 0x1B, + b'@', 0x1D, 0x76, 0x30, @@ -124,6 +114,8 @@ impl Job { let text = text.as_bytes(); let len = text.len() + 3; buf.extend_from_slice(&[ + 0x1B, + b'@', // Size 0x1D, 0x28, @@ -186,6 +178,8 @@ impl Job { let buf = &mut self.content; buf.extend([ + 0x1B, + b'@', 0x1D, 0x68, height, @@ -205,42 +199,3 @@ impl Job { 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, 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) - } -} diff --git a/src/escpos/mod.rs b/src/escpos/mod.rs new file mode 100644 index 0000000..b03aae7 --- /dev/null +++ b/src/escpos/mod.rs @@ -0,0 +1,3 @@ +pub mod errors; +pub mod job; +pub mod printer; diff --git a/src/escpos/printer.rs b/src/escpos/printer.rs new file mode 100644 index 0000000..42a0e3c --- /dev/null +++ b/src/escpos/printer.rs @@ -0,0 +1,49 @@ +use crate::escpos::{errors::EscPosError, job::Job}; +use std::io::Write; + +pub struct Printer { + pub queue: Vec, + 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, 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) + } +} diff --git a/src/main.rs b/src/main.rs index 740f049..1a5f710 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,33 +4,38 @@ mod escpos; use image::{ImageError, ImageReader}; use std::{env, process}; -use crate::escpos::Printer; +use crate::escpos::{ + job::{BARTextPosition, BARType}, + printer::Printer, +}; fn main() { - let args: Vec = env::args().collect(); + // let args: Vec = 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(); - 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 mut printer = Printer::new(384); let job = printer.new_job().unwrap(); - // job.write_qr("hi".to_string(), None, None).unwrap(); - job.write_barcode( - "hhhhhhhhhhh".to_string(), - None, - None, - None, - Some(escpos::BARType::UPCA), - ) - .unwrap(); + job.write_qr("https://pornhub.com".to_string(), None, None) + // .unwrap(); + // job.write_barcode( + // "kleintje".to_string(), + // Some(10), + // None, + // Some(BARTextPosition::Both), + // Some(BARType::CODE128), + // ) + .unwrap(); + job.content.extend_from_slice(&[b'\n', b'\n']); // job.write_bitmap(&img, None, None).unwrap(); job.ready = true; From 9710e81ac1f4bbe3ec1011f2a659583ea302d2bf Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Thu, 25 Sep 2025 22:57:20 +0200 Subject: [PATCH 3/8] Started implementation on text mode --- src/escpos/job.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/escpos/job.rs b/src/escpos/job.rs index 5a0a75f..7910ba4 100644 --- a/src/escpos/job.rs +++ b/src/escpos/job.rs @@ -21,11 +21,20 @@ pub enum BARType { EAN13 = 0x43, UPCA = 0x41, } - pub enum ImageOrientation { Preserve, Largest, } +#[repr(u8)] +pub enum JustifyOrientation {} + +#[repr(u8)] +pub enum TextEffect { + DoubleHeight = 16, + Bold = 8, + DoubleWidth = 32, + Justify(JustifyOrientation) = b'a', +} pub struct Job { pub ready: bool, @@ -46,6 +55,11 @@ impl Job { } } + pub fn write_feed(&mut self, amount: Option) -> () { + let amount = amount.unwrap_or(1); + self.content.extend_from_slice(&[0x1B, b'd', amount]) + } + // pub fn write_text(&mut self, text: String, ) pub fn write_bitmap( &mut self, img: &DynamicImage, From 998e5926dc089d65a4d1bdbe60b9956078f2821c Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Fri, 26 Sep 2025 15:25:11 +0200 Subject: [PATCH 4/8] Implemented different dithering algorithm with brightness check. --- src/dithering.rs | 55 ++++++++++++++++++----------------------------- src/escpos/job.rs | 18 ++++++++++++---- src/main.rs | 49 +++++++++++++++++++++-------------------- 3 files changed, 59 insertions(+), 63 deletions(-) diff --git a/src/dithering.rs b/src/dithering.rs index 8006276..bc33b6f 100644 --- a/src/dithering.rs +++ b/src/dithering.rs @@ -1,44 +1,31 @@ -use image::DynamicImage; +use image::{ + DynamicImage, GrayImage, + imageops::{self, colorops}, +}; +pub fn auto_brighten(gray: &mut GrayImage) -> () { + let avg = gray.pixels().map(|p| p.0[0] as u32).sum::() / gray.len() as u32; + + if avg < 100 { + colorops::brighten_in_place(gray, 40); + colorops::contrast_in_place(gray, 25.0); + } else if avg > 180 { + colorops::brighten_in_place(gray, -15); + } +} + +pub fn dither(gray: &mut GrayImage) -> Vec { + imageops::dither(gray, &imageops::colorops::BiLevel); -pub fn atkinson_mono(img: &DynamicImage) -> Vec { - let mut gray = img.to_luma8(); let (w, h) = (gray.width() as usize, gray.height() as usize); let pitch = (w + 7) >> 3; let mut bits = vec![0u8; pitch * h]; - let buf = gray.as_mut(); for y in 0..h { - let row = y * w; for x in 0..w { - let i = row + x; - let old = buf[i]; - let new = if old < 128 { 0 } else { 255 }; - buf[i] = new; - let bit = new == 0; - if bit { - let byte = y * pitch + (x >> 3); - bits[byte] |= 128 >> (x & 7); - } - let err = ((old as i16) - (new as i16)) / 8; - - if x + 1 < w { - buf[i + 1] = (buf[i + 1] as i16 + err).clamp(0, 255) as u8; - } - if x + 2 < w { - buf[i + 2] = (buf[i + 2] as i16 + err).clamp(0, 255) as u8; - } - if y + 1 < h { - let down = i + w; - if x > 0 { - buf[down - 1] = (buf[down - 1] as i16 + err).clamp(0, 255) as u8; - } - buf[down] = (buf[down] as i16 + err).clamp(0, 255) as u8; - if x + 1 < w { - buf[down + 1] = (buf[down + 1] as i16 + err).clamp(0, 255) as u8; - } - if y + 2 < h { - buf[down + w] = (buf[down + w] as i16 + err).clamp(0, 255) as u8; - } + let byte = y * pitch + (x >> 3); + let bit = 7 - (x & 7); + if gray[(x as u32, y as u32)].0[0] == 0 { + bits[byte] |= 1 << bit; } } } diff --git a/src/escpos/job.rs b/src/escpos/job.rs index 7910ba4..ebd34ba 100644 --- a/src/escpos/job.rs +++ b/src/escpos/job.rs @@ -1,4 +1,7 @@ -use crate::{dithering::atkinson_mono, escpos::errors::EscPosError}; +use crate::{ + dithering::{auto_brighten, dither}, + escpos::errors::EscPosError, +}; use image::{DynamicImage, imageops::FilterType}; #[repr(u8)] @@ -26,7 +29,11 @@ pub enum ImageOrientation { Largest, } #[repr(u8)] -pub enum JustifyOrientation {} +pub enum JustifyOrientation { + Left = 0, + Center = 1, + Rigth = 2, +} #[repr(u8)] pub enum TextEffect { @@ -59,7 +66,7 @@ impl Job { let amount = amount.unwrap_or(1); self.content.extend_from_slice(&[0x1B, b'd', amount]) } - // pub fn write_text(&mut self, text: String, ) + pub fn write_text(&mut self, text: String) {} pub fn write_bitmap( &mut self, img: &DynamicImage, @@ -91,7 +98,10 @@ impl Job { DynamicImage::ImageRgba8(image::imageops::resize(img, w, h, FilterType::Triangle)); let img = if swap { img.rotate90() } else { img }; - let mono = atkinson_mono(&img); + let mut gray = img.to_luma8(); + auto_brighten(&mut gray); + + let mono = dither(&mut gray); let width_bytes = ((w + 7) >> 3) as u16; let buf = &mut self.content; diff --git a/src/main.rs b/src/main.rs index 1a5f710..91bef30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,36 +10,35 @@ use crate::escpos::{ }; fn main() { - // let args: Vec = 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 args: Vec = 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 mut printer = Printer::new(384); let job = printer.new_job().unwrap(); - job.write_qr("https://pornhub.com".to_string(), None, None) - // .unwrap(); - // job.write_barcode( - // "kleintje".to_string(), - // Some(10), - // None, - // Some(BARTextPosition::Both), - // Some(BARType::CODE128), - // ) - .unwrap(); - job.content.extend_from_slice(&[b'\n', b'\n']); - // job.write_bitmap(&img, None, None).unwrap(); + job.write_feed(Some(2)); + // job.write_barcode( + // "kleintje".to_string(), + // Some(10), + // None, + // Some(BARTextPosition::Both), + // Some(BARType::CODE128), + // ) + // .unwrap(); + job.write_bitmap(&img, None, None).unwrap(); + job.write_feed(Some(2)); job.ready = true; - println!("{}", printer.queue.len()); + // println!("{:?}", job.content); let mut out = std::fs::File::create("/dev/usb/lp0").unwrap(); printer.print_job(&mut out).unwrap(); From 7d42fe7211db1edd7992b30d4dd53b3582b9f36d Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Fri, 26 Sep 2025 23:22:54 +0200 Subject: [PATCH 5/8] Implemented text --- src/escpos/errors.rs | 1 + src/escpos/job.rs | 44 +++++++++++++++++++++++++++++++++++++------- src/main.rs | 41 +++++++++++++++++++++++++++-------------- 3 files changed, 65 insertions(+), 21 deletions(-) diff --git a/src/escpos/errors.rs b/src/escpos/errors.rs index d40865f..d7761f1 100644 --- a/src/escpos/errors.rs +++ b/src/escpos/errors.rs @@ -4,4 +4,5 @@ pub enum EscPosError { InvalidQueueIndex, InvalidBitmapMode, InvalidBarcodeLength(String), + InvalidTextSize, } diff --git a/src/escpos/job.rs b/src/escpos/job.rs index ebd34ba..4c9fcd3 100644 --- a/src/escpos/job.rs +++ b/src/escpos/job.rs @@ -32,15 +32,15 @@ pub enum ImageOrientation { pub enum JustifyOrientation { Left = 0, Center = 1, - Rigth = 2, + Right = 2, } -#[repr(u8)] pub enum TextEffect { - DoubleHeight = 16, - Bold = 8, - DoubleWidth = 32, - Justify(JustifyOrientation) = b'a', + Bold, + DoubleHeight, + DoubleWidth, + InvertColor, + Justify(JustifyOrientation), } pub struct Job { @@ -53,6 +53,13 @@ fn is_numeric(s: &[u8]) -> bool { s.iter().all(|&b| b.is_ascii_digit()) } +impl Copy for JustifyOrientation {} +impl Clone for JustifyOrientation { + fn clone(&self) -> Self { + *self + } +} + impl Job { pub fn new(max_width: u16) -> Self { Job { @@ -66,7 +73,30 @@ impl Job { let amount = amount.unwrap_or(1); self.content.extend_from_slice(&[0x1B, b'd', amount]) } - pub fn write_text(&mut self, text: String) {} + pub fn write_text( + &mut self, + text: &String, + text_effect: &[TextEffect], + ) -> Result<(), EscPosError> { + let buf = &mut self.content; + buf.extend_from_slice(&[0x1B, b'@']); + + for i in text_effect { + match i { + TextEffect::Bold => buf.extend_from_slice(&[0x1B, b'E', 1]), + TextEffect::DoubleWidth => buf.extend_from_slice(&[0x1B, b'E', 1]), + TextEffect::DoubleHeight => buf.extend_from_slice(&[0x1B, b'E', 1]), + TextEffect::InvertColor => buf.extend_from_slice(&[0x1D, 0x42, 1]), + TextEffect::Justify(orientation) => { + buf.extend_from_slice(&[0x1B, b'a', orientation.clone() as u8]) + } + } + } + + buf.extend_from_slice(text.as_bytes()); + Ok(()) + } + pub fn write_bitmap( &mut self, img: &DynamicImage, diff --git a/src/main.rs b/src/main.rs index 91bef30..8c885ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,23 +5,23 @@ use image::{ImageError, ImageReader}; use std::{env, process}; use crate::escpos::{ - job::{BARTextPosition, BARType}, + job::{BARTextPosition, BARType, TextEffect}, printer::Printer, }; fn main() { - let args: Vec = 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 args: Vec = 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 mut printer = Printer::new(384); let job = printer.new_job().unwrap(); @@ -34,7 +34,20 @@ fn main() { // Some(BARType::CODE128), // ) // .unwrap(); - job.write_bitmap(&img, None, None).unwrap(); + // job.write_bitmap(&img, None, None).unwrap(); + job.write_text( + &"Kanker homo! Ik hoop dat je dood gaat \n Sorry grapje, hier is een mooie QR code die je mag scannen.".to_string(), + &[ + TextEffect::Justify(escpos::job::JustifyOrientation::Center), + TextEffect::Size(5, 5) + // TextEffect::Bold, + // TextEffect::InvertColor, + + ] + ).unwrap(); + job.write_qr("https://pornhub.com".to_string(), None, None) + .unwrap(); + job.write_feed(Some(2)); job.ready = true; From b2662dadb77264ca2af840636573233e2e0b9719 Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Sat, 27 Sep 2025 14:08:05 +0200 Subject: [PATCH 6/8] [BROKEN] started working on web server implementation --- Cargo.lock | 690 ++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 4 + src/escpos/printer.rs | 26 +- src/main.rs | 102 ++++--- 4 files changed, 732 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6baa49..456fc8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -46,6 +55,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" @@ -75,6 +90,76 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "multer", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "bit_field" version = "0.10.3" @@ -118,10 +203,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] -name = "cc" -version = "1.2.38" +name = "bytes" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", "jobserver", @@ -197,6 +288,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equator" version = "0.4.2" @@ -283,6 +383,54 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -316,6 +464,12 @@ dependencies = [ "weezl", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "half" version = "2.6.0" @@ -338,6 +492,89 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "image" version = "0.25.8" @@ -374,9 +611,9 @@ dependencies = [ [[package]] name = "imgref" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[package]] name = "indexmap" @@ -399,6 +636,17 @@ dependencies = [ "syn", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "itertools" version = "0.12.1" @@ -408,6 +656,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jobserver" version = "0.1.34" @@ -426,9 +680,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libfuzzer-sys" @@ -440,6 +694,16 @@ dependencies = [ "cc", ] +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.28" @@ -455,6 +719,12 @@ dependencies = [ "imgref", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -467,9 +737,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" @@ -487,6 +763,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys", +] + [[package]] name = "moxcms" version = "0.7.5" @@ -497,6 +784,23 @@ dependencies = [ "pxfm", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -569,18 +873,68 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.32" @@ -613,7 +967,10 @@ dependencies = [ name = "printserv" version = "0.1.0" dependencies = [ + "axum", "image", + "tokio", + "tower-http", ] [[package]] @@ -783,12 +1140,27 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + [[package]] name = "rgb" version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + [[package]] name = "rustversion" version = "1.0.22" @@ -796,34 +1168,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "serde" -version = "1.0.226" +name = "ryu" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" dependencies = [ "serde_core", ] [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -833,12 +1241,33 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -854,12 +1283,34 @@ dependencies = [ "quote", ] +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "syn" version = "2.0.106" @@ -871,6 +1322,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "system-deps" version = "6.2.2" @@ -924,6 +1381,37 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.8.23" @@ -958,6 +1446,71 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + [[package]] name = "unicode-ident" version = "1.0.19" @@ -981,6 +1534,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1007,9 +1566,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -1020,9 +1579,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -1034,9 +1593,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1044,9 +1603,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -1057,9 +1616,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -1070,6 +1629,85 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.7.13" diff --git a/Cargo.toml b/Cargo.toml index c3e666f..c9654db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,7 @@ edition = "2024" [dependencies] image = {version = "0.25.8", features = ["png"]} + +axum = { version = "0.8.4", features = ["multipart"] } +tokio = { version = "1.0", features = ["full"] } +tower-http = { version = "0.6.1", features = ["limit", "trace"] } diff --git a/src/escpos/printer.rs b/src/escpos/printer.rs index 42a0e3c..3fe7da9 100644 --- a/src/escpos/printer.rs +++ b/src/escpos/printer.rs @@ -17,32 +17,26 @@ impl Printer { 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 + + fn extract_job(&mut self) -> Result { + self.queue .extract_if(.., |j| j.ready) .next() - .ok_or(EscPosError::EmptyQueue)?; + .ok_or(EscPosError::EmptyQueue) + } + pub fn print_job(&mut self, writer: &mut impl Write) -> Result<(), EscPosError> { + let mut job = self.extract_job()?; + job.write_feed(Some(2)); - // 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, EscPosError> { - let page_feed: u8 = 0x0A; - let job = self - .queue - .extract_if(.., |j| j.ready) - .next() - .ok_or(EscPosError::EmptyQueue)?; + let mut job = self.extract_job()?; + job.write_feed(Some(2)); let mut out = Vec::with_capacity(2 + job.content.len()); - out.push(page_feed); out.extend(job.content); - out.push(page_feed); Ok(out) } diff --git a/src/main.rs b/src/main.rs index 8c885ae..4861c41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,58 +1,64 @@ mod dithering; mod escpos; -use image::{ImageError, ImageReader}; -use std::{env, process}; +use std::{io::Write, sync::Arc}; -use crate::escpos::{ - job::{BARTextPosition, BARType, TextEffect}, - printer::Printer, +use axum::{ + Router, + extract::{Extension, Multipart}, + http::StatusCode, + response::IntoResponse, + routing::post, }; +use image::DynamicImage; +use tokio::sync::Mutex; -fn main() { - // let args: Vec = 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(); +use crate::escpos::printer::Printer; - let mut printer = Printer::new(384); - let job = printer.new_job().unwrap(); - job.write_feed(Some(2)); - // job.write_barcode( - // "kleintje".to_string(), - // Some(10), - // None, - // Some(BARTextPosition::Both), - // Some(BARType::CODE128), - // ) - // .unwrap(); - // job.write_bitmap(&img, None, None).unwrap(); - job.write_text( - &"Kanker homo! Ik hoop dat je dood gaat \n Sorry grapje, hier is een mooie QR code die je mag scannen.".to_string(), - &[ - TextEffect::Justify(escpos::job::JustifyOrientation::Center), - TextEffect::Size(5, 5) - // TextEffect::Bold, - // TextEffect::InvertColor, +#[tokio::main] +async fn main() { + let printer = Arc::new(Mutex::new(Printer::new(380))); - ] - ).unwrap(); - job.write_qr("https://pornhub.com".to_string(), None, None) + let app = Router::new() + .route("/", post(upload)) + .layer(Extension(printer)); + + println!("Listening on http://127.0.0.1:8000"); + axum::Server::bind(&"192.168.1.112:8000".parse().unwrap()) + .serve(app.into_make_service()) + .await .unwrap(); - - job.write_feed(Some(2)); - job.ready = true; - - // println!("{:?}", job.content); - - let mut out = std::fs::File::create("/dev/usb/lp0").unwrap(); - printer.print_job(&mut out).unwrap(); +} + +async fn upload( + mut multipart: Multipart, + Extension(state): Extension>>, +) -> Result { + let mut field = match multipart.next_field().await { + Ok(Some(f)) => f, + Ok(None) => return (StatusCode::BAD_REQUEST, "no multipart field").into_response(), + Err(_) => return (StatusCode::BAD_REQUEST, "multipart parse error").into_response(), + }; + + let data = match field.bytes().await { + Ok(b) => b, + Err(_) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "read field bytes error").into_response(); + } + }; + + let img: DynamicImage = match image::load_from_memory(data.chunk()) { + Ok(i) => i, + Err(_) => { + return (StatusCode::UNSUPPORTED_MEDIA_TYPE, "image decode error").into_response(); + } + }; + + let s = state.lock().await; + let mut job = s.new_job().unwrap(); + job.write_bitmap(&img, None, None); + + let mut file = std::fs::File::create("/dev/usb/lp0").unwrap(); + s.print_job(&mut file); + (StatusCode::OK).into_response() } From e9d0580f7eba57005f5318b7568aa2ddab9aa1da Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Mon, 29 Sep 2025 11:05:31 +0200 Subject: [PATCH 7/8] Implemented basic webserver --- Cargo.lock | 51 ++++++++++++++++++++++++ Cargo.toml | 3 +- src/escpos/job.rs | 53 +++++++++++++++---------- src/main.rs | 98 +++++++++++++++++++++++++++++++---------------- 4 files changed, 149 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 456fc8a..9b365db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,6 +97,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ "axum-core", + "axum-macros", "bytes", "form_urlencoded", "futures-util", @@ -145,6 +146,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -413,6 +425,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + [[package]] name = "futures-task" version = "0.3.31" @@ -470,6 +488,25 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.6.0" @@ -548,6 +585,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -1412,6 +1450,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.23" diff --git a/Cargo.toml b/Cargo.toml index c9654db..96e58bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] image = {version = "0.25.8", features = ["png"]} -axum = { version = "0.8.4", features = ["multipart"] } +# axum = { version = "0.8.4", features = ["multipart", "debug"] } +axum = { version = "0.8.4", features = ["multipart", "http2", "macros"] } tokio = { version = "1.0", features = ["full"] } tower-http = { version = "0.6.1", features = ["limit", "trace"] } diff --git a/src/escpos/job.rs b/src/escpos/job.rs index 4c9fcd3..2677737 100644 --- a/src/escpos/job.rs +++ b/src/escpos/job.rs @@ -108,30 +108,41 @@ impl Job { return Err(EscPosError::InvalidBitmapMode); } - let (w, h) = (img.width(), img.height()); - let swap = matches!(orientation, Some(ImageOrientation::Largest)) - && h.min(self.max_width as u32) * w > w.min(self.max_width as u32) * h; + let (iw, ih) = (img.width(), img.height()); + let mw = self.max_width as u32; - let scale = if swap { - self.max_width as f32 / h as f32 - } else { - self.max_width as f32 / w as f32 + let rotate = matches!(orientation, Some(ImageOrientation::Largest)) && { + let scale_a = mw as f32 / iw as f32; + let out_h_a = (ih as f32 * scale_a).round() as u32; + let area_a = (mw as u64) * (out_h_a as u64); + let scale_b = mw as f32 / ih as f32; + let out_h_b = (iw as f32 * scale_b).round() as u32; + let area_b = (mw as u64) * (out_h_b as u64); + area_b > area_a }; - let (w, h) = if swap { - ((h as f32 * scale) as u32, self.max_width as u32) - } else { - (self.max_width as u32, (h as f32 * scale) as u32) - }; + let (pw, ph) = if rotate { (ih, iw) } else { (iw, ih) }; + let target_w = mw; + let target_h = (ph as f32 * (mw as f32 / pw as f32)).round() as u32; - let img = - DynamicImage::ImageRgba8(image::imageops::resize(img, w, h, FilterType::Triangle)); + let mut rotated: Option = None; + if rotate { + rotated = Some(img.rotate90()); + } + let src: &DynamicImage = rotated.as_ref().unwrap_or(img); + let resized = DynamicImage::ImageRgba8(image::imageops::resize( + src, + target_w, + target_h, + FilterType::Triangle, + )); - let img = if swap { img.rotate90() } else { img }; - let mut gray = img.to_luma8(); + let mut gray = resized.to_luma8(); auto_brighten(&mut gray); - let mono = dither(&mut gray); + + let w = target_w; + let h = target_h; let width_bytes = ((w + 7) >> 3) as u16; let buf = &mut self.content; @@ -143,10 +154,10 @@ impl Job { 0x76, 0x30, mode, - width_bytes as u8, - (width_bytes >> 8) as u8, - h as u8, - (h >> 8) as u8, + (width_bytes & 0xFF) as u8, + ((width_bytes >> 8) & 0xFF) as u8, + (h & 0xFF) as u8, + ((h >> 8) & 0xFF) as u8, ]); buf.extend_from_slice(&mono); diff --git a/src/main.rs b/src/main.rs index 4861c41..d48ba02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,60 +5,90 @@ use std::{io::Write, sync::Arc}; use axum::{ Router, + body::{Body, Bytes}, extract::{Extension, Multipart}, - http::StatusCode, + http::{Response, StatusCode, header}, response::IntoResponse, - routing::post, + routing::{get, post}, }; use image::DynamicImage; use tokio::sync::Mutex; -use crate::escpos::printer::Printer; +use crate::escpos::{errors::EscPosError, job::ImageOrientation, printer::Printer}; #[tokio::main] async fn main() { let printer = Arc::new(Mutex::new(Printer::new(380))); let app = Router::new() - .route("/", post(upload)) + .route("/upload", post(upload)) + .route("/print", get(print)) .layer(Extension(printer)); - println!("Listening on http://127.0.0.1:8000"); - axum::Server::bind(&"192.168.1.112:8000".parse().unwrap()) - .serve(app.into_make_service()) - .await - .unwrap(); + let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); + axum::serve(listener, app).await.unwrap(); } +#[axum::debug_handler] +async fn print( + Extension(printer): Extension>>, +) -> impl axum::response::IntoResponse { + let mut printer = printer.lock().await; + let bytes = match printer.export_job() { + Ok(data) => data, + Err(EscPosError::EmptyQueue) => { + println!("requested but no queue"); + return ( + StatusCode::NO_CONTENT, + [(header::CONTENT_TYPE, "application/octet-stream")], + Vec::new(), + ); + } + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + [(header::CONTENT_TYPE, "application/octet-stream")], + Vec::new(), + ); + } + }; + + ( + StatusCode::OK, + [(header::CONTENT_TYPE, "application/octet-stream")], + bytes, + ) +} + +#[axum::debug_handler] async fn upload( + Extension(printer): Extension>>, mut multipart: Multipart, - Extension(state): Extension>>, -) -> Result { - let mut field = match multipart.next_field().await { - Ok(Some(f)) => f, - Ok(None) => return (StatusCode::BAD_REQUEST, "no multipart field").into_response(), - Err(_) => return (StatusCode::BAD_REQUEST, "multipart parse error").into_response(), - }; +) -> Result, StatusCode> { + let field = multipart + .next_field() + .await + .map_err(|_| StatusCode::BAD_REQUEST)? + .ok_or(StatusCode::BAD_REQUEST)?; + let data = field + .bytes() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + .to_vec(); - let data = match field.bytes().await { - Ok(b) => b, - Err(_) => { - return (StatusCode::INTERNAL_SERVER_ERROR, "read field bytes error").into_response(); - } - }; + let img: DynamicImage = + image::load_from_memory(&data).map_err(|_| StatusCode::UNSUPPORTED_MEDIA_TYPE)?; - let img: DynamicImage = match image::load_from_memory(data.chunk()) { - Ok(i) => i, - Err(_) => { - return (StatusCode::UNSUPPORTED_MEDIA_TYPE, "image decode error").into_response(); - } - }; + println!("{}x{}", img.width(), img.height()); - let s = state.lock().await; - let mut job = s.new_job().unwrap(); - job.write_bitmap(&img, None, None); + let mut printer = printer.lock().await; + let job = printer.new_job().unwrap(); + job.write_bitmap(&img, Some(ImageOrientation::Largest), None) + .unwrap(); + job.ready = true; - let mut file = std::fs::File::create("/dev/usb/lp0").unwrap(); - s.print_job(&mut file); - (StatusCode::OK).into_response() + // let mut file = std::fs::File::create("/dev/usb/lp0").unwrap(); + // printer.print_job(&mut file).unwrap(); + + Ok((StatusCode::OK).into_response()) } From f726a374caeb14a6d1f0f123a7df5425000cf4d0 Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Mon, 29 Sep 2025 11:06:25 +0200 Subject: [PATCH 8/8] Changed port --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index d48ba02..dcdc5a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ async fn main() { .route("/print", get(print)) .layer(Extension(printer)); - let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); + let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); axum::serve(listener, app).await.unwrap(); }