From ca4e807c9ff410693740dc2183d4eb378c38072b Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 1 Mar 2025 21:40:07 +0100 Subject: [PATCH] Typstify /cells endpoint --- showbits-thermal-printer/src/documents.rs | 1 + .../src/documents/cells/data.json | 3 + .../src/documents/cells/image.png | Bin 0 -> 11493 bytes .../src/documents/cells/lib | 1 + .../src/documents/cells/main.typ | 10 ++ .../src/documents/cells/mod.rs | 110 ++++++++++++++++++ showbits-thermal-printer/src/drawer.rs | 7 +- showbits-thermal-printer/src/drawer/cells.rs | 93 --------------- showbits-thermal-printer/src/server.rs | 28 +---- 9 files changed, 133 insertions(+), 120 deletions(-) create mode 100644 showbits-thermal-printer/src/documents/cells/data.json create mode 100644 showbits-thermal-printer/src/documents/cells/image.png create mode 120000 showbits-thermal-printer/src/documents/cells/lib create mode 100644 showbits-thermal-printer/src/documents/cells/main.typ create mode 100644 showbits-thermal-printer/src/documents/cells/mod.rs delete mode 100644 showbits-thermal-printer/src/drawer/cells.rs 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 0000000000000000000000000000000000000000..f9c7c47f78fcc387019045c583ed72bb6af8ca33 GIT binary patch literal 11493 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 literal 0 HcmV?d00001 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)]