Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

LaTeX & Typst (papers, theses, resumes and slides)  Module 7 – Slides, posters and handouts

Typst slides with Touying and Polylux

Build a Typst slide deck with Touying and with Polylux, reveal it step by step, make the handout from one source and pin the exact package versions.

  • Intermediate
  • 20 minutes
  • Examples run with TeX Live 2026, TinyTeX 2026.10, Typst 0.15.1, Python 3.14.8, Pyodide 314.0.7, poppler 26.09.0, touying 0.8.0, polylux 0.4.0 and beamer 3.78
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Build a slide deck with Touying's simple theme, headings as slides and
  • Build the same deck with Polylux
  • Create a handout from one source with Touying's export mode or Polylux's handout mode, and predict what each keeps
  • Specify the exact version of every slide package and build the deck without network access
  • Choose between Touying, Polylux and Beamer for a talk

Before you start

What you’ll learn · Before you start

What you will learn

  • Build a slide deck with Touying's simple theme, headings as slides and
  • Build the same deck with Polylux
  • Create a handout from one source with Touying's export mode or Polylux's handout mode, and predict what each keeps
  • Specify the exact version of every slide package and build the deck without network access
  • Choose between Touying, Polylux and Beamer for a talk

Before you start

On this page

Typst has no slide class of its own: the four-frame talk of the Beamer lesson needs a package, and two packages on Typst Universe, Touying and Polylux, both turned it into the same 9 pages but into handouts of 4 and 5 pages. Which lines a handout keeps, which bullets a reveal leaves behind and which version a build downloads all depend on the package.

Below you will build the talk with Touying’s simple theme and with Polylux, make each handout from the same source with one command-line flag, pin both packages so that the build runs without network access, and compare the three systems on one table. Every page, file count and time on this page comes from the recorded builds.

Touying makes a slide of every second-level heading

Touying reads a deck like a document. A first-level heading, = The question, starts a section; a second-level heading, == Three questions, starts a slide; #pause splits a slide into subslide: In Touying and Polylux, one step of a slide, printed as one PDF page: a slide with two #pause marks has three subslides., one page each. #show: simple-theme.with(aspect-ratio: "16-9") applies one of the six themes listed on its Universe page (simple, metropolis, dewdrop, university, aqua and stargazer). #only and #uncover work as in Beamer: #only(1) takes its content away on the other subslides, while #uncover(2) keeps its space. talk-touying.typ is the Beamer talk, slide for slide:

The Beamer lesson's talk in Touying's simple theme

talk/talk-touying.typ

#import "@preview/touying:0.8.0": *
#import themes.simple: *

#set document(title: [How much rain one roof can catch], author: "R. Menon")
#show: simple-theme.with(
  aspect-ratio: "16-9",
  config-common(new-section-slide-fn: none),
)

#title-slide[
  #text(1.4em, weight: "bold")[How much rain one roof can catch]

  R. Menon, Station A rain gauge team
]

= The question

== Three questions

- How much rain falls on the roof in July?
#pause
- How much of it reaches the pipe?
#pause
- Where does the water go?

= The answer

== The volume a roof catches

$ V = A times R times C $
#pause
Roof area $A = 120 "m"^2$, July rain $R = 0.655 "m"$, runoff share $C = 0.8$.
#pause
$ V = #alert[$62.88 "m"^3$], "about 62,880 litres." $

== Where the water goes

A 10,000-litre tank holds part of it.

#only(1)[The tank fills in the first rainy week.]
#only(2)[The other 52,880 litres go to a recharge pit.]

#uncover(2)[Plan the pit before the tank.]

pages.py

"""Prints what each page of a PDF holds, one line a page: its number, then its lines of text joined by " | ".

    python3 pages.py talk.pdf

The text comes from pdftotext (poppler), one page at a time, so a slide that shows one more item than the page before
it shows one more piece here too. Empty lines are left out.
"""

import subprocess
import sys


def page_count(pdf):
    info = subprocess.run(["pdfinfo", pdf], capture_output=True, text=True, check=True).stdout
    return int(next(line.split()[-1] for line in info.splitlines() if line.startswith("Pages:")))


def page_text(pdf, number):
    done = subprocess.run(["pdftotext", "-f", str(number), "-l", str(number), pdf, "-"],
                          capture_output=True, text=True, check=True)
    return [line.strip() for line in done.stdout.splitlines() if line.strip()]


def main(pdf):
    for number in range(1, page_count(pdf) + 1):
        print(f"page {number}: " + " | ".join(page_text(pdf, number)))


if __name__ == "__main__":
    main(sys.argv[1])
The pages of talk-touying.typ Python · touying_session.py
from session import files, packages, run

# talk-touying.typ: the talk of the Beamer lesson in Touying's simple theme. Touying 0.8.0 and the one package it
# imports itself, uniwarn 0.1.1, are put into the session's package cache, where Typst keeps a downloaded package.
packages("touying:0.8.0", "uniwarn:0.1.1")
files("talk/talk-touying.typ", "pages.py")
run("""
typst compile talk-touying.typ
pdfinfo talk-touying.pdf | grep -E "^(Title|Pages|Page size|Tagged)"
python3 pages.py talk-touying.pdf
""")

Output

$ typst compile talk-touying.typ
$ pdfinfo talk-touying.pdf | grep -E "^(Title|Pages|Page size|Tagged)"
Title:           How much rain one roof can catch
Tagged:          yes
Pages:           9
Page size:       841.89 x 473.563 pts
$ python3 pages.py talk-touying.pdf
page 1: How much rain one roof can catch | R. Menon, Station A rain gauge team
page 2: The question | Three questions | • How much rain falls on the roof in July? | 1/3
page 3: The question | Three questions | • How much rain falls on the roof in July? | • How much of it reaches the pipe? | 1/3
page 4: The question | Three questions | • How much rain falls on the roof in July? | • How much of it reaches the pipe? | • Where does the water go? | 1/3
page 5: The answer | The volume a roof catches | 𝑉 =𝐴×𝑅×𝐶 | 2/3
page 6: The answer | The volume a roof catches | 𝑉 =𝐴×𝑅×𝐶 | Roof area 𝐴 = 120m2 , July rain 𝑅 = 0.655 m, runoff share 𝐶 = 0.8. | 2/3
page 7: The answer | The volume a roof catches | 𝑉 =𝐴×𝑅×𝐶 | Roof area 𝐴 = 120m2 , July rain 𝑅 = 0.655 m, runoff share 𝐶 = 0.8. | 𝑉 = 62.88m3 , about 62,880 litres. | 2/3
page 8: The answer | Where the water goes | A 10,000-litre tank holds part of it. | The tank fills in the first rainy week. | 3/3
page 9: The answer | Where the water goes | A 10,000-litre tank holds part of it. | The other 52,880 litres go to a recharge pit. | Plan the pit before the tank. | 3/3

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 touying_session.py

