commit 84e661d5f2d12623c0363bfef71f0cedd6addcee from: Benjamins Stürz date: Sat Feb 08 23:31:51 2025 UTC ppa6-print: add text mode commit - fb76226583ecb290335a0c2e5232863fe86380a1 commit + 84e661d5f2d12623c0363bfef71f0cedd6addcee blob - eb5a316cbd195d26e3f768c7dd8e1b47299e17f8 blob + d44cd35e09d0dd78e69d976ea385e7b155604d56 --- .gitignore +++ .gitignore @@ -1 +1,2 @@ target +bin blob - 3d82aa5c9abad8251bb0daaa9b1cb8273efb13a9 blob + b9b187c2ce5a5fa21504decfef318f828a0a13f5 --- Cargo.lock +++ Cargo.lock @@ -2881,6 +2881,7 @@ dependencies = [ "anyhow", "clap", "clap-num", + "cosmic-text", "image 0.25.5", "itertools 0.14.0", "open", blob - /dev/null blob + e72473a7ff7b5236806e22059fb0f9eba7d22472 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,18 @@ +PREFIX = /usr/local + +all: bin/ppa6-print + +clean: + rm -rf target + rm -f ppa6-print + +install: bin/ppa6-print + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f bin/* ${DESTDIR}${PREFIX}/bin/ + +bin/ppa6-print: + mkdir -p bin + cargo build --release -p ppa6-print + cp -f target/release/ppa6-print bin/ + + blob - 7fa890b15bdbf7c9f04e2db37647a965d25cc974 blob + 867716caa94b36a848fa267a7d41752729fd7012 --- ppa6-print/Cargo.toml +++ ppa6-print/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1.0.95" +cosmic-text = "0.12.1" clap = { version = "4.5.28", features = ["derive"] } clap-num = "1.2.0" image = "0.25.5" blob - 417239028a4d7b12ec9a4cfd5949a935bd2c05e4 blob + b5e082f9c9533ab45bbb3e11f6770346ae63aa96 --- ppa6-print/src/main.rs +++ ppa6-print/src/main.rs @@ -2,7 +2,8 @@ use std::path::{Path, PathBuf}; use anyhow::Result; use clap::Parser; use clap_num::maybe_hex; -use image::{imageops::{dither, ColorMap, FilterType}, DynamicImage, GrayImage, ImageFormat, ImageReader, Luma}; +use cosmic_text::{Attrs, Buffer, Color, FontSystem, Metrics, Shaping, SwashCache}; +use image::{imageops::{dither, ColorMap, FilterType}, DynamicImage, GrayImage, ImageFormat, ImageReader, Luma, RgbImage}; use ppa6::{usb_context, Document, Printer}; use itertools::Itertools; @@ -31,9 +32,25 @@ struct Cli { #[arg(short, long, default_value_t = 0)] rotate: usize, - /// Threshold for dithering - #[arg(short, long, default_value_t = 0x80, value_parser = maybe_hex::)] + /// Threshold for dithering. + #[arg(short = 'T', long, default_value_t = 0x80, value_parser = maybe_hex::)] threshold: u8, + + /// Treat `file` as a text file. + #[arg(short, long)] + text: bool, + + /// Font size for `--text`. Anything below 12 starts to be difficult to read. + #[arg(short = 'S', long, default_value_t = 18.0)] + size: f32, + + /// Font weight for `--text`. Good numbers are 600 and 800. + #[arg(short, long, default_value_t = 800)] + weight: u16, + + /// Line Height Factor. This gets multiplied with the font size to get the line height. + #[arg(short, long, default_value_t = 1.0)] + line_height: f32, } struct BlackWhiteMap(u8); @@ -92,9 +109,7 @@ fn rotate(img: GrayImage, deg: usize) -> GrayImage { } } -fn main() -> Result<()> { - let cli = Cli::parse(); - +fn picture(cli: &Cli) -> Result { let img = ImageReader::open(&cli.file)? .with_guessed_format()? .decode()? @@ -102,7 +117,67 @@ fn main() -> Result<()> { let mut img = resize(rotate(img, cli.rotate)); assert_eq!(img.width(), 384); dither(&mut img, &BlackWhiteMap(cli.threshold)); + Ok(img) +} +// TODO: parse ANSI escape sequences +fn text(cli: &Cli) -> Result { + let text = std::fs::read_to_string(&cli.file)?; + + let mut font_system = FontSystem::new(); + let mut cache = SwashCache::new(); + let metrics = Metrics::new(cli.size, cli.size * cli.line_height); + let mut buffer = Buffer::new(&mut font_system, metrics); + let mut buffer = buffer.borrow_with(&mut font_system); + buffer.set_size(Some(340.0), None); + let mut attrs = Attrs::new(); + attrs.weight.0 = cli.weight; + + buffer.set_text(&text, attrs, Shaping::Advanced); + buffer.shape_until_scroll(true); + + let mut pixels = Vec::new(); + let mut height = 0; + + buffer.draw(&mut cache, Color::rgb(0xff, 0, 0), |x, y, w, h, color| { + let a = color.a(); + if x < 0 || y < 0 || x > 384 || w != 1 || h != 1 || a == 0 { + return; + } + + let x = x as usize; + let y = y as usize; + + if y >= height { + height = y + 1; + pixels.resize(3 * 384 * height, 0xff); + } + + let scale = |c: u8| { + let c = c as f32 / 255.0; + let a = a as f32 / 255.0; + let c = (c * a) + (1.0 * (1.0 - a)); + (c * 255.0).clamp(0.0, 255.0) as u8 + }; + pixels[(y * 384 + x) * 3 + 0] = scale(color.r()); + pixels[(y * 384 + x) * 3 + 1] = scale(color.g()); + pixels[(y * 384 + x) * 3 + 2] = scale(color.b()); + }); + + + let img = DynamicImage::ImageRgb8(RgbImage::from_vec(384, height as u32, pixels).unwrap()); + Ok(img.into_luma8()) +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + let img = if cli.text { + text(&cli) + } else { + picture(&cli) + }?; + if cli.show { let temppath = Path::new("/tmp/ppa6-preview.png"); img.save_with_format(temppath, ImageFormat::Png)?;