Chapter 7. Template Format Reference

wxprint Last Updated: 2026-10-25 10:54 Views: 36

Chapter 7. Template Format Reference

7.1 Overview and storage

One template = one JSON document + a resource directory (UTF-8, no binary inlining — images and other assets sit alongside and are referenced by path):

~/.wxprint/templates/<id>/
├── template.json      # main template document (this specification)
├── waybill-scan.png   # scanned background (design-time aid)
└── stamp.png          # template asset image
Dimension Overlay template HTML template
Positioning Absolutely positioned elements, mm coordinates Document flow (HTML+CSS)
Best for Pre-printed forms, waybills, labels, tickets Reports, statements, manuals
Data injection {{variables}} inside elements Twig template ({{ }} / {% for %})
Pagination One page per record (per the template page) Automatic slicing by page height + break classes

Templates come from three sources: the local directory (scanned and registered at server start), POST /api/templates uploads, or a URL fetched at print time (Section 4.5).

7.2 Top-level fields

Field Type Required Description
id string ✓ Unique identifier (directory name), ^[a-z0-9][a-z0-9._-]{0,63}$
name string ✓ Display name
kind string ✓ "overlay" / "html"
version int ✓ Format version, currently 1
page object ✓ Page definition (Section 7.3)
background object Background and calibration (Section 7.9, overlay only)
elements array ✓ Element list (Section 7.4 onward, overlay only)
entry / assets string / array HTML template entry and assets (Section 7.8)
calibration_offset object Template-level overlay tweak {x, y} mm (stacked on printer calibration)
meta object Free-form extension (author/notes/business tags)

7.3 Page definition page

Field Type Default Description
width_mm / height_mm number — Physical paper width/height, any value (fixed-length continuous forms such as 100×93.5; height_mm: 0 = fit content height, overlay only)
orientation string portrait portrait / landscape (a convenience that swaps width/height)
continuous bool false Continuous feeding: gap-free pages, output uses the custom paper form
margin_mm object 0 {top, bottom, left, right}: overlay = designer safe-area hint only; html = content-box margins (the page keeps its full size)
size string — HTML template paper preset ("A4", …; alternative to width/height)

Coordinate system: origin = the physical top-left corner of the paper; x rightward, y downward; millimeters with two decimals (0.01mm precision). Precision chain: template coordinates → ×72/25.4 → PDF points (exact rational conversion) → 1:1 unscaled output → mechanical error absorbed by printer calibration (Section 7.10).

7.4 Common element fields

Fields shared by all elements:

Field Type Default Description
type string ✓ text / line / rect / image / barcode / qrcode / table / chart
x, y, w, h number ✓ Element box (mm)
rotate number 0 Rotation (degrees, clockwise around the box center — slanted text such as bank slips)
id string — Element identifier (anchor/reference); elements without an id are referenced by index
scope string body page-header / page-footer (drawn on every page), first-page / last-page (first/last page only)

7.5 text

{ "type": "text", "x": 42.5, "y": 60.0, "w": 60, "h": 6,
  "text": "{{sender.name}}, {{sender.phone}}",
  "font": { "family": "", "size_pt": 10, "weight": 400, "italic": false,
            "color": "#000000", "letter_spacing_em": 0, "line_height": 1.2 },
  "align": "left", "valign": "top", "origin": "top-left", "overflow": "shrink" }
Field Type Default Description
text string ✓ Content: variables + filters + literals; \n line breaks
font.family string "" Empty = system default stack (same stack as the HTML pipeline); alternates list allowed
font.size_pt number 10 Font size in points (the printing convention — mm coordinates coexist with pt sizes in overlay work)
font.weight int 400 Weight; 700 = bold
font.italic / font.underline bool false Italic / underline
font.color string "#000000" Foreground #rrggbb
font.background string null Background #rrggbb; null = transparent (not drawn)
font.letter_spacing_em number 0 Letter spacing (em multiples)
font.line_height number 1.2 Line-height multiple (effective with overflow: wrap)
align string left left / center / right (in-box horizontal)
valign string top top / middle / bottom (in-box vertical)
origin string top-left top-left = y is the box top; baseline = y is the first line’s baseline — text lands exactly inside grid lines regardless of font-size/line-height tweaks (invoice amount cells, voucher grids)
overflow string shrink shrink (recommended; binary-search font size until it fits, minimum configurable) / clip / wrap

