Lee Phillips’ Website


Subscribe:   RSS icon   twitter icon

Document programming in Typst and LuaLaTeX: some examples

Lee Phillips
September 5th, 2025

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.


Share with Facebook Share with Twitter Share with Reddit Share with Slashdot lee-phillips.org is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to amazon.com.
Quotilizer ... loading ...