Document programming in Typst and LuaLaTeX: some examples
Authors of LaTeX documents who wanted to perform complex typographical gymnastics requiring custom programming were obligated, until recently, to master LaTeX’s obscure macro language. This was ameliorated around 2015 with the maturity of the LuaTeX system. Now authors could hook into the TeX internals using a sane programming language.
Things are even better now with the emergence of Typst, a new typesetting system that aspires to compete with LaTeX. Here document programming is simpler because Typst’s custom embedded scripting language is an intimate part of the system. When programming in the LuaTeX system, we have to navigate the interface between the TeX internals, usually parts of the output routines, and Lua data structures. The upshot is that the Typst version of a LuaTeX program is much simpler and shorter. However, there are still some things that we can do with LuaTeX that we can’t do with Typst. This gap is likely to become narrower in the future as Typst improves.
A couple of examples
Prime number table
Here is a LuaLaTeX program that prints a table showing the convergence of an approximation to Euler’s number e. I’ve copied it from the article for convenient reference:
\documentclass{article}
\usepackage{luacode}
\begin{document}
\pagestyle{empty}
\begin{luacode*}
function esn (n)
return (1 + 1/n)^n
end
function etn (n)
tex.print(string.format('%5d & %1.8f \\\\', n, esn(n)))
end
\end{luacode*}
Convergence to $e$:
\begin{tabular}{ll}
\rule[-2mm]{0pt}{4mm}$n$ & $(1 + \frac{1}{n})^n$ \\
\hline
\luadirect{
for n = 10, 110, 10 do
etn(n)
end
}
\hline
\end{tabular}
\end{document}
The article contains a fairly detailed description of how the program works, and an image of its output. The equivalent program in Typst is
Convergence to $e$:
#table(
stroke: none,
columns: (1cm, 2cm),
[$n$], [$(1 + 1/n)^n$],
table.hline(),
..for n in range(10, 120, step:10){
([#n],[#calc.round(calc.pow(1 + 1/n, n), digits:8)])}
)
which produces an output nearly identical to the LaTeX version:
The main reason for the simplification (aside from the ability to dispense with all the boilerplate) is that in Typst we don’t need the awkward print statements. Code mode in Typst is entered with the #
character, and within code mode markup is interpolated by placing it within square brackets. Most of the code should be self explanatory even for those who’ve never seen a Typst program before. The only tricky bit is in the two dots that precede the for
loop. This is a splatting operator that takes the result of the loop, which is an array, and turns it into separate arguments for the table()
function; these trailing markup arguments are used to fill the cells of the table.
Text with a color gradient
In a followup article about LuaLaTeX I explained how this code:
\documentclass{article}
\usepackage{luacode}
\usepackage{fontspec}
\usepackage[total={10cm,25cm},centering]{geometry}
\begin{document}
\setlength{\parindent}{0pt}
\pagestyle{empty}
\begin{luacode*}
function fadelines(head)
GLYPH = node.id("glyph")
WHAT = node.id("whatsit")
COL = node.subtype("pdf_colorstack")
colorize = node.new(WHAT,COL)
cvalue = 0
for line in node.traverse_id(GLYPH,head) do
colorize.data = cvalue.." "..1 - cvalue.." .5".." rg"
node.insert_before(head, line, node.copy(colorize))
cvalue = math.min(cvalue + .0008, 1)
end
return head
end
luatexbase.add_to_callback(
"pre_linebreak_filter", fadelines, "fadelines")
\end{luacode*}
Call me Ishmael. [text deleted]
\end{document}
typesets the opening of a great American novel with a color gradient applied to the text. It’s not so much complicated as arcane, and took (me) a good deal of study to figure out how to do. The approximately equivalent Typst version is
#set page(width: 7in, height:5in, margin:(left:2in))
#set par(justify: true)
#set text(gradient.linear(green, red, dir:ttb))
Call me Ishmael [etc.]
The only problem this presented for me was all the time I lost trying to understand why the gradient.linear()
function was not behaving, and that was because the documentation is incorrect. The interesting thing to notice here is the final argument to that function, which is a keyword argument with one of Typst’s special types, called direction
. Here is the output of the Typst code:
The other main example in that article showed how to typeset the passage with curvy margins. That’s an example of something we can’t yet do in Typst because it has no equivalent to LaTeX’s parshape
command, but may get one soon.