1#![doc = include_str!("../README.md")]
2
3use std::{
4 fs::File,
5 io::{self, Write},
6};
7
8const BIT_IMAGE: &[u8] = &[0x1B, 0x2A];
10const TOTAL_CUT: &[u8] = &[0x1B, 0x69];
11const PARTIAL_CUT: &[u8] = &[0x1B, 0x6D];
12
13pub enum BitImageMode {
15 Dots8SingleDensity,
17 Dots8DoubleDensity,
19 Dots24SingleDensity,
21 Dots24DoubleDensity,
23}
24
25pub enum CutType {
27 TotalCut,
29 PartialCut,
31}
32
33pub struct CustomPrinter {
60 file: File,
61 cmd: Vec<u8>,
62}
63
64impl CustomPrinter {
65 pub fn new(dev: &str) -> Result<Self, io::Error> {
77 let file = File::options().read(true).write(true).open(dev)?;
78 Ok(Self {
79 file,
80 cmd: Vec::new(),
81 })
82 }
83
84 pub(crate) fn convert_bitmap_to_bitimage(
85 width: usize,
86 height: usize,
87 bitmap: &[u8],
88 mode: &BitImageMode,
89 ) -> Vec<u8> {
90 let bank = match mode {
92 BitImageMode::Dots8SingleDensity | BitImageMode::Dots8DoubleDensity => 8,
93 BitImageMode::Dots24SingleDensity | BitImageMode::Dots24DoubleDensity => 24,
94 };
95 let banks = (height + bank - 1) / bank;
97 let size = banks * (bank / 8) * width;
99 let mut bitimage = vec![0; size];
100 let step = width / 8;
102
103 for i in 0..banks {
104 for j in 0..width {
105 for k in 0..bank {
106 let src = i * step * bank + k * step + j / 8;
107 let dst = i * width * (bank / 8) + j * (bank / 8) + k / 8;
108 if src < bitmap.len() && bitmap[src] & (0x80 >> (j % 8)) != 0 {
109 bitimage[dst] |= 0x80 >> (k % 8);
110 }
111 }
112 }
113 }
114
115 bitimage
116 }
117
118 pub fn bit_image(&mut self, path: &str, mode: BitImageMode) -> Result<&mut Self, io::Error> {
136 let img = image::open(path)
138 .map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?
139 .grayscale();
140
141 let width = img.width() as usize;
142 let height = img.height() as usize;
143
144 let mut bitmap: Vec<u8> = vec![0; img.as_bytes().len() / 8];
146 for (i, byte) in img.as_bytes().iter().enumerate() {
147 if *byte == 0x00 {
149 bitmap[i / 8] |= 0x80 >> (i % 8);
150 }
151 }
152
153 let bitimage = Self::convert_bitmap_to_bitimage(width, height, &bitmap, &mode);
163
164 let (m, k) = match mode {
165 BitImageMode::Dots8SingleDensity => (0x00, width),
166 BitImageMode::Dots8DoubleDensity => (0x01, width),
167 BitImageMode::Dots24SingleDensity => (0x20, width * 3),
168 BitImageMode::Dots24DoubleDensity => (0x21, width * 3),
169 };
170
171 for i in 0..bitimage.len() / k {
172 self.cmd.extend_from_slice(BIT_IMAGE);
173 self.cmd
174 .extend_from_slice(&[m, (width % 256) as u8, (width / 256) as u8]);
175 self.cmd.extend_from_slice(&bitimage[i * k..(i + 1) * k]);
176 }
181
182 Ok(self)
183 }
184
185 pub fn cut_paper(&mut self, cut_type: CutType) -> &mut Self {
195 match cut_type {
196 CutType::TotalCut => {
197 self.cmd.extend_from_slice(TOTAL_CUT);
198 }
199 CutType::PartialCut => {
200 self.cmd.extend_from_slice(PARTIAL_CUT);
201 }
202 }
203
204 self
205 }
206
207 pub fn run(&mut self) -> Result<&mut Self, io::Error> {
230 self.file.write_all(&self.cmd)?;
231
232 self.cmd.clear();
233 Ok(self)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 const THERMAL_WIDTH: usize = 384;
242 const THERMAL_HEIGHT: usize = 288;
243 const THERMAL_TXT: &str = include_str!("../tests/data/thermal.txt");
244 const THERMAL_8DOTS: &[u8] = include_bytes!("../tests/data/thermal.b8");
245 const THERMAL_24DOTS: &[u8] = include_bytes!("../tests/data/thermal.b24");
246 const THERMAL_PNG_PATH: &str = "tests/data/Thermal_Test_Image.png";
247 const DEV_NULL: &str = "/dev/null";
248
249 #[test]
250 fn test_cut_paper() {
251 let mut printer = CustomPrinter::new(DEV_NULL).unwrap();
252 assert_eq!(printer.cut_paper(CutType::TotalCut).cmd, TOTAL_CUT);
253
254 let mut printer = CustomPrinter::new(DEV_NULL).unwrap();
255 assert_eq!(printer.cut_paper(CutType::PartialCut).cmd, PARTIAL_CUT);
256 }
257
258 #[test]
259 #[ignore]
260 fn helper_prepare_bitimage() {
261 let converter = |text: &str, inverted: bool, output: &mut File, bank: usize| {
262 let lines: Vec<&str> = text.trim().split('\n').collect();
263 let width = lines[0].len();
264 let banks = (lines.len() + (bank - 1)) / bank;
265
266 for i in 0..banks {
267 for j in 0..width {
268 let mut byte: u8 = 0;
269 for k in 0..bank {
270 let line_no = i * bank + k;
271 if line_no < lines.len() {
273 let b = lines[line_no].chars().nth(j).unwrap();
274 if inverted {
275 if b == '0' {
276 byte |= 0x80 >> (k % 8);
277 }
278 } else {
279 if b == '1' {
280 byte |= 0x80 >> (k % 8);
281 }
282 }
283 }
284 if k % 8 == 7 {
285 output.write(&[byte]).ok();
286 byte = 0;
287 }
288 }
289 }
290 }
291 };
292
293 let mut output = File::options()
294 .create(true)
295 .write(true)
296 .truncate(true)
297 .open("tests/data/thermal.b8")
298 .unwrap();
299 converter(THERMAL_TXT, true, &mut output, 8);
300
301 let mut output = File::options()
302 .create(true)
303 .write(true)
304 .truncate(true)
305 .open("tests/data/thermal.b24")
306 .unwrap();
307 converter(THERMAL_TXT, true, &mut output, 24);
308 }
309
310 fn convert_text_to_bitmap(text: &str, inverted: bool) -> Vec<u8> {
311 let mut data = Vec::new();
312
313 text.trim().split('\n').for_each(|line| {
314 if line.len() % 8 != 0 {
315 eprintln!("Length of each line of text must be dividable by 8");
316 return;
317 }
318 for i in (0..line.len()).step_by(8) {
319 if let Ok(byte) = u8::from_str_radix(&line[i..i + 8], 2) {
320 data.extend_from_slice(&[if inverted { !byte } else { byte }]);
321 } else {
322 eprintln!("text contains characters neither 0 nor 1");
323 return;
324 }
325 }
326 });
327
328 data
329 }
330
331 #[test]
332 fn test_convert_bitmap_to_bitimage_8dots() {
333 let bitmap = convert_text_to_bitmap(THERMAL_TXT, true);
334 assert_eq!(
335 &CustomPrinter::convert_bitmap_to_bitimage(
336 THERMAL_WIDTH,
337 THERMAL_HEIGHT,
338 &bitmap,
339 &BitImageMode::Dots8SingleDensity
340 ),
341 THERMAL_8DOTS
342 );
343 assert_eq!(
344 &CustomPrinter::convert_bitmap_to_bitimage(
345 THERMAL_WIDTH,
346 THERMAL_HEIGHT,
347 &bitmap,
348 &BitImageMode::Dots8DoubleDensity
349 ),
350 THERMAL_8DOTS
351 );
352 }
353
354 #[test]
355 fn test_convert_bitmap_to_bitimage_24dots() {
356 let bitmap = convert_text_to_bitmap(THERMAL_TXT, true);
357 assert_eq!(
358 &CustomPrinter::convert_bitmap_to_bitimage(
359 THERMAL_WIDTH,
360 THERMAL_HEIGHT,
361 &bitmap,
362 &BitImageMode::Dots24SingleDensity
363 ),
364 THERMAL_24DOTS
365 );
366 assert_eq!(
367 &CustomPrinter::convert_bitmap_to_bitimage(
368 THERMAL_WIDTH,
369 THERMAL_HEIGHT,
370 &bitmap,
371 &BitImageMode::Dots24DoubleDensity
372 ),
373 THERMAL_24DOTS
374 );
375 }
376
377 #[test]
378 fn test_bit_image() {
379 let mut printer = CustomPrinter::new(DEV_NULL).unwrap();
380 printer
381 .bit_image(THERMAL_PNG_PATH, BitImageMode::Dots8SingleDensity)
382 .unwrap();
383
384 let mut printer = CustomPrinter::new(DEV_NULL).unwrap();
385 printer
386 .bit_image(THERMAL_PNG_PATH, BitImageMode::Dots8DoubleDensity)
387 .unwrap();
388
389 let mut printer = CustomPrinter::new(DEV_NULL).unwrap();
390 printer
391 .bit_image(THERMAL_PNG_PATH, BitImageMode::Dots24SingleDensity)
392 .unwrap();
393
394 let mut printer = CustomPrinter::new(DEV_NULL).unwrap();
395 printer
396 .bit_image(THERMAL_PNG_PATH, BitImageMode::Dots24DoubleDensity)
397 .unwrap();
398 }
399
400 #[test]
401 fn test_multiple_run() {}
402}