From 4a26c6980b18467987d9be5f38d9fbd283c115bf Mon Sep 17 00:00:00 2001 From: Joel von der Weid Date: Wed, 19 Mar 2025 17:06:54 +0100 Subject: [PATCH] Improve letter typst template --- typst/letter/example.typ | 1 - typst/letter/letter.typ | 107 +++++++++++++++++++++++++++------------ 2 files changed, 76 insertions(+), 32 deletions(-) diff --git a/typst/letter/example.typ b/typst/letter/example.typ index d0e9ba6..ebd1799 100644 --- a/typst/letter/example.typ +++ b/typst/letter/example.typ @@ -4,7 +4,6 @@ lang: "en", name: "Joël von der Weid", subject: "Cover letter for the Software Engineer (Backend) position", - date: "Sézegnin, 16 March 2025", from-address: "Route de Sézegnin 63, CH-1285 Sézegnin", to-details: [ Adaptyv Bio \ diff --git a/typst/letter/letter.typ b/typst/letter/letter.typ index 7456ff7..0f3f83d 100644 --- a/typst/letter/letter.typ +++ b/typst/letter/letter.typ @@ -1,4 +1,9 @@ #let line-stroke = (paint: rgb("#731010"), thickness: 1pt, cap: "round") +#let header-font = ("Nimbus Sans", "Nimbus Sans L") +#let body-font = ("Libertinus Serif") + +#let months-en = ("", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December") +#let months-fr = ("", "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre") #let template( lang: "fr", @@ -9,41 +14,44 @@ from-phone: "+41 79 861 88 08", to-details: none, subject: none, - date: none, - margin: (x: 2.5cm, y: 2cm), + place: "Crissier", + date: datetime.today(), + margin: (top: 1.5cm, left: 2.5cm, right: 2.5cm, bottom: 2cm), + font-size: 11pt, vertical-center-level: 2, + appendices: (), doc ) = { - set page(fill: background, margin: margin) - set text(font: ("Libertinus Serif")) - - let subject = if subject != none { - let head = if lang == "en" { "Subject" } else { "Concerne " } - - [ - #text(weight: "bold", underline(head) + ": " + subject) - #v(0.5cm) + set page(fill: background, margin: margin, paper: "a4", footer: context { + if counter(page).final().first() > 1 [ + #align(right, counter(page).display("1/1", both: true)) ] - } else { - none + }) + + let full-date = { + let date = if date != none { + let months = if lang == "en" { months-en } else { months-fr } + str(date.day()) + " " + months.at(date.month()) + " " + str(date.year()) + } + if place == none { + date + } else if date == none { + place + } else { + let sep = if lang == "en" { ", " } else { ", le " } + place + sep + date + } } - let body = [ - #set text(size: 11pt, weight: "medium") - #show par: set par(spacing: 1.75em, justify: true) - #subject - #doc - ] - let header = { - set text(font: "Nimbus Sans", style: "italic") - set par(leading: 0.9em) + set text(font: header-font, style: "italic") + set par(leading: 0.8em) grid( - inset: (x: 0cm, y: 3mm), + inset: (x: 0cm, y: 2.5mm), columns: (50%, 1fr), grid.hline(start: 1, stroke: line-stroke), [ - #text(size: 25pt, weight: "bold", name) \ + #text(size: 22pt, weight: "bold", name) \ #text(size: 10pt, fill: rgb("444"), from-address) ], align(bottom + end, box( @@ -57,22 +65,61 @@ )), grid.hline(end: 1, stroke: line-stroke), ) - v(2cm) + v(1cm) set par(leading: 0.6em) set text(style: "normal") [ #h(10cm) #box(width: 100% - 10cm, [ #text(size: 10pt, to-details) - #v(1.5cm) - #if date != none { - date + #v(2cm) + #if full-date != none { + text(size: font-size, font: body-font, full-date) } ]) ] v(1cm) } + let subject = if subject != none { + let head = if lang == "en" { "Subject" } else { "Concerne " } + + [ + #text(weight: "bold", underline(head) + ": " + subject) + #v(0.5cm) + ] + } else { + none + } + + let appendices-content = { + if appendices.len() > 0 { + [ + #v(0.5cm) + #if lang == "en" { "Appendices: " } else { "Annexes : " } + #context { + box(baseline: 100% - measure("Appendices").height)[ + #if type(appendices) == array { + for (i, value) in appendices.enumerate() { + "- " + value + linebreak() + } + } else { + "- " + appendices + linebreak() + } + ] + } + ] + } + } + + let body = [ + #set text(font: body-font, size: font-size, weight: "regular") + #show par: set par(spacing: 1.75em, justify: true) + #subject + #doc + #appendices-content + ] + layout(size => context [ #let header-sz = measure(block(width: size.width, header)) #let body-sz = measure(block(width: size.width, body)) @@ -82,17 +129,15 @@ #if overflowing or vertical-center-level == none { header - subject body } else { // If no overflow of the first page, we do a bit of centering magic for style - grid( rows: (auto, 1fr), header, box([ #v(1fr * ratio) - #body + #body #v(vertical-center-level * 1fr) ]), )