Add xkcd document
This commit is contained in:
parent
f8076e6b66
commit
618f298cc6
13 changed files with 590 additions and 13 deletions
|
|
@ -8,6 +8,7 @@ pub mod image;
|
|||
pub mod sunrise;
|
||||
pub mod text;
|
||||
pub mod tictactoe;
|
||||
pub mod xkcd;
|
||||
|
||||
fn typst_with_lib() -> Typst {
|
||||
Typst::new().with_file("/lib/main.typ", include_str!("documents/lib/main.typ"))
|
||||
|
|
|
|||
6
showbits-thermal-printer/src/documents/xkcd/data.json
Normal file
6
showbits-thermal-printer/src/documents/xkcd/data.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"number": 3064,
|
||||
"title": "Lungfish",
|
||||
"alt": "I know having so many base pairs makes rebasing complicated, but you're in Bilateria, so shouldn't you at LEAST be better at using git head?",
|
||||
"feed": false
|
||||
}
|
||||
BIN
showbits-thermal-printer/src/documents/xkcd/image.png
Normal file
BIN
showbits-thermal-printer/src/documents/xkcd/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
1
showbits-thermal-printer/src/documents/xkcd/lib
Symbolic link
1
showbits-thermal-printer/src/documents/xkcd/lib
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../lib
|
||||
26
showbits-thermal-printer/src/documents/xkcd/main.typ
Normal file
26
showbits-thermal-printer/src/documents/xkcd/main.typ
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#import "lib/main.typ" as lib;
|
||||
#show: it => lib.init(it)
|
||||
|
||||
#let data = json("data.json")
|
||||
|
||||
#align(center)[
|
||||
xkcd #data.number
|
||||
#v(-12pt)
|
||||
#text(size: 32pt, data.title)
|
||||
]
|
||||
|
||||
// If the image is an odd number of pixels wide, we need to add an extra row of
|
||||
// pixels (in this case, on the right) to ensure that the image pixels fall on
|
||||
// screen pixels.
|
||||
#context {
|
||||
let img = image("image.png")
|
||||
let width = measure(img).width
|
||||
let additional = 2pt * calc.fract(width.pt() / 2)
|
||||
align(center, stack(dir: ltr, img, h(additional)))
|
||||
}
|
||||
|
||||
#align(center, data.alt)
|
||||
|
||||
#if data.feed {
|
||||
lib.feed
|
||||
}
|
||||
76
showbits-thermal-printer/src/documents/xkcd/mod.rs
Normal file
76
showbits-thermal-printer/src/documents/xkcd/mod.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use std::io::Cursor;
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
Form,
|
||||
extract::State,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use image::ImageFormat;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::server::{Server, somehow};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ComicInfo {
|
||||
num: u32,
|
||||
title: String,
|
||||
alt: String,
|
||||
img: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Data {
|
||||
number: u32,
|
||||
title: String,
|
||||
alt: String,
|
||||
feed: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FormData {
|
||||
pub number: Option<u32>,
|
||||
pub feed: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn post(server: State<Server>, Form(form): Form<FormData>) -> somehow::Result<Response> {
|
||||
let client = reqwest::Client::builder()
|
||||
.user_agent(crate::USER_AGENT)
|
||||
.build()?;
|
||||
|
||||
let url = match form.number {
|
||||
None => "https://xkcd.com/info.0.json".to_string(),
|
||||
Some(number) => format!("https://xkcd.com/{number}/info.0.json"),
|
||||
};
|
||||
|
||||
let info = client.get(url).send().await?.json::<ComicInfo>().await?;
|
||||
|
||||
let image_data = client.get(&info.img).send().await?.bytes().await?;
|
||||
let image = image::load_from_memory(&image_data)?.into_rgba8();
|
||||
|
||||
let max_width = Some(384);
|
||||
let max_height = Some(1024);
|
||||
let image = super::image::dither(image, max_width, max_height, false, "stucki")
|
||||
.map_err(somehow::Error)?;
|
||||
|
||||
let data = Data {
|
||||
number: info.num,
|
||||
title: info.title,
|
||||
alt: info.alt,
|
||||
feed: form.feed.unwrap_or(true),
|
||||
};
|
||||
|
||||
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"));
|
||||
|
||||
server.print_typst(typst).await?;
|
||||
Ok(().into_response())
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ use tokio::{runtime::Runtime, sync::mpsc};
|
|||
|
||||
use self::{drawer::Drawer, persistent_printer::PersistentPrinter};
|
||||
|
||||
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
/// Path to the queue directory.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ pub async fn run(tx: mpsc::Sender<Command>, addr: String) -> anyhow::Result<()>
|
|||
.route("/api/sunrise", post(documents::sunrise::post))
|
||||
.route("/api/text", post(documents::text::post))
|
||||
.route("/api/tictactoe", post(documents::tictactoe::post))
|
||||
.route("/api/xkcd", post(documents::xkcd::post))
|
||||
// Rest
|
||||
.layer(DefaultBodyLimit::max(32 * 1024 * 1024)) // 32 MiB
|
||||
.with_state(Server { tx });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue