Marmot renders dynamic PDFs and PNGs from packaged .psl templates and JSON data.
Build the binary:
cargo buildShow top-level help:
cargo run -- --helpBasic workflow:
- Create a template file, for example
template.psl. - Package it into a
.marmotarchive. - Validate JSON data against template slots.
- Render a PDF or PNG.
- (Optional) Batch-render many PDFs/PNGs from JSONL records.
Example:
cargo run -- pack test/test-1.psl demo
cargo run -- check demo.marmot data/test-1.json
cargo run -- render demo.marmot data/test-1.json --output out.pdf
cargo run -- batch demo.marmot data/batch-10k.jsonl --output-dir out --output-name "{sku}.pdf" --timingsValidate a .marmot package against a JSON data file.
marmot check <package> <data>Example:
marmot check demo.marmot data/test-1.jsonBehavior:
- Opens and unpacks the package.
- Reads and parses
template.pslfrom the archive. - Validates JSON against declared
slots. - Prints
OKon success. - Prints validation errors and exits with failure when data does not match.
Render a .marmot package into a PDF or PNG.
marmot render [--timings] [--output-type <pdf|png>] [--dpi <72-1200>] [--dither <type>] [--allow-host-assets] --output <output> <package> [data]Example with data:
marmot render demo.marmot data/test-1.json --output out.pdfExample without data:
marmot render demo.marmot --output out.pdfExample with stage timings:
marmot render demo.marmot data/test-1.json --output out.pdf --timingsExample PNG with dithering:
marmot render demo.marmot data/test-1.json --output out.png --output-type png --dither floydBehavior:
- Loads and parses
template.pslfrom package. - If
[data]is provided, parses JSON and validates slot types/required fields. - Builds render context, including package font aliases.
- Loads package layer/frame scripts from
scripts/*.luawhen present. - Allows
loadimagehost filesystem reads only with--allow-host-assets. - Resolves relative
loadimagehost paths from current working directory. - Renders to
--outputas PDF or PNG (--output-type, defaultpdf). - Prints non-fatal render warnings to stderr (for example empty frame values).
- With
--timings, prints elapsed time forprep,render,script,draw, andtotal.
Notes:
- If template uses slot values in
draw, rendering without[data]fails. - Template
frames begin ... endis required;layers begin ... endis optional.drawsupports bothframeandlayer -> framesections. --dpiapplies to PNG output (default300, range72..=1200).--ditherapplies to PNG output and requiresremap.pltin the package.--allow-host-assetsenables runtime host filesystem image reads via PSLloadimage.- Supported dither types:
floyd,atkinson,stucki,burkes,jarvis,sierra3. --timingsis intended for local profiling and benchmarking runs.
Create a .marmot package from a template and optional files.
marmot pack [OPTIONS] <template> <name>Options:
-a, --asset <PATH>: include an asset file in archive atassets/<filename>-f, --font <PATH>: include a font file in archive atfonts/<filename>-s, --script <PATH>: include a Lua script file in archive atscripts/<filename>-o, --output-dir <DIR>: output directory (defaults to current directory)--remap <PATH>: include a remap palette file in archive asremap.plt(used by PNG dithering)
Example:
marmot pack test/test-6.psl label -f fonts/Kablammo.ttf -s scripts/FRAME_1.lua -o buildCreates: build/label.marmot
Render many PDFs or PNGs from one package and a JSONL records file.
marmot batch [OPTIONS] <package> <records> --output-dir <dir> --output-name <template>Options:
--output-dir <DIR>: destination directory for generated files (created if missing)--output-name <TEMPLATE>: filename template supporting{index}and top-level JSON fields (e.g.{sku},{id})--output-type <TYPE>: output format,pdforpng(defaultpdf)--dpi <NUMBER>: PNG DPI (72..=1200, default300)--dither <TYPE>: PNG dither algorithm; requires packageremap.plt--allow-host-assets: enable runtime host filesystem image reads via PSLloadimage-j, --jobs <N>: worker count (0= auto-detect CPU parallelism)--trust-data: skip upfront per-record slot validation in batch mode--timings: print stage timings and per-record render latency distribution
Supported dither types: floyd, atkinson, stucki, burkes, jarvis, sierra3.
Examples:
marmot batch demo.marmot data/batch-10k.jsonl --output-dir out --output-name "{sku}.pdf"
marmot batch demo.marmot data/batch-10k.jsonl --output-dir out --output-name "{index}-{sku}-{buy_qty}-{get_qty}.pdf" -j 16 --timings
marmot batch demo.marmot data/batch-10k.jsonl --output-dir out --output-name "{sku}.pdf" --trust-data --timingsBehavior:
- Reads
<records>as JSON Lines (one JSON object per line). - Ignores blank lines.
- Uses a worker pool to render records in parallel.
- Resolves relative
loadimagehost paths from current working directory. - Produces one PDF/PNG per successful record.
- Prints per-record non-fatal render warnings to stderr.
- Prints
success,failed, andskippedcounts at completion.
Batch timing output (--timings) includes:
prep,process, andtotalwall-clock stages.- Render latency stats across rendered records:
avg,min,max,p90,p95,p99,p99.9. - Script and draw stats across rendered records:
script avg/min/max,draw avg/min/max.
Output name template notes:
{index}uses 1-based input line number.- Any other placeholder like
{sku}reads a top-level field from each JSONL record. - You can combine many fields, e.g.
{index}-{sku}-{buy_qty}-{get_qty}.pdf. - Referenced fields must be string/number/bool.
- Output names are sanitized to avoid invalid filesystem characters.
- Path
..segments are rejected.
A .marmot file is a zip archive with at least:
template.psl
When options are used, it can also contain:
fonts/<filename>(from--font)assets/<filename>(from--asset)scripts/<filename>(from--script)remap.plt(from--remap)
Important:
packdeduplicates archive paths and errors on duplicate filenames.
At render time:
- Font aliases from
fonts begin ... endare resolved to package files. - Font files are registered with fontconfig and family names are extracted.
fontoperator selects either:- packaged font alias (preferred when alias exists), or
- system font by name (fallback when alias is not declared).
Common validation behavior:
- Package path must exist, be a file, and end with
.marmot. - Data path (for
checkorrenderwith data) must exist and be a file. - Records path (for
batch) must exist and be a file. - Render output parent directory is created if missing.
- Batch output directory is created if missing.
pack --output-diris created if missing.packoutput file extension must be.marmot.
- Cause:
check/render/batchpackage path missing.marmotextension. - Fix: use a valid
.marmotpackage path.
- Cause: archive does not contain
template.psl. - Fix: rebuild package with
marmot pack.
- Cause: JSON is missing required slots or has wrong types.
- Fix: match your JSON fields to slot definitions in template.
- Cause: output folder cannot be created (permissions, invalid path, read-only filesystem).
- Fix: choose writable path or adjust permissions.
- Cause: duplicated font alias in template or repeated file names during packaging.
- Fix: make aliases and package filenames unique.
- Cause:
--ditherused withrenderorbatch, but package does not includeremap.plt. - Fix: rebuild package with
marmot pack ... --remap <palette-file>.
- Cause: file in package
scripts/does not map to declared layer/frame id, or script file is not.lua. - Fix: rename script to
<layer_id>.luaor<frame_id>.luaand keep only Lua files inscripts/.
- Cause: Lua runtime error or invalid assignment (
layer.visible,frame.visible,frame.value). - Fix: check script line and types; ensure
layer.visible/frame.visibleare boolean andframe.valueis string ornil.
- Cause:
--output-namereferences a JSON field that is missing in a record. - Fix: add the field to each record or adjust the template (for example use
{index}or an existing key like{sku}).
- Cause: all records failed before/during render.
- Fix: inspect per-line errors, validate sample records with
marmot check, then retry.
- Commands:
check,render,pack,batch - Input data formats: JSON object (
check/render) and JSONL records (batch) - Output formats: PDF, PNG
- Packaged fonts: supported
- Packaged image assets + draw-time image operator: supported
- Packaged layer/frame scripting (
scripts/*.lua): supported