Template syntax
Variables, loops and conditions in a Tuix PDF template.
Templates are rendered with Liquid. The
data object of a render request provides the variables, and each one is
addressed by its plain name.
Variables
Nested objects are addressed with dots:
No leading dot. {{ .customerName }} is not valid Liquid: the template
fails to parse and the request answers 500. If you are migrating a
template that used the old Go syntax, remove the dot from every
placeholder.
Missing values
A variable that is not in data renders as an empty string, the request still
succeeds. Same for null. Guard optional blocks explicitly if an empty line
would look wrong:
Conditions
Only null and false are false. Unlike most languages, 0, "" and an
empty array are true in Liquid, so {% if count %} runs the block even
when count is 0. Compare explicitly when that matters:
{% if count > 0 %} or {% if name != "" %}.
Other comparisons work as expected, and and / or combine them:
Loops
for iterates over an array, and you name the loop variable yourself:
Top-level variables stay visible inside the loop, no special prefix needed:
forloop.index is the position of the current entry starting at 1, and
{{ items.size }} gives the number of entries.
Filters
A filter transforms a value with |, and filters can be chained:
The full list is in the Liquid filter reference.
Formatting numbers
JSON numbers are rendered as they come in, so 249.5 prints as 249.5.
round: 2 caps the decimals but does not pad them: 249.5 still prints as
249.5, not 249.50. There is no filter that guarantees two decimal
places, so pass amounts you want in a fixed format as pre-formatted strings
in data ("249.50").
HTML in values
Values are not escaped. A data value of "<b>hi</b>" renders as bold
text, and any markup you pass through reaches the PDF as markup. Apply the
escape filter to values that come from untrusted input and should be shown
verbatim:
Styling and page setup
- Pages are rendered as A4 portrait at 300 DPI.
- Put your CSS in a
<style>block inside the template. - Images and fonts must be reachable over public URLs (or embedded as data URIs), since the renderer loads them from outside your network.
- Use the live preview in the editor and Set variables to check the layout before rendering. See Create a template.
Page breaks
Content that does not fit on one page continues on the next one. To decide
where that break falls, use page-break-before, page-break-after and
page-break-inside. The renderer honours all three, and the editor preview
shows each page as its own sheet.
Use those page-break-* names, not the modern break-before /
break-after / break-inside. The renderer is an older WebKit build: it
ignores the modern properties silently, so the PDF comes back as one long
page with no error.
Start a new page
page-break-after: always breaks after the element, page-break-before: always
breaks before it:
page-break-before on the very first element of the document does nothing, so
a repeated section can carry it without producing an empty first page. One
chapter per page:
Keep a block together
page-break-inside: avoid moves an element to the next page instead of
splitting it across the boundary:
A block taller than a single page is split anyway, because there is nowhere to move it to. One page holds roughly 1300 px of content at the renderer's scale.
A heading can end up alone at the bottom of a page, with its text starting on the next one. To keep them together, wrap the heading and its first paragraph in a block that avoids breaks:
Tables over several pages
A table that runs over several pages repeats its header on each page when the
header rows are in a thead. Rows are never split: a row that does not fit
moves whole to the next page, even when it holds several lines of text.
What is not available
@pagerules. Margins and page size cannot be changed from the template. Every PDF is A4 portrait with a margin of about 10 mm on each side.- Headers, footers and page numbers. There is no
counter(page)and no@bottom-center, so "Page 1 of 3" cannot be produced from a template. - JavaScript. It is disabled during rendering, so pagination cannot be computed by a script.