Typst wrote 9 pages, as Beamer did, on pages of 841.89 by 473.563 points, which is 297 by 167 mm: the simple theme sets its text at 25 points on that larger page. The header of each slide names its section, and the footer counts slides, not pages, so the three subslides of “Three questions” all say 1/3. pdfinfo found the PDF tagged, because Typst writes tagged PDF unless told not to. Step through how the source became those pages:

Interactive · stage by stage How talk-touying.typ becomes 9 pages Step through the source from the title slide to the last heading and watch the pages it makes.
How talk-touying.typ becomes 9 pages, step 7 of 7. Showing #title-slide[…], = The question, == Three questions (two #pause: pages 2 to 4), = The answer, == The volume a roof… (two #pause: pages 5 to 7), == Where the water goes (#only and #uncover: pages 8, 9). In focus: == Where the water goes.#title-slide[…]page 1= The questiona section: no page== Three questionstwo #pause: pages 2 to 4= The answera section: no page== The volume a roof…two #pause: pages 5 to 7== Where the water goes#only and #uncover: pages 8, 9

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. The title slide. #title-slide[…] is a slide function of the simple theme. It makes page 1 and does not count towards the slide numbers in the footer.
  2. A section, but no page. A first-level heading starts a section. The simple theme would give it a slide of its own; config-common(new-section-slide-fn: none) turns that off, so the section only names the header of the slides that follow.
  3. A heading of level 2 is a slide. Each second-level heading starts a new slide, and everything up to the next heading of level 1 or 2 is its body.
  4. #pause splits it. Two #pause marks split the slide into three subslides, pages 2, 3 and 4, each with one more question. The footer says 1/3 on all three: it counts slides, not pages.
  5. The second section. The next first-level heading changes the section, so the header of the following slides reads The answer.
  6. The formula slide. The volume slide has two #pause marks as well: the equation, then the numbers, then the result, pages 5 to 7.
  7. #only and #uncover. #only(2) and #uncover(2) ask for a second subslide, so the last slide makes pages 8 and 9. #only takes its line away on the other subslide; #uncover keeps its space, as in Beamer.

The simple theme would also give each section a slide of its own; config-common(new-section-slide-fn: none) turns that off here, so the page count matches the Beamer talk. One more mark is worth knowing: #meanwhile sets the subslide count back to 1, so what follows it appears in step with what came before it, for example a picture that stays beside a list whose items come in one by one.

One look for a whole deck

A theme is a show rule, so it changes every slide at once. Change one colour without changing theme with config-colors(primary: …) in the same simple-theme.with(…) call.

Checkpoint

Check: Touying's headings, pauses and subslides

  1. Question 1 of 2 A Touying slide, == Steps, holds four lines with a #pause between each two of them. How many pages does it make?

    Type a number.

    A hint

    Each #pause starts a subslide, and each subslide is a page.

    Show the answer: Question 1 of 2

    Answer: 4 pages

    Three pauses split the slide into four subslides, one page each, as two pauses gave the three pages of "Three questions".

  2. Question 2 of 2 Pages 2, 3 and 4 of talk-touying.pdf all show 1/3 in the footer. Why not 2/9, 3/9 and 4/9?
    Show the answer: Question 2 of 2

    Answer: The footer counts slides, and the three pages are subslides of the first counted slide

    A slide is what a heading starts; a page is one subslide of it. Footers here count slides.

Polylux builds each slide from a #slide block, and its reveals need a check

Polylux keeps to fewer ideas: each #slide[…] block is a slide, and you size the page and the text yourself with #set page(paper: "presentation-16-9") and #set text(size: 25pt). It has no themes in the package; its Universe page points to templates instead. Inside a slide, #uncover("2-") and #only(2) take the same specifications as Beamer’s, written as a number or as a string such as "2-", and #show: later hides everything after it until the next subslide, like \pause. talk-polylux.typ is the same talk, and bullets.typ reveals two items in three ways:

The talk in Polylux, and three ways to reveal a list

talk/talk-polylux.typ

#import "@preview/polylux:0.4.0": *

#set document(title: [How much rain one roof can catch], author: "R. Menon")
#enable-handout-mode(sys.inputs.at("handout", default: "no") == "yes")
#set page(paper: "presentation-16-9")
#set text(size: 25pt)

#slide[
  #set align(horizon)
  #text(1.4em, weight: "bold")[How much rain one roof can catch]

  R. Menon, Station A rain gauge team
]

#slide[
  == Three questions

  // Each item in a block of its own, so that a hidden item hides its bullet too.
  #uncover("1-", block[- How much rain falls on the roof in July?])
  #uncover("2-", block[- How much of it reaches the pipe?])
  #uncover("3-", block[- Where does the water go?])
]

#slide[
  == The volume a roof catches

  $ V = A times R times C $
  #show: later
  Roof area $A = 120 "m"^2$, July rain $R = 0.655 "m"$, runoff share $C = 0.8$.
  #show: later
  $ V = 62.88 "m"^3, "about 62,880 litres." $
]

#slide[
  == Where the water goes

  A 10,000-litre tank holds part of it.

  #only(1)[The tank fills in the first rainy week.]
  #only(2)[The other 52,880 litres go to a recharge pit.]

  #uncover(2)[Plan the pit before the tank.]
]

talk/bullets.typ

#import "@preview/polylux:0.4.0": *

#set page(paper: "presentation-16-9")
#set text(size: 25pt)

