Composition & Scripting
Composition & Scripting
A course is not one deck. It's fifteen lectures that share a contact slide, a
notation convention, a footer and a look. This page is about not writing that
fifteen times.
Everything here expands at parse time. There is no runtime cost: by the
time the renderer sees your deck, the macros are gone and only slides remain.
The double operators
Reveals use the single operators >[…] and <[…]. Composition uses the
double ones, so the two never collide:
| Syntax | Effect |
|---|---|
<<[<name> …body…] | Define a macro |
>>[<name>] | Insert it |
>>[<name> k=v, …] | Insert it with parameters, available inside as /var[k] |
>>[<each> VAR in: a, b, c] … [[/each]] | Loop over values |
>>[path.tac] | Include another file |
Macros
A macro is a named chunk of .tac. Define it in the deck frontmatter, in atac.config, or in the body — defining it emits nothing by itself:
<<[<term> **/var[name]** — /var[def]]
>>[<term> name="KaTeX", def="client-side math engine, bundled offline"]
>>[<term> name="Shiki", def="the syntax highlighter used for code blocks"]Each k=v you pass becomes /var[k] inside the body. A macro body has to keep
its [ and ] balanced — that's what marks where the body ends.
Macros can hold whole slides, not just fragments:
<<[<contact>
## Where to find me ::contact
- E-mail: **you@example.org**
- Office: 42, block B
]Includes
>>[path.tac] pulls in another file; the included file's frontmatter is
dropped. Paths are always relative to the top-level .tac, even inside a
nested include.
Includes are resolved before macros, which makes a file a library of
slides: define the slides once as macros, include the library, insert them
where you want them.
// shared/common.tac — the library
<<[<contact>
## Where to find me ::contact
- E-mail: **you@example.org**
]
<<[<notices>
## Notices — week /var[week] ::notices
- Homework due Friday
- Exam on /var[date]
]// lecture01.tac — any deck that wants them
>>[shared/common.tac]
## A slide of this deck's own
…
>>[<notices> week="1", date=12/08]
>>[<contact>]Change the library, every deck follows. If a slide needs no variation at all,
skip the macro entirely: put it alone in a file and >>[shared/final.tac]
where it goes.
Or skip the include
A macro defined in a tac.config is available to every deck below it in the
tree, exactly like a property. Put the definition in the course's roottac.config and each lecture writes only >>[<contact>] — no include line at
all.
The usual cascade applies: the nearest tac.config wins over the ones above
it, and a definition in the deck itself wins over both.
Loops
>>[<each> VAR in: a, b, c] repeats its body once per value, substituting/var[VAR]. It ends at [[/each]]. Good for card grids, figure series, option
lists:
## Why Tachyonn
>>[<each> f in: Fast, Offline, Scientific, Beautiful]
- /var[f]
[[/each]]The body can be several lines, so a loop over four values can emit four
highlight boxes or four grid cards just as
easily as four bullets.
Tables from CSV
A table can come from a file instead of being typed out — useful when the
numbers are generated by something else and you don't want to copy them by
hand:
| Syntax | Effect |
|---|---|
|[<f=data.csv>] | Build a table from data.csv (first row = header) |
|[<f=data.csv, sep=;>] | Custom field separator |
|[<f=data.csv>, s, b, c] | CSV table plus the usual table options |
Re-run the deck and the table follows the file.
CLI housekeeping
Two commands for keeping a folder of decks tidy.
unused-images
tachyonn unused-images lecture.tacLists every file in img/ that no deck in that directory references, with
sizes and a ready-to-run rm line. It scans all the .tac, tac.config, .tt
and .csv files there, because img/ is shared across sibling decks — so it
won't tell you to delete a figure that lecture 7 still uses. Pass a directory
instead of a file to check the whole folder; --include-cache also checksimg/.cache/.
paste-image
tachyonn paste-image lecture.tac --row 42Takes whatever is on the clipboard — a screenshot, a "copy image" from a
browser, or a file copied in Finder — writes it into img/ as<deck>-01.png, and inserts the ![…] line after row 42.
Options: --name, --caption, --modifiers "<c> w=60%", --no-insert. It is
bound to cmd-shift-v in the
Zed keymap template,
which turns "screenshot → figure on the slide" into one keystroke.
Two things to remember
Paths are relative to the top-level .tac, even three includes deep. And a
macro body ends at its matching ] — an unbalanced bracket inside the body is
the one way to confuse the expander.