Serialised specifications
Reaching the pipeline from a generator in another language.
display-table(spec: ..) renders a specification instead of data and directives, which is how a generator in another language reaches this pipeline without emitting table markup. A specification that arrives as data, from json() or from a tool, cannot carry closures, so it names what it wants:
{
"kind": "display-table",
"data": [{"product": "Bolt", "region": "North", "units": 1250, "margin": 0.182}],
"header": {"title": "Regional sales"},
"stub": {"rowname": "product", "group": "region"},
"formats": [{"name": "format-integer", "columns": "units"}],
"summaries": [{"name": "aggregate-sum", "label": "Subtotal", "columns": ["units"]}],
"styles": [
{
"style": {"text": {"weight": "bold"}},
"part": "body",
"columns": "margin",
"rows": {"column": "margin", "op": "<", "value": 0.05}
}
]
}Formatters and aggregations are named from the built-ins, and row predicates are comparisons with op in <, <=, >, >=, ==, !=, composed with and, or, and not. Comparing against null asks whether the cell is empty, which is the only way JSON has to spell absence. Every key is checked, inside a descriptor as well as at the top level, so a misspelled columns fails rather than quietly widening what it applies to. That is the whole subset: no arithmetic, no function calls, no set tests. Anything more expressive belongs in a Typst specification, which keeps the schema from drifting into an expression language.
A combine pattern arrives the same way, as a template rather than a closure: "{1} ({2})", numbering the sources from one in from order.
What the subset reaches
| Key | Holds |
|---|---|
kind |
"display-table". |
data |
Rows or columns, as display-table takes them. |
header |
title and subtitle. |
stub |
rowname, group, label, indent. |
row-groups |
Descriptors of label and rows. |
labels |
Column name to label. |
hidden |
A column name or an array of them. |
combines |
into, from, pattern, label, hide-sources. |
moves |
columns, and one of before or after. |
spanners |
label, columns, level. |
widths |
Column name to a width written as a string. |
alignments |
Descriptors of alignment and columns. |
formats |
Descriptors naming a formatter, with its own options. |
substitutions |
test, columns, rows, replacement. |
colours |
palette, columns, rows, domain, target, missing, reverse. |
summaries |
name, label, columns, groups. |
grand-summaries |
The same, over the whole body. |
styles |
A style dictionary and the location keys below. |
footnotes |
note, locations, mark. |
source-notes |
An array of strings. |
options |
Theme options, merged over the theme the caller gave. |
A location is written with part, columns, rows, groups, spanners, notes and parts, and a style and a footnote address cells the same way.
The nameable formatters are format-number, format-integer, format-percent, format-currency, format-scientific, format-bytes, format-date and format-markup. The formatters that cannot be named are format, format-cell and format-nanoplot: each is given a function, and JSON has no way to write one, so they are absent by nature rather than by omission.
The nameable aggregations are aggregate-sum, aggregate-mean, aggregate-median, aggregate-min, aggregate-max, aggregate-count and aggregate-standard-deviation. Not every aggregation can be named: aggregate-quantile takes a probability and returns the aggregation, so naming it would mean calling it.
A width is a string carrying a number and one of pt, mm, cm, in, em, fr or %, or "auto". A bare number is refused rather than given a unit nobody wrote. An alignment is one of start, end, center, left or right. The position of a currency symbol is one of start or end, since a symbol sits at one end of its number. Its columns is a name, an array of names, or null for every column. JSON writes no predicate, so an array is the only way a generator names several columns, and every name in it is held to the table: one the table does not carry is reported rather than skipped. A colour is a hex string, wherever one is wanted: a palette stop, a fill, a text fill, or the colour a missing value takes.
A style carries the same five properties style() takes, each written as JSON can write it.
| Property | Written as |
|---|---|
fill |
A colour. |
text |
A dictionary Typst reads, with fill as a colour and size as a length. |
align |
A horizontal name, a vertical name of top, horizon or bottom, or the two added: "center + horizon". |
inset |
A length, or a dictionary of left, right, top, bottom, x, y or rest, each a length. |
stroke |
A colour, a length for the thickness, null for no rule, or a dictionary of paint, thickness and whatever else Typst reads, such as dash. |
tests/unit/test-serialised-surface.typ holds this page to the tables the resolver reads, so a name added to one and not the other fails the suite.
A serialised specification is turned back into ordinary directives and folded through the same path as a hand-written table, so the two cannot diverge: one pipeline, entered two ways. examples/serialised.typ renders examples/table-spec.json and compiles with the test suite.