#slide[
  == item-by-item
  #item-by-item[
    - How much rain falls on the roof in July?
    - How much of it reaches the pipe?
  ]
]

#slide[
  == uncover, one block per item
  #uncover("1-", block[- How much rain falls on the roof in July?])
  #uncover("2-", block[- How much of it reaches the pipe?])
]

#slide[
  == show: later between items
  - How much rain falls on the roof in July?
  #show: later
  - How much of it reaches the pipe?
]
The pages of talk-polylux.typ and of bullets.typ Python · polylux_session.py
from session import files, packages, run

# talk-polylux.typ: the same talk with Polylux 0.4.0, then bullets.typ, three ways of revealing the same two items.
packages("polylux:0.4.0")
files("talk/talk-polylux.typ", "talk/bullets.typ", "pages.py")
run("""
typst compile talk-polylux.typ
pdfinfo talk-polylux.pdf | grep -E "^(Pages|Page size|Tagged)"
python3 pages.py talk-polylux.pdf
typst compile bullets.typ
python3 pages.py bullets.pdf
""")

Output

$ typst compile talk-polylux.typ
$ pdfinfo talk-polylux.pdf | grep -E "^(Pages|Page size|Tagged)"
Tagged:          yes
Pages:           9
Page size:       841.89 x 473.563 pts
$ python3 pages.py talk-polylux.pdf
page 1: How much rain one roof can catch | R. Menon, Station A rain gauge team
page 2: Three questions | • How much rain falls on the roof in July?
page 3: Three questions | • How much rain falls on the roof in July? | • How much of it reaches the pipe?
page 4: Three questions | • How much rain falls on the roof in July? | • How much of it reaches the pipe? | • Where does the water go?
page 5: The volume a roof catches | 𝑉 =𝐴×𝑅×𝐶
page 6: The volume a roof catches | 𝑉 =𝐴×𝑅×𝐶 | Roof area 𝐴 = 120m2 , July rain 𝑅 = 0.655 m, runoff share 𝐶 = 0.8.
page 7: The volume a roof catches | 𝑉 =𝐴×𝑅×𝐶 | Roof area 𝐴 = 120m2 , July rain 𝑅 = 0.655 m, runoff share 𝐶 = 0.8. | 𝑉 = 62.88m3 , about 62,880 litres.
page 8: Where the water goes | A 10,000-litre tank holds part of it. | The tank fills in the first rainy week.
page 9: Where the water goes | A 10,000-litre tank holds part of it. | The other 52,880 litres go to a recharge pit. | Plan the pit before the tank.
$ typst compile bullets.typ
$ python3 pages.py bullets.pdf
page 1: item-by-item | • How much rain falls on the roof in July? | •
page 2: item-by-item | • How much rain falls on the roof in July? | • How much of it reaches the pipe?
page 3: uncover, one block per item | • How much rain falls on the roof in July?
page 4: uncover, one block per item | • How much rain falls on the roof in July? | • How much of it reaches the pipe?
page 5: show: later between items | • How much rain falls on the roof in July? | •
page 6: show: later between items | • How much rain falls on the roof in July? | • How much of it reaches the pipe?

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 polylux_session.py

The talk made 9 pages again, on the same 297 by 167 mm page. bullets.typ shows why its list is written the way it is. With #item-by-item, Polylux’s own helper for lists, page 1 of the slide held the first question and a bare “•”: Polylux 0.4.0 under Typst 0.15.1 hid the second item’s words but drew its bullet. #show: later between the two items left the same bare bullet on page 5. With each item in a block of its own inside #uncover, the bullet went with its item, and page 3 held only the first question.

The same steps, written in each of the three systems:

What it does Beamer Touying Polylux
a slide \begin{frame}{Title} == Title #slide[== Title …]
hide the rest until the next step \pause #pause #show: later
from step 2 on, space kept \uncover<2->{…} #uncover("2-")[…] #uncover("2-")[…]
only on step 1, no space \only<1>{…} #only(1)[…] #only(1)[…]

Trusting a reveal you have not looked at

Wrong: revealing a list with #item-by-item[…] or #show: later and presenting it unseen, so the audience sees an empty bullet for every item still to come. Right: look at every subslide of the PDF, or run pages.py on it, before the talk; on these versions, put each item in its own block.

Checkpoint

Check: Polylux slides and checking their reveals

  1. Question 1 of 2 What does #show: later do in the middle of a Polylux #slide?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It hides everything after it until the next subslide, as \pause does in Beamer

    #show: later wraps the rest of the slide body in later, which shows it from the next subslide on.

  2. Question 2 of 2 With Polylux 0.4.0 and Typst 0.15.1, #item-by-item left a bare bullet under the first question. Which change fixed it in bullets.typ?

    Choose one answer.

    A hint

    Look at what each slide of bullets.pdf printed on its first page.

    Show the answer: Question 2 of 2

    Answer: Each item in a block of its own inside #uncover("2-", …)

    Look at every subslide of a deck before you present it, and keep the variant that hides whole items.

One source file gives the slides and the handout

Both packages make the handout from the same file, and neither needs the source edited: Typst’s --input passes a value on the command line that the source reads from sys.inputs. Touying reads export-mode itself, so --input export-mode=handout gives its handout. Polylux switches with #enable-handout-mode(true); talk-polylux.typ passes it sys.inputs.at("handout", default: "no") == "yes", so --input handout=yes does the same:

The two handouts, page by page Python · handout_session.py
from session import files, packages, run

# One source file each, two PDFs each: Touying's export mode and Polylux's handout mode, both chosen on the command
# line with --input, which the source reads through sys.inputs.
packages("touying:0.8.0", "uniwarn:0.1.1", "polylux:0.4.0")
files("talk/talk-touying.typ", "talk/talk-polylux.typ", "pages.py")
run("""
typst compile --input export-mode=handout talk-touying.typ touying-handout.pdf
python3 pages.py touying-handout.pdf
typst compile --input handout=yes talk-polylux.typ polylux-handout.pdf
python3 pages.py polylux-handout.pdf
""")

Output

