Implement Tree printing
This commit is contained in:
parent
9a25856548
commit
c8c0759015
6 changed files with 122 additions and 33 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1174,9 +1174,11 @@ dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"clap",
|
"clap",
|
||||||
"escpos",
|
"escpos",
|
||||||
|
"image",
|
||||||
"palette",
|
"palette",
|
||||||
"serde",
|
"serde",
|
||||||
"showbits-common",
|
"showbits-common",
|
||||||
|
"taffy",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ image = "0.24.9"
|
||||||
palette = "0.7.5"
|
palette = "0.7.5"
|
||||||
showbits-common.path = "./showbits-common"
|
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]
|
[workspace.lints]
|
||||||
rust.unsafe_code = "forbid"
|
rust.unsafe_code = "forbid"
|
||||||
rust.future_incompatible = "warn"
|
rust.future_incompatible = "warn"
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,7 @@ anyhow.workspace = true
|
||||||
cosmic-text = "0.11.2"
|
cosmic-text = "0.11.2"
|
||||||
image.workspace = true
|
image.workspace = true
|
||||||
palette.workspace = true
|
palette.workspace = true
|
||||||
|
taffy.workspace = true
|
||||||
[dependencies.taffy]
|
|
||||||
version = "0.4.0"
|
|
||||||
default-features = false
|
|
||||||
features = ["std", "taffy_tree", "flexbox", "grid", "block_layout"]
|
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ anyhow.workspace = true
|
||||||
axum = "0.7.4"
|
axum = "0.7.4"
|
||||||
clap = { version = "4.5.1", features = ["derive", "deprecated"] }
|
clap = { version = "4.5.1", features = ["derive", "deprecated"] }
|
||||||
escpos = { version = "0.7.2", features = ["full"] }
|
escpos = { version = "0.7.2", features = ["full"] }
|
||||||
|
image.workspace = true
|
||||||
palette.workspace = true
|
palette.workspace = true
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
showbits-common.workspace = true
|
showbits-common.workspace = true
|
||||||
|
taffy.workspace = true
|
||||||
tokio = { version = "1.36.0", features = ["full"] }
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|
|
||||||
|
|
@ -42,31 +42,6 @@ impl Drawer {
|
||||||
Ok(())
|
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<()> {
|
// fn on_rip(&mut self) -> anyhow::Result<()> {
|
||||||
// self.printer.init()?.feeds(6)?.print()?;
|
// self.printer.init()?.feeds(6)?.print()?;
|
||||||
// Ok(())
|
// Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use escpos::{
|
use escpos::{
|
||||||
driver::FileDriver,
|
driver::FileDriver,
|
||||||
printer::Printer as EPrinter,
|
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 {
|
pub struct Printer {
|
||||||
printer: Option<EPrinter<FileDriver>>,
|
printer: Option<EPrinter<FileDriver>>,
|
||||||
|
|
@ -16,9 +19,18 @@ impl Printer {
|
||||||
/// code can't be changed.
|
/// code can't be changed.
|
||||||
///
|
///
|
||||||
/// https://en.wikipedia.org/wiki/Code_page_437
|
/// 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;
|
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(
|
pub fn new(
|
||||||
printer_path: Option<PathBuf>,
|
printer_path: Option<PathBuf>,
|
||||||
export_path: Option<PathBuf>,
|
export_path: Option<PathBuf>,
|
||||||
|
|
@ -44,4 +56,101 @@ impl Printer {
|
||||||
|
|
||||||
Ok(())
|
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