Repository navigation
Printer reliability and preview accuracy. calibrate() no longer
mis-registers labels when the printer is busy, send() can be cancelled and no
longer exhausts the thread pool under a large fan-out, and the renderer now
previews ^SN serial numbers and scopes fonts per field the way a printer does.
Every renderer rule was checked against Labelary, and the printer behavior was
measured on a GX420t (see HARDWARE-VALIDATION.md).
One source-breaking addition: PrinterError gains a printerBusy case. A
switch over PrinterError with no default needs a new arm. Everything else
is additive.
Added
ZPLPrinter.maxConcurrentSends(8): at most this manysend()calls run
at once per process. Further sends wait for a free slot, without holding a
thread, before connecting. Each in-flight send blocks a GCD thread for its
connect and write, so a large fan-out to unreachable printers could
previously exhaust the thread pool and stall every other send and unrelated
global-queue work.ZPLPrinter.waitUntilIdle(timeout:): polls~HSuntil no formats are
queued, no partial format is in progress, and no batch is printing (two
consecutive idle readings). Throws the newPrinterError.printerBusyif the
printer is still busy at the timeout.
Fixed
calibrate()(and sosetup()) could stall queued labels and print them
out of register. Tilde commands run the moment they arrive, ahead of queued
formats. Measured on a GX420t (V56.17.17Z): with two labels queued,~JC
calibrated first and the two labels did not print at all until the next job
arrived minutes later, and then printed out of register (~HSreported no
formats queued while they were stuck). This matches the
misaligned labels previously seen aftersetup().calibrate()and
calibrateFull()now wait for the printer to go idle first (idleTimeout,
default 60s). With the fix,calibrate()held the~JCfor 2.7s and the
calibration ran only after every queued label had printed.send()now observes task cancellation once it has started. Previously
it checked cancellation only before dispatching, so a cancelled send to an
unreachable or stalled printer still blocked for the full timeout (10s by
default). Now a queued send never connects, a pending connect is abandoned
within about 50 ms, and a blocked write is cut off by shutting down the
socket; all three throwCancellationError. A send whose bytes were all
written before the cancel still reports success.^SNserial numbers now appear in previews. The parser had no^SN
case, so aSerialNumberelement (or any^SNfield, including a serialized
barcode) silently vanished from the rendered image. A preview now shows the
start value, which is what the first printed label carries.- A field's
^Afont no longer leaks into later fields.^Aapplies to
one field only; a field without its own^Auses the^CFdefault, or Font
A at 9x5 dots when there is no^CF(verified against Labelary). The renderer
carried the last^Afont, size and rotation into every following field, so
a field that relied on the default rendered in the previous field's font. The
^FWdefault rotation still applies. - Font A rendered at about half its width. Its width was scaled by
width / heightlike the scalable Font 0, but^AAN,9,5is Font A's native
9x5 cell, not a squeeze. Each character now advances6 * width / 5dots,
matching Labelary. - Two data commands in one field produced two elements. A second
^FDor
^SNbefore^FSnow replaces the first, at the field's latest^FO, as on
Labelary. This was visible once^SNrendered: a^FXcomment mentioning
^SNdrew its own text at the top of theserial_numberfixture. calibrateFull()was documented as a more thorough calibration.~JGis
Zebra's graphing calibration: the same measurement as~JCplus a printed
graph of the media sensor profile, so it is a diagnostic that costs extra
labels.- Tests: the loopback
FakePrintercould close the same descriptor twice
(once inshutdown(), again when its connection handler finished), which
could close a socket belonging to another test running in parallel. - Tests: refreshed 36 visual-test reference images that had drifted from
current Labelary output. None of the drift was a renderer regression: ZPLKit
agrees better with the fresh references in 27 of the 36. Tengraphic_*_fill
references predated the switch from SF Symbols to FontAwesome icons, and
seven fetched in 2026-07 were rendered from a percent-encoded request body,
so every space in their label text came out as a literal+. The rest are
small Labelary-side changes (a different QR matrix for the same data, and
shape-edge rasterization). The four^FRreferences, which are ZPLKit
renders on purpose, were left alone. Visual accuracy rises from 90.9% to
91.1%. - Visual suite:
features_textblockrises from 88.6% to 90.7% (the font
leak).serial_numberandretail_labelchange because they now draw
their serial fields; nothing else moves by more than 0.1 point.
Changed
PrinterErrorhas a new case,printerBusy(host:port:). Exhaustive
switchstatements overPrinterErrorwithout adefaultneed a new arm.- CI: the WebAssembly job moved to the
swift:6.4.1container and the 6.4.1
Wasm SDK. The Linux job already follows the floatingswift:6.4tag.