Implement Tree printing
This commit is contained in:
parent
9a25856548
commit
c8c0759015
6 changed files with 122 additions and 33 deletions
|
|
@ -8,9 +8,11 @@ anyhow.workspace = true
|
|||
axum = "0.7.4"
|
||||
clap = { version = "4.5.1", features = ["derive", "deprecated"] }
|
||||
escpos = { version = "0.7.2", features = ["full"] }
|
||||
image.workspace = true
|
||||
palette.workspace = true
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
showbits-common.workspace = true
|
||||
taffy.workspace = true
|
||||
tokio = { version = "1.36.0", features = ["full"] }
|
||||
|
||||
[lints]
|
||||
|
|
|
|||
|
|
@ -42,31 +42,6 @@ impl Drawer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// fn on_test(&mut self) -> anyhow::Result<()> {
|
||||
// self.printer.init()?;
|
||||
|
||||
// let x = 48; // bytes
|
||||
// let y = 48; // dots
|
||||
|
||||
// let m = 0;
|
||||
// let x_l = x as u8;
|
||||
// let x_h = (x >> 8) as u8;
|
||||
// let y_l = y as u8;
|
||||
// let y_h = (y >> 8) as u8;
|
||||
// let mut command = vec![0x1D, b'v', b'0', m, x_l, x_h, y_l, y_h];
|
||||
// for _y in 0..y {
|
||||
// for _x in 0..x {
|
||||
// // command.push((x + y) as u8);
|
||||
// command.push(0b0000_0011);
|
||||
// }
|
||||
// }
|
||||
// self.printer.custom(&command)?;
|
||||
|
||||
// self.printer.print()?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// fn on_rip(&mut self) -> anyhow::Result<()> {
|
||||
// self.printer.init()?.feeds(6)?.print()?;
|
||||
// Ok(())
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use escpos::{
|
||||
driver::FileDriver,
|
||||
printer::Printer as EPrinter,
|
||||
utils::{PageCode, Protocol},
|
||||
utils::{PageCode, Protocol, GS},
|
||||
};
|
||||
use image::{Rgb, RgbImage};
|
||||
use showbits_common::Tree;
|
||||
use taffy::{AvailableSpace, NodeId, Size};
|
||||
|
||||
pub struct Printer {
|
||||
printer: Option<EPrinter<FileDriver>>,
|
||||
|
|
@ -16,9 +19,18 @@ impl Printer {
|
|||
/// code can't be changed.
|
||||
///
|
||||
/// https://en.wikipedia.org/wiki/Code_page_437
|
||||
/// https://www.epson-biz.com/modules/ref_charcode_en/index.php?content_id=10
|
||||
/// https://download4.epson.biz/sec_pubs/pos/reference_en/charcode/page_00.html
|
||||
const PAGE_CODE: PageCode = PageCode::PC437;
|
||||
|
||||
/// Width of the printable area in pixels.
|
||||
// TODO Figure out actual width
|
||||
const WIDTH: u32 = 8 * 32;
|
||||
|
||||
/// Images are printed in chunks because a single print command can only
|
||||
/// print so much data.
|
||||
// TODO Figure out sensible chunk height
|
||||
const CHUNK_HEIGHT: u32 = 42;
|
||||
|
||||
pub fn new(
|
||||
printer_path: Option<PathBuf>,
|
||||
export_path: Option<PathBuf>,
|
||||
|
|
@ -44,4 +56,101 @@ impl Printer {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_tree<C>(
|
||||
&mut self,
|
||||
tree: &mut Tree<C>,
|
||||
ctx: &mut C,
|
||||
root: NodeId,
|
||||
) -> anyhow::Result<()> {
|
||||
let available = Size {
|
||||
width: AvailableSpace::Definite(Self::WIDTH as f32),
|
||||
// TODO Maybe MinContent? If not, why not?
|
||||
height: AvailableSpace::MaxContent,
|
||||
};
|
||||
|
||||
let image = tree.render(ctx, root, available)?;
|
||||
|
||||
if let Some(path) = &self.export_path {
|
||||
image.save(path)?;
|
||||
}
|
||||
|
||||
if let Some(printer) = &mut self.printer {
|
||||
Self::print_image_to_printer(printer, &image)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Uses the obsolete `GS v 0` command to print an image.
|
||||
///
|
||||
/// The image is printed in chunks because the command used has a maximum
|
||||
/// amount of data it can handle. In-between chunks, the paper is not moved,
|
||||
/// meaning that chunks connect to each other seamlessly.
|
||||
///
|
||||
/// https://download4.epson.biz/sec_pubs/pos/reference_en/escpos/gs_lv_0.html
|
||||
fn print_image_to_printer(
|
||||
printer: &mut EPrinter<FileDriver>,
|
||||
image: &RgbImage,
|
||||
) -> anyhow::Result<()> {
|
||||
assert_eq!(Self::WIDTH % 8, 0);
|
||||
assert_eq!(image.width(), Self::WIDTH);
|
||||
|
||||
printer.init()?;
|
||||
|
||||
for y_offset in (0..image.height()).step_by(Self::CHUNK_HEIGHT as usize) {
|
||||
// The command takes the width in bytes (groups of 8 pixels) and the
|
||||
// height in pixels. Both are then split into two bytes and sent.
|
||||
let chunk_width = Self::WIDTH / 8;
|
||||
let chunk_height = Self::CHUNK_HEIGHT.min(image.height() - y_offset);
|
||||
|
||||
let m = 0; // Normal resolution
|
||||
let [_, _, x_h, x_l] = chunk_width.to_be_bytes();
|
||||
let [_, _, y_h, y_l] = chunk_height.to_be_bytes();
|
||||
let mut command = vec![GS, b'v', b'0', m, x_l, x_h, y_l, y_h];
|
||||
|
||||
for y in y_offset..y_offset + chunk_height {
|
||||
for x in (0..Self::WIDTH).step_by(8) {
|
||||
command.push(Self::get_horizontal_byte_starting_at(image, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
printer.custom(&command)?;
|
||||
}
|
||||
|
||||
printer.print()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_horizontal_byte_starting_at(image: &RgbImage, x: u32, y: u32) -> u8 {
|
||||
let p7 = Self::pixel_to_bit(*image.get_pixel(x, y));
|
||||
let p6 = Self::pixel_to_bit(*image.get_pixel(x + 1, y));
|
||||
let p5 = Self::pixel_to_bit(*image.get_pixel(x + 2, y));
|
||||
let p4 = Self::pixel_to_bit(*image.get_pixel(x + 3, y));
|
||||
let p3 = Self::pixel_to_bit(*image.get_pixel(x + 4, y));
|
||||
let p2 = Self::pixel_to_bit(*image.get_pixel(x + 5, y));
|
||||
let p1 = Self::pixel_to_bit(*image.get_pixel(x + 6, y));
|
||||
let p0 = Self::pixel_to_bit(*image.get_pixel(x + 7, y));
|
||||
|
||||
let b7 = if p7 { 0b1000_0000 } else { 0 };
|
||||
let b6 = if p6 { 0b0100_0000 } else { 0 };
|
||||
let b5 = if p5 { 0b0010_0000 } else { 0 };
|
||||
let b4 = if p4 { 0b0001_0000 } else { 0 };
|
||||
let b3 = if p3 { 0b0000_1000 } else { 0 };
|
||||
let b2 = if p2 { 0b0000_0100 } else { 0 };
|
||||
let b1 = if p1 { 0b0000_0010 } else { 0 };
|
||||
let b0 = if p0 { 0b0000_0001 } else { 0 };
|
||||
|
||||
b7 + b6 + b5 + b4 + b3 + b2 + b1 + b0
|
||||
}
|
||||
|
||||
/// Convert pixel to bit, `true` is black and `false` is white.
|
||||
///
|
||||
/// Instead of doing the physically accurate thing, I do what makes the most
|
||||
/// sense visually.
|
||||
fn pixel_to_bit(pixel: Rgb<u8>) -> bool {
|
||||
let [r, g, b] = pixel.0;
|
||||
let sum = (r as u32) + (g as u32) + (b as u32);
|
||||
sum <= 3 * 255 / 2 // true == black
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue