Lately, I’ve been pondering the ways in which Typst’s layout model
differs from TeX’s. While Typst adopts parts of TeX’s model, in
particular the paragraph layout algorithm, there are also significant
differences. Most of these are related to block-level layout — things
like line placement, widow & orphan prevention, tables, and
floats. I want to use this post to explore these differences, to
identify the benefits and limitations of both models, and to present
my thinking on the future of Typst’s layout engine.
Basics
Let me first (very briefly) explain how the layout engines of TeX and
Typst operate at their foundational level.
TeX
The layout model of TeX is built around the concepts of
boxes and glue:
-
A box is a rectangular container containing
elements that were already laid out. It has the three metrics
“width”, “height”, and “depth.” The latter two determine the box’s
extent above and below the baseline. -
Glue is stretchable space between boxes. It has a
natural size, but has shrinkability and
stretchability which allows it to occupy less or more
space depending on the needs of the layout.
With these two simple primitives, TeX builds everything from letters
and words to lines, paragraphs, and pages. To do so, it constructs
horizontal and vertical lists (or hlist/vlist for short). A
horizontal list contains inline content and is processed by the
linebreaker to produce boxes for each line. These lines go into a
vertical list, which is then processed by the pagebreaker to ship out
pages.
Of utmost importance for TeX’s model is the movability of
boxes. In most of the cases, when making a box, TeX doesn’t yet know
where it will place it. This allows the linebreak and pagebreak
routines to be completely separate. The only thing the linebreaker
needs to be aware of is the available width. From this, it produces
justified and optimized line boxes. The pagebreaker then distributes
those boxes across the pages. (In practice, these two things run
somewhat interleaved to save memory, but conceptually they are
separate.)
This gives TeX a lot of flexibility in juggling things around for a
better layout. An example: It’s simple to prevent things like widows
& orphans while distributing the vertical boxes.
Typst
Typst adopts some of TeX’s ideas, but differs significantly in other
aspects. The central concept of Typst’s layout engine is the
region: A region describes a shape into which elements can be
laid out. A layouter receives a (potentially infinite) sequence of
regions into which it shall lay out its contents. The result of this
is a number of frames, which are just like TeX’s boxes.
When content is laid out, it is first realized into a uniform
structure called a flow, which is a collection of block-level
elements. This includes spacing, paragraphs, blocks, placed elements,
and a few other, minor elements.
When laying out its children, the flow keeps adjusting the regions to
account for already laid out content. For instance, if we’ve already
visited two paragraphs that took two thirds of the available space of
the first page, a subsequent table would get a first region with the
remaining third of the space followed by an infinite sequence of
page-sized regions.
For implementation reasons, Typst currently restricts the general
region model in two ways:
- All regions in a sequence must currently have the same width.
-
Regions can currently only be rectangular. They do not allow for
“cutouts.”
Together, these two restrictions let Typst linebreak a paragraph
independently of where on the pages it ends up, just like TeX. Still,
block-level elements like tables are able to react to where they are
placed on the page. This leaves Typst with way less flexibility in
juggling things around, but more flexibility in adjusting a layout
based on its own position.
Comparing TeX and Typst
When comparing TeX and Typst,