LaTeX & Typst (papers, theses, resumes and slides) Module 9 – Resumes, CVs and letters
Academic CVs, cover letters and a shared style
Print an academic CV's publication lists from one .bib file in LaTeX and Typst, write a matching cover letter, and share one style file across all three.
What you will learn
- Generate a CV's publication lists from a .bib file, grouped by entry type and newest first, in LaTeX and in Typst
- Write a cover letter that shares the resume's look through the same style file or module
- Build a resume, a CV and a letter from one style file or module, and check that their fonts match
- Update every publication list by adding one entry to the .bib file, with keywords for a shorter list
Before you start
What you’ll learn · Before you start
What you will learn
- Generate a CV's publication lists from a .bib file, grouped by entry type and newest first, in LaTeX and in Typst
- Write a cover letter that shares the resume's look through the same style file or module
- Build a resume, a CV and a letter from one style file or module, and check that their fonts match
- Update every publication list by adding one entry to the .bib file, with keywords for a shorter list
Before you start
On this page
An academic CV lists every paper, talk and dataset, and typed by hand those lists drift: a paper appears in the CV but
not in the resume, or with a different page range in each. Kavya Iyer, the invented researcher of this lesson, has
seven works in one .bib file, and her CV prints four lists from it in LaTeX and in Typst with no reference typed twice.
You will group a .bib file’s works by entry type, newest first, in both systems, fix the one list that Typst’s IEEE
style printed without its details, give a resume, a CV and a cover letter one style file, check that their fonts match,
and add a paper in one place. Every build ran on TeX Live 2026 with Biber 2.22 and on Typst 0.15.1.
One .bib file and four type filters make the CV’s lists in LaTeX
cv.bib holds Kavya’s two journal articles, two conference papers, a dataset and two talks. A talk is an @unpublished
entry, the type that the biblatex manual describes as a work not formally published, such as the script of a talk, and
its note keeps the event and the place:
cv.bib
% Kavya Iyer's works, the one source of the publication lists of her CV and resume. Every name, title, venue and DOI
% is invented. Titles are in sentence case, with proper nouns braced; keywords = {selected} marks the works her
% one-page resume lists; talks are @unpublished entries whose note holds the event and the place.
@article{iyer2024monsoon,
author = {Iyer, Kavya and Østergaard, Mikkel},
title = {Monsoon rainfall records from {Assamese} tea gardens},
journal = {Journal of Invented Climate Studies},
year = {2024},
volume = {12},
number = {3},
pages = {41--58},
doi = {10.5555/jics.2024.0312},
keywords = {selected},
}
@article{iyer2022soil,
author = {Iyer, Kavya and Baruah, Pranjal},
title = {Soil moisture after the first monsoon week in {Assam}},
journal = {Journal of Invented Climate Studies},
year = {2022},
volume = {10},
number = {2},
pages = {77--90},
doi = {10.5555/jics.2022.0207},
}
@inproceedings{iyer2025sensors,
author = {Iyer, Kavya and Okafor, Chidi},
title = {Low-cost rain sensors for tea estates},
booktitle = {Proceedings of the Sixth Workshop on Invented Data},
year = {2025},
pages = {33--40},
doi = {10.5555/wid.2025.033},
keywords = {selected},
}
@inproceedings{iyer2023gaps,
author = {Iyer, Kavya},
title = {Filling gaps in hourly rain gauge records},
booktitle = {Proceedings of the Fifth Workshop on Invented Data},
year = {2023},
pages = {87--94},
doi = {10.5555/wid.2023.087},
}
@dataset{rainset2025,
author = {Menon, Deepa and Iyer, Kavya and Okafor, Chidi and Rao, Sunita},
title = {Hourly rain gauge readings, 2015--2024},
year = {2025},
doi = {10.5555/rain.hourly.2025},
}
@unpublished{iyer2025talk,
author = {Iyer, Kavya},
title = {What rain gauges miss},
note = {Invited talk, Climate Data Seminar, Example University, Guwahati},
year = {2025},
}
@unpublished{iyer2024talk,
author = {Iyer, Kavya},
title = {Monsoon records from tea gardens},
note = {Talk, Example Monsoon Meeting, Shillong},
year = {2024},
} latex/cv.tex
% cv.tex: Kavya Iyer's academic CV. Its publication lists come from cv.bib, grouped by entry type, newest first.
\documentclass[11pt,a4paper]{article}
\usepackage{cv-style}
\usepackage[style=ieee, sorting=ydnt, defernumbers=true]{biblatex}
\addbibresource{cv.bib}
\begin{document}
\cvheader{Kavya Iyer, curriculum vitae}
\cvsection{Positions}
\entry{Example Institute of Atmospheric Science}{Guwahati}{Research fellow}{2024 -- present}
\cvsection{Education}
\entry{Example University}{Guwahati}{PhD in Atmospheric Science}{2019 -- 2024}
\entry{Example University}{Guwahati}{MSc in Physics}{2017 -- 2019}
\nocite{*}
\cvsection{Journal articles}
\printbibliography[type=article, heading=none, resetnumbers=true]
\cvsection{Conference papers}
\printbibliography[type=inproceedings, heading=none, resetnumbers=true]
\cvsection{Datasets}
\printbibliography[type=dataset, heading=none, resetnumbers=true]
\cvsection{Talks}
\printbibliography[type=unpublished, heading=none, resetnumbers=true]
\end{document} cv.tex asks biblatex for five things. \nocite{*} cites every entry of the file, so all seven reach the bibliography.
sorting=ydnt sorts them by year, newest first, then by name and title. Each \printbibliography takes one
type, so the articles, the papers, the dataset and the talks print as separate lists. heading=none leaves each
heading to \cvsection from the style file, and defernumbers with resetnumbers numbers every list from 1. Follow
the entries through those options:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Every entry is cited. \nocite{*} cites every entry of cv.bib without printing anything, so all seven works reach the bibliography, not only the ones the text cites.
- Newest first. Biber sorts the entries with the sorting option of the package, ydnt, which the biblatex manual describes as year descending, then name, then title.
- The style's heading. \cvsection{Journal articles} prints the heading with the rule of the shared style, so the publication lists look like every other section.
- One type per list. \printbibliography[type=article, heading=none] prints only the articles, and heading=none leaves the heading to \cvsection.
- Numbered from 1. With defernumbers=true, an entry gets its number when a list first prints it, and resetnumbers=true makes each list start again at [1], as the biblatex manual describes both.
- The Typst way. Typst has no type filter. style.typ, which cv.typ imports, reads the types and years from the text of cv.bib, cites each work of a type with form: none inside a block, and gives that block a bibliography of its own.
from session import files, run
# cv.tex, the style file it loads and cv.bib: pdfLaTeX, Biber, then pdfLaTeX twice, and the text of the CV.
files("cv.bib", "latex/cv-style.sty", "latex/cv.tex")
run(r"""
pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null
biber cv > /dev/null
pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null
pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null
pdfinfo cv.pdf | grep -E '^(Title|Pages)'
pdftotext cv.pdf - | grep .
""") Output
$ pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null $ biber cv > /dev/null $ pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null $ pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null $ pdfinfo cv.pdf | grep -E '^(Title|Pages)' Title: Kavya Iyer, curriculum vitae Pages: 1 $ pdftotext cv.pdf - | grep . Kavya Iyer Guwahati, India | kavya.iyer@example.com | kavya.example.com Positions Example Institute of Atmospheric Science, Guwahati | 2024 – present Research fellow Education Example University, Guwahati | 2019 – 2024 PhD in Atmospheric Science Example University, Guwahati | 2017 – 2019 MSc in Physics Journal articles [1] K. Iyer and M. Østergaard, “Monsoon rainfall records from Assamese tea gardens,” Journal of Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024. doi: 10.5555/jics.2024.0312 [2] K. Iyer and P. Baruah, “Soil moisture after the first monsoon week in Assam,” Journal of Invented Climate Studies, vol. 10, no. 2, pp. 77–90, 2022. doi: 10.5555/jics.2022.0207 Conference papers [1] K. Iyer and C. Okafor, “Low-cost rain sensors for tea estates,” in Proceedings of the Sixth Workshop on Invented Data, 2025, pp. 33–40. doi: 10.5555/wid.2025.033 [2] K. Iyer, “Filling gaps in hourly rain gauge records,” in Proceedings of the Fifth Workshop on Invented Data, 2023, pp. 87–94. doi: 10.5555/wid.2023.087 Datasets [1] D. Menon, K. Iyer, C. Okafor, and S. Rao, Hourly rain gauge readings, 2015–2024, 2025. doi: 10.5555/rain.hourly.2025 Talks [1] K. Iyer, “What rain gauges miss,” Invited talk, Climate Data Seminar, Example University, Guwahati, 2025. [2] K. Iyer, “Monsoon records from tea gardens,” Talk, Example Monsoon Meeting, Shillong, 2024.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 latex_cv_session.py
The lists came out as planned: the 2024 article before the 2022 one, each list from [1], and the talks with their
event and place from the note. The session runs pdfLaTeX twice after Biber because, the biblatex manual says,
defernumbers needs two LaTeX runs once Biber has written the .bbl file. Nothing in cv.tex names a work, so the
lists follow the file.
One list for everything
Wrong: one \printbibliography with every work, so that a talk sits between two papers. Right: one list per entry
type, with type= and resetnumbers=true, under headings of your own, so that a reader finds the journal articles
without reading past the talks.
Checkpoint
Check: Publication lists by entry type in biblatex
Typst prints the same lists, with one bibliography each
Typst’s bibliography has no type filter: full: true would print every work of the file in one list. What Typst 0.15
added, its changelog says, is several bibliographies in one document, each taking the citations its target selects.
style.typ builds the lists on that:
typst/style.typ
// style.typ: the look that Kavya Iyer's resume, CV and letter share, and the parts all three print. Each of them
// starts with #import "style.typ": * and #show: look.with(title: …).
#let me = (name: "Kavya Iyer", place: "Guwahati, India", email: "kavya.iyer@example.com", web: "kavya.example.com")
#let look(title: none, doc) = {
set document(title: title, author: me.name)
set page(paper: "a4", margin: 18mm)
set text(font: "New Computer Modern", size: 11pt, lang: "en", region: "GB")
set par(spacing: 0.65em)
set list(spacing: 0.4em, indent: 0.2em)
show heading.where(level: 1): it => block(above: 1.1em, below: 0.5em, width: 100%,
stroke: (bottom: 0.5pt), inset: (bottom: 3pt), text(size: 12pt, weight: "bold", it.body))
doc
}
// The name and the contact line, the same at the top of every document.
#let header() = [
#title(me.name)
#me.place | #link("mailto:" + me.email)[#me.email] | #link("https://" + me.web)[#me.web]
]
// One entry: the organisation, the place and the dates on one line, in reading order, and the role under it.
#let entry(org, place, dates, role) = [*#org*, #place | #dates \ #emph(role)]
// The entries of a .bib file as their type, key, year and keywords, read from its text: enough for a tidy file of
// one's own works, whose entries each end with a } alone on a line.
#let works(path) = read(path).matches(regex("(?s)@(\w+)\{([^,\s]+),(.*?)\n\}")).map(m => {
let (type, key, body) = m.captures
let year = body.match(regex("\byear\s*=\s*\{(\d{4})\}"))
let words = body.match(regex("\bkeywords\s*=\s*\{([^}]*)\}"))
(
type: type,
key: key,
year: if year == none { 0 } else { int(year.captures.first()) },
keywords: if words == none { () } else { words.captures.first().split(",").map(str.trim) },
)
})
// A list of works under a heading: every work of the .bib file that `keep` accepts, newest first, cited without a
// mark and printed by a bibliography that takes only the citations inside this block, numbered from 1.
#let publications(path, title, keep, style: "ieee") = {
heading(title)
context block({
for work in works(path).filter(keep).sorted(key: w => -w.year) {
cite(label(work.key), form: none)
}
bibliography(path, title: none, target: selector(cite).within(here()), group: none, style: style)
})
}
// A cover letter: the header, the date, the recipient's lines, a subject in bold, the text and the signature.
#let letter(to: (), subject: none, body) = {
header()
v(0.8em)
datetime.today().display("[month repr:long] [day padding:none], [year]")
v(0.4em)
to.join(linebreak())
if subject != none {
v(0.4em)
strong(subject)
}
v(0.4em)
body
v(0.8em)
[With best regards,]
v(0.4em)
me.name
} typst/cv.typ
// cv.typ: Kavya Iyer's academic CV. Its publication lists come from cv.bib, grouped by entry type, newest first.
#import "style.typ": *
#show: look.with(title: [Kavya Iyer, curriculum vitae])
#header()
= Positions
#entry[Example Institute of Atmospheric Science][Guwahati][2024 – present][Research fellow]
= Education
#entry[Example University][Guwahati][2019 – 2024][PhD in Atmospheric Science] \
#entry[Example University][Guwahati][2017 – 2019][MSc in Physics]
#publications("cv.bib", [Journal articles], w => w.type == "article")
#publications("cv.bib", [Conference papers], w => w.type == "inproceedings")
#publications("cv.bib", [Datasets], w => w.type == "dataset")
#publications("cv.bib", [Talks], w => w.type == "unpublished", style: "talks.csl") typst/talks.csl
<?xml version="1.0" encoding="utf-8"?>
<!-- talks.csl: a list numbered like IEEE that also prints a talk's note, where cv.bib keeps its event and place. -->
<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0" default-locale="en-GB">
<info>
<title>CV talks</title>
<id>https://example.com/csl/cv-talks</id>
<updated>2026-01-01T00:00:00+00:00</updated>
</info>
<citation>
<layout>
<text variable="citation-number" prefix="[" suffix="]"/>
</layout>
</citation>
<bibliography second-field-align="flush">
<layout suffix=".">
<text variable="citation-number" prefix="[" suffix="]"/>
<group delimiter=", ">
<names variable="author">
<name initialize-with=". " and="text"/>
</names>
<text variable="title" quotes="true"/>
<text variable="note"/>
<date variable="issued">
<date-part name="year"/>
</date>
</group>
</layout>
</bibliography>
</style> works reads cv.bib as text with read and a regular expression, and returns each entry’s type, key, year and
keywords: enough for a tidy file of one’s own works. publications then takes the works a test accepts, such as
w => w.type == "article", sorts them newest first, cites each with form: none, which prints nothing, inside a
block, and gives that block a bibliography whose target is selector(cite).within(here()). group: none numbers each
list from 1, as resetnumbers did in LaTeX.
from session import files, read, run, write
# The works of cv.bib as style.typ's reader sees them; a copy of cv.typ whose talks use IEEE like the other lists, and
# its talks; then cv.typ itself, with talks.csl, and the text of the CV.
files("cv.bib", "typst/style.typ", "typst/cv.typ", "typst/talks.csl")
write("cv-ieee.typ", read("cv.typ").replace(', style: "talks.csl"', ""))
run(r"""
typst eval '{ import "style.typ": works; works("cv.bib").map(w => (w.type, w.key, w.year)) }'
typst compile --pdf-standard ua-1 cv-ieee.typ
pdftotext cv-ieee.pdf - | grep -A 3 Talks
typst compile --pdf-standard ua-1 cv.typ
pdfinfo cv.pdf | grep -E '^(Title|Tagged|Pages)'
pdftotext cv.pdf - | grep .
""") Output
$ typst eval '{ import "style.typ": works; works("cv.bib").map(w => (w.type, w.key, w.year)) }'
[["article","iyer2024monsoon",2024],["article","iyer2022soil",2022],["inproceedings","iyer2025sensors",2025],["inproceedings","iyer2023gaps",2023],["dataset","rainset2025",2025],["unpublished","iyer2025talk",2025],["unpublished","iyer2024talk",2024]]
$ typst compile --pdf-standard ua-1 cv-ieee.typ
$ pdftotext cv-ieee.pdf - | grep -A 3 Talks
Talks
[1] K. Iyer, ‘What rain gauges miss’, 2025.
[2] K. Iyer, ‘Monsoon records from tea gardens’, 2024.
$ typst compile --pdf-standard ua-1 cv.typ
$ pdfinfo cv.pdf | grep -E '^(Title|Tagged|Pages)'
Title: Kavya Iyer, curriculum vitae
Tagged: yes
Pages: 1
$ pdftotext cv.pdf - | grep .
Kavya Iyer
Guwahati, India | kavya.iyer@example.com | kavya.example.com
Positions
Example Institute of Atmospheric Science, Guwahati | 2024 – present
Research fellow
Education
Example University, Guwahati | 2019 – 2024
PhD in Atmospheric Science
Example University, Guwahati | 2017 – 2019
MSc in Physics
Journal articles
[1] K. Iyer and M. Østergaard, ‘Monsoon rainfall records from Assamese tea gardens’, Journal of
Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024, doi: 10.5555/jics.2024.0312.
[2] K. Iyer and P. Baruah, ‘Soil moisture after the first monsoon week in Assam’, Journal of Invented
Climate Studies, vol. 10, no. 2, pp. 77–90, 2022, doi: 10.5555/jics.2022.0207.
Conference papers
[1] K. Iyer and C. Okafor, ‘Low-cost rain sensors for tea estates’, in Proceedings of the Sixth
Workshop on Invented Data, 2025, pp. 33–40. doi: 10.5555/wid.2025.033.
[2] K. Iyer, ‘Filling gaps in hourly rain gauge records’, in Proceedings of the Fifth Workshop on
Invented Data, 2023, pp. 87–94. doi: 10.5555/wid.2023.087.
Datasets
[1] D. Menon, K. Iyer, C. Okafor, and S. Rao, Hourly rain gauge readings, 2015–2024. (2025). doi:
10.5555/rain.hourly.2025.
Talks
[1] K. Iyer, ‘What rain gauges miss’, Invited talk, Climate Data Seminar, Example University,
Guwahati, 2025.
[2] K. Iyer, ‘Monsoon records from tea gardens’, Talk, Example Monsoon Meeting, Shillong, 2024.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 typst_cv_session.py
The first build used IEEE for every list, and its talks lost their notes: K. Iyer, ‘What rain gauges miss’, 2025.
Typst’s IEEE style did not print the note for these entries. talks.csl, 29 lines of the Citation Style Language,
is numbered like IEEE and adds the note variable, and with it the CV’s talks read
Invited talk, Climate Data Seminar, Example University, Guwahati, 2025 as biblatex’s did.
| The talk of 2025 | as printed |
|---|---|
| biblatex with biblatex-ieee | K. Iyer, “What rain gauges miss,” Invited talk, Climate Data Seminar, …, 2025. |
| Typst with its IEEE style | K. Iyer, ‘What rain gauges miss’, 2025. |
| Typst with talks.csl | K. Iyer, ‘What rain gauges miss’, Invited talk, Climate Data Seminar, …, 2025. |
The quotation marks differ too. With region: "GB" in the style, Typst’s lists used single quotes with the comma
outside them, the British way, while biblatex-ieee kept double quotes; the citations module’s lesson on Typst
bibliographies compares the two systems’ IEEE output in detail.
Proof-read every list a style prints
A style prints the fields it knows for each type and quietly leaves out the rest. Read each list of a new CV once
against its .bib entries, as the talks show.
Checkpoint
Check: Publication lists by entry type in Typst
One style file gives the resume, the CV and the letter one look
Kavya sends three documents: a one-page resume for a data role, the CV and a cover letter. If each sets its own fonts,
margins and headings, they drift apart with every edit. Here none of them sets any. In Typst they start with
#import "style.typ": * and #show: look.with(title: […]); in LaTeX they load cv-style.sty with
\usepackage{cv-style}, a package: A file of extra LaTeX commands, usually ending in .sty, that a document loads with \usepackage. CTAN collects packages, and a TeX distribution installs them. More in “LaTeX and Typst: what they are and when to use each” of the documents’ own. Each shared file holds the
look and the parts every document prints:
latex/cv-style.sty
% cv-style.sty: the look that Kavya Iyer's resume, CV and letter share. Each of them loads it with \usepackage{cv-style}.
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{cv-style}
\RequirePackage[margin=18mm]{geometry}
\RequirePackage[T1]{fontenc}
\RequirePackage{lmodern}
\RequirePackage{enumitem}
\RequirePackage[hidelinks]{hyperref}
\pagestyle{empty}
\setlength{\parindent}{0pt}
\setlist[itemize]{nosep, leftmargin=1.2em}
% Headings: LaTeX's \section*, set close, with a rule under them (as in the module's one-page resume).
\renewcommand\section{\@startsection{section}{1}{0pt}{8pt}{1sp}{\large\bfseries}}
\newcommand{\cvsection}[1]{\section*{#1}\vspace{2pt}\hrule\vspace{3pt}}
% One entry: the organisation, the place and the dates on one line, in reading order, and the role under it.
\newcommand{\entry}[4]{\textbf{#1}, #2 | #4\par\textit{#3}\par}
% The name and the contact line, the same at the top of every document. #1 is the PDF's title.
\newcommand{\cvheader}[1]{%
\hypersetup{pdftitle={#1}, pdfauthor={Kavya Iyer}}%
{\LARGE\bfseries Kavya Iyer}\par\smallskip
Guwahati, India | \href{mailto:kavya.iyer@example.com}{kavya.iyer@example.com}
| \href{https://kavya.example.com}{kavya.example.com}\par} typst/resume.typ
// resume.typ: Kavya Iyer's one-page resume for a data role. Its publications are the works cv.bib marks selected.
#import "style.typ": *
#show: look.with(title: [Kavya Iyer, resume])
#header()
= Experience
#entry[Example Institute of Atmospheric Science][Guwahati][2024 – present][Research fellow]
- Built the pipeline that cleans 4.2 million hourly gauge readings a year for the institute’s rainfall maps.
- Wrote the Python package the lab uses to fill gaps in gauge records, with 96 tests.
= Education
#entry[Example University][Guwahati][2019 – 2024][PhD in Atmospheric Science]
#publications("cv.bib", [Selected publications], w => "selected" in w.keywords)
= Skills
Python, R, SQL, pandas, xarray, Git, LaTeX, Typst latex/resume.tex
% resume.tex: Kavya Iyer's one-page resume for a data role. Its publications are the works cv.bib marks selected.
\documentclass[11pt,a4paper]{article}
\usepackage{cv-style}
\usepackage[style=ieee, sorting=ydnt, defernumbers=true]{biblatex}
\addbibresource{cv.bib}
\begin{document}
\cvheader{Kavya Iyer, resume}
\cvsection{Experience}
\entry{Example Institute of Atmospheric Science}{Guwahati}{Research fellow}{2024 -- present}
\begin{itemize}
\item Built the pipeline that cleans 4.2 million hourly gauge readings a year for the institute's rainfall maps.
\item Wrote the Python package the lab uses to fill gaps in gauge records, with 96 tests.
\end{itemize}
\cvsection{Education}
\entry{Example University}{Guwahati}{PhD in Atmospheric Science}{2019 -- 2024}
\nocite{*}
\cvsection{Selected publications}
\printbibliography[keyword=selected, heading=none]
\cvsection{Skills}
Python, R, SQL, pandas, xarray, Git, LaTeX, Typst
\end{document} Step through what the Typst module: In Typst, a .typ file whose definitions other files take with #import "style.typ": name, so that several documents share one set of functions and rules. gives each document:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- Three documents. Kavya's resume, CV and cover letter are three short files. None of them sets a font, a margin or a heading style of its own.
- The look. Each one starts with #import "style.typ": * and #show: look.with(title: …). look sets the page, New Computer Modern at 11 pt, the lists and the heading rule, and writes the title and author.
- The parts they print. header() prints the name and the contact line, and entry() the same entry layout as the resume of this module's first lesson, so the top of every page is the same.
- The letter. letter() adds what a letter needs, the date, the recipient's lines, the subject and the signature, around the same header and type.
- The works. cv.bib is read by the CV for its four lists and by the resume for the works marked selected. A new entry reaches every list that should show it.
- The talks. talks.csl is a small style for the CV's talks, because Typst's IEEE style printed them without the event and the place kept in their note.
- One change, three documents. A new font or margin is one line of style.typ, and all three PDFs follow it. pdffonts found only New Computer Modern fonts in all three.
- The same in LaTeX. cv-style.sty plays the same part for the LaTeX documents, with \cvheader, \cvsection and \entry, and each of them loads it with \usepackage{cv-style}.
A matching look can be checked. pdffonts lists the fonts a PDF embeds, each behind a
subset prefix: The six capital letters and plus sign before a font's name in a PDF, as in DSRJEH+NewCM10-Regular, which mark a font embedded with only the glyphs the document uses. They differ from file to file. that differs from file to file, so the sessions strip the prefix before
comparing:
from session import files, run
# Kavya's LaTeX resume, CV and letter, built with the style file they share, and the fonts each PDF uses, with the
# six-letter prefix of an embedded subset removed. family_typst_session.py does the same for the Typst documents.
files("cv.bib", "latex/cv-style.sty", "latex/resume.tex", "latex/cv.tex", "latex/letter.tex")
run(r"""
for f in resume cv; do pdflatex -interaction=nonstopmode -halt-on-error $f.tex > /dev/null && biber -q $f > /dev/null && pdflatex -interaction=nonstopmode -halt-on-error $f.tex > /dev/null && pdflatex -interaction=nonstopmode -halt-on-error $f.tex > /dev/null; done
pdflatex -interaction=nonstopmode -halt-on-error letter.tex > /dev/null
for f in resume cv letter; do echo "$f.pdf: $(pdffonts $f.pdf | tail -n +3 | awk '{print $1}' | sed 's/^[A-Z]\{6\}+//' | sort -u | tr '\n' ' ')"; done
""") Output
$ for f in resume cv; do pdflatex -interaction=nonstopmode -halt-on-error $f.tex > /dev/null && biber -q $f > /dev/null && pdflatex -interaction=nonstopmode -halt-on-error $f.tex > /dev/null && pdflatex -interaction=nonstopmode -halt-on-error $f.tex > /dev/null; done
$ pdflatex -interaction=nonstopmode -halt-on-error letter.tex > /dev/null
$ for f in resume cv letter; do echo "$f.pdf: $(pdffonts $f.pdf | tail -n +3 | awk '{print $1}' | sed 's/^[A-Z]\{6\}+//' | sort -u | tr '\n' ' ')"; done
resume.pdf: LMMono10-Regular LMRoman10-Bold LMRoman10-Italic LMRoman10-Regular LMRoman12-Bold LMRomanCaps10-Regular
cv.pdf: LMMono10-Regular LMRoman10-Bold LMRoman10-Italic LMRoman10-Regular LMRoman12-Bold LMRomanCaps10-Regular
letter.pdf: LMRoman10-Bold LMRoman10-Regular LMRoman12-Bold
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 family_latex_session.py
from session import files, read, run, write
# Kavya's Typst resume, CV and letter, built with the style module they share, and the fonts each PDF uses, with the
# six-letter prefix of an embedded subset removed. Last, the letter once more with the import kept and the line that
# applies the look left out.
files("cv.bib", "typst/style.typ", "typst/resume.typ", "typst/cv.typ", "typst/letter.typ", "typst/talks.csl")
write("letter-plain.typ", read("letter.typ").replace("#show: look.with(title: [Kavya Iyer, cover letter])\n", ""))
run(r"""
for f in resume cv letter letter-plain; do typst compile $f.typ; done
for f in resume cv letter letter-plain; do echo "$f.pdf: $(pdffonts $f.pdf | tail -n +3 | awk '{print $1}' | sed 's/^[A-Z]\{6\}+//' | sort -u | tr '\n' ' ')"; done
""") Output
$ for f in resume cv letter letter-plain; do typst compile $f.typ; done
$ for f in resume cv letter letter-plain; do echo "$f.pdf: $(pdffonts $f.pdf | tail -n +3 | awk '{print $1}' | sed 's/^[A-Z]\{6\}+//' | sort -u | tr '\n' ' ')"; done
resume.pdf: NewCM10-Bold-Identity-H NewCM10-Italic-Identity-H NewCM10-Regular-Identity-H
cv.pdf: NewCM10-Bold-Identity-H NewCM10-Italic-Identity-H NewCM10-Regular-Identity-H
letter.pdf: NewCM10-Bold-Identity-H NewCM10-Regular-Identity-H
letter-plain.pdf: LibertinusSerif-Bold-Identity-H LibertinusSerif-Regular-Identity-H
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 family_typst_session.py
In LaTeX the resume and the CV used the same six Latin Modern fonts and the letter three of them; in Typst all three
used New Computer Modern. The last Typst file shows the slip this check catches. letter-plain.typ kept the import, so
letter and header worked, but left out the #show: look.with(…) line, and its PDF came out in Libertinus Serif,
Typst’s default font: a letter that no longer matched the resume, without any error.
Key idea
Put the look in one style file or module and nothing of it in the documents, then compare the fonts of the PDFs with their subset prefixes removed: a document that forgot the style shows up at once.
Checkpoint
Check: One style file for three documents
The cover letter is a function of the same style
A letter has parts a resume lacks: a date, the recipient’s lines, a subject and a signature. In Typst they are a
function of style.typ, letter(to: …, subject: …)[…], that wraps the body in the same header and type, so
letter.typ holds only what this letter says:
typst/letter.typ
// letter.typ: Kavya Iyer's cover letter, built with the letter function of the style module her resume and CV use.
#import "style.typ": *
#show: look.with(title: [Kavya Iyer, cover letter])
#letter(
to: ([Hiring committee], [Example Climate Analytics], [Bengaluru]),
subject: [Research scientist, rainfall data],
)[
Dear members of the committee,
I am applying for the post of research scientist in your rainfall data team. For five years I have worked with
hourly rain gauge records from tea estates in Assam: cleaning them, filling their gaps and turning them into the
maps my institute publishes every season.
At the Example Institute of Atmospheric Science I built the pipeline that cleans 4.2 million readings a year, and
the method behind it is in my paper on filling gaps in hourly records. I would bring the same care for messy field
data to your forecasting products.
Thank you for your time. I would be glad to talk about the post.
] latex/letter.tex
% letter.tex: Kavya Iyer's cover letter, on the article class with the same style file as her resume and CV.
\documentclass[11pt,a4paper]{article}
\usepackage{cv-style}
\begin{document}
\cvheader{Kavya Iyer, cover letter}
\bigskip
\today
\medskip
Hiring committee\\
Example Climate Analytics\\
Bengaluru
\medskip
\textbf{Research scientist, rainfall data}
\medskip
Dear members of the committee,
I am applying for the post of research scientist in your rainfall data team. For five years I have worked with hourly
rain gauge records from tea estates in Assam: cleaning them, filling their gaps and turning them into the maps my
institute publishes every season.
At the Example Institute of Atmospheric Science I built the pipeline that cleans 4.2 million readings a year, and the
method behind it is in my paper on filling gaps in hourly records. I would bring the same care for messy field data to
your forecasting products.
Thank you for your time. I would be glad to talk about the post.
\bigskip
With best regards,\par
\medskip
Kavya Iyer
\end{document} from session import files, read, run, write
# The cover letter in both systems: letter.tex with the style file and letter.typ with the style module, each built
# once, then each PDF's title and the start of its text. Last, a copy of letter.tex on LaTeX's letter class instead of
# the article class, and the first error in its log.
files("latex/cv-style.sty", "latex/letter.tex", "typst/style.typ", "typst/letter.typ")
ARTICLE = "\\documentclass[11pt,a4paper]{article}\n"
text = read("letter.tex")
assert ARTICLE in text
write("letter-class.tex", text.replace(ARTICLE, "\\documentclass[11pt,a4paper]{letter}\n"))
run(r"""
pdflatex -interaction=nonstopmode -halt-on-error letter.tex > /dev/null
typst compile letter.typ letter-typst.pdf
pdfinfo letter.pdf | grep -E '^(Title|Pages)'
pdftotext letter.pdf - | grep . | head -n 9
pdfinfo letter-typst.pdf | grep -E '^(Title|Pages)'
pdftotext letter-typst.pdf - | grep . | head -n 9
!pdflatex -interaction=nonstopmode -halt-on-error letter-class.tex > /dev/null
grep -a -m 1 '^!' letter-class.log
""") Output
$ pdflatex -interaction=nonstopmode -halt-on-error letter.tex > /dev/null $ typst compile letter.typ letter-typst.pdf $ pdfinfo letter.pdf | grep -E '^(Title|Pages)' Title: Kavya Iyer, cover letter Pages: 1 $ pdftotext letter.pdf - | grep . | head -n 9 Kavya Iyer Guwahati, India | kavya.iyer@example.com | kavya.example.com January 1, 2026 Hiring committee Example Climate Analytics Bengaluru Research scientist, rainfall data Dear members of the committee, I am applying for the post of research scientist in your rainfall data team. For five years I have worked $ pdfinfo letter-typst.pdf | grep -E '^(Title|Pages)' Title: Kavya Iyer, cover letter Pages: 1 $ pdftotext letter-typst.pdf - | grep . | head -n 9 Kavya Iyer Guwahati, India | kavya.iyer@example.com | kavya.example.com January 1, 2026 Hiring committee Example Climate Analytics Bengaluru Research scientist, rainfall data Dear members of the committee, I am applying for the post of research scientist in your rainfall data team. For five years I have $ pdflatex -interaction=nonstopmode -halt-on-error letter-class.tex > /dev/null [exit status 1] $ grep -a -m 1 '^!' letter-class.log ! LaTeX Error: Command \section undefined.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 letter_session.py
The two letters begin the same, word for word, under the same header as the resume and the CV; only one line break of
the first paragraph falls a word apart. The date comes from datetime.today() in Typst and \today in LaTeX, and both
sessions fix it through SOURCE_DATE_EPOCH, so the two letters printed the same date. LaTeX has a standard letter
class, and the tagging status list marks it partially compatible, with tagging that is not ideal. letter.tex uses the
article class with cv-style.sty instead, and the last build of the session shows why: on the letter class the build
stopped with Command \section undefined, because the letter class defines no \section for cv-style.sty to
redefine.
Write the letter's own words only
Keep the letter’s file to what changes from one application to the next: the recipient, the subject and three short paragraphs. Everything that should look the same comes from the style, like the resume’s.
Checkpoint
Check: A cover letter from the same style
A new paper is one entry, and every list that should show it does
A CV is kept current by adding to the data, never to a list. update_session.py adds one article of 2026 to the end of
cv.bib, marked with keywords = {selected}, and builds both CVs and both resumes again:
from session import files, read, run, write
# One new article, marked selected, added to the end of cv.bib, then both CVs and both resumes built again: LaTeX's into
# cv.pdf and resume.pdf, Typst's into cv-typst.pdf and resume-typst.pdf. Each CV lists the new article first among the
# journal articles, because both systems sort the newest first, and each resume lists it first among the selected works.
files("cv.bib", "latex/cv-style.sty", "latex/cv.tex", "latex/resume.tex", "typst/style.typ", "typst/cv.typ",
"typst/resume.typ", "typst/talks.csl")
NEW = """
@article{iyer2026dry,
author = {Iyer, Kavya and Baruah, Pranjal},
title = {Dry spells in the {Brahmaputra} valley, 1990--2025},
journal = {Journal of Invented Climate Studies},
year = {2026},
volume = {14},
number = {1},
pages = {5--19},
doi = {10.5555/jics.2026.0105},
keywords = {selected},
}
"""
write("cv.bib", read("cv.bib") + NEW)
run(r"""
tail -n 12 cv.bib
pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null
biber cv > /dev/null
pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null
pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null
typst compile --pdf-standard ua-1 cv.typ cv-typst.pdf
pdftotext cv.pdf - | grep -A 6 'Journal articles'
pdftotext cv-typst.pdf - | grep -A 6 'Journal articles'
pdflatex -interaction=nonstopmode -halt-on-error resume.tex > /dev/null && biber -q resume > /dev/null && pdflatex -interaction=nonstopmode -halt-on-error resume.tex > /dev/null && pdflatex -interaction=nonstopmode -halt-on-error resume.tex > /dev/null
typst compile --pdf-standard ua-1 resume.typ resume-typst.pdf
pdftotext resume.pdf - | grep . | grep -A 6 'Selected publications'
pdftotext resume-typst.pdf - | grep -A 4 'Selected publications'
""") Output
$ tail -n 12 cv.bib
@article{iyer2026dry,
author = {Iyer, Kavya and Baruah, Pranjal},
title = {Dry spells in the {Brahmaputra} valley, 1990--2025},
journal = {Journal of Invented Climate Studies},
year = {2026},
volume = {14},
number = {1},
pages = {5--19},
doi = {10.5555/jics.2026.0105},
keywords = {selected},
}
$ pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null
$ biber cv > /dev/null
$ pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null
$ pdflatex -interaction=nonstopmode -halt-on-error cv.tex > /dev/null
$ typst compile --pdf-standard ua-1 cv.typ cv-typst.pdf
$ pdftotext cv.pdf - | grep -A 6 'Journal articles'
Journal articles
[1]
K. Iyer and P. Baruah, “Dry spells in the Brahmaputra valley, 1990–2025,” Journal of Invented
Climate Studies, vol. 14, no. 1, pp. 5–19, 2026. doi: 10.5555/jics.2026.0105
[2]
$ pdftotext cv-typst.pdf - | grep -A 6 'Journal articles'
Journal articles
[1] K. Iyer and P. Baruah, ‘Dry spells in the Brahmaputra valley, 1990–2025’, Journal of Invented
Climate Studies, vol. 14, no. 1, pp. 5–19, 2026, doi: 10.5555/jics.2026.0105.
[2] K. Iyer and M. Østergaard, ‘Monsoon rainfall records from Assamese tea gardens’, Journal of
Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024, doi: 10.5555/jics.2024.0312.
[3] K. Iyer and P. Baruah, ‘Soil moisture after the first monsoon week in Assam’, Journal of Invented
Climate Studies, vol. 10, no. 2, pp. 77–90, 2022, doi: 10.5555/jics.2022.0207.
$ pdflatex -interaction=nonstopmode -halt-on-error resume.tex > /dev/null && biber -q resume > /dev/null && pdflatex -interaction=nonstopmode -halt-on-error resume.tex > /dev/null && pdflatex -interaction=nonstopmode -halt-on-error resume.tex > /dev/null
$ typst compile --pdf-standard ua-1 resume.typ resume-typst.pdf
$ pdftotext resume.pdf - | grep . | grep -A 6 'Selected publications'
Selected publications
[1]
K. Iyer and P. Baruah, “Dry spells in the Brahmaputra valley, 1990–2025,” Journal of Invented
Climate Studies, vol. 14, no. 1, pp. 5–19, 2026. doi: 10.5555/jics.2026.0105
[2]
K. Iyer and C. Okafor, “Low-cost rain sensors for tea estates,” in Proceedings of the Sixth Workshop
on Invented Data, 2025, pp. 33–40. doi: 10.5555/wid.2025.033
$ pdftotext resume-typst.pdf - | grep -A 4 'Selected publications'
Selected publications
[1] K. Iyer and P. Baruah, ‘Dry spells in the Brahmaputra valley, 1990–2025’, Journal of Invented
Climate Studies, vol. 14, no. 1, pp. 5–19, 2026, doi: 10.5555/jics.2026.0105.
[2] K. Iyer and C. Okafor, ‘Low-cost rain sensors for tea estates’, in Proceedings of the Sixth
Workshop on Invented Data, 2025, pp. 33–40. doi: 10.5555/wid.2025.033.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 update_session.py
Both CVs put the new article first among the journal articles, as [1], without any change to cv.tex or cv.typ:
the sort is by year, not by place in the file. The session builds in a clean folder, and that matters for LaTeX: the
biblatex manual warns that with defernumbers a change to the .bib file usually needs the old .aux file deleted
before the next build.
The resumes choose by that mark. Two entries of cv.bib already carried keywords = {selected}, a
keywords field: A .bib field of comma-separated words, such as keywords = {selected}, that biblatex's keyword and notkeyword filters read to choose the entries of a list., and each resume prints only the works that carry it, with
keyword=selected in LaTeX and w => "selected" in w.keywords in Typst. The new article carries it too, so both
resumes now list it first, as [1], ahead of the 2025 conference paper.
Key idea
Keep every work once, in the .bib file, with its type and its keywords, and let the lists choose: a new entry then
reaches the CV and, when it is marked, the resume, in both systems.
Checkpoint
Check: Keeping every list current
Practise: check a CV’s sources before you build it
The two steps are the checks of this lesson as code: the first groups a .bib file by type, newest first, as
style.typ does, the second reads pdffonts output without its subset prefixes. Their sample tests use this lesson’s
files and outputs, and the first hint of each step is open:
Code lab · 2 steps
Check a CV's works and fonts
Group the works of a .bib file by entry type, newest first, then list the fonts of a PDF without their subset prefixes.
- Group a CV's .bib file by entry type, newest first To do
- Read the fonts of a PDF without their subset prefixes To do
Exercise · Medium · Python
Group a CV's .bib file by entry type, newest first
style.typ reads cv.bib as text to find each entry's type, key and year, then prints one list per type, newest first. A script that checks a CV before it is built needs the same grouping.
Write works_by_type(bib) in works.py. Given the text of a .bib file, it returns a dictionary that maps each entry type, in lower case, to the list of its keys, newest first. Entries of the same year keep the order they have in the file, and an entry without a year field counts as year 0, after all the others.
Read an entry the way style.typ does: it starts with @type{key, and ends at a } alone at the start of a line. Inside it, year = {2024} gives the year; a field may have more spaces around =, and the entry type may be written @Article as well as @article. Lines that start with % outside the entries are comments.
Starter code · works.py
import re
def works_by_type(bib):
"""Return {entry type: [keys, newest first]} for the entries of a .bib file's text."""
# Replace this line: find each entry's type, key and year, then group the keys by type and sort them.
return {} The sample tests · test_works.py
from works import works_by_type
CV = """% Kavya Iyer's works.
@article{iyer2022soil,
title = {Soil moisture after the first monsoon week in {Assam}},
year = {2022},
}
@article{iyer2024monsoon,
title = {Monsoon rainfall records from {Assamese} tea gardens},
year = {2024},
keywords = {selected},
}
@inproceedings{iyer2025sensors,
title = {Low-cost rain sensors for tea estates},
year = {2025},
}
@unpublished{iyer2024talk,
title = {Monsoon records from tea gardens},
note = {Talk, Example Monsoon Meeting, Shillong},
year = {2024},
}
"""
def test_groups_and_order():
"""groups cv.bib's entries by type, newest first"""
assert works_by_type(CV) == {
"article": ["iyer2024monsoon", "iyer2022soil"],
"inproceedings": ["iyer2025sensors"],
"unpublished": ["iyer2024talk"],
}
def test_same_year_keeps_file_order():
"""keeps the file's order for two works of the same year"""
bib = "@misc{b,\n year = {2023},\n}\n@misc{a,\n year = {2023},\n}\n"
assert works_by_type(bib) == {"misc": ["b", "a"]}
def test_upper_case_type_and_spaces():
"""reads @Article and a year written with spaces around ="""
bib = "@Article{rao2021,\n year={2021},\n}\n@article{rao2025,\n year = {2025},\n}\n"
assert works_by_type(bib) == {"article": ["rao2025", "rao2021"]}
def test_no_year_last():
"""puts a work without a year after the dated ones"""
bib = "@misc{undated,\n title = {Notes},\n}\n@misc{dated,\n year = {2020},\n}\n"
assert works_by_type(bib) == {"misc": ["dated", "undated"]} A hint
re.finditer(r"@(\w+)\{([^,\s]+),(.*?)\n\}", bib, re.S) gives each entry's type, key and body; search the body for r"\byear\s*=\s*\{(\d{4})\}". Collect (year, key) pairs per type, then sort each list by the year with reverse=True: Python's sort keeps entries of the same year in the order of the file.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Exercise · Easy · Python
Read the fonts of a PDF without their subset prefixes
The lesson compared the fonts of Kavya's resume, CV and letter with pdffonts. Its names begin with a prefix such as DSRJEH+, six capital letters and a plus sign that mark a font embedded as a subset, and the prefix differs from file to file, so the names only match once it is removed.
Write font_names(output) in fonts.py. Given the text that pdffonts printed for one PDF, it returns the sorted list of the different font names in it, each without its prefix. Skip the two header lines (the column names and the line of dashes); the name is the first word of every other line. A name without a prefix is kept as it is, and a prefix is only six capital letters followed by +, so ABC+Font keeps its ABC+.
Starter code · fonts.py
def font_names(output):
"""Return the sorted, different font names of pdffonts output, without their subset prefixes."""
# Replace this line: skip the two header lines, take the first word of each line and strip a six-letter prefix.
return [] The sample tests · test_fonts.py
from fonts import font_names
HEADER = (
"name type encoding emb sub uni object ID\n"
"------------------------------------ ----------------- ---------------- --- --- --- ---------\n"
)
CV = HEADER + (
"DSRJEH+NewCM10-Bold-Identity-H CID Type 0C Identity-H yes yes yes 105 0\n"
"MDYYKN+NewCM10-Regular-Identity-H CID Type 0C Identity-H yes yes yes 108 0\n"
"NWSSOI+NewCM10-Italic-Identity-H CID Type 0C Identity-H yes yes yes 111 0\n"
)
LETTER = HEADER + (
"QWERTY+NewCM10-Regular-Identity-H CID Type 0C Identity-H yes yes yes 40 0\n"
"ZXCVBN+NewCM10-Bold-Identity-H CID Type 0C Identity-H yes yes yes 43 0\n"
)
def test_cv_fonts():
"""lists the three fonts of the Typst CV, sorted, without prefixes"""
assert font_names(CV) == ["NewCM10-Bold-Identity-H", "NewCM10-Italic-Identity-H", "NewCM10-Regular-Identity-H"]
def test_prefixes_differ():
"""gives the same names for the same fonts under other prefixes"""
assert set(font_names(LETTER)) <= set(font_names(CV))
def test_repeated_font_once():
"""lists a font that appears twice only once"""
twice = HEADER + "ABCDEF+LMRoman10-Regular Type 1 Custom yes yes yes 8 0\nGHIJKL+LMRoman10-Regular Type 1 Custom yes yes yes 9 0\n"
assert font_names(twice) == ["LMRoman10-Regular"]
def test_no_prefix_kept():
"""keeps a name without a prefix, and a short prefix, as they are"""
plain = HEADER + "Helvetica Type 1 WinAnsi no no no 5 0\nABC+Font Type 1 Custom yes yes yes 6 0\n"
assert font_names(plain) == ["ABC+Font", "Helvetica"] A hint
Take output.splitlines()[2:], skip empty lines, and keep line.split()[0]. re.sub(r"^[A-Z]{6}\+", "", name) removes a prefix and leaves any other name alone; a set, then sorted, gives each name once.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Last, a round of questions on the five ideas, from the type filters to the new paper, which opens with the idea you know least well so far:
Practice that adapts to you
Practise this lesson
13 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): How does biblatex print only the journal articles of a CV’s .bib file, under a heading of their own?
With a filter on \printbibliography. \nocite{*} first puts every entry of the file into the bibliography without a
citation in the text. Then \printbibliography[type=article, heading=none] prints only the entries of type article,
and a heading command of my own, such as \cvsection{Journal articles}, goes before it, or heading=subbibliography
with a title. Another \printbibliography with type=inproceedings gives the conference papers, and so on. With
sorting=ydnt the newest come first, and with defernumbers=true and resetnumbers=true each list starts again at
one. A keyword=selected filter makes a short list for a resume from the same file.
Two harder questions close the module: one source for a researcher’s whole publication record, and three documents that drifted apart. Try each before you look at its first step:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A researcher's CV exists as a word-processor file and a LaTeX file, each with about 40 hand-typed references, and the two now disagree on page ranges and titles. Design one source that groups the works by type, marks a few for a short resume, builds in LaTeX and Typst, and stays right as works are added.
-
Fresher to mid
Your resume, CV and cover letter each carry their own copy of the margins, fonts and heading style, and after a year of edits they no longer match. Restructure them around one shared style, and show how you check that the three PDFs really match.
Key takeaways
- One
.bibfile can feed every publication list:\nocite{*}, atypefilter per list,sorting=ydntand numbers reset for each list in LaTeX. - Typst builds the same lists with one bibliography per block, each citing its works with
form: none, and a small CSL file prints what its built-in style leaves out. - A style file or module holds the look and the shared parts, so the resume, the CV and the letter set nothing of their
own, and their fonts, read with
pdffonts, show whether they match. - The cover letter is a function or a few commands of the same style, with only its own words in its file.
- Adding a paper is one entry in the
.bibfile; the year sorts it and a keyword puts it on the resume, in both systems.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
How does biblatex list every work of a .bib file without citing each one?
\nocite{*} puts every entry into the bibliography; sorting=ydnt then sorts them by year, newest first.
-
How do you print one list per entry type, each numbered from 1?
One \printbibliography per type, such as [type=article, heading=none] under a heading of your own, with defernumbers and resetnumbers=true.
-
Which entry type holds a talk in biblatex?
@unpublished, a work not formally published, with the event and the place kept in its note field.
-
How does Typst print a CV's separate publication lists?
One bibliography per block: each work is cited with form: none inside the block, and the block's bibliography targets selector(cite).within(here()); group: none numbers each list from 1.
-
Why did the Typst CV's talks lose their event and place?
Typst's IEEE style did not print the note field for those entries; a CSL file of 29 lines that adds the note variable fixed it.
-
How do you check that a resume, a CV and a letter share one look?
List their fonts with pdffonts and compare the names with the subset prefixes removed; a document that forgot the style shows other fonts.
-
Why did letter-plain.typ come out in Libertinus Serif?
It imported the style but left out #show: look.with(...), so Typst's default font applied, with no error.
-
How does a new paper reach both the CV and the resume?
Add one entry to the .bib file: its year sorts it into the CV, and keywords = {selected} puts it on resumes that filter by that keyword.
Terms in this lesson
- keywords field
- A .bib field of comma-separated words, such as keywords = {selected}, that biblatex's keyword and notkeyword filters read to choose the entries of a list.
- module
- In Typst, a .typ file whose definitions other files take with #import "style.typ": name, so that several documents share one set of functions and rules.
- subset prefix
- The six capital letters and plus sign before a font's name in a PDF, as in DSRJEH+NewCM10-Regular, which mark a font embedded with only the glyphs the document uses. They differ from file to file.
References
- biblatex: sophisticated bibliographies in LaTeX (CTAN)
- biblatex-ieee: IEEE style files for BibLaTeX (CTAN)
- Typst reference: bibliography (Typst)
- Typst reference: cite (Typst)
- Typst 0.15.0 changelog (Typst)
- Typst reference: scripting (Typst)
- Typst reference: read (Typst)
- Typst reference: datetime (Typst)
- Citation Style Language 1.0.2 specification (Citation Style Language project)
- LaTeX for package and class authors (The LaTeX Project)
- Tagging status of LaTeX packages and classes (The LaTeX Project (tagging project))
- Poppler (freedesktop.org)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress