229 lines
5.7 KiB
Typst
229 lines
5.7 KiB
Typst
#import "constants.typ": *
|
|
#import "helpers.typ": *
|
|
#import "state.typ": __get, __set
|
|
|
|
// Main resume function
|
|
#let resume(
|
|
name: "YOUR NAME HERE",
|
|
tagline: none,
|
|
paper: "us-letter",
|
|
heading-font: HEADING_FONT,
|
|
body-font: BODY_FONT,
|
|
body-size: BODY_SIZE,
|
|
email: none,
|
|
phone: none,
|
|
linkedin-username: none,
|
|
keywords: "",
|
|
thumbnail: none, // check out https://qrframe.kylezhe.ng/ for QR code generation
|
|
body
|
|
) = {
|
|
// Document setup, mostly metadata in a PDF
|
|
set document(
|
|
title: "Résumé/CV of " + name,
|
|
author: name,
|
|
keywords: keywords,
|
|
)
|
|
|
|
// Page setup, including header and footer
|
|
set page(
|
|
paper: paper,
|
|
margin: PAGE_MARGIN,
|
|
numbering: "1 / 1",
|
|
header: context {
|
|
if here().page() == 1 {
|
|
text(
|
|
size: HEADER_SIZE,
|
|
grid(
|
|
columns: (1fr, auto, auto, auto, auto, auto),
|
|
gutter: 1em,
|
|
align: center+horizon,
|
|
"",
|
|
icon_and_contact("icons/email.svg", link("mailto:" + email, email)),
|
|
sym.divides,
|
|
icon_and_contact("icons/phone.svg", link("tel:" + phone, phone)),
|
|
sym.divides,
|
|
icon_and_contact("icons/linkedin.svg", link("https://www.linkedin.com/in/" + linkedin-username, linkedin-username))
|
|
)
|
|
)
|
|
}
|
|
},
|
|
footer: context {
|
|
let multi_page = counter(page).final().first() > 1
|
|
let date = datetime.today().display("[month repr:long] [day], [year]")
|
|
text(size: FOOTER_SIZE, weight: FOOTER_TEXT_WEIGHT, font: heading-font)[
|
|
#grid(
|
|
columns: (1fr, 1fr, 1fr),
|
|
align: (left, center, right + horizon),
|
|
// left column
|
|
[Last updated: #date],
|
|
// center column
|
|
if multi_page { counter(page).display(both: true, "1 of 1") },
|
|
// right column
|
|
if thumbnail != none {
|
|
move(dy: -0.25in, box(height: 0.5in, thumbnail))
|
|
} else {
|
|
[ ]
|
|
}
|
|
)
|
|
]
|
|
},
|
|
)
|
|
|
|
// Global text settings
|
|
set text(font: body-font, size: body-size, weight: BODY_WEIGHT, fallback: true)
|
|
set par(justify: true)
|
|
|
|
// Heading styles
|
|
set heading(numbering: "I.A.")
|
|
show heading.where(level: 1): it => {
|
|
block(
|
|
above: SECTION_HEADING_SPACE_ABOVE,
|
|
below: SECTION_HEADING_SPACE_BELOW,
|
|
breakable: false,
|
|
[
|
|
#text(size: SECTION_HEADING_SIZE, weight: SECTION_HEADING_WEIGHT, font: heading-font)[
|
|
#context counter(heading).display()
|
|
#upper(it.body)
|
|
]
|
|
#hrule(stroke: SECTION_HEADING_HRULE_STROKE)
|
|
]
|
|
)
|
|
}
|
|
|
|
// Table style
|
|
set table(
|
|
stroke: none,
|
|
fill: (x, y) => {
|
|
if y == 0 {
|
|
TABLE_HEADER_COLOR
|
|
} else if calc.rem(y, 2) == 0 {
|
|
TABLE_ZEBRA_COLOR_0
|
|
} else {
|
|
TABLE_ZEBRA_COLOR_1
|
|
}
|
|
},
|
|
)
|
|
|
|
// Save our settings needed in other functions
|
|
__set("heading-font", heading-font)
|
|
|
|
// Main Headline
|
|
text(size: HEADLINE_NAME_SIZE, weight: HEADLINE_NAME_WEIGHT, font: heading-font)[#smallcaps(name)]
|
|
h(1fr)
|
|
text(size: TAGLINE_SIZE, style: TAGLINE_STYLE, weight: TAGLINE_WEIGHT)[#tagline]
|
|
hrule(stroke: HEADLINE_HRULE_STROKE)
|
|
|
|
// The rest of the content
|
|
body
|
|
}
|
|
|
|
// Alias resume to cv for non-Americans
|
|
#let cv = resume
|
|
|
|
#let company-heading(name, start: none, end: none, icon: none, body) = {
|
|
let icon_spacing = if icon != none { COMPANY_ICON_SPACING } else { 0em }
|
|
block(
|
|
below: COMPANY_BLOCK_BELOW,
|
|
breakable: false,
|
|
|
|
grid(
|
|
columns: (auto, auto, 1fr, auto),
|
|
column-gutter: (icon_spacing, 0.5em),
|
|
row-gutter: 0.5em,
|
|
align: (left + horizon, left + horizon, right + horizon),
|
|
|
|
// first column, optional icon
|
|
if icon != none { box(height: COMPANY_ICON_SIZE, icon) },
|
|
|
|
// second column, company name
|
|
context text(
|
|
weight: COMPANY_NAME_WEIGHT,
|
|
size: COMPANY_NAME_SIZE,
|
|
font: __get("heading-font"),
|
|
name
|
|
),
|
|
|
|
// third column, dots
|
|
if start != none {
|
|
repeat(h(0.3em) + "." + h(0.3em))
|
|
} else {
|
|
[]
|
|
},
|
|
|
|
// fourth column, optional dates
|
|
context text(
|
|
weight: COMPANY_DATE_WEIGHT,
|
|
size: COMPANY_DATE_SIZE,
|
|
font: __get("heading-font"),
|
|
|
|
if start != none {
|
|
[#start -- #end]
|
|
} else {
|
|
[ ]
|
|
}
|
|
),
|
|
|
|
// second row, company content
|
|
grid.cell(colspan: 4, body)
|
|
)
|
|
)
|
|
}
|
|
|
|
// Alias to school heading
|
|
#let school-heading = company-heading
|
|
|
|
#let job-heading(title, location: none, start: none, end: none, comment: none, body) = {
|
|
block(
|
|
above: JOB_BLOCK_ABOVE,
|
|
below: JOB_BLOCK_BELOW,
|
|
breakable: false,
|
|
|
|
// heading
|
|
grid(
|
|
columns: (auto, auto, auto, 1fr, auto),
|
|
gutter: 0.5em,
|
|
align: (left + horizon, right + horizon),
|
|
|
|
// first column, title
|
|
context text(
|
|
style: JOB_NAME_STYLE,
|
|
weight: JOB_NAME_WEIGHT,
|
|
size: JOB_NAME_SIZE,
|
|
font: __get("heading-font"),
|
|
title
|
|
),
|
|
|
|
// second column, job comment ellipsis
|
|
if comment != none { [ ... ] },
|
|
|
|
// third column, job comment ellipsis
|
|
if comment != none { [ #comment ] },
|
|
|
|
// fourth column, hfill
|
|
h(1fr),
|
|
|
|
// fifth column, optional location
|
|
context text(
|
|
style: JOB_LOCATION_STYLE,
|
|
weight: JOB_LOCATION_WEIGHT,
|
|
size: JOB_LOCATION_SIZE,
|
|
font: __get("heading-font"),
|
|
|
|
if location != none and start != none {
|
|
[#location#[;] #h(0.25em)#start#if end != none [ -- #end]]
|
|
} else if location != none {
|
|
location
|
|
} else if start != none {
|
|
[#start#if end != none [ -- #end]]
|
|
}
|
|
),
|
|
|
|
grid.cell(colspan: 5, body)
|
|
)
|
|
)
|
|
}
|
|
|
|
// Alias to degree heading
|
|
#let degree-heading = job-heading
|
|
|