Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/core/src/lib/code128Subset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,33 @@ describe("code128FdToSymbols", () => {
]);
});

it("reads a Subset A pair 96 to 99 as the symbol the escape would spell", () => {
expect(code128FdToSymbols(">9991234")).toEqual([103, 99, 12, 34]);
expect(code128FdToDisplayText(">9991234")).toBe("1234");
expect(code128FdToSymbols(">99833")).toEqual([103, 98, 19]);
expect(code128FdToDisplayText(">99833")).toBe("3");
});

it("reads a switch under a SHIFT in the shifted subset, where 100 and 101 are FNC4", () => {
expect(code128FdToSymbols(">9>4>6a")).toEqual([103, 98, 100]);
expect(code128FdToDisplayText(">9>4>6a")).toBe("");
expect(code128FdToSymbols(">:AB>4>7CD")).toEqual([104, 33, 34, 98, 101, 35, 36]);
expect(code128FdToDisplayText(">:AB>4>7CD")).toBe("ABCD");
});

it("emits SHIFT as its own codeword at once and keeps its effect for the next emitted symbol", () => {
// ZD230 at ^BY2: >:>4 92 dots, >:>4x33 114, >:>4Xdr 92, >:>4>433 136.
// Labelary draws >:>4Xdr at 136 and lets the shift lapse. The project follows the device.
expect(code128FdToSymbols(">:>4")).toEqual([104, 98]);
expect(code128FdToSymbols(">:>4>8x")).toEqual([104, 98, 102, 88]);
expect(code128FdToSymbols(">:>4x33")).toEqual([104, 98, 33]);
expect(code128FdToDisplayText(">:>4x33")).toBe("A");
expect(code128FdToSymbols(">:>4Xdr")).toEqual([104, 98]);
expect(code128FdToDisplayText(">:>4Xdr")).toBe("");
expect(code128FdToSymbols(">:>4>433")).toEqual([104, 98, 98, 33]);
expect(code128FdToDisplayText(">:>4>433")).toBe("A");
});

it("bails only on a byte no subset carries", () => {
expect(code128FdToSymbols("ä>5")).toBeNull();
});
Expand Down
139 changes: 81 additions & 58 deletions packages/core/src/lib/code128Subset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const START_B = 104;
const START_C = 105;
const CODE_B = 100;
const CODE_A = 101;
const CODE_C = 99;
const SHIFT = 98;
const FNC3 = 96;
const FNC2 = 97;
const FNC1 = 102;

