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) -> Vec { 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>) -> 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 { Router::new().route("/print", get(index)) }