import
This commit is contained in:
commit
c995bfa0c4
2 changed files with 270 additions and 0 deletions
262
lib.typ
Normal file
262
lib.typ
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* Customisation
|
||||
*/
|
||||
|
||||
#let lightgray = rgb("#eeeeee")
|
||||
|
||||
// Default font (embedded in Typst)
|
||||
#let font-text = "libertinus serif"
|
||||
|
||||
#let mtext = text.with(font: font-text)
|
||||
|
||||
/*
|
||||
* Macros
|
||||
*/
|
||||
|
||||
// Frame for table, no vertical lines, top and bottom are bold
|
||||
// s: stroke, to specify e.g. the width and color of strokes
|
||||
#let frame(s) = (x, y) => (
|
||||
left: none,
|
||||
right: none,
|
||||
top: if y > 0 { s } else { 2*s },
|
||||
bottom: 2*s,
|
||||
)
|
||||
|
||||
#let capitalize(string) = {
|
||||
return string.replace(
|
||||
regex("[A-Za-z]+('[A-Za-z]+)?"),
|
||||
word => upper(word.text.first()) + lower(word.text.slice(1)),
|
||||
)
|
||||
}
|
||||
|
||||
#let get_L(k, cap:false) = context {
|
||||
let translations = state("translations").get()
|
||||
let term = translations.at(k, default:k)
|
||||
if cap {
|
||||
return capitalize(term)
|
||||
} else {
|
||||
return term
|
||||
}
|
||||
}
|
||||
|
||||
// Formats number of points, "1 Punkt", "2 Punkte", etc.
|
||||
// p: int
|
||||
#let format_points = (p) => (
|
||||
context {
|
||||
if p <= 1 {
|
||||
[#p #get_L("point")]
|
||||
} else {
|
||||
[#p #get_L("points")]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Displays a list of sub-questions and the associated points
|
||||
// items: array of (int, expression), where
|
||||
// - int is the number of points this item is worth,
|
||||
// - expression is the item to typeset.
|
||||
// example:
|
||||
// #exercise_items((
|
||||
// (1, [Untersuchen Sie, ob $f$ injektiv ist.]),
|
||||
// (1, [Untersuchen Sie, ob $f$ surjektiv ist.]),
|
||||
// (1, [Geben Sie im Falle ihrer Existenz die Umkehrfunktion $f^(−1)$ an.])
|
||||
//))
|
||||
#let exercise_items(items) = block[
|
||||
#let c = counter("exercise_items")
|
||||
#c.update(0)
|
||||
#table(
|
||||
columns: (1fr, auto),
|
||||
stroke: none,
|
||||
..for (points, statement) in items {
|
||||
let pt
|
||||
if points <= 1 {
|
||||
pt = get_L("point_short")
|
||||
} else {
|
||||
pt = get_L("points_short")
|
||||
}
|
||||
(
|
||||
[#c.step()#v(4pt) *#context c.display("a)")* #statement],
|
||||
rect(radius:1cm, inset: (x:5pt, y:3pt), stroke: gray+0.5pt)[#points #pt],
|
||||
)
|
||||
}
|
||||
)]
|
||||
|
||||
// Displays a header for the exercise, ex. number is incremented automatically
|
||||
// Used to compute the overview table (see below)
|
||||
// title: string, title of the exercise
|
||||
// points: int, the number of points the exercise is worth
|
||||
#let exercise_header(title, points) = {
|
||||
let exercise_counter = counter("exercise")
|
||||
context {
|
||||
let exercise_registry = state("exercise-registry").get()
|
||||
exercise_counter.step()
|
||||
let exercise_label = exercise_counter.display()
|
||||
// exercise_registry.update(entries => entries + ((exercise_label, title, points),))
|
||||
}
|
||||
block(radius: 1cm, stroke: none, fill: lightgray, inset: 1pt,
|
||||
table(
|
||||
columns: (auto, 1fr, auto),
|
||||
align: (left, center, right),
|
||||
stroke: none,
|
||||
[#rect(radius:1cm, outset: 4pt, inset: (x:2pt, y:0pt), fill: white)[*#get_L("exercise") #context{exercise_counter.display()}*]], [#title], [#rect(radius:1cm, outset: 4pt, inset: (x:2pt, y:0pt), fill: white)[*#format_points(points)*]],
|
||||
)
|
||||
)}
|
||||
|
||||
#let exam(
|
||||
title: "%KLAUSUR or EXAM%",
|
||||
|
||||
subtitle: [%COURSE_TITLE_FIRST_LINE% \ %COURSE_TITLE_SECOND_LINE%],
|
||||
header_left: "%COURSE_SHORT_TITLE%",
|
||||
header_right: "%DATE%",
|
||||
|
||||
course_number: "%COURSE_NUMBER%",
|
||||
duration_minutes: "%DURATION%",
|
||||
|
||||
instructions: none,
|
||||
|
||||
language: "de", // or "en"
|
||||
font-size: 12pt,
|
||||
paper-size: "a4",
|
||||
|
||||
body
|
||||
) = {
|
||||
|
||||
/*
|
||||
* Setup
|
||||
*/
|
||||
|
||||
// Paper and text size
|
||||
set page(paper: paper-size)
|
||||
set text(size: font-size, lang: language, hyphenate: true)
|
||||
|
||||
/*
|
||||
* Localization
|
||||
*/
|
||||
|
||||
let translations = none
|
||||
if language == "en" {
|
||||
translations = (
|
||||
course_number: "Course number",
|
||||
exam_duration: "Exam duration",
|
||||
student_name: "Name",
|
||||
student_number: "Student number",
|
||||
trainer_name: "Trainer",
|
||||
exercise: "Exercise",
|
||||
point: "point",
|
||||
points: "points",
|
||||
point_short: "pt",
|
||||
points_short: "pts",
|
||||
total_points: "Total",
|
||||
minutes: "minutes",
|
||||
)
|
||||
} else if language == "de" {
|
||||
translations = (
|
||||
course_number: "LV-Nummer",
|
||||
exam_duration: "Arbeitszeit",
|
||||
student_name: "Name",
|
||||
student_number: "Matrikelnr.",
|
||||
trainer_name: "Übungsleiter",
|
||||
exercise: "Beispiel",
|
||||
point: "Punkt",
|
||||
points: "Punkte",
|
||||
point_short: "P.",
|
||||
points_short: "P.",
|
||||
total_points: "Gesamt",
|
||||
minutes: "Minuten",
|
||||
)
|
||||
}
|
||||
|
||||
state("translations").update(translations)
|
||||
|
||||
// State and counter for exercises
|
||||
context { let exercise_registry = state("exercise-registry", ()) }
|
||||
|
||||
// Displays an overview of the exam as a table, with
|
||||
// a row per exercise, the corresponding points and space to write the mark.
|
||||
// The
|
||||
let exercise_overview_table() = context {
|
||||
let entries = state("exercise-registry").final()
|
||||
let total_points = entries.fold(0, (sum, entry) => sum + entry.at(2))
|
||||
align(center)[
|
||||
#set text(size: 14pt)
|
||||
#table(
|
||||
columns: (auto, auto),
|
||||
stroke: frame(0.5pt),
|
||||
fill: none,
|
||||
inset: 10pt,
|
||||
align: horizon,
|
||||
table.header[#get_L("exercise")][#get_L("points", cap: true)],
|
||||
table.hline(stroke: 1pt),
|
||||
..for entry in entries {
|
||||
(
|
||||
[#entry.at(0)],
|
||||
[#h(2cm) / #entry.at(2)],
|
||||
)
|
||||
},
|
||||
table.hline(stroke: 1pt),
|
||||
[*#get_L("total_points")*], [#h(2.2cm) / *#total_points*]
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
// Header and footer
|
||||
set page(
|
||||
header: [
|
||||
#header_left
|
||||
#h(1fr)
|
||||
_#(header_right)_
|
||||
],
|
||||
footer: [#align(right)[#context(counter(page).display("1 / 1", both:true))]],
|
||||
)
|
||||
|
||||
// Title page
|
||||
|
||||
v(1fr)
|
||||
align(center)[= #title]
|
||||
v(0.5cm)
|
||||
align(center)[== #subtitle]
|
||||
v(1fr)
|
||||
|
||||
[*#get_L("course_number")*: #course_number* \ #get_L("exam_duration"): #duration_minutes #get_L("minutes")*]
|
||||
|
||||
v(0.5cm)
|
||||
|
||||
table(
|
||||
fill: (lightgray, none),
|
||||
stroke: frame(0.5pt),
|
||||
columns: (auto, 1fr),
|
||||
inset: 10pt,
|
||||
align: horizon,
|
||||
[#get_L("student_name")], [],
|
||||
[#get_L("student_number")], [],
|
||||
[#get_L("trainer_name")], [], // comment this line if unneeded
|
||||
)
|
||||
|
||||
v(0.5cm)
|
||||
|
||||
if (instructions != none) {
|
||||
[ #instructions ]
|
||||
} else {
|
||||
if language == "de" [
|
||||
- Es sind keine Unterlagen und auch keine Taschenrechner erlaubt.
|
||||
- Alle Rechenschritte (inklusive Zwischenresultate und Lösungswege) sind anzugeben und alle Antworten genau zu begründen bzw. zu beweisen!
|
||||
- Schreiben Sie auf jedes _lose_ Blatt Ihren Namen und Ihre Matrikelnummer!
|
||||
- Nicht mit roter Farbe schreiben.
|
||||
]
|
||||
if language == "en" [
|
||||
- Course material and calculators are not allowed.
|
||||
- Write down all the steps, including intermediate results. All answers must be substantiated!
|
||||
- Do not write in red.
|
||||
]
|
||||
}
|
||||
|
||||
v(1fr)
|
||||
|
||||
// exercise_overview_table()
|
||||
|
||||
v(1fr)
|
||||
|
||||
pagebreak()
|
||||
|
||||
body
|
||||
}
|
||||
8
typst.toml
Normal file
8
typst.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "exam"
|
||||
version = "0.1.0"
|
||||
entrypoint = "lib.typ"
|
||||
authors = ["Gaspard Jankowiak"]
|
||||
license = "MIT"
|
||||
description = "Exams for NAWI Graz."
|
||||
compiler = "0.14.0"
|
||||
Loading…
Add table
Add a link
Reference in a new issue