/** * Linear (1D) barcode encoder — own, zero-dependency. * * Implements the open symbology standards: ISO/IEC 15417 (Code 128), * ISO/IEC 15420 (EAN-13 / EAN-8 / UPC-A / UPC-E), ISO/IEC 16388 (Code 39) and * ISO/IEC 16390 (ITF / ITF-14). Every table below is published specification * data transcribed from the standard, or derived from the standard's own * construction rule. No npm dependency — `$libs` is zero-dep. JsBarcode and * bwip-js were used to cross-check correctness, never imported. * * The output is a module lattice (`true` = dark), the same shape `$libs/qr` * returns for a QR matrix: geometry-free data the renderer turns into SVG. */ // ── Public API types ──────────────────────────────────────────────────────── export type Symbology = 'code128' | 'ean13' | 'ean8' | 'upca' | 'upce' | 'code39' | 'itf' | 'itf14'; /** A half-open module range `[start, end)`. */ export interface BarcodeRange { start: number; end: number; } /** * A run of human-readable text anchored in module space. `start` may be * negative (the group sits in the left quiet zone, as EAN-13's first digit * does) and `end` may exceed the symbol width (the right quiet zone). */ export interface BarcodeTextGroup { text: string; start: number; end: number; } export interface BarcodeResult { /** Module lattice, left→right. `true` = dark (bar). Excludes the quiet zone. */ modules: boolean[]; /** Symbol width in modules (= `modules.length`). */ size: number; /** Quiet zone in modules per side — the symbology's spec minimum. */ quietZone: { start: number; end: number }; /** * Module ranges whose bars extend below the baseline into the text band * (EAN / UPC guard patterns). Renderers MUST honour them: the long guards * are how a scanner finds the symbol's edges and centre. */ guards: BarcodeRange[]; /** Human-readable interpretation, anchored in module space. */ text: BarcodeTextGroup[]; /** The value as encoded — the input plus any computed check digit. */ value: string; symbology: Symbology; /** The check digit computed, when the symbology carries one. */ checkDigit?: string; /** ITF-14 only: the symbol must be framed by a bearer bar. */ bearerBar?: boolean; } export type BarcodeErrorReason = | 'empty' | 'invalid-characters' | 'invalid-length' | 'invalid-check-digit'; /** * A value that cannot be encoded in the requested symbology. Consumers treat * this as a *state*, not a crash: a half-typed EAN is a normal editing step. */ export class BarcodeError extends Error { readonly reason: BarcodeErrorReason; readonly symbology: Symbology; constructor(reason: BarcodeErrorReason, symbology: Symbology, message: string) { super(message); this.name = 'BarcodeError'; this.reason = reason; this.symbology = symbology; } } export interface EncodeOptions { /** @default 'code128' */ symbology?: Symbology; /** Quiet zone override, in modules. Defaults to the symbology's spec minimum. */ quietZone?: number; } // ── Shared primitives ─────────────────────────────────────────────────────── /** * Wide-to-narrow ratio for the two-width symbologies (Code 39, ITF). The * standards allow 2:1 … 3:1; 3:1 is the canonical nominal and what every * renderer ships. */ const WIDE = 3; /** Quiet zone minimums in modules, per the symbology's standard. */ const QUIET_ZONE: Record = { code128: { start: 10, end: 10 }, ean13: { start: 11, end: 7 }, ean8: { start: 7, end: 7 }, upca: { start: 9, end: 9 }, upce: { start: 9, end: 7 }, code39: { start: 10, end: 10 }, itf: { start: 10, end: 10 }, itf14: { start: 10, end: 10 } }; const DIGITS = /^[0-9]+$/; /** * The numeric symbologies, whose values are written with punctuation in the * real world — an ISBN's hyphens, a GTIN's spaces. Code 128 and Code 39 are NOT * here: a hyphen is encodable data there. */ const NUMERIC_SYMBOLOGIES = new Set(['ean13', 'ean8', 'upca', 'upce', 'itf', 'itf14']); /** Separators dropped from a numeric value before encoding. */ const SEPARATORS = /[\s-]/g; /** 9 digits + a base-11 check character. */ const ISBN10 = /^[0-9]{9}[0-9X]$/; /** * ISBN-10's own check digit: the ten characters weighted 10…1 must sum to a * multiple of 11, where the check character `X` carries the value 10 (ISO 2108 * — the base-11 modulus is why the digit 10 needs a symbol at all). */ function isValidIsbn10(value: string): boolean { let sum = 0; for (let i = 0; i < 10; i++) { sum += (value[i] === 'X' ? 10 : Number(value[i])) * (10 - i); } return sum % 11 === 0; } function fail(reason: BarcodeErrorReason, symbology: Symbology, message: string): never { throw new BarcodeError(reason, symbology, message); } /** Expand a run-length width string (`'212222'`) into modules, starting with a bar. */ function widthsToModules(widths: string): string { let out = ''; for (let i = 0; i < widths.length; i++) { out += (i % 2 === 0 ? '1' : '0').repeat(Number(widths[i])); } return out; } /** Expand a narrow/wide element string (`'nnwwn'`) into modules, starting with a bar. */ function elementsToModules(elements: string, startsWithBar = true): string { let out = ''; for (let i = 0; i < elements.length; i++) { const dark = startsWithBar ? i % 2 === 0 : i % 2 === 1; out += (dark ? '1' : '0').repeat(elements[i] === 'w' ? WIDE : 1); } return out; } /** * The UPC/EAN mod-10 check digit: weight the digits alternately 3 and 1 from * the RIGHT, sum, and take what is missing to the next multiple of 10. */ function mod10(digits: string): number { let sum = 0; for (let i = 0; i < digits.length; i++) { const weight = (digits.length - i) % 2 === 1 ? 3 : 1; sum += Number(digits[i]) * weight; } return (10 - (sum % 10)) % 10; } // ── Two-of-five bar code ──────────────────────────────────────────────────── /** * The two-of-five code shared by Code 39's bars and ITF: exactly two of the * five elements are wide, and the digit is the sum of the wide positions' * weights `1 · 2 · 4 · 7 · 0` (so `0` is the 4+7 combination). */ const TWO_OF_FIVE = [ 'nnwwn', // 0 → 4+7 'wnnnw', // 1 → 1+0 'nwnnw', // 2 → 2+0 'wwnnn', // 3 → 1+2 'nnwnw', // 4 → 4+0 'wnwnn', // 5 → 1+4 'nwwnn', // 6 → 2+4 'nnnww', // 7 → 7+0 'wnnwn', // 8 → 1+7 'nwnwn' // 9 → 2+7 ]; // ── Code 39 (ISO/IEC 16388) ───────────────────────────────────────────────── /** Character set in check-value order: value = index. */ const CODE39_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%'; /** * Build a Code 39 character from the standard's construction rule rather than * from a transcribed table — the rule cannot carry a typo. * * Nine elements alternate bar/space starting and ending with a bar * (`b s b s b s b s b`). For the 40 alphanumerics: the five bars carry the * two-of-five code, and the single wide space's position selects the group — * left to right: `U–Z…` · `digits` · `A–J` · `K–T`. The last four characters * (`$ / + %`) have all-narrow bars and three wide spaces instead. */ function code39FromGroup(spaceIndex: number, digit: number): string { const bars = TWO_OF_FIVE[digit]; let out = ''; for (let i = 0; i < 4; i++) out += bars[i] + (i === spaceIndex ? 'w' : 'n'); return out + bars[4]; } function code39Elements(value: number): string { // $ / + % — all bars narrow, one narrow space among the four (moving right // to left as the value grows). if (value >= 39) { const narrowSpace = 42 - value; let out = ''; for (let i = 0; i < 4; i++) out += 'n' + (i === narrowSpace ? 'n' : 'w'); return out + 'n'; } // group → the index of the wide space; digit → the two-of-five bar value. if (value <= 9) return code39FromGroup(1, value); if (value <= 19) return code39FromGroup(2, (value - 9) % 10); if (value <= 29) return code39FromGroup(3, (value - 19) % 10); return code39FromGroup(0, (value - 29) % 10); } /** * `*` is the start/stop symbol — the 10th slot of the `U–Z…` group, which the * alphabet itself does not carry (it is not an encodable character). */ const CODE39_START_STOP = code39FromGroup(0, 0); function encodeCode39(value: string): { modules: string; value: string } { const text = value.toUpperCase(); for (const ch of text) { if (!CODE39_ALPHABET.includes(ch)) { fail('invalid-characters', 'code39', `Code 39 cannot encode ${JSON.stringify(ch)}.`); } } // Characters are separated by a narrow inter-character space. const parts = [CODE39_START_STOP]; for (const ch of text) parts.push(code39Elements(CODE39_ALPHABET.indexOf(ch))); parts.push(CODE39_START_STOP); const modules = parts.map((p) => elementsToModules(p)).join('0'); // The `*` delimiters are structure, not data: ISO/IEC 16388 keeps the // start/stop out of the human-readable interpretation, and every scanner // returns the bare value. return { modules, value: text }; } // ── ITF (ISO/IEC 16390) ───────────────────────────────────────────────────── function encodeItf(value: string, symbology: Symbology): { modules: string; check?: string } { if (!DIGITS.test(value)) { fail('invalid-characters', symbology, 'ITF encodes digits only.'); } let digits = value; let check: string | undefined; if (symbology === 'itf14') { if (digits.length !== 13 && digits.length !== 14) { fail('invalid-length', symbology, 'ITF-14 needs 13 digits (14 with the check digit).'); } const body = digits.slice(0, 13); const expected = String(mod10(body)); if (digits.length === 14 && digits[13] !== expected) { fail('invalid-check-digit', symbology, `Check digit should be ${expected}.`); } check = expected; digits = body + expected; } else if (digits.length % 2 !== 0) { fail('invalid-length', symbology, 'ITF encodes digit PAIRS — the count must be even.'); } // Start `nnnn`, then each pair interleaved (first digit in the bars, second // in the spaces), then stop `wnn`. let modules = elementsToModules('nnnn'); for (let i = 0; i < digits.length; i += 2) { const bars = TWO_OF_FIVE[Number(digits[i])]; const spaces = TWO_OF_FIVE[Number(digits[i + 1])]; for (let k = 0; k < 5; k++) { modules += '1'.repeat(bars[k] === 'w' ? WIDE : 1); modules += '0'.repeat(spaces[k] === 'w' ? WIDE : 1); } } modules += elementsToModules('wnn'); return { modules, check }; } // ── EAN / UPC (ISO/IEC 15420) ─────────────────────────────────────────────── /** Left-hand odd-parity ("L") digit patterns. G and R derive from these. */ const EAN_L = [ '0001101', '0011001', '0010011', '0111101', '0100011', '0110001', '0101111', '0111011', '0110111', '0001011' ]; /** Right-hand ("R") = the bitwise complement of L. */ const EAN_R = EAN_L.map((bits) => [...bits].map((b) => (b === '0' ? '1' : '0')).join('')); /** Left-hand even-parity ("G") = R reversed. */ const EAN_G = EAN_R.map((bits) => [...bits].reverse().join('')); /** Which parity the six left-hand digits use, indexed by EAN-13's first digit. */ const EAN13_PARITY = [ 'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL' ]; /** * UPC-E parity for number system 0, indexed by the check digit. Number * system 1 uses the inverse (`E` ↔ `O`). `O` = odd parity = the L table, * `E` = even parity = the G table. */ const UPCE_PARITY = [ 'EEEOOO', 'EEOEOO', 'EEOOEO', 'EEOOOE', 'EOEEOO', 'EOOEEO', 'EOOOEE', 'EOEOEO', 'EOEOOE', 'EOOEOE' ]; const EAN_GUARD = '101'; const EAN_CENTRE = '01010'; const UPCE_END = '010101'; /** EAN-13 / UPC-A share one geometry: 95 modules, 3 guards, two 42-module halves. */ function encodeEan13(digits: string): string { const parity = EAN13_PARITY[Number(digits[0])]; let modules = EAN_GUARD; for (let i = 0; i < 6; i++) { const d = Number(digits[i + 1]); modules += parity[i] === 'L' ? EAN_L[d] : EAN_G[d]; } modules += EAN_CENTRE; for (let i = 7; i < 13; i++) modules += EAN_R[Number(digits[i])]; return modules + EAN_GUARD; } function encodeEan8(digits: string): string { let modules = EAN_GUARD; for (let i = 0; i < 4; i++) modules += EAN_L[Number(digits[i])]; modules += EAN_CENTRE; for (let i = 4; i < 8; i++) modules += EAN_R[Number(digits[i])]; modules += EAN_GUARD; return modules; } /** * Expand a 6-digit UPC-E body to its 12-digit UPC-A equivalent. The last * digit of the body selects the suppression rule. */ function upceToUpca(numberSystem: string, body: string): string { const [a, b, c, d, e, f] = body; switch (f) { case '0': case '1': case '2': return `${numberSystem}${a}${b}${f}0000${c}${d}${e}`; case '3': return `${numberSystem}${a}${b}${c}00000${d}${e}`; case '4': return `${numberSystem}${a}${b}${c}${d}00000${e}`; default: return `${numberSystem}${a}${b}${c}${d}${e}0000${f}`; } } function encodeUpce(numberSystem: string, body: string, check: number): string { const parity = UPCE_PARITY[check]; let modules = EAN_GUARD; for (let i = 0; i < 6; i++) { // Number system 1 inverts the whole parity pattern. const odd = numberSystem === '0' ? parity[i] === 'O' : parity[i] === 'E'; const d = Number(body[i]); modules += odd ? EAN_L[d] : EAN_G[d]; } return modules + UPCE_END; } // ── Code 128 (ISO/IEC 15417) ──────────────────────────────────────────────── /** * The 107 symbol characters as bar/space width runs (bar, space, bar, space, * bar, space). Values 0–102 are data, 103–105 the Start A/B/C characters and * 106 the Stop — which carries a seventh element (13 modules instead of 11). * Every data pattern sums to 11 modules with an even total bar width; the * encoder's test asserts both invariants over the whole table. */ const CODE128_WIDTHS = [ '212222', '222122', '222221', '121223', '121322', '131222', '122213', '122312', '132212', '221213', '221312', '231212', '112232', '122132', '122231', '113222', '123122', '123221', '223211', '221132', '221231', '213212', '223112', '312131', '311222', '321122', '321221', '312212', '322112', '322211', '212123', '212321', '232121', '111323', '131123', '131321', '112313', '132113', '132311', '211313', '231113', '231311', '112133', '112331', '132131', '113123', '113321', '133121', '313121', '211331', '231131', '213113', '213311', '213131', '311123', '311321', '331121', '312113', '312311', '332111', '314111', '221411', '431111', '111224', '111422', '121124', '121421', '141122', '141221', '112214', '112412', '122114', '122411', '142112', '142211', '241211', '221114', '413111', '241112', '134111', '111242', '121142', '121241', '114212', '124112', '124211', '411212', '421112', '421211', '212141', '214121', '412121', '111143', '111341', '131141', '114113', '114311', '411113', '411311', '113141', '114131', '311141', '411131', '211412', '211214', '211232', '2331112' ]; const CODE128_START_A = 103; const CODE128_START_B = 104; const CODE128_START_C = 105; const CODE128_CODE_A = 101; const CODE128_CODE_B = 100; const CODE128_CODE_C = 99; const CODE128_STOP = 106; function isDigitAt(text: string, i: number): boolean { return i < text.length && text[i] >= '0' && text[i] <= '9'; } /** Length of the digit run starting at `i`. */ function digitRun(text: string, i: number): number { let n = 0; while (isDigitAt(text, i + n)) n++; return n; } /** Value of `ch` in subset A (control characters live at 64–95). */ function valueInA(code: number): number { return code < 32 ? code + 64 : code - 32; } /** * Build the Code 128 code-value sequence with automatic subset switching. * Any valid switching choice decodes to the same data — the heuristic only * decides how compact the symbol is: subset C (two digits per symbol * character) for digit runs long enough to pay for the switch, A for control * characters, B otherwise. */ function code128Values(text: string): number[] { const values: number[] = []; let mode: 'A' | 'B' | 'C'; const startRun = digitRun(text, 0); if (startRun >= 4 || (startRun === text.length && startRun >= 2 && startRun % 2 === 0)) { mode = 'C'; values.push(CODE128_START_C); } else if (text.charCodeAt(0) < 32) { mode = 'A'; values.push(CODE128_START_A); } else { mode = 'B'; values.push(CODE128_START_B); } let i = 0; while (i < text.length) { if (mode === 'C') { if (isDigitAt(text, i) && isDigitAt(text, i + 1)) { values.push(Number(text.slice(i, i + 2))); i += 2; continue; } // Out of digit pairs — leave subset C. const next: 'A' | 'B' = text.charCodeAt(i) < 32 ? 'A' : 'B'; values.push(next === 'A' ? CODE128_CODE_A : CODE128_CODE_B); mode = next; continue; } const run = digitRun(text, i); const evenRun = run - (run % 2); if (evenRun >= 6 || (evenRun >= 4 && i + run === text.length)) { values.push(CODE128_CODE_C); mode = 'C'; continue; } const code = text.charCodeAt(i); if (mode === 'B' && code < 32) { values.push(CODE128_CODE_A); mode = 'A'; continue; } if (mode === 'A' && code > 95) { values.push(CODE128_CODE_B); mode = 'B'; continue; } values.push(mode === 'A' ? valueInA(code) : code - 32); i++; } return values; } function encodeCode128(text: string): string { for (let i = 0; i < text.length; i++) { if (text.charCodeAt(i) > 127) { fail( 'invalid-characters', 'code128', `Code 128 encodes ASCII 0–127; ${JSON.stringify(text[i])} is outside it.` ); } } const values = code128Values(text); // Checksum: the start value plus each subsequent value weighted by its // position, modulo 103. let sum = values[0]; for (let i = 1; i < values.length; i++) sum += values[i] * i; values.push(sum % 103); values.push(CODE128_STOP); return values.map((v) => widthsToModules(CODE128_WIDTHS[v])).join(''); } // ── Text placement ────────────────────────────────────────────────────────── /** Centre a text group over a module range. */ function group(text: string, start: number, end: number): BarcodeTextGroup { return { text, start, end }; } /** * One group per character, each centred over its own module cell. EAN / UPC * print the HRI digit-under-digit: every digit sits under the 7 modules that * encode it, which is what gives the family its look (and lets a human check a * misread digit against its bars). */ function cells(text: string, start: number, cell: number): BarcodeTextGroup[] { return [...text].map((ch, i) => group(ch, start + i * cell, start + (i + 1) * cell)); } // ── Public API ────────────────────────────────────────────────────────────── /** * Encode `value` into a module lattice for the requested symbology. * Throws {@link BarcodeError} when the value cannot be encoded — callers are * expected to treat that as a state (a half-typed EAN), not as a crash. * * Two input conveniences, both scoped to the numeric symbologies: * separators are tolerated (an ISBN travels hyphenated, a GTIN spaced), and a * 10-character value in `ean13` is read as an ISBN-10 and converted. */ export function encode(value: string, options: EncodeOptions = {}): BarcodeResult { const symbology = options.symbology ?? 'code128'; if (!value) fail('empty', symbology, 'Nothing to encode.'); // In Code 128 / Code 39 a hyphen or a space is DATA and must survive; in the // numeric symbologies it is punctuation the value was written with. let input = NUMERIC_SYMBOLOGIES.has(symbology) ? value.replace(SEPARATORS, '') : value; if (!input) fail('empty', symbology, 'Nothing to encode once the separators are dropped.'); // ISBN-10 → Bookland EAN-13. An ISBN barcode IS an EAN-13 (ISO 2108), so // this is an input profile, never a symbology of its own: the 10-digit form // carries a base-11 check digit (possibly `X`) that we validate BEFORE // converting — a mistyped ISBN-10 would otherwise become a perfectly valid // EAN-13 pointing at a different book, the costliest failure in this domain. if (symbology === 'ean13' && input.length === 10) { // Only the check character can be a letter, so this is a no-op elsewhere. input = input.toUpperCase(); if (!ISBN10.test(input)) { fail( 'invalid-characters', symbology, 'A 10-character value is read as an ISBN-10: 9 digits plus a check digit (0-9 or X).' ); } if (!isValidIsbn10(input)) { fail('invalid-check-digit', symbology, 'The ISBN-10 check digit does not validate (mod 11).'); } input = `978${input.slice(0, 9)}`; } // From here on the normalised form IS the value: everything below encodes it // and reports it back through `result.value`. value = input; let bits = ''; let encoded = value; let checkDigit: string | undefined; let guards: BarcodeRange[] = []; let text: BarcodeTextGroup[] = []; let bearerBar: boolean | undefined; switch (symbology) { case 'code128': { bits = encodeCode128(value); text = [group(value, 0, bits.length)]; break; } case 'code39': { const res = encodeCode39(value); bits = res.modules; encoded = res.value; text = [group(res.value, 0, bits.length)]; break; } case 'itf': case 'itf14': { const res = encodeItf(value, symbology); bits = res.modules; checkDigit = res.check; encoded = checkDigit ? value.slice(0, 13) + checkDigit : value; text = [group(encoded, 0, bits.length)]; bearerBar = symbology === 'itf14'; break; } case 'ean13': case 'upca': { const body = symbology === 'upca' ? `0${value}` : value; if (!DIGITS.test(value)) fail('invalid-characters', symbology, 'Digits only.'); const want = symbology === 'upca' ? [11, 12] : [12, 13]; if (!want.includes(value.length)) { fail( 'invalid-length', symbology, `${symbology === 'upca' ? 'UPC-A' : 'EAN-13'} needs ${want[0]} digits (${want[1]} with the check digit).` ); } const withoutCheck = body.slice(0, 12); const expected = String(mod10(withoutCheck)); if (body.length === 13 && body[12] !== expected) { fail('invalid-check-digit', symbology, `Check digit should be ${expected}.`); } checkDigit = expected; const digits = withoutCheck + expected; bits = encodeEan13(digits); // 101 · 6×7 · 01010 · 6×7 · 101 = 95 modules. guards = [ { start: 0, end: 3 }, { start: 45, end: 50 }, { start: 92, end: 95 } ]; encoded = symbology === 'upca' ? digits.slice(1) : digits; text = symbology === 'upca' ? [ // The number-system digit prints outside the symbol, its own // cell being the first of the left half; the check digit does // the same on the right. group(digits[1], -9, 0), ...cells(digits.slice(2, 7), 10, 7), ...cells(digits.slice(7, 12), 50, 7), group(digits[12], 95, 104) ] : [ // EAN-13's first digit is encoded in the PARITY of the left // half, not in bars of its own, so it prints in the quiet zone. group(digits[0], -11, 0), ...cells(digits.slice(1, 7), 3, 7), ...cells(digits.slice(7), 50, 7) ]; break; } case 'ean8': { if (!DIGITS.test(value)) fail('invalid-characters', symbology, 'Digits only.'); if (value.length !== 7 && value.length !== 8) { fail('invalid-length', symbology, 'EAN-8 needs 7 digits (8 with the check digit).'); } const withoutCheck = value.slice(0, 7); const expected = String(mod10(withoutCheck)); if (value.length === 8 && value[7] !== expected) { fail('invalid-check-digit', symbology, `Check digit should be ${expected}.`); } checkDigit = expected; const digits = withoutCheck + expected; bits = encodeEan8(digits); // 101 · 4×7 · 01010 · 4×7 · 101 = 67 modules. guards = [ { start: 0, end: 3 }, { start: 31, end: 36 }, { start: 64, end: 67 } ]; encoded = digits; text = [...cells(digits.slice(0, 4), 3, 7), ...cells(digits.slice(4), 36, 7)]; break; } case 'upce': { if (!DIGITS.test(value)) fail('invalid-characters', symbology, 'Digits only.'); if (value.length !== 6 && value.length !== 7 && value.length !== 8) { fail( 'invalid-length', symbology, 'UPC-E needs 6 digits (7 with the number system, 8 with the check digit too).' ); } // 6 digits → number system 0 implied. 7+ → the first digit is the // number system and must be 0 or 1. const numberSystem = value.length === 6 ? '0' : value[0]; if (numberSystem !== '0' && numberSystem !== '1') { fail('invalid-characters', symbology, 'UPC-E number system must be 0 or 1.'); } const body = value.length === 6 ? value : value.slice(1, 7); const upca = upceToUpca(numberSystem, body); const expected = String(mod10(upca)); if (value.length === 8 && value[7] !== expected) { fail('invalid-check-digit', symbology, `Check digit should be ${expected}.`); } checkDigit = expected; bits = encodeUpce(numberSystem, body, Number(expected)); // 101 · 6×7 · 010101 = 51 modules. The end guard is long; the start // guard too — the middle guard does not exist in UPC-E. guards = [ { start: 0, end: 3 }, { start: 45, end: 51 } ]; encoded = numberSystem + body + expected; text = [group(numberSystem, -9, 0), ...cells(body, 3, 7), group(expected, 51, 58)]; break; } } const spec = QUIET_ZONE[symbology]; const quietZone = options.quietZone === undefined ? { ...spec } : { start: options.quietZone, end: options.quietZone }; return { modules: [...bits].map((b) => b === '1'), size: bits.length, quietZone, guards, text, value: encoded, symbology, ...(checkDigit === undefined ? {} : { checkDigit }), ...(bearerBar === undefined ? {} : { bearerBar }) }; } /** The symbologies this encoder supports, in catalog order. */ export const SYMBOLOGIES: readonly Symbology[] = [ 'code128', 'ean13', 'ean8', 'upca', 'upce', 'code39', 'itf', 'itf14' ]; /** Internals exposed for the test suite's structural assertions. */ export const __internals = { CODE128_WIDTHS, CODE39_ALPHABET, CODE39_START_STOP, TWO_OF_FIVE, EAN_L, EAN_G, EAN_R, EAN13_PARITY, UPCE_PARITY, code39Elements, upceToUpca, mod10 };