Typstify /image endpoint

This commit is contained in:
Joscha 2025-03-01 18:04:22 +01:00
parent 98071dfe32
commit fa43074f3d
11 changed files with 179 additions and 189 deletions

View file

@ -0,0 +1,6 @@
{
"seamless": true,
"feed": true,
"bright": true,
"algo": "floyd-steinberg"
}

View file

@ -1,4 +1,19 @@
#import "lib.typ";
#show: it => lib.init(it)
#lib.dither("image.png")
#let data = json("data.json")
#let dithered = lib.dither(
"image.png",
bright: data.bright,
algorithm: data.algo,
)
#if data.seamless {
set page(margin: 0pt)
dithered
if data.feed { lib.feed }
} else {
dithered
if data.feed { lib.feed }
}

View file

@ -1,24 +1,78 @@
use std::io::Cursor;
use anyhow::Context;
use image::{ImageFormat, RgbaImage};
use showbits_typst::Typst;
use axum::{
extract::{Multipart, State},
http::StatusCode,
response::{IntoResponse, Redirect, Response},
};
use image::ImageFormat;
use serde::Serialize;
pub struct Image {
pub image: RgbaImage,
use crate::{
drawer::{Command, NewTypstDrawing},
server::{Server, somehow, statuscode::status_code},
};
#[derive(Serialize)]
pub struct Data {
pub seamless: bool,
pub feed: bool,
pub bright: bool,
pub algo: String,
}
impl Image {
pub fn into_typst(self) -> anyhow::Result<Typst> {
let mut bytes: Vec<u8> = Vec::new();
self.image
.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Png)
.context("failed to encode image as png")?;
pub async fn post(server: State<Server>, mut multipart: Multipart) -> somehow::Result<Response> {
let mut image = None;
let mut data = Data {
seamless: false,
feed: true,
bright: true,
algo: "floyd-steinberg".to_string(),
};
let typst = super::typst_with_lib()
.with_file("/image.png", bytes)
.with_main_file(include_str!("main.typ"));
Ok(typst)
while let Some(field) = multipart.next_field().await? {
match field.name() {
Some("image") => {
let data = field.bytes().await?;
let decoded = image::load_from_memory(&data)?.into_rgba8();
image = Some(decoded);
}
Some("seamless") => {
data.seamless = !field.text().await?.is_empty();
}
Some("feed") => {
data.feed = !field.text().await?.is_empty();
}
Some("bright") => {
data.bright = !field.text().await?.is_empty();
}
Some("algo") => {
data.algo = field.text().await?;
}
_ => {}
}
}
let Some(image) = image else {
return Ok(status_code(StatusCode::UNPROCESSABLE_ENTITY));
};
let mut bytes: Vec<u8> = 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(Redirect::to("image").into_response())
}

View file

@ -26,18 +26,32 @@
#import plugin("plugin.wasm") as p
#let _length_to_bytes(len) = {
let len = len.pt()
let n = if len > 10000 { -1 } else { int(len) }
n.to-bytes(size: 8)
#let _number_to_bytes(n) = int(n).to-bytes(size: 8)
#let _bool_to_bytes(b) = _number_to_bytes(if b { 1 } else { 0 })
#let _str_to_bytes(s) = {
bytes(s)
}
#let dither(path) = layout(size => {
let bytes = read(path, encoding: none)
#let _length_to_bytes(l) = {
let l = l.pt()
let n = if l > 10000 { -1 } else { int(l) }
_number_to_bytes(n)
}
#let dither(
path,
bright: true,
algorithm: "floyd-steinberg",
) = layout(size => {
let data = read(path, encoding: none)
let dithered = p.dither(
bytes,
data,
_length_to_bytes(size.width),
_length_to_bytes(size.height),
_bool_to_bytes(bright),
_str_to_bytes(algorithm),
)
image(dithered)
})