/** * @file barcodeCode128.js * @description Кодирование Code 128 без внешних зависимостей (нужно для PWA: работает офлайн). * * Коды выдачи — четыре цифры, поэтому используется подмножество C: одна пара цифр = * один символ (штрих-код получается компактнее). Для строк с нецифровыми символами * или нечётной длины — подмножество B (каждый символ = один символ кода). * * Контрольная сумма считается по спецификации: (старт + Σ значение_i × позиция_i) mod 103. * Правильность таблицы и алгоритма закреплена тестом на эталонных значениях, снятых * с независимой реализации (python-barcode) — см. barcodeCode128.test.js. */ import { CODE128_PATTERNS, CODE128_STOP } from "./code128Patterns"; /** Стартовые символы подмножеств. */ export const CODE128_START_B = 104; export const CODE128_START_C = 105; /** Подмножество C годится только для чётного количества цифр. */ export const isCode128CApplicable = (value) => /^\d+$/.test(String(value)) && String(value).length % 2 === 0; /** Значения символов (включая контрольный) без старта и STOP. */ export const code128Codes = (value) => { const chars = String(value ?? ""); if (!chars) return []; const useC = isCode128CApplicable(chars); const codes = [useC ? CODE128_START_C : CODE128_START_B]; if (useC) { for (let i = 0; i < chars.length; i += 2) { codes.push(Number.parseInt(chars.slice(i, i + 2), 10)); } } else { for (const ch of chars) { const index = ch.charCodeAt(0) - 32; if (index < 0 || index > 94) { throw new Error(`Символ «${ch}» нельзя закодировать в Code 128B`); } codes.push(index); } } let sum = codes[0]; for (let i = 1; i < codes.length; i += 1) sum += i * codes[i]; codes.push(sum % 103); return codes; }; /** Полная битовая строка: 1 = штрих, 0 = пробел (включая финальный штрих). */ export const code128Bits = (value) => { const codes = code128Codes(value); if (!codes.length) return ""; return codes.map((code) => CODE128_PATTERNS[code]).join("") + CODE128_STOP + "11"; }; /** * Биты → полосы для отрисовки: [{ black, width }], ширины в модулях. * Соседние одинаковые биты склеиваются — иначе SVG получит тысячи прямоугольников. */ export const bitsToBars = (bits) => { const bars = []; for (const bit of String(bits)) { const black = bit === "1"; const last = bars[bars.length - 1]; if (last && last.black === black) last.width += 1; else bars.push({ black, width: 1 }); } return bars; }; /** Готовые полосы для значения. */ export const code128Bars = (value) => bitsToBars(code128Bits(value)); /** Ширина кода в модулях (без «тихой зоны»). */ export const code128Modules = (value) => code128Bits(value).length;