7.6 Variables and filters

  • Syntax {{path}}; dot paths {{a.b.c}}, array indices {{items.0.qty}}.
  • Filter chains {{v|f1|f2:arg}}:
Filter Example Description
date {{created_at\|date:"Y-m-d H:i"}} PHP/Twig format characters (Y m d H i s); timestamps or ISO strings in
number {{amount\|number:2}} Decimals + thousands separators; a negative argument = decimals only
upper / lower {{name\|upper}} Case conversion
default {{remark\|default:"--"}} Fallback for empty values
truncate {{addr\|truncate:20}} Truncate with an ellipsis
pad {{no\|pad:8}} Left-pad with zeros (order numbers)
  • Built-in page variables {{page}} / {{pages}} (current page / total pages): valid only inside scope: page-header/ page-footer elements.
  • Missing variables: rendered as empty strings and listed in the job’s warnings (Section 4.7).
  • Overlay text is not HTML-escaped (not an HTML context); the HTML pipeline escapes by default with |raw as the opt-out.

7.7 line / rect / image / table / chart

{ "type": "line", "x": 10, "y": 55, "w": 190, "h": 0,
  "stroke_mm": 0.3, "color": "#000000", "dash": [2, 1] }
{ "type": "rect", "x": 10, "y": 15, "w": 190, "h": 35,
  "stroke_mm": 0.3, "fill": null, "corner_mm": 1 }
{ "type": "image", "x": 5, "y": 5, "w": 20, "h": 20,
  "src": "stamp.png", "fit": "contain" }
{ "type": "table", "x": 10, "y": 100, "w": 190,
  "columns": [
    { "header": "Item", "key": "name", "width": 0.5, "align": "left" },
    { "header": "Qty", "key": "qty", "width": 0.2, "align": "right" } ],
  "rows": "{{items}}", "row_height_mm": 6, "header_rows": 1,
  "grid": { "stroke_mm": 0.2, "color": "#000000" },
  "font": { "size_pt": 9 } }
Element Field essentials
line stroke_mm width, color, dash pattern (e.g. [2,1])
rect stroke_mm border, fill color (null = transparent), corner_mm rounding, border color
image src: template-relative asset path / http(s) / data: URL (fetched at print time under size/timeout limits); fit: contain / stretch
table columns[].width is a width fraction (sum = 1±0.001); rows binds an array; cells support variables and filters; zebra via odd_color/even_color; v1 uses fixed row heights without page spanning (overlong rows truncate with a warning) — use the HTML pipeline for long cross-page tables
chart kind: bar/hbar/line/pie/funnel/gauge/radar/scatter/histogram/candlestick/parallel/polar_bar/theme_river (13 types); categories/series bindings; show_legend/title/theme

7.8 barcode / qrcode, and HTML templates

Barcode and QR code:

{ "type": "barcode", "symbology": "code128", "x": 10, "y": 20, "w": 80, "h": 12,
  "content": "{{order.no}}", "show_text": true, "text_size_pt": 7 }
{ "type": "qrcode", "x": 170, "y": 20, "size": 22, "content": "{{order.url}}",
  "ec_level": "M", "quiet_zone_mm": 2 }
Field Description
symbology code128 / ean13 / ean8 / code39 / itf14 / upca; drawn as vector paths, no bitmap jaggies
w / h (barcode), size (QR) Target physical size (mm); module widths snap to device pixels to avoid smudged codes
ec_level QR error correction: L(7%) / M(15%) / Q(25%) / H(30%)
quiet_zone_mm QR quiet-zone margin

HTML templates (kind: "html"):

{ "id": "monthly-report", "kind": "html", "entry": "report.html.twig",
  "engine": "twig",
  "assets": ["styles.css", "logo.png"],
  "page": { "size": "A4", "margin_mm": { "top": 15, "bottom": 15,
            "left": 12, "right": 12 } },
  "sample_data": "sample.json" }
  • entry is a Twig template: {{ }} interpolation, {% for %} / {% if %} control, common filters (date, number_format, nl2br, raw, escape) and the include function. All data loading happens server-side; templates contain no JavaScript (<script> is ignored outright).
  • Pagination control: the CSS classes .wxprint-page-break (force a break before an element) and .wxprint-avoid-break (row-level orphan avoidance).
  • The CSS support is a modern subset: box model, tables, flex, float, fonts/colors/backgrounds, zebra stripes; @page paged media is not supported (use the break classes).

