Typstify /egg endpoint
This commit is contained in:
parent
6d4db1ca2e
commit
db06addc42
12 changed files with 141 additions and 112 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
use showbits_typst::Typst;
|
use showbits_typst::Typst;
|
||||||
|
|
||||||
|
pub mod egg;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
|
|
|
||||||
9
showbits-thermal-printer/src/documents/egg/data.json
Normal file
9
showbits-thermal-printer/src/documents/egg/data.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"covers": 7,
|
||||||
|
"patterns": 40,
|
||||||
|
"bad_covers": 1,
|
||||||
|
"bad_patterns": 7,
|
||||||
|
"seed": 1,
|
||||||
|
"mode": null,
|
||||||
|
"feed": true
|
||||||
|
}
|
||||||
1
showbits-thermal-printer/src/documents/egg/egg
Symbolic link
1
showbits-thermal-printer/src/documents/egg/egg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../showbits-assets/data/egg
|
||||||
1
showbits-thermal-printer/src/documents/egg/egg_bad
Symbolic link
1
showbits-thermal-printer/src/documents/egg/egg_bad
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../showbits-assets/data/egg_bad
|
||||||
1
showbits-thermal-printer/src/documents/egg/lib.typ
Symbolic link
1
showbits-thermal-printer/src/documents/egg/lib.typ
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../lib.typ
|
||||||
55
showbits-thermal-printer/src/documents/egg/main.typ
Normal file
55
showbits-thermal-printer/src/documents/egg/main.typ
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#import "@preview/oxifmt:0.2.1": strfmt
|
||||||
|
#import "@preview/suiji:0.3.0": *
|
||||||
|
#import "lib.typ";
|
||||||
|
#show: it => lib.init(it)
|
||||||
|
|
||||||
|
#let data = json("data.json")
|
||||||
|
#let rng = gen-rng(data.seed)
|
||||||
|
|
||||||
|
#let file_series(n, fmt) = array.range(n).map(n => strfmt(fmt, n))
|
||||||
|
|
||||||
|
#let good_covers = file_series(data.covers, "egg/cover_{:02}.png")
|
||||||
|
#let good_patterns = file_series(data.patterns, "egg/pattern_{:02}.png")
|
||||||
|
#let bad_covers = file_series(data.bad_covers, "egg_bad/cover_{:02}.png")
|
||||||
|
#let bad_patterns = file_series(data.bad_patterns, "egg_bad/pattern_{:02}.png")
|
||||||
|
|
||||||
|
// Always generate random value to so that egg looks the same whether we chose
|
||||||
|
// the mode directly or randomly.
|
||||||
|
#let (rng, val) = random(rng)
|
||||||
|
|
||||||
|
#let (covers, patterns) = if data.mode == "good" {
|
||||||
|
(good_covers, good_patterns)
|
||||||
|
} else if data.mode == "bad" {
|
||||||
|
(bad_covers, bad_patterns)
|
||||||
|
} else if val < 1 / 8 {
|
||||||
|
(bad_covers, bad_patterns)
|
||||||
|
} else {
|
||||||
|
(good_covers, good_patterns)
|
||||||
|
}
|
||||||
|
|
||||||
|
#context {
|
||||||
|
let (rng, cover) = choice(rng, covers)
|
||||||
|
let cover = image(cover, width: lib.width)
|
||||||
|
let cover_size = measure(cover)
|
||||||
|
|
||||||
|
let pattern_stack_height = 0pt
|
||||||
|
let pattern_stack = while pattern_stack_height < cover_size.height {
|
||||||
|
let pattern = ()
|
||||||
|
(rng, pattern) = choice(rng, patterns)
|
||||||
|
let pattern = image(pattern, width: cover_size.width)
|
||||||
|
pattern_stack_height += measure(pattern).height
|
||||||
|
(pattern,)
|
||||||
|
}
|
||||||
|
|
||||||
|
box(
|
||||||
|
height: cover_size.height,
|
||||||
|
clip: true,
|
||||||
|
stack(dir: ttb, ..pattern_stack),
|
||||||
|
)
|
||||||
|
|
||||||
|
place(top + left, cover)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if data.feed {
|
||||||
|
lib.feed
|
||||||
|
}
|
||||||
64
showbits-thermal-printer/src/documents/egg/mod.rs
Normal file
64
showbits-thermal-printer/src/documents/egg/mod.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
use axum::{Form, extract::State};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
drawer::{Command, NewTypstDrawing},
|
||||||
|
server::Server,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Data {
|
||||||
|
covers: usize,
|
||||||
|
patterns: usize,
|
||||||
|
bad_covers: usize,
|
||||||
|
bad_patterns: usize,
|
||||||
|
seed: i64,
|
||||||
|
mode: Option<String>,
|
||||||
|
feed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct FormData {
|
||||||
|
pub seed: Option<i64>,
|
||||||
|
pub mode: Option<String>,
|
||||||
|
pub feed: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn post(server: State<Server>, Form(form): Form<FormData>) {
|
||||||
|
let seed = form.seed.unwrap_or_else(rand::random);
|
||||||
|
|
||||||
|
let data = Data {
|
||||||
|
covers: showbits_assets::EGG_COVERS.len(),
|
||||||
|
patterns: showbits_assets::EGG_PATTERNS.len(),
|
||||||
|
bad_covers: showbits_assets::EGG_BAD_COVERS.len(),
|
||||||
|
bad_patterns: showbits_assets::EGG_BAD_PATTERNS.len(),
|
||||||
|
seed,
|
||||||
|
mode: form.mode,
|
||||||
|
feed: form.feed.unwrap_or(true),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut typst = super::typst_with_lib()
|
||||||
|
.with_json("/data.json", &data)
|
||||||
|
.with_main_file(include_str!("main.typ"));
|
||||||
|
|
||||||
|
for (i, cover) in showbits_assets::EGG_COVERS.iter().enumerate() {
|
||||||
|
typst.add_file(format!("/egg/cover_{i:02}.png"), *cover);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, pattern) in showbits_assets::EGG_PATTERNS.iter().enumerate() {
|
||||||
|
typst.add_file(format!("/egg/pattern_{i:02}.png"), *pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, cover) in showbits_assets::EGG_BAD_COVERS.iter().enumerate() {
|
||||||
|
typst.add_file(format!("/egg_bad/cover_{i:02}.png"), *cover);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, pattern) in showbits_assets::EGG_BAD_PATTERNS.iter().enumerate() {
|
||||||
|
typst.add_file(format!("/egg_bad/pattern_{i:02}.png"), *pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = server
|
||||||
|
.tx
|
||||||
|
.send(Command::draw(NewTypstDrawing::new(typst)))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
1
showbits-thermal-printer/src/documents/egg/plugin.wasm
Symbolic link
1
showbits-thermal-printer/src/documents/egg/plugin.wasm
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../plugin.wasm
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
#let width = 384pt
|
||||||
|
|
||||||
#let init(it) = {
|
#let init(it) = {
|
||||||
set page(
|
set page(
|
||||||
width: 384pt,
|
width: width,
|
||||||
height: auto,
|
height: auto,
|
||||||
margin: (x: 0pt, y: 4pt),
|
margin: (x: 0pt, y: 4pt),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ mod backlog;
|
||||||
mod calendar;
|
mod calendar;
|
||||||
mod cells;
|
mod cells;
|
||||||
mod chat_message;
|
mod chat_message;
|
||||||
mod egg;
|
|
||||||
mod new_typst;
|
mod new_typst;
|
||||||
mod photo;
|
mod photo;
|
||||||
mod tictactoe;
|
mod tictactoe;
|
||||||
|
|
@ -15,8 +14,8 @@ use crate::persistent_printer::PersistentPrinter;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
backlog::BacklogDrawing, calendar::CalendarDrawing, cells::CellsDrawing,
|
backlog::BacklogDrawing, calendar::CalendarDrawing, cells::CellsDrawing,
|
||||||
chat_message::ChatMessageDrawing, egg::EggDrawing, new_typst::NewTypstDrawing,
|
chat_message::ChatMessageDrawing, new_typst::NewTypstDrawing, photo::PhotoDrawing,
|
||||||
photo::PhotoDrawing, tictactoe::TicTacToeDrawing, typst::TypstDrawing,
|
tictactoe::TicTacToeDrawing, typst::TypstDrawing,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FEED: f32 = 96.0;
|
pub const FEED: f32 = 96.0;
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
use image::{RgbaImage, imageops};
|
|
||||||
use rand::{Rng, seq::IndexedRandom};
|
|
||||||
use showbits_assets::{EGG_BAD_COVERS, EGG_BAD_PATTERNS, EGG_COVERS, EGG_PATTERNS};
|
|
||||||
use showbits_common::{
|
|
||||||
Node, Tree, WidgetExt,
|
|
||||||
color::{self, WHITE},
|
|
||||||
widgets::{Image, Text},
|
|
||||||
};
|
|
||||||
use taffy::{AlignItems, Display, FlexDirection, prelude::length, style_helpers::percent};
|
|
||||||
|
|
||||||
use crate::persistent_printer::PersistentPrinter;
|
|
||||||
|
|
||||||
use super::{Context, Drawing, FEED};
|
|
||||||
|
|
||||||
pub struct EggDrawing;
|
|
||||||
|
|
||||||
fn load_image(bytes: &[u8]) -> RgbaImage {
|
|
||||||
image::load_from_memory(bytes)
|
|
||||||
.expect("malformed image data")
|
|
||||||
.into_rgba8()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drawing for EggDrawing {
|
|
||||||
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
|
|
||||||
let mut rng = rand::rng();
|
|
||||||
|
|
||||||
// Choose which set of egg images to use
|
|
||||||
let bad_egg = rng.random_range(0..8) == 0;
|
|
||||||
let (covers, patterns) = if bad_egg {
|
|
||||||
(EGG_BAD_COVERS, EGG_BAD_PATTERNS)
|
|
||||||
} else {
|
|
||||||
(EGG_COVERS, EGG_PATTERNS)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load images from memory
|
|
||||||
let covers = covers.iter().map(|img| load_image(img)).collect::<Vec<_>>();
|
|
||||||
let patterns = patterns
|
|
||||||
.iter()
|
|
||||||
.map(|img| load_image(img))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Choose a random cover
|
|
||||||
let cover = covers.choose(&mut rng).expect("too few covers");
|
|
||||||
|
|
||||||
// Prepare image of appropriate size
|
|
||||||
let mut image =
|
|
||||||
RgbaImage::from_pixel(cover.width(), cover.height(), color::to_image_color(WHITE));
|
|
||||||
|
|
||||||
// Draw patterns onto egg
|
|
||||||
let mut last_idx = None;
|
|
||||||
let mut y = rng.random_range(-100_i64..0);
|
|
||||||
let height: i64 = image.height().into();
|
|
||||||
while y < height {
|
|
||||||
let idx = loop {
|
|
||||||
let idx = rng.random_range(0..patterns.len());
|
|
||||||
if Some(idx) != last_idx {
|
|
||||||
break idx;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let paint = &patterns[idx];
|
|
||||||
imageops::overlay(&mut image, paint, 0, y);
|
|
||||||
y += <_ as Into<i64>>::into(paint.height());
|
|
||||||
last_idx = Some(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, draw the cover
|
|
||||||
imageops::overlay(&mut image, cover, 0, 0);
|
|
||||||
|
|
||||||
let mut tree = Tree::<Context>::new(WHITE);
|
|
||||||
|
|
||||||
let image = Image::new(image)
|
|
||||||
.with_grow(false)
|
|
||||||
.with_shrink(false)
|
|
||||||
.node()
|
|
||||||
.register(&mut tree)?;
|
|
||||||
|
|
||||||
let text = Text::new()
|
|
||||||
.with_metrics(Text::default_metrics().scale(2.0))
|
|
||||||
.and_plain("Frohe Ostern!")
|
|
||||||
.widget(&mut ctx.font_stuff)
|
|
||||||
.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)
|
|
||||||
.and_child(text)
|
|
||||||
.register(&mut tree)?;
|
|
||||||
|
|
||||||
printer.print_tree(&mut tree, ctx, root)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -15,8 +15,8 @@ use tokio::{net::TcpListener, sync::mpsc};
|
||||||
use crate::{
|
use crate::{
|
||||||
documents,
|
documents,
|
||||||
drawer::{
|
drawer::{
|
||||||
CalendarDrawing, CellsDrawing, ChatMessageDrawing, Command, EggDrawing, PhotoDrawing,
|
CalendarDrawing, CellsDrawing, ChatMessageDrawing, Command, PhotoDrawing, TicTacToeDrawing,
|
||||||
TicTacToeDrawing, TypstDrawing,
|
TypstDrawing,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ pub async fn run(tx: mpsc::Sender<Command>, addr: String) -> anyhow::Result<()>
|
||||||
.route("/calendar", post(post_calendar))
|
.route("/calendar", post(post_calendar))
|
||||||
.route("/cells", post(post_cells))
|
.route("/cells", post(post_cells))
|
||||||
.route("/chat_message", post(post_chat_message))
|
.route("/chat_message", post(post_chat_message))
|
||||||
.route("/egg", post(post_egg).fallback(get_static_file))
|
.route("/egg", post(documents::egg::post).fallback(get_static_file))
|
||||||
.route(
|
.route(
|
||||||
"/image",
|
"/image",
|
||||||
post(documents::image::post).fallback(get_static_file),
|
post(documents::image::post).fallback(get_static_file),
|
||||||
|
|
@ -109,13 +109,6 @@ async fn post_chat_message(server: State<Server>, request: Form<PostChatMessageF
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /egg
|
|
||||||
|
|
||||||
async fn post_egg(server: State<Server>) -> impl IntoResponse {
|
|
||||||
let _ = server.tx.send(Command::draw(EggDrawing)).await;
|
|
||||||
Redirect::to("egg")
|
|
||||||
}
|
|
||||||
|
|
||||||
// /photo
|
// /photo
|
||||||
|
|
||||||
async fn post_photo(server: State<Server>, mut multipart: Multipart) -> somehow::Result<Response> {
|
async fn post_photo(server: State<Server>, mut multipart: Multipart) -> somehow::Result<Response> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue