fix(print): out-of-window slip Code128 was too wide to print — width 2 + left-align

The slip printed the QR but NOT the Code128 barcode on the Rongta. Root cause:
the ~20-char occurrence id (SUBSESS-…) at module width 3 is ~765 dots wide —
over the 80mm head's ~576 printable dots — so the firmware silently aborts the
barcode (prints nothing). It was also emitted while ALIGN_CENTER (set for the
title) was active, which shifts the start point right and makes it overflow
even sooner. The QR, being compact, rendered fine — hence QR-only output.

code128() now takes a moduleWidth (default 3, so the shorter entry-ticket id is
unchanged) and forces ALIGN_LEFT (a wide barcode must hug the margin). The slip
passes width 2 (~510 dots — fits with quiet zones) and re-centers the QR/text
after. renderReceipt's voucher barcode re-asserts ALIGN_CENTER for the lines
that follow it.

Verified: width n=2 in the byte stream, est 510 dots; Code128 + QR both present;
build+lint 14/14.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-21 13:44:01 +02:00
parent 8acef0464c
commit 663bf0e925
+21 -7
View File
@@ -119,13 +119,20 @@ function line(text = ""): Buffer {
// dependency). The same code is printed as large human-readable digits below, so // dependency). The same code is printed as large human-readable digits below, so
// the operator can hand-key it if every reader fails. // the operator can hand-key it if every reader fails.
/** GS k — Code128 1D barcode. Height/width set first, then HRI off, then data. */ /** GS k — Code128 1D barcode. Height/width set first, then HRI off, then data.
function code128(data: string): Buffer { * `moduleWidth` (narrow-bar dots, 1–6) trades scan-tolerance for total width: at ~11
* modules/char a 13-char id fits 80mm (576 printable dots) at width 3, but a ~20-char
* id needs width 2 or it OVERFLOWS the head and the firmware silently aborts the
* barcode (prints nothing). Keep the symbol LEFT-aligned — centering shifts the start
* point right and makes a wide barcode overflow even sooner. */
function code128(data: string, moduleWidth = 3): Buffer {
// Code128 code set B (printable ASCII) — prefix the data with the {B selector. // Code128 code set B (printable ASCII) — prefix the data with the {B selector.
const payload = Buffer.from(`{B${data}`, "ascii"); const payload = Buffer.from(`{B${data}`, "ascii");
const w = Math.max(1, Math.min(6, moduleWidth));
return Buffer.concat([ return Buffer.concat([
ALIGN_LEFT, // a wide barcode must start at the left margin to fit the 80mm head
Buffer.from([GS, 0x68, 0x64]), // GS h 100 — barcode height = 100 dots (taller = tolerant of scan angle) Buffer.from([GS, 0x68, 0x64]), // GS h 100 — barcode height = 100 dots (taller = tolerant of scan angle)
Buffer.from([GS, 0x77, 0x03]), // GS w 3 — module width = 3 (wider bars for the short-range "Simple" QR/barcode engine; 13-digit Code128 ≈ 495/576 dots, fits 80mm with quiet zones) Buffer.from([GS, 0x77, w]), // GS w n — module (narrow-bar) width in dots
Buffer.from([GS, 0x48, 0x00]), // GS H 0 — HRI text off (we print the id ourselves) Buffer.from([GS, 0x48, 0x00]), // GS H 0 — HRI text off (we print the id ourselves)
// GS k 73 n <data> — function B form: 73 = Code128, n = data byte length. // GS k 73 n <data> — function B form: 73 = Code128, n = data byte length.
Buffer.from([GS, 0x6b, 0x49, payload.length]), Buffer.from([GS, 0x6b, 0x49, payload.length]),
@@ -418,9 +425,12 @@ export function renderReceipt(data: ReceiptData): Buffer {
]; ];
if (data.voucher) { if (data.voucher) {
// The same ticket id, scannable at the exit reader, + the grace emphasis. // The same ticket id, scannable at the exit reader, + the grace emphasis. code128
// forces ALIGN_LEFT (a wide barcode must hug the margin); restore centering for the
// human-readable id + grace lines that follow.
parts.push( parts.push(
code128(data.ticketId), code128(data.ticketId),
ALIGN_CENTER,
line(), line(),
line(data.ticketId), line(data.ticketId),
line(), line(),
@@ -456,10 +466,14 @@ export function renderWindowChargeNotice(data: WindowChargeNoticeData): Buffer {
line(STR.windowTitle), line(STR.windowTitle),
BOLD_OFF, BOLD_OFF,
line(), line(),
// The occurrence id, scannable two ways: Code128 for a 1D laser scanner, QR for // The occurrence id, scannable two ways. Code128 for a 1D laser scanner FIRST —
// the booth's combo reader. Either pulls the occurrence up in the pay modal. // module width 2 + left-aligned (code128 forces ALIGN_LEFT) because the ~20-char
code128(data.occurrenceId), // occurrence id is too wide to fit the 80mm head at width 3 (the firmware would
// abort it). Then the QR for the booth's combo reader. Either pulls the occurrence
// up in the pay modal.
code128(data.occurrenceId, 2),
line(), line(),
ALIGN_CENTER,
qrCode(data.occurrenceId), qrCode(data.occurrenceId),
line(), line(),
// The id in text, as the hand-key fallback if neither scans. // The id in text, as the hand-key fallback if neither scans.