/** Subset A: C0 bytes plus SP..`_`. */
function valueInA(code: number): number | null {
Expand Down Expand Up @@ -145,75 +150,94 @@ export function code128ControlFd(text: string): string | null {
return values ? code128SymbolsToFd(values) : null;
}

const ESCAPE_SYMBOL = new Map([
["2", FNC3],
["3", FNC2],
["4", SHIFT],
["5", CODE_C],
["6", CODE_B],
["7", CODE_A],
["8", FNC1],
]);

/** Shared Table-2 walk over a ^BC ^FD payload: symbol values plus the data
* characters the interpretation line shows. `ok` is false when a byte fits no
* subset (the only unreadable case under the lenient rules). */
function readCode128Fd(fd: string): { values: number[]; text: string; ok: boolean } {
let mode: "A" | "B" | "C" = "B";
let subset: "A" | "B" | "C" = "B";
let i = 0;
const values = [START_B];
if (fd.startsWith(">9")) { mode = "A"; values[0] = START_A; i = 2; }
if (fd.startsWith(">9")) { subset = "A"; values[0] = START_A; i = 2; }
else if (fd.startsWith(">:")) { i = 2; }
else if (fd.startsWith(">;")) { mode = "C"; values[0] = START_C; i = 2; }
// SHIFT reads exactly the next character in the other subset (A<->B).
else if (fd.startsWith(">;")) { subset = "C"; values[0] = START_C; i = 2; }
// A SHIFT's effect reaches the next emitted symbol, so it outlasts the
// bytes the pair rule drops, ZD230-measured.
let shifted = false;
const readingSubset = (): "A" | "B" | "C" => (shifted ? (subset === "A" ? "B" : "A") : subset);
const emit = (value: number): void => {
shifted = false;
values.push(value);
};
let text = "";
// Value 100 is CODE B from A and C but FNC4 from B, 101 is CODE A from B
// and C but FNC4 from A, read in the subset a SHIFT selects (p.96).
// Table 2's C column is blank for SHIFT, CODE C, FNC2 and FNC3.
const symbol = (value: number): void => {
const reading = readingSubset();
if (value === SHIFT) {
if (reading !== "C") {
values.push(SHIFT);
shifted = true;
}
} else if (value === CODE_C) {
if (reading !== "C") { emit(CODE_C); subset = "C"; }
} else if (value === CODE_B) {
emit(CODE_B);
if (reading !== "B") subset = "B";
} else if (value === CODE_A) {
emit(CODE_A);
if (reading !== "A") subset = "A";
} else if (value === FNC1 || reading !== "C") {
emit(value);
}
};
let ok = true;
while (i < fd.length) {
const litMode = shifted ? (mode === "A" ? "B" : "A") : mode;
const reading = readingSubset();
if (fd[i] === ">") {
const esc = fd[i + 1];
i += 2;
const literal = esc === undefined ? undefined : ESCAPE_VALUE.get(esc);
if (literal !== undefined && litMode !== "C") {
values.push(literal);
text += litMode === "A" ? charFromA(literal) : charFromB(literal);
shifted = false;
if (literal !== undefined && reading !== "C") {
text += reading === "A" ? charFromA(literal) : charFromB(literal);
emit(literal);
continue;
}
// FNC symbols consume a pending SHIFT; the shift never reaches past them.
switch (esc) {
case "2": if (litMode !== "C") values.push(96); shifted = false; continue; // FNC3
case "3": if (litMode !== "C") values.push(97); shifted = false; continue; // FNC2
case "4":
if (mode === "C") continue;
values.push(98);
shifted = true;
continue;
// Table 2's C column is blank for >5 and SHIFT: ignored there.
case "5": if (mode !== "C") { values.push(99); mode = "C"; } shifted = false; continue;
// Value 100 is CODE B from A/C and FNC4 from B; 101 is CODE A from
// B/C and FNC4 from A. Either way the subset afterwards is the same.
case "6": values.push(100); mode = "B"; shifted = false; continue;
case "7": values.push(101); mode = "A"; shifted = false; continue;
case "8": values.push(102); shifted = false; continue; // FNC1
// Firmware swallows `>` plus an invalid escape char from the symbol
// (ZD230-measured on a bare `>`), so model the same loss. `><`/`>=`
// in C land here too (see the literal gate above).
default: continue;
}
// The firmware swallows a `>` whose escape char means nothing in this
// subset, ZD230-measured on a bare `>`.
const value = esc === undefined ? undefined : ESCAPE_SYMBOL.get(esc);
if (value !== undefined) symbol(value);
continue;
}
// A/C data is two-digit values, B is ASCII (p.98). Lenient per p.99:
// noninteger at D1 ignored, at D2 kills the pair, unpaired digit dropped.
if (litMode !== "B") {
const d1 = fd[i];
if (!/\d/.test(d1 ?? "")) { i += 1; continue; }
const d2 = fd[i + 1];
if (d2 === undefined || d2 === ">") { i += 1; continue; }
if (reading !== "B") {
const d1 = fd.charAt(i);
const d2 = fd.charAt(i + 1);
if (!/\d/.test(d1)) { i += 1; continue; }
if (d2 === ">") { i += 1; continue; }
i += 2;
if (!/\d/.test(d2)) continue;
const value = parseInt(d1 + d2, 10);
values.push(value);
if (litMode === "C") text += d1 + d2;
else if (value <= 95) text += charFromA(value);
shifted = false;
const pair = parseInt(d1 + d2, 10);
if (reading === "A" && pair >= 96) { symbol(pair); continue; }
text += reading === "C" ? d1 + d2 : charFromA(pair);
emit(pair);
continue;
}
const value = valueInB(fd.charCodeAt(i));
if (value === null) { ok = false; text += fd[i]; i += 1; continue; }
values.push(value);
text += fd[i];
shifted = false;
if (value === null) { ok = false; text += fd.charAt(i); i += 1; continue; }
text += fd.charAt(i);
emit(value);
i += 1;
}
return { values, text, ok };
Expand Down Expand Up @@ -245,12 +269,12 @@ export function code128FdToDisplayText(fd: string): string {
*/
export function code128FdToBytes(fd: string): string | null {
// No start code means Subset B (spec p.98).
let mode: "A" | "B" | "C" = "B";
let subset: "A" | "B" | "C" = "B";
let out = "";
let i = 0;
if (fd.startsWith(">9")) { mode = "A"; i = 2; }
else if (fd.startsWith(">:")) { mode = "B"; i = 2; }
else if (fd.startsWith(">;")) { mode = "C"; i = 2; }
if (fd.startsWith(">9")) { subset = "A"; i = 2; }
else if (fd.startsWith(">:")) { subset = "B"; i = 2; }
else if (fd.startsWith(">;")) { subset = "C"; i = 2; }
while (i < fd.length) {
if (fd[i] === ">") {
const esc = fd[i + 1];
Expand All @@ -259,22 +283,21 @@ export function code128FdToBytes(fd: string): string | null {
// subset and FNC4 from the other, and Subset C has no literal column.
const literal = esc === undefined ? undefined : ESCAPE_VALUE.get(esc);
if (literal !== undefined) {
if (mode === "C") return null;
out += mode === "A" ? charFromA(literal) : charFromB(literal);
if (subset === "C") return null;
out += subset === "A" ? charFromA(literal) : charFromB(literal);
continue;
}
switch (esc) {
case "5": mode = "C"; continue;
case "6": if (mode === "B") return null; mode = "B"; continue;
case "7": if (mode === "A") return null; mode = "A"; continue;
default: return null;
}
const value = esc === undefined ? undefined : ESCAPE_SYMBOL.get(esc);
if (value === CODE_C) { subset = "C"; continue; }
if (value === CODE_B && subset !== "B") { subset = "B"; continue; }
if (value === CODE_A && subset !== "A") { subset = "A"; continue; }
return null;
}
if (mode === "B") { out += fd[i]; i += 1; continue; }
if (subset === "B") { out += fd[i]; i += 1; continue; }
const pair = fd.slice(i, i + 2);
if (!/^\d\d$/.test(pair)) return null;
i += 2;
if (mode === "C") { out += pair; continue; }
if (subset === "C") { out += pair; continue; }
const value = parseInt(pair, 10);
if (value > 95) return null;
out += charFromA(value);
Expand Down
Loading