206 lines
3.8 KiB
Typst
206 lines
3.8 KiB
Typst
|
/*
|
||
|
* Twenty Seconds Curriculum Vitae in Typst
|
||
|
* Author: Xiaoliang Wang
|
||
|
* Date: 2023-08-04
|
||
|
* License: MIT (see included file LICENSE)
|
||
|
*/
|
||
|
|
||
|
#import "@preview/fontawesome:0.2.1": *
|
||
|
|
||
|
#let headercolor = gray
|
||
|
#let pblue = rgb("#0395DE")
|
||
|
#let gray80 = rgb("#333333") // \color{black!80}
|
||
|
#let sidecolor = rgb("#E7E7E7")
|
||
|
#let mainblue = rgb("#0E5484")
|
||
|
#let maingray = rgb("#B9B9B9")
|
||
|
|
||
|
#let fontSize = (
|
||
|
tiny: 5pt,
|
||
|
scriptsize: 7pt,
|
||
|
footnotesize: 8pt,
|
||
|
small: 9pt,
|
||
|
normalsize: 10pt,
|
||
|
large: 12pt,
|
||
|
Large: 14pt,
|
||
|
LARGE: 17pt,
|
||
|
huge: 20pt,
|
||
|
Huge: 25pt,
|
||
|
)
|
||
|
|
||
|
/**
|
||
|
* @pages {int} total pages to calculate left block height,
|
||
|
since it's difficult to calculate using typst. default to 1.
|
||
|
* @left {block} left block
|
||
|
* @right {block} right block
|
||
|
*/
|
||
|
#let main(
|
||
|
pages: 1,
|
||
|
left,
|
||
|
right,
|
||
|
) = {
|
||
|
set page(
|
||
|
margin: (
|
||
|
left: 0cm,
|
||
|
right: 0cm,
|
||
|
top: 0cm,
|
||
|
bottom: 0cm,
|
||
|
)
|
||
|
)
|
||
|
grid(
|
||
|
columns: (35%, 65%),
|
||
|
rows: auto,
|
||
|
// column-gutter: 1em,
|
||
|
block(
|
||
|
fill: sidecolor,
|
||
|
height: pages * 100%,
|
||
|
pad(
|
||
|
top: 1cm,
|
||
|
rest: 0.5cm,
|
||
|
left
|
||
|
)
|
||
|
),
|
||
|
block(
|
||
|
height: auto,
|
||
|
pad(
|
||
|
top: 0.7cm,
|
||
|
rest: 0.5cm,
|
||
|
right,
|
||
|
)
|
||
|
),
|
||
|
)
|
||
|
}
|
||
|
|
||
|
#let profile(
|
||
|
name: "",
|
||
|
jobtitle: "",
|
||
|
) = {
|
||
|
text(fill: pblue, size: fontSize.Huge, name) // {\Huge\color{pblue}\cvname}
|
||
|
linebreak()
|
||
|
v(2mm)
|
||
|
text(fill: gray80, size: fontSize.Large, jobtitle) // {\Large\color{black!80}\cvjobtitle}
|
||
|
}
|
||
|
|
||
|
#let profile_section(title) = {
|
||
|
v(3mm)
|
||
|
align(left)[
|
||
|
#text(size: fontSize.huge, fill: gray80)[#title]
|
||
|
#box(width: 1fr, baseline: -0.5em, line(length: 100%, stroke: gray80))
|
||
|
]
|
||
|
}
|
||
|
|
||
|
// score is 1 base
|
||
|
#let skill_checkbox(checked) = {
|
||
|
box(
|
||
|
width: 1em,
|
||
|
height: 1em,
|
||
|
stroke: mainblue,
|
||
|
radius: 0.2em,
|
||
|
if checked {
|
||
|
rect(
|
||
|
width: 0.6em,
|
||
|
height: 0.6em,
|
||
|
fill: mainblue,
|
||
|
radius: 0.1em,
|
||
|
)
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
interest item is dictionary
|
||
|
(
|
||
|
interest: "数据可视化",
|
||
|
subskills: (
|
||
|
(name: "Tableau", checked: true),
|
||
|
(name: "Python matplotlib", checked: true),
|
||
|
(name: "D3.js", checked: false),
|
||
|
)
|
||
|
)
|
||
|
*/
|
||
|
#let show_interests(interests) = {
|
||
|
set text(size: fontSize.large, fill: gray80)
|
||
|
for interest in interests {
|
||
|
text(interest.interest)
|
||
|
linebreak()
|
||
|
if interest.at("subskills", default: none) != none {
|
||
|
grid(
|
||
|
columns: (auto, 1fr),
|
||
|
column-gutter: 0.5em,
|
||
|
row-gutter: 0.3em,
|
||
|
..interest.subskills.map(subskill => (
|
||
|
[#skill_checkbox(subskill.checked)],
|
||
|
[#text(size: fontSize.normalsize)[#subskill.name]]
|
||
|
)).flatten()
|
||
|
)
|
||
|
} else {
|
||
|
v(1em)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
contact item is dictionary
|
||
|
(
|
||
|
icon: "linkedin",
|
||
|
solid: false, // Whether to use the solid version of the icon
|
||
|
text: "https://www.linkedin.com/in/someone",
|
||
|
)
|
||
|
*/
|
||
|
#let show_contacts(contacts) = {
|
||
|
v(3mm)
|
||
|
let c = ()
|
||
|
for contact in contacts {
|
||
|
c.push(fa-icon(contact.icon, solid: contact.at("solid", default: false), fill: pblue))
|
||
|
c.push(contact.text)
|
||
|
}
|
||
|
|
||
|
grid(
|
||
|
columns: (auto, auto),
|
||
|
column-gutter: 1em,
|
||
|
row-gutter: 1em,
|
||
|
..c
|
||
|
)
|
||
|
}
|
||
|
|
||
|
#let body_section(slice:6, title) = {
|
||
|
let (header, tailer) = (title.slice(0, slice), title.slice(slice))
|
||
|
set text(size: fontSize.LARGE)
|
||
|
block[
|
||
|
#v(3mm)
|
||
|
#strong()[
|
||
|
#text(fill: pblue, header)#text(fill: headercolor, tailer)
|
||
|
]
|
||
|
]
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
#1 period, like From - To
|
||
|
#2 title
|
||
|
#3 note, basic note
|
||
|
#4 addtional_note
|
||
|
#5 body: the main body
|
||
|
*/
|
||
|
#let twentyitem(
|
||
|
period: "",
|
||
|
title: "",
|
||
|
note: "",
|
||
|
addtional_note: "",
|
||
|
body: ""
|
||
|
) = {
|
||
|
grid(
|
||
|
columns: (20%, 80%),
|
||
|
period,
|
||
|
par([
|
||
|
#block()[
|
||
|
#strong(title)
|
||
|
#box(width: 1fr)
|
||
|
#text(size: fontSize.footnotesize, note)
|
||
|
]
|
||
|
#if (addtional_note.len() > 0) {
|
||
|
block(addtional_note)
|
||
|
}
|
||
|
#body
|
||
|
])
|
||
|
)
|
||
|
}
|