7.9 Background calibration (design time)

The background only serves as a tracing aid inside the designer; calibration establishes the pixel→millimeter mapping so that drag-placed coordinates are physical millimeters right away. Three modes by scan quality:

Mode Input Conversion Best for
physical-size The scan’s physical width/height in mm Uniform px_per_mm = img_px_w / width_mm Clean, undistorted scans
reference A line of known physical length in the image (two points) px_per_mm = pixel distance / known mm A known reference object
affine-3pt ≥3 points with their physical mm coordinates Solves 2D affine (6 parameters; least squares for >3) Skewed/perspective scans
"background": {
  "image": "waybill-scan.png",
  "calibration": { "type": "affine-3pt", "points": [
    { "px": [18, 22],   "mm": [0, 0] },
    { "px": [2010, 25], "mm": [209.8, 0.3] },
    { "px": [21, 2831], "mm": [0.2, 296.5] } ] },
  "opacity": 0.6, "print": false }

print: false (default) = overlay semantics, the background is not printed; true is only for proofing. The calibration result is stored in the template together with the raw inputs; the background takes no part at run time.

7.10 Printer calibration (run time)

Per-printer calibration (absorbs the systematic part of the ±0.1~0.3mm mechanical repeatability):

~/.wxprint/calibration/<printer>.json
{ "offset_mm": { "x": 0.35, "y": -0.20 }, "scale": { "x": 1.0002, "y": 1.0002 } }

Source: POST /api/printers/{name}/calibration-page prints the calibration page (crosshair rules + an mm grid) → measure the deviation → PUT /api/printers/{name}/calibration writes it back (endpoints in Section 4.10). Applied to all element coordinates as p' = offset + scale·p before rendering.

Render stacking order: template coordinates → printer calibration (global) → template-level calibration_offset ({x, y} mm, a per-template tweak for a specific machine).

7.11 Validation rules (on upload/registration)

  • id naming rules (^[a-z0-9][a-z0-9._-]{0,63}$);
  • semantic checks (Template::warnings): coordinate/size legality, column-width sums, unresolved variables, … — returned as a warning list with the registration response and in jobs (non-blocking);
  • an invalid id or a directory-creation failure returns E_TEMPLATE_INVALID.

7.12 Complete example (express waybill, continuous paper)

{
  "id": "sf-waybill-100x93",
  "name": "SF waybill 100×93.5 continuous",
  "kind": "overlay",
  "version": 1,
  "page": { "width_mm": 100, "height_mm": 93.5, "continuous": true },
  "background": {
    "image": "scan.png",
    "calibration": { "type": "physical-size", "width_mm": 100, "height_mm": 93.5 },
    "print": false },
  "elements": [
    { "type": "barcode", "symbology": "code128", "x": 8, "y": 6, "w": 60, "h": 10,
      "content": "{{waybill_no}}", "show_text": false },
    { "type": "qrcode", "x": 74, "y": 4, "size": 18, "content": "{{pickup_url}}",
      "ec_level": "M" },
    { "type": "text", "x": 8, "y": 20, "w": 40, "h": 5, "origin": "baseline",
      "text": "{{receiver.name}}  {{receiver.phone}}",
      "font": { "size_pt": 9, "weight": 700 } },
    { "type": "text", "x": 8, "y": 26, "w": 84, "h": 10, "overflow": "shrink",
      "text": "{{receiver.province}}{{receiver.city}}{{receiver.district}}{{receiver.addr}}",
      "font": { "size_pt": 9 } },
    { "type": "table", "x": 8, "y": 40, "w": 84, "row_height_mm": 5.5,
      "header_rows": 1,
      "columns": [
        { "header": "Item", "key": "name", "width": 0.6 },
        { "header": "Qty", "key": "qty", "width": 0.2, "align": "right" },
        { "header": "Insured", "key": "insured", "width": 0.2, "align": "right" } ],
      "rows": "{{items}}" },
    { "type": "text", "x": 8, "y": 88, "w": 50, "h": 4, "origin": "baseline",
      "text": "{{printed_at|date:\"Y-m-d H:i\"}} from {{sender.name}}",
      "font": { "size_pt": 7 } }
  ]
}
← Previous Article Chapter 8. Print Designer Next Article → Chapter 6. Script API Reference

Comments

No comments yet