sard-uniapp
Version:
sard-uniapp 是一套基于 Uniapp + Vue3 框架开发的兼容多端的 UI 组件库
495 lines (494 loc) • 15.2 kB
JavaScript
export const BarcodeFormatList = ['CODE128', 'EAN13', 'UPC-A', 'ITF14'];
export const BarcodeTextPositionList = ['top', 'bottom'];
const EAN_DIGIT_ENCODINGS = {
L: [
'0001101',
'0011001',
'0010011',
'0111101',
'0100011',
'0110001',
'0101111',
'0111011',
'0110111',
'0001011',
],
G: [
'0100111',
'0110011',
'0011011',
'0100001',
'0011101',
'0111001',
'0000101',
'0010001',
'0001001',
'0010111',
],
R: [
'1110010',
'1100110',
'1101100',
'1000010',
'1011100',
'1001110',
'1010000',
'1000100',
'1001000',
'1110100',
],
};
const EAN13_STRUCTURES = [
'LLLLLL',
'LLGLGG',
'LLGGLG',
'LLGGGL',
'LGLLGG',
'LGGLLG',
'LGGGLL',
'LGLGLG',
'LGLGGL',
'LGGLGL',
];
const ITF_ENCODINGS = [
'00110',
'10001',
'01001',
'11000',
'00101',
'10100',
'01100',
'00011',
'10010',
'01010',
];
const CODE128_PATTERNS = [
'11011001100',
'11001101100',
'11001100110',
'10010011000',
'10010001100',
'10001001100',
'10011001000',
'10011000100',
'10001100100',
'11001001000',
'11001000100',
'11000100100',
'10110011100',
'10011011100',
'10011001110',
'10111001100',
'10011101100',
'10011100110',
'11001110010',
'11001011100',
'11001001110',
'11011100100',
'11001110100',
'11101101110',
'11101001100',
'11100101100',
'11100100110',
'11101100100',
'11100110100',
'11100110010',
'11011011000',
'11011000110',
'11000110110',
'10100011000',
'10001011000',
'10001000110',
'10110001000',
'10001101000',
'10001100010',
'11010001000',
'11000101000',
'11000100010',
'10110111000',
'10110001110',
'10001101110',
'10111011000',
'10111000110',
'10001110110',
'11101110110',
'11010001110',
'11000101110',
'11011101000',
'11011100010',
'11011101110',
'11101011000',
'11101000110',
'11100010110',
'11101101000',
'11101100010',
'11100011010',
'11101111010',
'11001000010',
'11110001010',
'10100110000',
'10100001100',
'10010110000',
'10010000110',
'10000101100',
'10000100110',
'10110010000',
'10110000100',
'10011010000',
'10011000010',
'10000110100',
'10000110010',
'11000010010',
'11001010000',
'11110111010',
'11000010100',
'10001111010',
'10100111100',
'10010111100',
'10010011110',
'10111100100',
'10011110100',
'10011110010',
'11110100100',
'11110010100',
'11110010010',
'11011011110',
'11011110110',
'11110110110',
'10101111000',
'10100011110',
'10001011110',
'10111101000',
'10111100010',
'11110101000',
'11110100010',
'10111011110',
'10111101110',
'11101011110',
'11110101110',
'11010000100',
'11010010000',
'11010011100',
'1100011101011',
];
const CODE128_START_B = 104;
const CODE128_START_C = 105;
const CODE128_CODE_B = 100;
const CODE128_CODE_C = 99;
const CODE128_FNC1 = 102;
const CODE128_STOP = 106;
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function getDisplayText(value, options) {
return options.text ?? value;
}
function createEncoding(data, text, options) {
return {
data,
text,
options,
};
}
function encodeEANDigits(data, structure, separator = '') {
return data
.split('')
.map((digit, index) => {
const digits = EAN_DIGIT_ENCODINGS[structure[index]];
return digits[Number(digit)];
})
.join(separator);
}
function getGuardHeight(options) {
const fontSize = Math.min(options.fontSize, options.width * 10);
return options.height + fontSize / 2 + options.textMargin;
}
function checksumEAN13(value) {
const sum = value
.slice(0, 12)
.split('')
.reduce((total, digit, index) => {
return total + Number(digit) * (index % 2 === 0 ? 1 : 3);
}, 0);
return (10 - (sum % 10)) % 10;
}
function checksumUPC(value) {
let sum = 0;
for (let index = 0; index < 11; index++) {
sum += Number(value[index]) * (index % 2 === 0 ? 3 : 1);
}
return (10 - (sum % 10)) % 10;
}
function checksumITF14(value) {
const sum = value
.slice(0, 13)
.split('')
.reduce((total, digit, index) => {
return total + Number(digit) * (index % 2 === 0 ? 3 : 1);
}, 0);
return (10 - (sum % 10)) % 10;
}
function countLeadingDigits(value, start) {
let end = start;
while (end < value.length && /[0-9]/.test(value[end])) {
end++;
}
return end - start;
}
function encodeCODE128CodeBChar(char) {
const code = char.charCodeAt(0);
assert(code >= 32 && code <= 127, `Invalid barcode input for format CODE128: ${char}`);
return code - 32;
}
function encodeCODE128(value, options) {
assert(value.length > 0, 'Invalid barcode input for format CODE128: empty value');
assert(/^[\x20-\x7F]+$/.test(value), `Invalid barcode input for format CODE128: ${value}`);
const startDigitRun = countLeadingDigits(value, 0);
let activeSet = startDigitRun >= 4 && startDigitRun % 2 === 0 ? 'C' : 'B';
const codes = [activeSet === 'C' ? CODE128_START_C : CODE128_START_B];
let index = 0;
if (options.ean128) {
codes.push(CODE128_FNC1);
}
while (index < value.length) {
if (activeSet === 'B') {
const digitRun = countLeadingDigits(value, index);
if (digitRun >= 4) {
if (digitRun % 2 === 1) {
codes.push(encodeCODE128CodeBChar(value[index]));
index++;
continue;
}
codes.push(CODE128_CODE_C);
activeSet = 'C';
continue;
}
codes.push(encodeCODE128CodeBChar(value[index]));
index++;
continue;
}
const digitRun = countLeadingDigits(value, index);
if (digitRun < 2) {
codes.push(CODE128_CODE_B);
activeSet = 'B';
continue;
}
const pairLength = digitRun - (digitRun % 2);
const stop = index + pairLength;
while (index < stop) {
codes.push(Number(value.slice(index, index + 2)));
index += 2;
}
if (index < value.length) {
codes.push(CODE128_CODE_B);
activeSet = 'B';
}
}
const checksum = codes.reduce((total, code, codeIndex) => {
return total + code * (codeIndex === 0 ? 1 : codeIndex);
}, 0);
const checksumCode = checksum % 103;
const data = [...codes, checksumCode, CODE128_STOP]
.map((code) => CODE128_PATTERNS[code])
.join('');
return createEncoding(data, getDisplayText(value, options));
}
function encodeEAN13(value, options) {
const normalizedValue = /^[0-9]{12}$/.test(value)
? value + checksumEAN13(value)
: value;
assert(/^[0-9]{13}$/.test(normalizedValue) &&
Number(normalizedValue[12]) === checksumEAN13(normalizedValue), `Invalid barcode input for format EAN13: ${value}`);
const displayText = getDisplayText(normalizedValue, options);
const guardHeight = getGuardHeight(options);
const leftText = normalizedValue.slice(1, 7);
const rightText = normalizedValue.slice(7);
return [
createEncoding('000000000000', displayText.slice(0, 1), {
textAlign: 'left',
fontSize: Math.min(options.fontSize, options.width * 10),
}),
createEncoding('101', '', { height: guardHeight }),
createEncoding(encodeEANDigits(leftText, EAN13_STRUCTURES[Number(normalizedValue[0])]), displayText.slice(1, 7), { fontSize: Math.min(options.fontSize, options.width * 10) }),
createEncoding('01010', '', { height: guardHeight }),
createEncoding(encodeEANDigits(rightText, 'RRRRRR'), displayText.slice(7), {
fontSize: Math.min(options.fontSize, options.width * 10),
}),
createEncoding('101', '', { height: guardHeight }),
];
}
function encodeUPC(value, options) {
const normalizedValue = /^[0-9]{11}$/.test(value)
? value + checksumUPC(value)
: value;
assert(/^[0-9]{12}$/.test(normalizedValue) &&
Number(normalizedValue[11]) === checksumUPC(normalizedValue), `Invalid barcode input for format UPC-A: ${value}`);
const displayText = getDisplayText(normalizedValue, options);
const fontSize = Math.min(options.fontSize, options.width * 10);
const guardHeight = options.height + fontSize / 2 + options.textMargin;
return [
createEncoding('00000000', displayText.slice(0, 1), {
textAlign: 'left',
fontSize,
}),
createEncoding('101' + encodeEANDigits(normalizedValue.slice(0, 1), 'L'), '', {
height: guardHeight,
}),
createEncoding(encodeEANDigits(normalizedValue.slice(1, 6), 'LLLLL'), displayText.slice(1, 6), { fontSize }),
createEncoding('01010', '', { height: guardHeight }),
createEncoding(encodeEANDigits(normalizedValue.slice(6, 11), 'RRRRR'), displayText.slice(6, 11), { fontSize }),
createEncoding(encodeEANDigits(normalizedValue.slice(11), 'R') + '101', '', {
height: guardHeight,
}),
createEncoding('00000000', displayText.slice(11), {
textAlign: 'right',
fontSize,
}),
];
}
function encodeITFPair(pair) {
const left = ITF_ENCODINGS[Number(pair[0])];
const right = ITF_ENCODINGS[Number(pair[1])];
return left
.split('')
.map((bit, index) => {
const bar = bit === '1' ? '111' : '1';
const space = right[index] === '1' ? '000' : '0';
return bar + space;
})
.join('');
}
function encodeITF14Data(value, options) {
assert(/^([0-9]{2})+$/.test(value), `Invalid barcode input for format ITF14: ${value}`);
return createEncoding('1010' +
(value.match(/.{2}/g) ?? []).map((pair) => encodeITFPair(pair)).join('') +
'11101', getDisplayText(value, options));
}
function encodeITF14(value, options) {
const normalizedValue = /^[0-9]{13}$/.test(value)
? value + checksumITF14(value)
: value;
assert(/^[0-9]{14}$/.test(normalizedValue) &&
Number(normalizedValue[13]) === checksumITF14(normalizedValue), `Invalid barcode input for format ITF14: ${value}`);
return encodeITF14Data(normalizedValue, {
...options,
text: getDisplayText(normalizedValue, options),
});
}
export function normalizeBarcodeOptions(options = {}) {
const margin = options.margin ?? 10;
return {
format: options.format ?? 'CODE128',
width: options.width ?? 2,
height: options.height ?? 100,
lineColor: options.color ?? '#000',
background: options.background ?? '#fff',
displayValue: options.displayValue ?? true,
text: options.text,
textPosition: options.textPosition ?? 'bottom',
textAlign: options.textAlign ?? 'center',
textMargin: options.textMargin ?? 2,
fontStyle: options.fontStyle ?? 'normal',
fontWeight: options.fontWeight ?? 'normal',
fontSize: options.fontSize ?? 20,
fontFamily: options.fontFamily ?? 'monospace',
margin,
marginTop: options.marginTop ?? margin,
marginBottom: options.marginBottom ?? margin,
marginLeft: options.marginLeft ?? margin,
marginRight: options.marginRight ?? margin,
ean128: options.ean128,
flat: options.flat,
lastChar: options.lastChar,
};
}
function normalizeRawEncoding(encoding, options) {
return {
data: encoding.data,
text: encoding.text ?? '',
options: {
...options,
...encoding.options,
},
};
}
export function barcode(value, options = {}) {
const normalizedOptions = normalizeBarcodeOptions(options);
const encoder = createEncoder(normalizedOptions.format, value, normalizedOptions);
if (!encoder.valid()) {
throw new Error(`Invalid barcode input for format ${normalizedOptions.format}: ${value}`);
}
const encodings = encoder.encode();
const normalizedEncodings = Array.isArray(encodings) ? encodings : [encodings];
return normalizedEncodings.map((encoding) => normalizeRawEncoding(encoding, normalizedOptions));
}
function getSegmentHeight(segment) {
const { options, text } = segment;
return (options.height +
(options.displayValue && text ? options.fontSize + options.textMargin : 0) +
options.marginTop +
options.marginBottom);
}
function getBarcodePadding(textWidth, barcodeWidth, options) {
if (!options.displayValue || barcodeWidth >= textWidth) {
return 0;
}
if (options.textAlign === 'right') {
return Math.floor(textWidth - barcodeWidth);
}
if (options.textAlign === 'center') {
return Math.floor((textWidth - barcodeWidth) / 2);
}
return 0;
}
export function calculateBarcodeLayout(encodings, measureText) {
const segments = encodings.map((segment) => {
const textWidth = segment.options.displayValue && segment.text
? measureText(segment.text, segment.options)
: 0;
const barcodeWidth = segment.data.length * segment.options.width;
const width = Math.ceil(Math.max(textWidth, barcodeWidth));
return {
...segment,
width,
height: getSegmentHeight(segment),
textWidth,
barcodeWidth,
barcodePadding: getBarcodePadding(textWidth, barcodeWidth, segment.options),
};
});
const totalContentWidth = segments.reduce((sum, segment) => sum + segment.width, 0);
const baseOptions = segments[0]?.options ?? normalizeBarcodeOptions();
const height = segments.reduce((maxHeight, segment) => Math.max(maxHeight, segment.height), 0);
return {
width: totalContentWidth + baseOptions.marginLeft + baseOptions.marginRight,
height,
segments,
};
}
function createEncoder(format, value, options) {
switch (format) {
case 'CODE128':
return {
valid: () => /^[\x20-\x7F]+$/.test(value),
encode: () => encodeCODE128(value, options),
};
case 'EAN13':
return {
valid: () => /^[0-9]{12,13}$/.test(value),
encode: () => encodeEAN13(value, options),
};
case 'UPC-A':
return {
valid: () => /^[0-9]{11,12}$/.test(value),
encode: () => encodeUPC(value, options),
};
case 'ITF14':
return {
valid: () => /^[0-9]{13,14}$/.test(value),
encode: () => encodeITF14(value, options),
};
}
}