$ typst compile --input export-mode=handout talk-touying.typ touying-handout.pdf
$ python3 pages.py touying-handout.pdf
page 1: How much rain one roof can catch | R. Menon, Station A rain gauge team
page 2: The question | Three questions | • How much rain falls on the roof in July? | • How much of it reaches the pipe? | • Where does the water go? | 1/3
page 3: The answer | The volume a roof catches | 𝑉 =𝐴×𝑅×𝐶 | Roof area 𝐴 = 120m2 , July rain 𝑅 = 0.655 m, runoff share 𝐶 = 0.8. | 𝑉 = 62.88m3 , about 62,880 litres. | 2/3
page 4: The answer | Where the water goes | A 10,000-litre tank holds part of it. | The other 52,880 litres go to a recharge pit. | Plan the pit before the tank. | 3/3
$ typst compile --input handout=yes talk-polylux.typ polylux-handout.pdf
$ python3 pages.py polylux-handout.pdf
page 1: How much rain one roof can catch | R. Menon, Station A rain gauge team
page 2: Three questions | • How much rain falls on the roof in July? | • How much of it reaches the pipe? | • Where does the water go?
page 3: The volume a roof catches | 𝑉 =𝐴×𝑅×𝐶 | Roof area 𝐴 = 120m2 , July rain 𝑅 = 0.655 m, runoff share 𝐶 = 0.8. | 𝑉 = 62.88m3 , about 62,880 litres.
page 4: Where the water goes | A 10,000-litre tank holds part of it. | The tank fills in the first rainy week. | Plan the pit before the tank.
page 5: Where the water goes | A 10,000-litre tank holds part of it. | The other 52,880 litres go to a recharge pit. | Plan the pit before the tank.

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 handout_session.py

The two handouts differ on the last slide. Touying’s handout keeps only the last subslide of each slide, which its configuration describes as the default, so the line that #only(1) showed on the first subslide, “The tank fills in the first rainy week”, is not in it: 4 pages. Polylux’s handout mode switches off the reveals that keep their space, #uncover and #show: later, but the Polylux book says that only and alternatives are exempt, so the last slide still made two pages: 5 pages. With the Beamer talk beside them, each system treats that last slide its own way:

The last slide, with only(1), only(2) and uncover(2) In the handout
Beamer, class option handout one page with both \only lines, unless one says handout:0
Touying, export-mode=handout one page: the last subslide, without the only(1) line
Polylux, enable-handout-mode(true) two pages: one with each only line

To keep a line in Touying’s handout, show it on the last subslide too, pick other subslides with config-common(handout-subslides: …), or add text for the handout alone with #handout-only[…].

Key idea

The same only gave three handouts in three systems, so read the handout page by page before you share it, and decide for each only which version must keep its line.

Checkpoint

Check: Handouts from one source

  1. Question 1 of 2 In talk-touying.typ, "The tank fills in the first rainy week." is inside #only(1). Where is it in the handout made with --input export-mode=handout?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Nowhere; the handout keeps only the last subslide of each slide

    To keep such a line, show it on the last subslide too, choose other subslides with handout-subslides, or write it in #handout-only.

  2. Question 2 of 2 A Polylux slide holds #only(1)[A], #only(2)[B], #only(3)[C] and #uncover("2-")[D]. How many pages does it make in handout mode?

    Type a number.

    A hint

    Handout mode switches off the reveals that keep their space; only is exempt.

    Show the answer: Question 2 of 2

    Answer: 3 pages

    #uncover stops mattering in handout mode, but each #only still asks for its own subslide, so the slide still needs three; the talk's last slide made 2 handout pages the same way.

Pin every package to the exact version you built with

#import "@preview/touying:0.8.0": * names a namespace, a package and a version, and the package repository’s rules say you must always give the full version. Typst downloads the package on first use and keeps it in its package cache: The folder where Typst keeps each package it has downloaded, so that later imports of the same version need no network access.; the same rules say that importing a cached package does not use the network. A new build machine has an empty cache, and a machine without network access then cannot build at all. pins_session.py plays that machine: a proxy that refuses every connection stands in for the missing network, and the project keeps its packages in a folder of its own, vendor, in Typst’s own layout, preview/<name>/<version>/. Step through where Typst looked:

Interactive · stage by stage Where Typst looks for @preview/touying:0.8.0 Step through one import on a build machine without network access, first without the vendor folder, then with it.
Where Typst looks for @preview/touying:0.8.0, step 6 of 6. Showing import touying:0.8.0, Package path (--package-path vendor), Package cache (empty: nothing downloaded), packages.typst.org. In focus: Package path.import touying:0.8.0namespace, name, versionPackage path--package-path vendorPackage cacheempty: nothing downloadedpackages.typst.orgdownload into the cache

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The import names a version. The import names the namespace preview, the package touying and the full version 0.8.0. A version without its patch number, 0.8, stopped the compile before any search.
  2. First the package path. Typst first looks in the package path. With no --package-path and an empty data folder, there is nothing there yet.
  3. Then the cache. Next the package cache, where a package lands after its first download. On a fresh build machine it is empty too.
  4. Then the network. Only now does Typst download touying-0.8.0.tar.gz. A proxy that refuses every connection stands in for a machine without network access, so the download fails.
  5. The build stops. The compile ended with "failed to download package" and exit status 1, at the line of the import.
  6. With the vendor folder. With --package-path vendor, the first place Typst looks holds preview/touying/0.8.0 and preview/uniwarn/0.1.1, so the compile finished without the network and read 28 and 3 files from them.

Two helpers: the files a compile read, by package, and the size of each vendored package

deps.py

"""Summarises the dependency list that `typst compile --deps deps.json` writes: how many files the compile read from
each package, by namespace, name and version, and how many from the project itself.

    python3 deps.py deps.json

A package folder is found by its path, …/<namespace>/<name>/<version>/…, so the summary is the same wherever the
packages were kept: Typst's cache, a package path or a vendor folder of the project.
"""

import json
import re
import sys
from collections import Counter


def main(path):
    inputs = json.load(open(path, encoding="utf-8"))["inputs"]
    packages = Counter()
    project = 0
    for name in inputs:
        found = re.search(r"/(preview|local)/([a-z0-9-]+)/(\d+\.\d+\.\d+)/", name)
        if found:
            packages["@{}/{}:{}".format(*found.groups())] += 1
        else:
            project += 1
    for package, count in sorted(packages.items()):
        print(f"{package}: {count} files")
    print(f"the project itself: {project} file" + ("s" if project != 1 else ""))


