Implement Tree printing

This commit is contained in:
Joscha 2024-03-08 13:37:38 +01:00
parent 9a25856548
commit c8c0759015
6 changed files with 122 additions and 33 deletions

2
Cargo.lock generated
View file

@ -1174,9 +1174,11 @@ dependencies = [
"axum",
"clap",
"escpos",
"image",
"palette",
"serde",
"showbits-common",
"taffy",
"tokio",
]

View file

@ -12,6 +12,11 @@ image = "0.24.9"
palette = "0.7.5"
showbits-common.path = "./showbits-common"
[workspace.dependencies.taffy]
version = "0.4.0"
default-features = false
features = ["std", "taffy_tree", "flexbox", "grid", "block_layout"]
[workspace.lints]
rust.unsafe_code = "forbid"
rust.future_incompatible = "warn"

View file

@ -8,11 +8,7 @@ anyhow.workspace = true
cosmic-text = "0.11.2"
image.workspace = true
palette.workspace = true
[dependencies.taffy]
version = "0.4.0"
default-features = false
features = ["std", "taffy_tree", "flexbox", "grid", "block_layout"]
taffy.workspace = true
[lints]
workspace = true

View file

@ -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]

View file

@ -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(())

View file

@ -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
}
}