Skip to content

v1.2.0

Latest

Choose a tag to compare

@jonathanspiva jonathanspiva released this 21 Oct 16:03
5b96a86

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 many send() 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 ~HS until no formats are
    queued, no partial format is in progress, and no batch is printing (two
    consecutive idle readings). Throws the new PrinterError.printerBusy if the
    printer is still busy at the timeout.

Fixed

  • calibrate() (and so setup()) 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 (~HS reported no
    formats queued while they were stuck). This matches the
    misaligned labels previously seen after setup(). calibrate() and
    calibrateFull() now wait for the printer to go idle first (idleTimeout,
    default 60s). With the fix, calibrate() held the ~JC for 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 throw CancellationError. A send whose bytes were all
    written before the cancel still reports success.
  • ^SN serial numbers now appear in previews. The parser had no ^SN
    case, so a SerialNumber element (or any ^SN field, 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 ^A font no longer leaks into later fields. ^A applies to
    one field only; a field without its own ^A uses the ^CF default, or Font
    A at 9x5 dots when there is no ^CF (verified against Labelary). The renderer
    carried the last ^A font, size and rotation into every following field, so
    a field that relied on the default rendered in the previous field's font. The
    ^FW default rotation still applies.
  • Font A rendered at about half its width. Its width was scaled by
    width / height like the scalable Font 0, but ^AAN,9,5 is Font A's native
    9x5 cell, not a squeeze. Each character now advances 6 * width / 5 dots,
    matching Labelary.
  • Two data commands in one field produced two elements. A second ^FD or
    ^SN before ^FS now replaces the first, at the field's latest ^FO, as on
    Labelary. This was visible once ^SN rendered: a ^FX comment mentioning
    ^SN drew its own text at the top of the serial_number fixture.
  • calibrateFull() was documented as a more thorough calibration. ~JG is
    Zebra's graphing calibration: the same measurement as ~JC plus a printed
    graph of the media sensor profile, so it is a diagnostic that costs extra
    labels.
  • Tests: the loopback FakePrinter could close the same descriptor twice
    (once in shutdown(), 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. Ten graphic_*_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 ^FR references, which are ZPLKit
    renders on purpose, were left alone. Visual accuracy rises from 90.9% to
    91.1%.
  • Visual suite: features_textblock rises from 88.6% to 90.7% (the font
    leak). serial_number and retail_label change because they now draw
    their serial fields; nothing else moves by more than 0.1 point.

Changed

  • PrinterError has a new case, printerBusy(host:port:). Exhaustive
    switch statements over PrinterError without a default need a new arm.
  • CI: the WebAssembly job moved to the swift:6.4.1 container and the 6.4.1
    Wasm SDK. The Linux job already follows the floating swift:6.4 tag.