if __name__ == "__main__":
    main(sys.argv[1])

sizes.py

"""Prints how many files each vendored package folder holds and how large they are together, in KB (1,000 bytes).

    python3 sizes.py vendor

The folder has Typst's own layout, <namespace>/<name>/<version>/, as a package path or the package cache does.
"""

import sys
from pathlib import Path


def main(root):
    for folder in sorted(Path(root).glob("*/*/*")):
        files = [f for f in folder.rglob("*") if f.is_file()]
        size = sum(f.stat().st_size for f in files)
        namespace, name, version = folder.parts[-3:]
        print(f"@{namespace}/{name}:{version}: {len(files)} files, {size / 1000:.1f} KB")


if __name__ == "__main__":
    main(sys.argv[1])
An offline build, with and without the vendor folder Python · pins_session.py
from session import files, run

# The packages as a project keeps them: typst-packages/ of this folder copied to ~/demo/vendor, and nothing in Typst's
# package cache. A proxy address that refuses every connection (port 9 on this computer) stands in for a build machine
# without network access. Then the files the compile read, by package; what Typst says about versions that are not
# exact; the compiler version Touying's manifest asks for; and the size of each vendored package.
files("talk/talk-touying.typ", "typst-packages=vendor", "deps.py", "sizes.py")
OFFLINE = "HTTPS_PROXY=http://127.0.0.1:9 HTTP_PROXY=http://127.0.0.1:9"
run(f"""
!{OFFLINE} typst compile talk-touying.typ
{OFFLINE} typst compile --package-path vendor --deps deps.json talk-touying.typ
python3 deps.py deps.json
sed 's/touying:0.8.0/touying:0.8/' talk-touying.typ > loose.typ
!{OFFLINE} typst compile --package-path vendor loose.typ
grep -E "^(version|compiler)" vendor/preview/touying/0.8.0/typst.toml
python3 sizes.py vendor
""")

Output

