···11+[package]
22+name = "problem_generator"
33+version = "0.1.0"
44+edition = "2024"
55+66+[dependencies]
77+axum = "0.8.9"
88+genpdf = { version = "0.2.0", features = ["hyphenation"] }
99+hyphenation = { version = "0.8.4", features = ["embed_en-us"] }
1010+tokio = { version = "1.52.1", features = ["rt-multi-thread"] }
+35
README.md
···11+# Problem set generator
22+33+API to generate random problem sets by subject.
44+WIP for now.
55+66+77+## Format
88+99+GET request that will return a `application/pdf`, with generated questions, and page of answers.
1010+1111+Path would be `https://$url/<Subject>/<Theme>/<Option<Pages>>`
1212+1313+- `Subject` being Math or Physics,
1414+- `Theme` breakdown by subject, representing a single chapter in Mechanics by taylor or
1515+Multivariable Calculus by Stewart.
1616+- `Pages` optional how many pages you would like, default being 1.
1717+- On error, a string with api documentation would be returned? (openapi generated docs would be nice)
1818+1919+## Goals
2020+- [ ] Create handler function to extract problem request parameters
2121+- [ ] Write a question bank by chapter (separate text files)
2222+- [ ] Write question parser, with number generator for questions ( variables encoded in question text with `{{ }}` potentially?)
2323+- [ ] Port axum to run on [shuttle.dev](https://www.shuttle.dev/)
2424+- [ ] Write a front-end page with a basic form that would open a new tab with the pdf requested on [my website](https://tangled.org/sevenpigeons.ca/sevenpigeons.ca)
2525+2626+## Fonts
2727+2828+I am not sure about distributing Roboto font for now, please add
2929+3030+- `Roboto-Bold.ttf`
3131+- `Roboto-BoldItalic.ttf`
3232+- `Roboto-Italic.ttf`
3333+- `Roboto-Regular.ttf`
3434+3535+in the `fonts` folder, you get get it from [google fonts](fonts.google.com/specimen/Roboto)
+86
src/main.rs
···11+use genpdf::{elements::{self, Paragraph}, fonts::FontData, style};
22+use hyphenation::{Load, Standard};
33+44+55+use axum::{Router, extract::{Path, State}, http::{HeaderMap, header}, response::IntoResponse, routing::get};
66+77+#[derive(Clone)]
88+pub struct GenpdfState {
99+ pub font_family:genpdf::fonts::FontFamily<FontData>,
1010+ pub hyphenator: Standard
1111+}
1212+1313+1414+async fn send_pdf(
1515+ State(state):State<GenpdfState>,
1616+ key: Option<Path<i32>>)
1717+-> impl IntoResponse {
1818+1919+2020+ let len = match key {
2121+ Some(Path(a)) => a,
2222+ None => 1,
2323+ };
2424+2525+ // this block should be really moved somewhere in a function
2626+ let mut doc = genpdf::Document::new(state.font_family);
2727+ doc.set_title("Test document title");
2828+ let mut decorator = genpdf::SimplePageDecorator::new();
2929+ decorator.set_margins(10);
3030+ doc.set_page_decorator(decorator);
3131+ let mut title = Paragraph::default();
3232+ title.push_styled("title", style::Effect::Bold);
3333+ title.set_alignment(genpdf::Alignment::Center);
3434+ doc.push(title);
3535+3636+3737+3838+ doc.push(elements::Break::new(5));
3939+ doc.set_hyphenator(state.hyphenator);
4040+4141+ for i in 0..len {
4242+ doc.push(
4343+ genpdf::elements::Paragraph::new(
4444+ format!("this is line {}", i)
4545+ ));
4646+4747+4848+ }
4949+5050+ let mut headers = HeaderMap::new();
5151+ headers.insert(header::CONTENT_TYPE, "application/pdf".parse().unwrap());
5252+5353+5454+5555+ let mut w: Vec<u8> = vec![];
5656+ let _ = doc.render(&mut w).expect("error during pdf rendering");
5757+ (headers,w.clone())
5858+}
5959+6060+6161+#[tokio::main]
6262+async fn main() {
6363+6464+ let en_us = Standard::from_embedded(hyphenation::Language::EnglishUS).unwrap();
6565+ let font_family = match genpdf::fonts::from_files("./fonts", "Roboto", None) {
6666+ Ok(a) => a,
6767+ Err(err) => panic!("errpr {err}")
6868+ };
6969+7070+ let genpdf_state = GenpdfState {
7171+ font_family:font_family,
7272+ hyphenator:en_us
7373+ };
7474+7575+ let routes = Router::new()
7676+ .route("/{key}", get(send_pdf))
7777+ .route("/", get(send_pdf))
7878+ .with_state(genpdf_state);
7979+8080+8181+8282+ let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
8383+ axum::serve(listener,routes).await.unwrap();
8484+8585+8686+}