- 3 Minutes Wednesdays
- Posts
- The Pattern Behind Every Lua Filter We Write
The Pattern Behind Every Lua Filter We Write
Guten Tag! š
Many greetings from Munich, Germany. Last email I showed you how BIRD Investmentās fact sheet keeps its branding consistent across HTML and PDF.
Today, I want to show you a different part. This one needs a Lua filter: getting a table to sit off to one side with the surrounding paragraph text wrapping around it. You know, the way youād see in a printed magazine layout:

In the .qmd source, thatās marked with a simple div:
::: {.wrap-table}
(table and paragraph content here)
:::On the HTML side, thatās handled with plain CSS:
float the tableās wrapper right,
give it a fixed width,
let the paragraph text flow around it.
Nothing too unusual that a code agent canāt set up for you.
The filter that makes the Typst output work
On the Typst side, thereās no built-in āwrap-tableā concept. So this is where the filter comes in. By this point of our email chain, you may recognize the pattern:
The filter checks whether a div is marked .wrap-table
The first block inside this div is treated as the thing that needs to be wrapped around. Everything else is the flowing body text.
Then each block is handed off to Pandocās own writer to get real Typst syntax back as a string
And then we glue both strings into a call to a Typst function,
wrap-table(), that already knows how to lay a table and flowing text out around each other.
In code, the glue part of this equation looks roughly like this:

Here, obstacle_typst and body_typst are the table and the paragraph text, already converted to Typst syntax. The filterās only real job is finding them and sticking them into that wrap-table() function. All the actual āhow does a table and flowing text share a pageā logic lives inside plain Typst code as part of the wrap-table() function.
I wonāt pretend this is entirely without gotchas:
Quarto wraps R or Python chunk output in its own div wrappers before your filter ever sees it, and
numbered tables go through their own internal scaffolding that a naive filter would just skip over.
Both of those need to be handled explicitly which is more than annoying. But as soon as youāve seen this in action in a real example, you can deal with it (or instruct a coding agent to do that.)
But the cool thing is: Itās only this transition from Quarto to Typst thatās troublesome. Inside Typst, you have access to the vast sea of extensions. Like the meander package that makes the āflowing text around an obstacleā part really easy:

So there you have it. Even for the seemingly complicated layout stuff, the same three step process applies.
Lua filters are only the glue (the annoying scaffolding if you will) that you have to apply so that the Quarto content is translated to Typst. This is where the real magic happens. And thatās exactly the type of thing youāll learn in my upcoming workshop.
Best,
Albert
PS: If you havenāt grabbed a seat yet, registration for September 23 or September 25 is still open here:
Whenever youāre ready, there are three I can help you:
Automate Your Data Reports: This course helps data analysts eliminate manual copy-paste reporting by automating PDF reports end-to-end, saving hours every cycle and preventing costly mistakes. (Using the lovely Typst language š)
Generate Insights in Minutes, not Hours: This comprehensive course teaches you to handle data faster, smarter, and more efficiently.
Bespoke Data Science Solutions: Iāve helped clients build their own data science solutions. Whether building custom web apps, PDF reports, AI automations or teaching workshops, Iāve got you covered. You can reach out to me via this form (or simply hit reply to this email)

Reply