$ HTTPS_PROXY=http://127.0.0.1:9 HTTP_PROXY=http://127.0.0.1:9 typst compile talk-touying.typ
error: failed to download package (https://packages.typst.org/preview/touying-0.8.0.tar.gz: Connection Failed: Connect error: Connection refused (os error 61))
  ┌─ talk-touying.typ:1:8
  │
1 │ #import "@preview/touying:0.8.0": *
  │         ^^^^^^^^^^^^^^^^^^^^^^^^

[exit status 1]
$ HTTPS_PROXY=http://127.0.0.1:9 HTTP_PROXY=http://127.0.0.1:9 typst compile --package-path vendor --deps deps.json talk-touying.typ
$ python3 deps.py deps.json
@preview/touying:0.8.0: 28 files
@preview/uniwarn:0.1.1: 3 files
the project itself: 1 file
$ sed 's/touying:0.8.0/touying:0.8/' talk-touying.typ > loose.typ
$ HTTPS_PROXY=http://127.0.0.1:9 HTTP_PROXY=http://127.0.0.1:9 typst compile --package-path vendor loose.typ
error: version number is missing patch version
  ┌─ loose.typ:1:8
  │
1 │ #import "@preview/touying:0.8": *
  │         ^^^^^^^^^^^^^^^^^^^^^^

[exit status 1]
$ grep -E "^(version|compiler)" vendor/preview/touying/0.8.0/typst.toml
version = "0.8.0"
compiler = "0.15.0"
$ python3 sizes.py vendor
@preview/polylux:0.4.0: 7 files, 14.8 KB
@preview/touying:0.8.0: 29 files, 759.5 KB
@preview/uniwarn:0.1.1: 4 files, 5.3 KB

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pins_session.py

Without the folder the compile stopped with “failed to download package”. With --package-path vendor it finished, and --deps listed what it read: 28 files of Touying 0.8.0, 3 files of uniwarn 0.1.1, a package that Touying itself imports, and the talk. So pinning Touying pins uniwarn too, and a vendor folder must hold both. An import of touying:0.8 stopped before any search with “version number is missing patch version”. Touying’s manifest, typst.toml, asks for Typst 0.15.0 or newer, so the Typst version belongs in the build notes as well. The vendor folder costs little: 759.5 KB in 29 files for Touying, 14.8 KB for Polylux and 5.3 KB for uniwarn.

Versions on this page

Every page here was built with Typst 0.15.1, Touying 0.8.0, uniwarn 0.1.1 and Polylux 0.4.0. Polylux’s own README warns that there are no backwards compatibility guarantees, which is one more reason to name the version.

Checkpoint

Check: Pinning and vendoring packages

  1. Question 1 of 2 What does Typst 0.15.1 do with #import "@preview/touying:0.8": *?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It stops at once with "version number is missing patch version"

    Every import names one exact version, so the version is in the source and the build is the same everywhere.

  2. Question 2 of 2 A project vendors its packages for builds without network access. Which folders must its vendor folder hold for talk-touying.typ?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: preview/touying/0.8.0 and preview/uniwarn/0.1.1

    typst compile --deps lists every file a compile read, including the packages that packages import.

Choose by what the talk needs

The three systems made the same 9 pages; they differ in what they ask of you. The build times below are from one computer and depend on what else it was doing; the pages do not:

The same talk in Beamer, Touying and Polylux, timed Python · timing_session.py
"""Times the same talk in three systems on this computer: Beamer with latexmk from a clean folder (it runs pdfLaTeX as
often as the deck needs), and Typst with Touying and with Polylux, three compiles each (the table gives the median).
A time is wall-clock time, so it depends on the computer and on what else it is doing; the pages do not.
"""

import re
import statistics
import time

from session import files, packages, shell

packages("touying:0.8.0", "uniwarn:0.1.1", "polylux:0.4.0")
files("talk/talk-beamer.tex", "talk/talk-touying.typ", "talk/talk-polylux.typ")


def timed(command):
    """Runs `command` in ~/demo and returns the seconds it took and what it printed; stops if it fails."""
    start = time.perf_counter()
    status, output = shell(command)
    seconds = time.perf_counter() - start
    if status != 0:
        raise SystemExit(f"`{command}` ended with exit status {status}:\n{output[-1500:]}")
    return seconds, output


def pages(pdf):
    _, info = shell(f"pdfinfo {pdf}")
    return int(re.search(r"^Pages:\s+(\d+)", info, re.M).group(1))


seconds, output = timed("latexmk -pdf -interaction=nonstopmode talk-beamer.tex 2>&1")
runs = len(re.findall(r"^Run number \d+ of rule 'pdflatex'", output, re.M))
rows = [("Beamer, latexmk", f"{runs} pdfLaTeX runs", pages("talk-beamer.pdf"), seconds)]
for name, source in [("Touying", "talk-touying"), ("Polylux", "talk-polylux")]:
    times = [timed(f"typst compile {source}.typ")[0] for _ in range(3)]
    rows.append((f"{name}, typst compile", "median of 3", pages(f"{source}.pdf"), statistics.median(times)))

print(f"{'build':<23}{'runs':<17}{'pages':>5}{'seconds':>9}")
for what, runs, page_count, taken in rows:
    print(f"{what:<23}{runs:<17}{page_count:>5}{taken:>9.2f}")

Output

build                  runs             pages  seconds
Beamer, latexmk        2 pdfLaTeX runs      9     8.54
Touying, typst compile median of 3          9     0.25
Polylux, typst compile median of 3          9     0.11

This output changes from run to run: the build times are wall-clock times, which change with the computer and its load on every run

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 timing_session.py

latexmk needed two pdfLaTeX runs and 8.54 seconds for the Beamer talk; Touying took 0.25 seconds and Polylux 0.11. The Typst builds were more than thirty times faster, which is what you notice while you edit. Answer the three questions for your own talk:

Interactive · answer and compare Touying, Polylux or Beamer for your talk? Answer three questions about your talk; the table marks the systems that fit and why.
Do you want a ready-made look, rather than writing your own set and show rules?
Do your co-authors and your templates already use LaTeX?
Will you rebuild the deck after every small edit while you write?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Three ways to build the same four-slide talk, with the numbers of the recorded builds
System Ready-made look Handout from one source Build of the talk What a build needs
Touying 0.8.0 Gives you: six themes, headings as slides, export modes Good six themes in the package; simple was used here Good --input export-mode=handout: 4 pages, the last subslide of each slide Good 0.25 s for 9 pages Mixed Typst 0.15.0 or newer, and Touying with uniwarn: 765 KB to vendor
Polylux 0.4.0 Gives you: #slide blocks and reveals; the look is yours Poor none in the package; templates on Typst Universe Mixed enable-handout-mode: 5 pages, because only is kept Good 0.11 s for 9 pages Good one package of 14.8 KB
Beamer 3.78 Gives you: LaTeX frames, themes and modes Good many themes with the class, more on CTAN Good class option handout: 4 pages, with mode specifications to choose Mixed 8.54 s with latexmk, which ran pdfLaTeX twice Mixed a TeX distribution with beamer and the theme packages
How the answers decide
  • “Do you want a ready-made look, rather than writing your own set and show rules?” Yes, a theme: adds a caveat to Polylux 0.4.0. Polylux has no themes in the package; its Universe page points to templates, or you write the look with set and show rules.
  • “Do your co-authors and your templates already use LaTeX?” Yes, LaTeX: adds a caveat to Touying 0.8.0 and Polylux 0.4.0. A Typst deck cannot reuse a LaTeX preamble or Beamer theme; the talk and its macros would be written again.
  • “Will you rebuild the deck after every small edit while you write?” Yes, many builds: adds a caveat to Beamer 3.78. latexmk took 8.54 s for the 9-page talk, against under a second for either Typst package; label the frame you edit and use \includeonlyframes.

In short: Touying for a ready-made theme and a handout from one flag; Polylux for a small package whose look you write yourself; Beamer when co-authors and templates are already in LaTeX.

Checkpoint

Check: Choosing a system for a talk

  1. One question A teacher wants a ready-made look, a handout from the same file without editing it, and nobody on the course writes LaTeX. Which system fits best?

    Choose one answer.

    Show the answer: One question

    Answer: Touying

    Choose by what the talk needs: a look, a handout, the people who edit it and how often it is built.

Practise: count the pages, check the pins

Two checks to run on a Touying deck before you share it: the first counts the pages a source makes, as the talk and as the handout; the second finds imports whose version is not exact. Run each step’s sample tests in the browser; a first hint is there when you need one:

Code lab · 2 steps

Check a Touying deck before you share it

Count the pages a Touying source makes as slides and as a handout, then list the package imports that do not name a full version.

  1. Count the pages a Touying deck makes To do
  2. Find the package imports that are not pinned To do

Exercise · Medium · Python

Count the pages a Touying deck makes

Before you share a deck, check how many pages it should have. Write page_count(source, handout=False) in pages.py. source is the text of a Touying deck in the style of talk-touying.typ, with the simple theme and no section slides; return the number of PDF pages it makes, as the talk or, with handout=True, as the handout.

  • A line that starts with #title-slide is a slide of its own. A line that starts with == starts a new slide.
  • A line that starts with = (one = and a space) starts a section, which makes no page.
  • Inside a slide, every line that is #pause (spaces around it allowed) adds one subslide.
  • #only( or #uncover( followed by a number, or by a quoted specification such as "2-", asks for at least that many subslides. A slide has as many subslides as its pauses give or its largest such number asks for, whichever is more.
  • In the handout, every slide is one page. Lines before the first slide (imports, set and show rules) make no page.

Starter code · pages.py

def page_count(source, handout=False):
    """Return how many pages this Touying source makes, as the talk or as the handout."""
    # Replace this line: keep one count of subslides per slide while you read the lines.
    return 0
The sample tests · test_pages.py
from pages import page_count

TALK = '''#import "@preview/touying:0.8.0": *
#import themes.simple: *
#show: simple-theme.with(aspect-ratio: "16-9", config-common(new-section-slide-fn: none))

#title-slide[
  How much rain one roof can catch
]

= The question

== Three questions

- How much rain falls on the roof in July?
#pause
- How much of it reaches the pipe?
#pause
- Where does the water go?

= The answer

== The volume a roof catches

$ V = A times R times C $
#pause
Roof area, rain and runoff share.
#pause
The result.

== Where the water goes

A 10,000-litre tank holds part of it.

#only(1)[The tank fills in the first rainy week.]
#only(2)[The other 52,880 litres go to a recharge pit.]

#uncover(2)[Plan the pit before the tank.]
'''


def test_the_talk_of_the_lesson():
    """talk-touying.typ made 9 pages and a 4-page handout"""
    assert page_count(TALK) == 9
    assert page_count(TALK, handout=True) == 4


def test_a_slide_without_marks_is_one_page():
    assert page_count("== One\nText.\n== Two\nMore text.\n") == 2


def test_sections_and_set_rules_make_no_page():
    assert page_count("#set text(size: 25pt)\n= Part one\n== A slide\nText.\n") == 1


def test_a_quoted_specification_counts_its_number():
    assert page_count('== Steps\nFirst.\n#uncover("3-")[Third.]\n') == 3


def test_pauses_and_numbers_take_the_larger():
    source = "== Mixed\nA\n#pause\nB\n#only(4)[D]\n"
    assert page_count(source) == 4
    assert page_count(source + "#pause\nE\n#pause\nF\n") == 4
    assert page_count("== Many\nA\n  #pause  \nB\n#pause\nC\n#pause\nD\n#pause\nE\n#only(2)[x]\n") == 5


def test_an_empty_deck():
    assert page_count("") == 0
A hint

Go through the lines once and keep two numbers for each slide: its pauses and the largest number an #only or #uncover asks for. A new slide starts both at 0 and 1. At the end, a slide makes max(pauses + 1, largest) pages; the talk is the sum over the slides and the handout their number.

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

Find the package imports that are not pinned

A deck builds the same way next year only if each package it imports is named with a full version. Write loose_imports(source) in pins.py. It returns, in the order they appear, the package specifications of the #import lines of source that do not name a full version.

  • A package import looks like #import "@preview/touying:0.8.0": *: an @, a namespace, a /, a name, then a colon and the version. Only the text in quotes after #import counts; an import of a file, such as #import "theme.typ": *, is not a package and is never returned.
  • A full version is three whole numbers separated by dots, as in 0.8.0 or 1.10.2. Return the specification, such as "@preview/touying:0.8", when the version has fewer or more parts, has anything but digits in a part, or is missing altogether ("@preview/polylux").
  • Ignore spaces before #import and anything after the closing quote.

Starter code · pins.py

def loose_imports(source):
    """Return the package specifications in source whose version is not a full x.y.z version."""
    # Replace this line: find each #import of a package and check its version.
    return []
The sample tests · test_pins.py
from pins import loose_imports


def test_the_lessons_imports_are_pinned():
    source = '#import "@preview/touying:0.8.0": *\n#import "@preview/polylux:0.4.0": *\n'
    assert loose_imports(source) == []


def test_a_version_without_its_patch_number():
    """the import that stopped with "version number is missing patch version" """
    assert loose_imports('#import "@preview/touying:0.8": *') == ["@preview/touying:0.8"]


def test_a_missing_version():
    assert loose_imports('#import "@preview/polylux"') == ["@preview/polylux"]


def test_files_are_not_packages():
    assert loose_imports('#import "theme.typ": *\n#import "lib/slides.typ": slide\n') == []


def test_odd_versions():
    source = '\n'.join([
        '#import "@preview/cetz:0.5.2.1": canvas',
        '#import "@preview/zero:v0.7.1": num',
        '  #import "@local/house-style:1.10.2": *',
        '#import "@preview/fletcher:latest": diagram',
    ])
    assert loose_imports(source) == ["@preview/cetz:0.5.2.1", "@preview/zero:v0.7.1", "@preview/fletcher:latest"]
A hint

A regular expression such as #import\s+"(@[^"]+)" finds the package specifications. Split each at its last :; when there is no colon, the version is missing. Then check that the version splits at . into exactly three parts, each made of digits only.

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.

The practice questions take the five ideas in turn and come back to any you miss:

Practice that adapts to you

Practise this lesson

14 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 14 With Touying and its default slide level, which line starts a new slide?

    Choose one answer.

    Show the answer: Question 1 of 14

    Answer: == Where the water goes

    In Touying a deck reads like a document: sections are level 1, slides level 2.

  2. Question 2 of 14 A Touying deck in the simple theme, with its section slides left on, has a title slide, 3 first-level headings and 5 second-level headings without any #pause. How many pages?

    Type a number.

    A hint

    The title slide is one page, each section gets a slide of its own, and each heading of level 2 is one page.

    Show the answer: Question 2 of 14

    Answer: 9 pages

    1 title page, 3 section slides and 5 slides give 9 pages; config-common(new-section-slide-fn: none) would leave 6.

  3. Question 3 of 14 On a Touying slide, three items appear one by one with #pause, and a picture beside them should be there from the first subslide. Which mark goes before the picture?

    Choose one answer.

    Show the answer: Question 3 of 14

    Answer: #meanwhile

    #meanwhile starts counting again from subslide 1, which is how two things appear in step.

  4. Question 4 of 14 What gives a Polylux deck its slide size?

    Choose one answer.

    Show the answer: Question 4 of 14

    Answer: A set rule of your own, such as #set page(paper: "presentation-16-9")

    The same paper size in both decks gave both the same 297 by 167 mm pages.

  5. Question 5 of 14 On a Polylux slide, what is the difference between #only(2)[…] and #uncover(2)[…] on subslide 1?

    Choose one answer.

    Show the answer: Question 5 of 14

    Answer: only takes no space there; uncover keeps the space and does not draw it

    only removes, uncover covers: the same rule in all three systems.

  6. Question 6 of 14 You move a Polylux deck to a newer Typst release the night before a talk. What do you check, and how?

    Choose one answer.

    Show the answer: Question 6 of 14

    Answer: Every subslide's page, for example with pages.py or by paging through the PDF, for bare bullets and lines in the wrong step

    Pin the compiler and the packages, and read the output after any change to either.

  7. Question 7 of 14 How does talk-polylux.typ know that typst compile was given --input handout=yes?

    Choose one answer.

    Show the answer: Question 7 of 14

    Answer: It reads sys.inputs, the dictionary of the --input values

    --input key=value is visible in the document as sys.inputs, always as a string.

  8. Question 8 of 14 A line in #only(1) must be in Touying's handout as well as on subslide 1. Which change is not one of the ways the lesson names?

    Choose one answer.

    Show the answer: Question 8 of 14

    Answer: Wrapping it in #uncover(1)

    Decide for each only what the handout needs, because the last subslide alone is what Touying keeps.

  9. Question 9 of 14 The last slide has only(1), only(2) and uncover(2). Which system's handout gives that slide two pages?

    Choose one answer.

    Show the answer: Question 9 of 14

    Answer: Polylux in handout mode

    One construct, three handouts: Beamer stacks, Touying keeps the last step, Polylux keeps the steps of only.

  10. Question 10 of 14 Which import names its package the way Typst requires?

    Choose one answer.

    Show the answer: Question 10 of 14

    Answer: #import "@preview/polylux:0.4.0": *

    The package repository's rules: you must always specify the full package version.

  11. Question 11 of 14 When does typst compile download a package?

    Choose one answer.

    Show the answer: Question 11 of 14

    Answer: When the package is neither in the package path nor in the package cache

    The cache makes later builds offline on one machine; a vendor folder makes them offline on every machine.

  12. Question 12 of 14 A CI job without network access fails with "failed to download package" for the slides. Which fix keeps every future build the same?

    Choose one answer.

    Show the answer: Question 12 of 14

    Answer: Commit the packages the deck reads to a vendor folder and build with --package-path vendor

    Pin the version in the source, keep the packages with the project, and name the Typst version too.

  13. Question 13 of 14 You want the smallest package to vendor and you will write the look yourself. Which system?

    Choose one answer.

    Show the answer: Question 13 of 14

    Answer: Polylux

    A small package suits a deck whose look you write yourself; a theme saves the work at the cost of more code.

  14. Question 14 of 14 Your department gives every speaker a Beamer theme with its logo, and your co-authors edit slides in LaTeX. What do you build with?

    Choose one answer.

    Show the answer: Question 14 of 14

    Answer: Beamer, with the department theme, and \includeonlyframes while you edit

    The best tool is the one the talk's people and templates already use, unless a need such as tagged PDF says otherwise.

Multiple Pages per Sheet (N-up) Put the handout's slides four or six to a sheet in the browser before you print it.

Interview questions

Warm-up (fresher to 2–5 years): how does Touying decide where one slide ends and the next begins?

It reads the headings. With the default slide level, a heading of level 2 starts a new slide and everything up to the next heading of level 1 or 2 is its body; a heading of level 1 starts a section, which the simple theme shows in the header and, unless you turn it off, as a slide of its own. Within a slide, each #pause starts a subslide, one PDF page, and #only or #uncover with a higher number ask for more. That is why talk-touying.typ gave 9 pages from four slides: 1 for the title, 3 and 3 for the two slides with two pauses each, and 2 for the slide with only(2). The footer counts slides, so the three subslides of “Three questions” all say 1/3.

Two harder questions follow, about a build that fails in CI and a handout that lost a line. Decide your first step before you open each one:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    Your Touying slides build on your laptop, but the CI job fails with "failed to download package" at the import line, and the job has no network access. Why does the build work on your laptop, and how do you make it the same on both, now and next year?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    After a lecture, a student says the handout you made with Touying's export-mode=handout is missing a sentence that was on the slides, although both come from the same file. How did the sentence get lost, and how do you fix the deck and keep it from happening again?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Touying turns second-level headings into slides and each #pause into a subslide: the talk came out as 9 tagged pages of 297 by 167 mm, with a footer that counts slides, 1/3, not pages.
  • Polylux builds slides from #slide blocks with Beamer-like specifications; on these versions #item-by-item and #show: later left bare bullets on the page, and one block per item inside #uncover fixed it.
  • --input export-mode=handout gave Touying’s 4-page handout, which keeps only each slide’s last subslide, while Polylux’s handout mode kept only and gave 5 pages; read a handout before you share it.
  • Imports name a full version, Touying also pulls in uniwarn 0.1.1, and a vendor folder with both, passed with --package-path, built the talk without network access.
  • The three systems made the same 9 pages; latexmk took 8.54 seconds where Touying took 0.25 and Polylux 0.11, so the choice rests on the look, the handout and the people who edit the deck.

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How does Touying split a document into slides?

    A first-level heading starts a section, a second-level heading starts a slide, and each #pause starts a subslide, which is one PDF page.

  2. What does the footer of Touying's simple theme count?

    Slides, not pages: the three subslides of one slide all say 1/3.

  3. What does #meanwhile do in Touying?

    It sets the subslide count back to 1, so what follows appears in step with what came before it, such as a picture that stays beside a list whose items appear one at a time.

  4. How does Polylux mark a slide and a reveal?

    Each #slide[...] block is a slide; #uncover("2-") and #only(2) take Beamer-like specifications, and #show: later hides the rest until the next subslide.

  5. Why give each Polylux list item a block of its own inside #uncover?

    On Polylux 0.4.0 with Typst 0.15.1, #item-by-item and #show: later left a bare bullet for each hidden item; one block per item inside #uncover took the bullet with it.

  6. How do you build a Touying handout without editing the source?

    typst compile --input export-mode=handout. It keeps only the last subslide of each slide, so a line shown only on an earlier subslide is not in it.

  7. How did the talk build without network access?

    From a vendor folder in Typst's preview/<name>/<version>/ layout, passed with --package-path, holding Touying 0.8.0 and uniwarn 0.1.1, the package Touying itself imports.

  8. How did Beamer, Touying and Polylux compare on the same talk?

    All three made the same 9 pages; latexmk took 8.54 s for Beamer, Touying 0.25 s and Polylux 0.11 s in the recorded run.

Terms in this lesson

package cache
The folder where Typst keeps each package it has downloaded, so that later imports of the same version need no network access.
subslide
In Touying and Polylux, one step of a slide, printed as one PDF page: a slide with two #pause marks has three subslides.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.