supersam/src/components/client/barcodeCode128.js

77 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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;