diff --git a/showbits-thermal-printer/src/documents.rs b/showbits-thermal-printer/src/documents.rs index ffe9d12..5cd0acb 100644 --- a/showbits-thermal-printer/src/documents.rs +++ b/showbits-thermal-printer/src/documents.rs @@ -1,5 +1,6 @@ use showbits_typst::Typst; +pub mod cells; pub mod egg; pub mod image; pub mod text; diff --git a/showbits-thermal-printer/src/documents/cells/data.json b/showbits-thermal-printer/src/documents/cells/data.json new file mode 100644 index 0000000..986ac9d --- /dev/null +++ b/showbits-thermal-printer/src/documents/cells/data.json @@ -0,0 +1,3 @@ +{ + "feed": true +} diff --git a/showbits-thermal-printer/src/documents/cells/image.png b/showbits-thermal-printer/src/documents/cells/image.png new file mode 100644 index 0000000..f9c7c47 Binary files /dev/null and b/showbits-thermal-printer/src/documents/cells/image.png differ diff --git a/showbits-thermal-printer/src/documents/cells/lib b/showbits-thermal-printer/src/documents/cells/lib new file mode 120000 index 0000000..dc598c5 --- /dev/null +++ b/showbits-thermal-printer/src/documents/cells/lib @@ -0,0 +1 @@ +../lib \ No newline at end of file diff --git a/showbits-thermal-printer/src/documents/cells/main.typ b/showbits-thermal-printer/src/documents/cells/main.typ new file mode 100644 index 0000000..36b8649 --- /dev/null +++ b/showbits-thermal-printer/src/documents/cells/main.typ @@ -0,0 +1,10 @@ +#import "lib/main.typ" as lib; +#show: it => lib.init(it) + +#let data = json("data.json") + +#image("image.png") + +#if data.feed { + lib.feed +} diff --git a/showbits-thermal-printer/src/documents/cells/mod.rs b/showbits-thermal-printer/src/documents/cells/mod.rs new file mode 100644 index 0000000..c08fc89 --- /dev/null +++ b/showbits-thermal-printer/src/documents/cells/mod.rs @@ -0,0 +1,110 @@ +use std::io::Cursor; + +use anyhow::Context; +use axum::{Form, extract::State}; +use image::{ImageFormat, Rgba, RgbaImage, imageops}; +use serde::{Deserialize, Serialize}; + +use crate::{ + drawer::{Command, NewTypstDrawing}, + printer::Printer, + server::{Server, somehow}, +}; + +const BLACK: Rgba = Rgba([0, 0, 0, 255]); +const WHITE: Rgba = Rgba([255, 255, 255, 255]); + +fn b2c(bool: bool) -> Rgba { + match bool { + true => BLACK, + false => WHITE, + } +} + +fn c2b(color: Rgba) -> bool { + color == BLACK +} + +fn neighbors_at(image: &RgbaImage, x: u32, y: u32) -> [bool; 3] { + let left = x + .checked_sub(1) + .map(|x| *image.get_pixel(x, y)) + .unwrap_or(WHITE); + + let mid = *image.get_pixel(x, y); + + let right = image.get_pixel_checked(x + 1, y).copied().unwrap_or(WHITE); + + [c2b(left), c2b(mid), c2b(right)] +} + +fn apply_rule(rule: u8, neighbors: [bool; 3]) -> bool { + let [left, mid, right] = neighbors.map(|n| n as u8); + let index = (left << 2) | (mid << 1) | right; + rule & (1 << index) != 0 +} + +#[derive(Serialize)] +struct Data { + feed: bool, +} + +#[derive(Deserialize)] +pub struct FormData { + pub rule: Option, + pub rows: Option, + pub scale: Option, + pub feed: Option, +} + +pub async fn post(server: State, Form(form): Form) -> somehow::Result<()> { + let data = Data { + feed: form.feed.unwrap_or(true), + }; + + let rule = form.rule.unwrap_or_else(rand::random); + let scale = form.scale.unwrap_or(4).clamp(1, 16); + let rows = form.rows.unwrap_or(128 * 4 / scale).clamp(1, 1024 / scale); + let cols = Printer::WIDTH / scale; + + let mut image: image::ImageBuffer, Vec> = RgbaImage::new(cols, rows); + + // Initialize first line randomly + for x in 0..image.width() { + image.put_pixel(x, 0, b2c(rand::random())); + } + + // Calculate next rows + for y in 1..image.height() { + for x in 0..image.width() { + let neighbors = neighbors_at(&image, x, y - 1); + let state = apply_rule(rule, neighbors); + image.put_pixel(x, y, b2c(state)); + } + } + + let image = imageops::resize( + &image, + image.width() * scale, + image.height() * scale, + imageops::Nearest, + ); + + let mut bytes: Vec = Vec::new(); + image + .write_to(&mut Cursor::new(&mut bytes), ImageFormat::Png) + .context("failed to encode image as png") + .map_err(somehow::Error)?; + + let typst = super::typst_with_lib() + .with_json("/data.json", &data) + .with_file("/image.png", bytes) + .with_main_file(include_str!("main.typ")); + + let _ = server + .tx + .send(Command::draw(NewTypstDrawing::new(typst))) + .await; + + Ok(()) +} diff --git a/showbits-thermal-printer/src/drawer.rs b/showbits-thermal-printer/src/drawer.rs index 030f1ad..71efde0 100644 --- a/showbits-thermal-printer/src/drawer.rs +++ b/showbits-thermal-printer/src/drawer.rs @@ -1,6 +1,5 @@ mod backlog; mod calendar; -mod cells; mod chat_message; mod new_typst; mod photo; @@ -13,9 +12,9 @@ use tokio::sync::mpsc; use crate::persistent_printer::PersistentPrinter; pub use self::{ - backlog::BacklogDrawing, calendar::CalendarDrawing, cells::CellsDrawing, - chat_message::ChatMessageDrawing, new_typst::NewTypstDrawing, photo::PhotoDrawing, - tictactoe::TicTacToeDrawing, typst::TypstDrawing, + backlog::BacklogDrawing, calendar::CalendarDrawing, chat_message::ChatMessageDrawing, + new_typst::NewTypstDrawing, photo::PhotoDrawing, tictactoe::TicTacToeDrawing, + typst::TypstDrawing, }; pub const FEED: f32 = 96.0; diff --git a/showbits-thermal-printer/src/drawer/cells.rs b/showbits-thermal-printer/src/drawer/cells.rs deleted file mode 100644 index 1f5363a..0000000 --- a/showbits-thermal-printer/src/drawer/cells.rs +++ /dev/null @@ -1,93 +0,0 @@ -use image::{ - Rgba, RgbaImage, - imageops::{self, FilterType}, -}; -use showbits_common::{Node, Tree, WidgetExt, color, widgets::Image}; -use taffy::{AlignItems, Display, FlexDirection, prelude::length, style_helpers::percent}; - -use crate::{persistent_printer::PersistentPrinter, printer::Printer}; - -use super::{Context, Drawing, FEED}; - -const BLACK: Rgba = Rgba([0, 0, 0, 255]); -const WHITE: Rgba = Rgba([255, 255, 255, 255]); - -fn b2c(bool: bool) -> Rgba { - match bool { - true => BLACK, - false => WHITE, - } -} - -fn c2b(color: Rgba) -> bool { - color == BLACK -} - -fn neighbors_at(image: &RgbaImage, x: u32, y: u32) -> [bool; 3] { - let left = x - .checked_sub(1) - .map(|x| *image.get_pixel(x, y)) - .unwrap_or(WHITE); - - let mid = *image.get_pixel(x, y); - - let right = image.get_pixel_checked(x + 1, y).copied().unwrap_or(WHITE); - - [c2b(left), c2b(mid), c2b(right)] -} - -fn apply_rule(rule: u8, neighbors: [bool; 3]) -> bool { - let [left, mid, right] = neighbors.map(|n| n as u8); - let index = (left << 2) | (mid << 1) | right; - rule & (1 << index) != 0 -} - -pub struct CellsDrawing { - pub rule: u8, - pub rows: u32, - pub scale: u32, -} - -impl Drawing for CellsDrawing { - fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> { - let mut image: image::ImageBuffer, Vec> = - RgbaImage::new(Printer::WIDTH / self.scale, self.rows); - - // Initialize first line randomly - for x in 0..image.width() { - image.put_pixel(x, 0, b2c(rand::random())); - } - - // Calculate next rows - for y in 1..self.rows { - for x in 0..image.width() { - let neighbors = neighbors_at(&image, x, y - 1); - let state = apply_rule(self.rule, neighbors); - image.put_pixel(x, y, b2c(state)); - } - } - - let image = imageops::resize( - &image, - image.width() * self.scale, - image.height() * self.scale, - FilterType::Nearest, - ); - - let mut tree = Tree::::new(color::WHITE); - - let image = Image::new(image).node().register(&mut tree)?; - - let root = Node::empty() - .with_size_width(percent(1.0)) - .with_padding_bottom(length(FEED)) - .with_display(Display::Flex) - .with_flex_direction(FlexDirection::Column) - .with_align_items(Some(AlignItems::Center)) - .and_child(image) - .register(&mut tree)?; - - printer.print_tree(&mut tree, ctx, root)?; - Ok(()) - } -} diff --git a/showbits-thermal-printer/src/server.rs b/showbits-thermal-printer/src/server.rs index d9b0167..da3de38 100644 --- a/showbits-thermal-printer/src/server.rs +++ b/showbits-thermal-printer/src/server.rs @@ -15,8 +15,7 @@ use tokio::{net::TcpListener, sync::mpsc}; use crate::{ documents, drawer::{ - CalendarDrawing, CellsDrawing, ChatMessageDrawing, Command, PhotoDrawing, TicTacToeDrawing, - TypstDrawing, + CalendarDrawing, ChatMessageDrawing, Command, PhotoDrawing, TicTacToeDrawing, TypstDrawing, }, }; @@ -30,7 +29,10 @@ pub struct Server { pub async fn run(tx: mpsc::Sender, addr: String) -> anyhow::Result<()> { let app = Router::new() .route("/calendar", post(post_calendar)) - .route("/cells", post(post_cells)) + .route( + "/cells", + post(documents::cells::post).fallback(get_static_file), + ) .route("/chat_message", post(post_chat_message)) .route("/egg", post(documents::egg::post).fallback(get_static_file)) .route( @@ -71,26 +73,6 @@ async fn post_calendar(server: State, request: Form) { .await; } -// /cells - -#[derive(Deserialize)] -struct PostCellsForm { - rule: u8, - rows: Option, - scale: Option, -} - -async fn post_cells(server: State, request: Form) { - let _ = server - .tx - .send(Command::draw(CellsDrawing { - rule: request.0.rule, - rows: request.0.rows.unwrap_or(32).min(512), - scale: request.0.scale.unwrap_or(4), - })) - .await; -} - // /chat_message #[derive(Deserialize)]