Skip to content

Add native label printing (ZPL + TSPL) - #4

Merged
ellykits merged 7 commits into
mainfrom
feat/label-printing
Jul 25, 2026
Merged

Add native label printing (ZPL + TSPL)#4
ellykits merged 7 commits into
mainfrom
feat/label-printing

Conversation

@ellykits

Copy link
Copy Markdown
Owner

Adds native label printing to spooler as a second content model alongside HTML documents. A device-agnostic Label — positioned Barcode (Code128 or QR) and Text elements plus label geometry — renders to a printer's native command language and prints over the raw TCP 9100 transport spooler already uses. Two dialects ship together: ZplRenderer (Zebra) and TsplRenderer (TSC), reached through renderLabel(label, dialect); a new PrintEngine.printLabel(label, target) and NetworkLabelDriver(host, dialect, port) send them. The HTML/ESC/POS pipeline is untouched — labels are never routed through HTML, which would discard the native ^BC/^BQ barcodes. Shipping ZPL and TSPL together keeps the Label model genuinely device-agnostic rather than ZPL-shaped. The public surface stays small (Label, LabelElement, BarcodeSymbology, LabelDialect, NetworkLabelDriver, renderLabel, printLabel); the renderers are internal. A content/driver mismatch returns PrintResult.Failure rather than throwing; web returns Failure (no raw sockets); SaveToFile writes the ZPL for inspection.

Verification: both emitted command streams were reviewed against the ZPL II and TSPL2 references and signed off as valid. Unit tests cover the ZPL and TSPL output (Code128, QR, text, coordinates, ^FH hex-escaping incl. underscore, TSPL quote-escaping and font sizing), the renderLabel dispatch, and printLabel's SaveToFile and driver-mismatch paths; all targets compile (Android, iOS, Desktop, JS/Wasm) and :spooler:desktopTest is green. The one thing not verifiable in code is a physical Zebra/TSC rendering the stream and a scanner reading the label — that is the on-hardware check. README updated with a Labels section, snippet, and the API/transport tables.

More dialects (EPL, DPL, CPCL) can be added later through the same renderer seam; USB/serial/Bluetooth label transports are out of scope (network first, matching the ESC/POS driver).

ellykits added 7 commits July 25, 2026 16:43
Add a generic, dialect-independent Label content model (Barcode
CODE128/QR, Text, positioned by dots) plus NetworkLabelDriver as a
PrinterDriver variant, and a ZplRenderer that emits a complete ZPL II
stream for it. User-supplied text and barcode data is hex-escaped
(^FH + _5E/_7E/_5C) so control characters in item names can't corrupt
the stream.

Adding NetworkLabelDriver to the sealed PrinterDriver made every
PrintEngine actual's driver dispatch non-exhaustive; each now returns
a Failure for it, since label content isn't renderable through the
HTML/execute path (printLabel dispatch lands in a later change).
ZplRenderer.hexEscape escaped backslash, caret, and tilde but not
underscore, which is ZPL's own hex-escape trigger under ^FH. Any
literal underscore in barcode data or text (e.g. SKU ITEM_1) would be
misread by the printer as the start of a stray _XX escape, silently
corrupting the payload. Escape underscore first so it round-trips as
_5F before the other substitutions run.

TsplRenderer's TEXT command hard-coded the size multiplier to 1,1,
dropping fontHeightDots entirely, while ZplRenderer honored it via
^A0N. Derive the TSPL multiplier from fontHeightDots against a base
glyph height so both renderers scale text in the same direction for
the same Label.

Extend both renderer test suites to pin the exact escaped/sized
tokens rather than loose substrings.
Adds renderLabel(label, dialect) to dispatch onto the ZPL/TSPL
renderers, and printLabel(label, target) on PrintEngine so labels
actually leave the process. Desktop/Android/iOS send NetworkLabelDriver
targets over the same TCP transport used for ESC/POS, reject other
drivers, and default SaveToFile to ZPL since PrintTarget carries no
dialect. Web has no raw-socket transport, so it always fails.
Mark LabelRenderer, ZplRenderer, and TsplRenderer internal: the only
intended entry points are renderLabel(label, dialect) and printLabel,
so the renderers were never meant to be public surface.

Also reorder NetworkLabelDriver and Label constructor params so no
required argument follows a defaulted one, matching the existing
NetworkEscPosDriver convention and making positional construction
usable.
@ellykits
ellykits merged commit 7013f81 into main Jul 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant