Philipp Hofer 8c0746e585
Some checks failed
CI/CD Pipeline / test (push) Successful in 18m56s
CI/CD Pipeline / deploy (push) Has been cancelled
add station pdf generation; Fixes #26
2025-04-21 17:49:49 +02:00

107 lines
2.4 KiB
Rust

use crate::{admin::station::typst::TypstWrapperWorld, url, AppState, Station};
use axum::{
extract::State,
http::{header, StatusCode},
response::IntoResponse,
routing::get,
Router,
};
use sqlx::SqlitePool;
use std::{fmt::Write, sync::Arc};
use typst_pdf::PdfOptions;
pub(crate) async fn station_pdf(stations: Vec<Station>) -> Vec<u8> {
let mut content = String::from(
r#"
#import "@preview/cades:0.3.0": qr-code
#set page(paper: "a4")
#set page(margin: 0pt)
#let card_width = 105mm // A4 width (210mm) divided by 2
#let card_height = 74.25mm // A4 height (297mm) divided by 4
// Custom function to create a card with title and QR code for a URL
#let create_url_card(title, url) = {
box(
width: card_width,
height: card_height,
stroke: (
paint: black,
dash: "loosely-dashed",
thickness: 0.5pt
),
[
#align(center + horizon)[
#text(weight: "bold", size: 14pt)[Station #title]
#qr-code(url, width: 4cm)
#text(size: 8pt)[#link(url)]
]
]
)
}
// Function to create a grid of cards
#let create_card_grid(cards) = {
grid(
columns: 2,
rows: 4,
gutter: 0pt,
..cards
)
}
#let cards = (
"#,
);
for station in stations {
if !station.crewless() {
let name = station.name;
let link = format!("{}/s/{}/{}", url(), station.id, station.pw);
write!(content, "create_url_card(\"Station {name}\", \"{link}\"),").unwrap();
}
}
write!(
content,
r#")
#create_card_grid(cards)"#
)
.unwrap();
// Create world with content.
let world = TypstWrapperWorld::new("../".to_owned(), content);
// Render document
let document = typst::compile(&world)
.output
.expect("Error compiling typst");
// Output to pdf
typst_pdf::pdf(&document, &PdfOptions::default()).expect("Error exporting PDF")
}
async fn index(State(db): State<Arc<SqlitePool>>) -> impl IntoResponse {
let stations = Station::all(&db).await;
let pdf = station_pdf(stations).await;
(
StatusCode::OK,
[
(header::CONTENT_TYPE, "application/pdf"),
(
header::CONTENT_DISPOSITION,
"attachment; filename=\"stations.pdf\"",
),
],
pdf,
)
.into_response()
}
pub(super) fn routes() -> Router<AppState> {
Router::new().route("/print", get(index))
}