Repository navigation
Use ^FB field blocks for list layout instead of host-computed per-line coordinates #54
Description
The list-label examples compute an absolute Y coordinate on the host for every
single line and emit a separate ^FO/^A/^FD triple per item. ZPL can do
this layout itself with ^FB (Field Block): one ^FD carries a whole section,
with lines separated by \&, and the printer performs the line advance.
^FO100,110^FB1040,99,20,L,0^FDeggs\&bananas\&berries\&crackers^FS
The third ^FB parameter is extra dot spacing between lines, so 20
reproduces the current 70-dot pitch exactly. Verified against Labelary.
Payoff
For the 19-line sample label:
| commands | bytes | computed Y coords | |
|---|---|---|---|
| today | 82 | 771 | 19 |
with ^FB |
24 | 423 | 2 (one per header) |
This is the change that makes a stored template viable — with ^FB the layout
contains no per-item coordinates, so it can be expressed declaratively
(e.g. as a Stencil template via the existing ResolveTemplates /
StencilTemplateStore path) rather than assembled by string interpolation.
^FB also fixes silent truncation of long items
Without ^FB, a line wider than ^PW is silently clipped at the right
edge — verified, ...that exceeds the pri rendered with the remainder simply
gone, no error. Inside an ^FB block the same text wraps to the next line.
No current shopping-list item is long enough to hit this, so it is latent
rather than visible, but it is one long item away from losing data.
Blockers — why this is not bundled with #53
Two pieces of our own tooling do not understand ^FB:
-
ZPLLengthEstimatorhas no^FBcase. It handles^FO/^A/^CF/
^GB/^BC/^LL(Sources/LabelKit/Utilities/ZPLLengthEstimator.swift),
soInjectLengthwould compute a wrong^LLfor any^FBlabel. This
must be fixed first — it is the real work in this issue, and it needs to
account for wrapped lines, not just\&count. -
The bundled
zpl2pnghelper does not implement^FBline breaks at
all. It renders the literal\&as glyphs. A^FBlabel prints correctly
on the ZD620 but previews wrong viaZPL2PNGRenderer, which is what
example-listlabel previewandexample-reminderlist previewuse.
Labelary renders it correctly, soLabelaryRendereris unaffected.
Decide how to handle (2) as part of this work — options are to accept the
preview divergence, document it, or default the list examples to
LabelaryRenderer for preview.
Sequencing
To be implemented after the planned brainstorm on refactoring
example-reminderlist and example-listlabel, and after #53.
Activity
PeteRichardson commentedon Aug 31, 2026
Proposed fix — PR #57, open and unmerged
Note
Nothing has changed on
mainyet. If #57 is revised or rejected, everything belowthe diagnosis changes with it. #57 is the source of truth.
Diagnosis
Confirmed as filed:
ListLayout.makeLabelcomputed an absolute Y for every row and emitted an^FO/^FDpair per item, andZPLLengthEstimatorhad no^FBcase, soInjectLengthwould have produced a wrong^LLfor any block label.Two details differ from the issue text, both checked against Labelary rather than assumed:
zpl2pngdoesn't render\&as literal glyphs — it drops every line after the first. A three-item block previewed as justeggs. That makes blocker (2) worse than described: not a cosmetic divergence but silent data loss in the preview.\&in item text cannot be escaped. The printer applies^FHsubstitution before it scans for line breaks, so_5C&becomes\&and breaks the line anyway. Verified directly.What #57 proposes (this issue's part)
^FBblock: one^FO, items joined with\&, printer does the line advance. Measured on a 19-line list: 83 → 38 commands, 557 → 395 bytes, 18 → 6 computed origins.ZPLLengthEstimatorgains^FB: block width, line-spacing parameter, max-lines cap, and estimated wrapping. Wrapping is estimated from a fixed fraction of the declared cell width (0.6; font 0 measures ~0.44 against Labelary), biased to run^LLlong rather than short.labelprint previewandexample-reminderlist previewswitch toLabelaryRenderer. Previews now need network; printing is unaffected.Files:
Sources/LabelKit/Labels/ListLayout.swift(makeLabel,escaped),Sources/LabelKit/Utilities/ZPLLengthEstimator.swift(estimateLengthDots,measureFDText,wrappedLineCount,parseFB,parseA),Examples/labelprint/LabelPrint.swift,Examples/ReminderList/ReminderList.swift.If it merges, you'll notice
A list item too long for the label wraps onto the next line instead of being clipped at the right edge.
labelprint previewrequires network access. 133 tests pass in the worktree.Deliberately not covered
zpl2pngabout^FB. It ships as a prebuilt binary with no source in this repo.\&. Not possible under^FH, as above; documented instead.