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;
|
||||
|
||||
pub mod egg;
|
||||
pub mod image;
|
||||
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) = {
|
||||
set page(
|
||||
width: 384pt,
|
||||
width: width,
|
||||
height: auto,
|
||||
margin: (x: 0pt, y: 4pt),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ mod backlog;
|
|||
mod calendar;
|
||||
mod cells;
|
||||
mod chat_message;
|
||||
mod egg;
|
||||
mod new_typst;
|
||||
mod photo;
|
||||
mod tictactoe;
|
||||
|
|
@ -15,8 +14,8 @@ use crate::persistent_printer::PersistentPrinter;
|
|||
|
||||
pub use self::{
|
||||
backlog::BacklogDrawing, calendar::CalendarDrawing, cells::CellsDrawing,
|
||||
chat_message::ChatMessageDrawing, egg::EggDrawing, new_typst::NewTypstDrawing,
|
||||
photo::PhotoDrawing, tictactoe::TicTacToeDrawing, typst::TypstDrawing,
|
||||
chat_message::ChatMessageDrawing, new_typst::NewTypstDrawing, photo::PhotoDrawing,
|
||||
tictactoe::TicTacToeDrawing, typst::TypstDrawing,
|
||||
};
|
||||
|
||||
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::{
|
||||
documents,
|
||||
drawer::{
|
||||
CalendarDrawing, CellsDrawing, ChatMessageDrawing, Command, EggDrawing, PhotoDrawing,
|
||||
TicTacToeDrawing, TypstDrawing,
|
||||
CalendarDrawing, CellsDrawing, ChatMessageDrawing, Command, PhotoDrawing, TicTacToeDrawing,
|
||||
TypstDrawing,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ pub async fn run(tx: mpsc::Sender<Command>, addr: String) -> anyhow::Result<()>
|
|||
.route("/calendar", post(post_calendar))
|
||||
.route("/cells", post(post_cells))
|
||||
.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(
|
||||
"/image",
|
||||
post(documents::image::post).fallback(get_static_file),
|
||||
|
|
@ -109,13 +109,6 @@ async fn post_chat_message(server: State<Server>, request: Form<PostChatMessageF
|
|||
.await;
|
||||
}
|
||||
|
||||
// /egg
|
||||
|
||||
async fn post_egg(server: State<Server>) -> impl IntoResponse {
|
||||
let _ = server.tx.send(Command::draw(EggDrawing)).await;
|
||||
Redirect::to("egg")
|
||||
}
|
||||
|
||||
// /photo
|
||||
|
||||
async fn post_photo(server: State<Server>, mut multipart: Multipart) -> somehow::Result<Response> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue