Typst Backend¶
The Typst backend generates PDF, SVG, and PNG documents using the Typst typesetting system. It converts card-yaml payload fields to Typst markup, injects them into the plate as JSON via a helper package, and compiles to the requested format.
Data Access¶
Plates are plain Typst code. Document metadata reaches the plate as a JSON dictionary exposed by the virtual @local/quillmark-helper package:
#import "@local/quillmark-helper:0.1.0": data
#data.title // direct — errors if missing
#data.at("title", default: "Untitled") // safe with default
Fields declared type: markdown in Quill.yaml arrive as Typst content (ready to render); type: datetime fields arrive as Typst datetime values (the helper parses the string and calls Typst's datetime()).
Checking for Optional Fields¶
Use Typst's in operator to check for optional fields:
#if "subtitle" in data {
[Subtitle: #data.subtitle]
}
// Or use spread syntax for function arguments
#show: template.with(
title: data.title,
..if "subtitle" in data {
(subtitle: data.subtitle,)
} else {
(:)
},
)
Body, arrays, and cards¶
The document body is exposed under the $body key, accessed via data.at("$body") because Typst identifiers exclude $. Arrays come through as Typst arrays. Cards live under the $cards key, each carrying its own $kind discriminator, fields, and $body:
#data.at("$body", default: "")
#for author in data.authors [- #author]
#for card in data.at("$cards", default: ()) {
if card.at("$kind") == "product" {
[Product: #card.name — #card.at("$body")]
}
}
Typst Packages¶
Declare packages in Quill.yaml, then #import them from the plate:
#import "@local/quillmark-helper:0.1.0": data
#import "@preview/appreciated-letter:0.1.0": letter
#show: letter.with(sender: data.sender, recipient: data.recipient)
Browse the full catalog at Typst Universe.
Fonts¶
System-installed fonts work out of the box (#set text(font: "Arial")). To bundle fonts with the Quill, drop them in assets/fonts/:
Then reference them by family name (#set text(font: "CustomFont")).
Typesetting¶
Plate authors style output with Typst's standard #set directives:
#set page(paper: "us-letter", margin: 1in, numbering: "1")
#set text(font: "Linux Libertine", size: 11pt, lang: "en")
#set par(justify: true, leading: 0.65em)
See the Typst tutorial for the full styling vocabulary. For worked plates that combine data access with real layout, see the usaf_memo and taro examples in crates/quillmark/examples/.
Signature Fields¶
Import signature-field from the helper package to drop an unsigned PDF signature box anywhere in your plate:
#import "@local/quillmark-helper:0.1.0": signature-field
Approving authority:
#signature-field("approver")
Witness:
#signature-field("witness", width: 220pt, height: 60pt)
PDF output gains a clickable AcroForm SigField widget at each call site. Open the result in Acrobat (or any reader that supports form signing) and the widget presents a "Sign Here" affordance. SVG and PNG outputs reserve the same invisible layout space — useful for preview but no widget visual.
Important: the widget is unsigned. Quillmark does not perform any cryptography. To produce a signed PDF, run the output through pyHanko, Acrobat, endesive, or another signing tool.
Positioning¶
signature-field is ordinary Typst inline content sized width × height. It participates in layout the same way #rect(width: 200pt, height: 50pt) would — content after it gets pushed by the box's dimensions. Two modes:
In-flow (reserves layout space). Drop the call where you want to claim that block of space and let the rest of the document flow around it:
Sign here:
#signature-field("approver") // reserves 200×50pt below the label
The above signature acknowledges receipt.
Overlay (no displacement). Wrap in #place(...) to anchor the widget without consuming flow. This is what you want when the surrounding template already reserves space — for example, the four blank lines above a typed-name signature block in a USAF memo:
// At the cursor position where the typed-name signature block begins:
#place(dx: 0pt, dy: -3.5in,
signature-field("approver", width: 3in, height: 0.5in))
#place without an alignment argument anchors the widget at the current cursor (then offsets by dx/dy); #place(top + left, ...) anchors to the containing block's top-left. Either way, the call consumes no flow space and the surrounding template stays put.
Inside #box, #table, #figure, #footnote, #move, #pad — signature-field tracks the layout system normally. Multi-page documents work; each field's page is the page it lays out on, not where it was written in source.
Parameters¶
| Name | Type | Default | Notes |
|---|---|---|---|
name |
str |
required (positional) | Field name — must be unique within the document and match [A-Za-z0-9_]+. Surfaces as the widget's /T entry. |
width |
length |
200pt |
Must be an absolute length (pt, mm, cm, in) — relative lengths like 2em or 50% are rejected. |
height |
length |
50pt |
Same constraint as width. |
Errors¶
- Two calls with the same
nameraise a compilation error (typst::duplicate_signature_field). - A non-absolute
widthorheightraises a Typst assert pointing atsignature-field. - Names violating
[A-Za-z0-9_]+raise a Typst assert.
The label <__qm_sig__> and metadata kind: "__qm_sig__" are reserved for this hand-off — don't use them for unrelated metadata in your plate.
signature-fieldemits a document-globalmetadataelement (standard Typst introspection). If your plate or its packages read config viaquery(metadata), filter to your own elements rather than assuming a single or last metadata element.
Output Formats¶
PDF and SVG render as a single artifact. PNG renders one artifact per page.
Python binding (rendering lives on the engine, not the quill):
from quillmark import OutputFormat
result = engine.render(quill, doc, OutputFormat.PDF) # or .SVG, .PNG
WASM/JS binding (rendering lives on the engine, not the quill):
engine.render(quill, doc, { format: 'png' }); // 144 PPI
engine.render(quill, doc, { format: 'png', ppi: 300 }); // print quality
PNG resolution is set via the ppi option (default 144 — 2× at 72pt/inch, suitable for retina previews):
| PPI | Use case |
|---|---|
| 72 | Low-res web thumbnails |
| 144 | Retina screen preview (2×) |
| 192 | High-DPI screen display |
| 300 | Standard print quality |
| 600 | High-quality print / archival |
Resources¶
- Typst Documentation
- Typst Universe — package directory