77 lines
3.4 KiB
JavaScript
77 lines
3.4 KiB
JavaScript
/**
|
||
* @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;
|