diff --git a/lib.typ b/lib.typ index d387bf6..747d1df 100644 --- a/lib.typ +++ b/lib.typ @@ -51,7 +51,8 @@ } ) -// Displays a list of sub-questions and the associated points +// Displays a list of sub-questions and the associated points. +// Updates the exercise's points automatically. // items: array of (int, expression), where // - int is the number of points this item is worth, // - expression is the item to typeset. @@ -63,11 +64,13 @@ //)) #let exercise_items(items) = block[ #let c = counter("exercise_items") + #let exercise_points = 0 #c.update(0) #table( columns: (1fr, auto), stroke: none, ..for (points, statement) in items { + exercise_points += points let pt if points <= 1 { pt = get_L("point_short") @@ -79,35 +82,54 @@ rect(radius:1cm, inset: (x:5pt, y:3pt), stroke: gray+0.5pt)[#points #pt], ) } - )] + ) + #context { + state("exercise-registry").update( arr => { + if (arr.len() == 0) { + arr + } else { + let new_arr = arr.slice(0, arr.len()-1) + new_arr.push((arr.last().at(0), arr.last().at(1), exercise_points)) + new_arr + } + }) + } +] // 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) = { + // state("exercise_points").update(points) let exercise_counter = counter("exercise") + exercise_counter.step() context { - let exercise_registry = state("exercise-registry").get() - exercise_counter.step() + let current_exercise = exercise_counter.get().at(0) + let exercise_registry = state("exercise-registry") + let final_points = 0 + if (exercise_registry.final().len() >= current_exercise) { + final_points = exercise_registry.final().at(exercise_counter.get().at(0)-1).at(2) + } let exercise_label = exercise_counter.display() - // exercise_registry.update(entries => entries + ((exercise_label, title, points),)) - } + 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)*]], + [#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(final_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_title: [%COURSE_TITLE_FIRST_LINE% \ %COURSE_TITLE_SECOND_LINE%], + course_short_title: "%COURSE_SHORT_TITLE%", + date: "%DATE%", + + institution: smallcaps("NAWI Graz"), course_number: "%COURSE_NUMBER%", duration_minutes: "%DURATION%", @@ -142,10 +164,12 @@ if language == "en" { student_number: "Student number", trainer_name: "Trainer", exercise: "Exercise", + exercise_short: "Ex.", point: "point", points: "points", point_short: "pt", points_short: "pts", + points_obtained: "Points", total_points: "Total", minutes: "minutes", ) @@ -157,10 +181,12 @@ if language == "en" { student_number: "Matrikelnr.", trainer_name: "Übungsleiter", exercise: "Beispiel", + exercise_short: "Bsp.", point: "Punkt", points: "Punkte", point_short: "P.", points_short: "P.", + points_obtained: "Erreichte Punkte", total_points: "Gesamt", minutes: "Minuten", ) @@ -169,42 +195,67 @@ if language == "en" { state("translations").update(translations) // State and counter for exercises -context { let exercise_registry = state("exercise-registry", ()) } +state("exercise-registry").update(()) // 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 exercise_registry = state("exercise-registry") + let entries = 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*] - ) - ] + + let columns = entries.map((i) => 1fr) + columns.push(1fr) + [== #get_L("points_obtained")] + set text(size: 14pt) + block(radius: 0.3cm, stroke: none, fill: lightgray, inset: (x:0pt, y:4pt), + table( + columns: columns, + stroke: none, + fill: none, + gutter: 0pt, + inset: (x, y) => { + let top = 8pt + if y == 0 { top = 2pt } + if x == 0 { + (left:4pt, right:0pt, top: top, bottom: 0pt) + } else if x == entries.len() { + (left:4pt, right:4pt, top: top, bottom: 0pt) + } else { + (left:2pt, right:0pt, top: top, bottom: 0pt) + } + }, + align: (x, y) => { if (y == 1) { right+horizon } else { center+horizon }}, + ..for entry in entries { + ( + [#get_L("exercise_short", cap: true) #entry.at(0)], + ) + }, + [*#get_L("total_points")*], + ..for entry in entries { + ( + rect(radius:0.2cm, fill: white, width: 100%, inset: 8pt, outset: 0pt, stroke:none)[/#entry.at(2)], + ) + }, + rect(radius:0.2cm, fill: white, width: 100%, inset: 8pt, outset: 0pt, stroke:black)[#strong([/#total_points])], + ) +) } // Header and footer set page( + header-ascent: 20%, header: [ - #header_left - #h(1fr) - _#(header_right)_ + #set text(size: 8pt) + #table( + stroke: none, + gutter: 4pt, + inset: 0pt, + align: (x, y) => { if x < 2 { left+bottom } else { right }}, + columns: (auto, 1fr, auto), + [#institution], [], [#course_short_title \ _#(date)_], + ) ], footer: [#align(right)[#context(counter(page).display("1 / 1", both:true))]], ) @@ -214,23 +265,24 @@ set page( v(1fr) align(center)[= #title] v(0.5cm) -align(center)[== #subtitle] +align(center)[== #course_title] 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), +block(radius: 0.2cm, fill: lightgray, inset: 3pt, clip: true, table( + fill: lightgray, + stroke: none, columns: (auto, 1fr), - inset: 10pt, + gutter: 3pt, + inset: (left:7pt, right:0pt, y:0pt), align: horizon, - [#get_L("student_name")], [], - [#get_L("student_number")], [], - [#get_L("trainer_name")], [], // comment this line if unneeded -) + [#get_L("student_name")], rect(fill: white, width: 100%, radius: 0.2cm), + [#get_L("student_number")], rect(fill: white, width: 100%, radius: 0.2cm), + [#get_L("trainer_name")], rect(fill: white, width: 100%, radius: 0.2cm), +)) v(0.5cm) @@ -238,21 +290,25 @@ if (instructions != none) { [ #instructions ] } else { if language == "de" [ +- _Lesen Sie die ganze Prüfungsaufgabe durch_. - 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. +- Viel Erfolg! ] if language == "en" [ +- _Before starting, read the whole exam._ - 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. +- Good luck! ] } v(1fr) -// exercise_overview_table() +exercise_overview_table() v(1fr)