diff --git a/Cargo.lock b/Cargo.lock index ee10ebb..8245df9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,6 +122,7 @@ dependencies = [ "matchit", "memchr", "mime", + "multer", "percent-encoding", "pin-project-lite", "rustversion", @@ -805,6 +806,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "multer" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "num-traits" version = "0.2.18" diff --git a/showbits-thermal-printer/Cargo.toml b/showbits-thermal-printer/Cargo.toml index 64d7a80..fb0e753 100644 --- a/showbits-thermal-printer/Cargo.toml +++ b/showbits-thermal-printer/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true [dependencies] anyhow.workspace = true -axum = "0.7.4" +axum = { version = "0.7.4", features = ["multipart"] } clap = { version = "4.5.1", features = ["derive", "deprecated"] } cosmic-text.workspace = true escpos = { version = "0.7.2", features = ["full"] } diff --git a/showbits-thermal-printer/src/drawer.rs b/showbits-thermal-printer/src/drawer.rs index e58372c..7d1023b 100644 --- a/showbits-thermal-printer/src/drawer.rs +++ b/showbits-thermal-printer/src/drawer.rs @@ -1,6 +1,7 @@ +use image::RgbaImage; use showbits_common::{ color::{BLACK, WHITE}, - widgets::{Block, FontStuff, HasFontStuff, Text}, + widgets::{Block, FontStuff, HasFontStuff, Image, Text}, Node, Tree, WidgetExt, }; use taffy::{ @@ -16,6 +17,7 @@ pub enum Command { Rip, Test, Text(String), + Image(RgbaImage), ChatMessage { username: String, content: String }, } @@ -62,6 +64,7 @@ impl Drawer { Command::Rip => self.printer.rip()?, Command::Test => self.on_test()?, Command::Text(text) => self.on_text(text)?, + Command::Image(image) => self.on_image(image)?, Command::ChatMessage { username, content } => { self.on_chat_message(username, content)? } @@ -120,6 +123,24 @@ impl Drawer { Ok(()) } + fn on_image(&mut self, image: RgbaImage) -> anyhow::Result<()> { + let mut tree = Tree::::new(WHITE); + + let image = Image::new(image) + .with_dither_palette(&[BLACK, WHITE]) + .node() + .register(&mut tree)?; + + let root = Node::empty() + .with_size_width(percent(1.0)) + .with_padding_bottom(length(32.0)) + .and_child(image) + .register(&mut tree)?; + + self.printer.print_tree(&mut tree, &mut self.ctx, root)?; + Ok(()) + } + fn on_chat_message(&mut self, username: String, content: String) -> anyhow::Result<()> { let mut tree = Tree::::new(WHITE); diff --git a/showbits-thermal-printer/src/server.rs b/showbits-thermal-printer/src/server.rs index a4b5816..826755b 100644 --- a/showbits-thermal-printer/src/server.rs +++ b/showbits-thermal-printer/src/server.rs @@ -1,4 +1,9 @@ -use axum::{extract::State, routing::post, Form, Router}; +use axum::{ + extract::{Multipart, State}, + http::StatusCode, + routing::post, + Form, Router, +}; use serde::Deserialize; use tokio::{net::TcpListener, sync::mpsc}; @@ -15,6 +20,7 @@ pub async fn run(tx: mpsc::Sender, addr: String) -> anyhow::Result<()> .route("/test", post(post_test)) .route("/rip", post(post_rip)) .route("/text", post(post_text)) + .route("/image", post(post_image)) .route("/chat_message", post(post_chat_message)) .with_state(Server { tx }); @@ -44,6 +50,37 @@ async fn post_text(server: State, request: Form) { let _ = server.tx.send(Command::Text(request.0.text)).await; } +async fn post_image(server: State, mut multipart: Multipart) -> Result<(), StatusCode> { + let mut image = None; + + while let Some(field) = multipart + .next_field() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + { + let name = field.name().ok_or(StatusCode::INTERNAL_SERVER_ERROR)?; + if name == "image" { + let data = field + .bytes() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let decoded = image::load_from_memory(&data) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + .into_rgba8(); + + image = Some(decoded); + } + } + + if let Some(image) = image { + let _ = server.tx.send(Command::Image(image)).await; + Ok(()) + } else { + Err(StatusCode::INTERNAL_SERVER_ERROR) + } +} + #[derive(Deserialize)] struct PostChatMessageForm { username: String,