diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cf5b82ae..986edd3a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -25,7 +25,7 @@ export interface PrinterOptions { width?: number | undefined } -export type PrinterModel = null | "qsprinter"; +export type PrinterModel = null | "qsprinter" | "xprinter"; /** * 'dhdw', 'dwh' and 'dhw' are treated as 'dwdh' @@ -642,7 +642,13 @@ export class Printer extends EventEmitter { if (type === "CODE128" || type === "CODE93") codeLength = utils.codeLength(convertCode); - this.buffer.write(`${codeLength + convertCode + (options.includeParity ? parityBit : "")}\x00`); // Allow to skip the parity byte + if ((this._model === "xprinter") && type === "CODE128") { + const code128Data = utils.genCode128forXprinter(convertCode); + this.buffer.write(code128Data); + } else { + this.buffer.write(`${codeLength + convertCode + (options.includeParity ? parityBit : "")}\x00`); // Allow to skip the parity byte + } + if (this._model === "qsprinter") this.buffer.write(_.MODEL.QSPRINTER.BARCODE_MODE.OFF); diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 3e7fcfdf..49010043 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -63,4 +63,56 @@ export function intLowHighHex(input: number, length: number = 1): string { input = Math.floor(input / 256); } return ret; -} \ No newline at end of file +} + +const SPLIT_REGEX = /(^(?:\d\d){2,})|((?<=\D)(?:(?:\d\d){3,})(?=\d?\D))|((?:\d\d){2,}$)/; +// /(--beginning--)|--------(------middle--)----------|(-----end-----)/ +/** + * Helper function for barcode type CODE128, split string into blocks to optimize barcode length + * Refer to following Wikipedia page + * https://en.wikipedia.org/wiki/Code_128#Barcode_length_optimization_by_Code_128_Type-C + * @param {[string]} input + * @returns {[string[]]} blocks of string for optimized CODE128 barcode length + */ +export function splitForCode128(str: string): string[] { + // No need to match REGEX for short string + if(str.length <= 4) + return [str]; + + // Find 4+ consecutive and even number of digits at the beginning or end + // Find 6+ consecutive and even number of digits at the middle + // + // "12345ABC2345678BCD3456789CDE98765" => + // ["1234","5ABC","234567","8BCD","345678","9CDE9","8765"] + return str.split(SPLIT_REGEX).filter(s => s !== "" && s !== undefined); +} + +const USE_CODEC_REGEX = /^((?:\d\d){1,})$/; +/** + * Generate control code to print CODE128 on Xprinter brand printer + * Barcode length is optimized + * Documentation (Chinese only): + * https://www.xprinter.net/companyfile/1/ + * @param {[string]} input + * @returns {[string]} control code for printing + */ +export function genCode128forXprinter(barcode:string): string { + const toCodeC = (s:string) => + "{C" + s.match(/\d{2}/g) // split every 2 digit + ?.map(num => String.fromCharCode(Number(num))) + ?.join(''); + + const toCodeB = (s:string) => "{B" + s.replace('{','{{'); + + const blocks = splitForCode128(barcode) + const dataPart = blocks.map(block => USE_CODEC_REGEX.test(block) ? toCodeC(block) : toCodeB(block)) + .join(''); + const dataLength = dataPart.length; + + if (dataLength > 255) + throw new Error("Barcode data is too long"); + + return String.fromCharCode(dataLength) + dataPart; +} + + diff --git a/packages/core/test/index.test.ts b/packages/core/test/index.test.ts index 401553c6..cff2c287 100644 --- a/packages/core/test/index.test.ts +++ b/packages/core/test/index.test.ts @@ -1,7 +1,85 @@ import { describe, expect, it } from 'vitest' +import { splitForCode128, genCode128forXprinter } from '../src/utils'; describe('should', () => { it('exported', () => { expect(1).toEqual(1) - }) + }); + + it('Split string to optimized blocks for CODE128 barcode', () => { + const inputs: string[] = []; + const expected: string[][] = []; + + inputs.push("12"); + expected.push(["12"]); + + inputs.push("123"); + expected.push(["123"]); + + inputs.push("1234"); + expected.push(["1234"]); + + inputs.push("12345"); + expected.push(["1234", '5']); + + inputs.push("123456789"); + expected.push(["12345678","9"]); + + inputs.push("123A4567C"); + expected.push(["123A4567C"]); + + inputs.push("1234A4567C"); + expected.push(["1234","A4567C"]); + + inputs.push("AAA1234"); + expected.push(["AAA","1234"]); + + inputs.push("AAA12345"); + expected.push(["AAA1","2345"]); + + inputs.push("4321AAA1234"); + expected.push(["4321","AAA","1234"]); + + inputs.push("AAA123456BBB"); + expected.push(["AAA","123456","BBB"]); + + inputs.push("12345ABC2345678BCD3456789CDE98765"); + expected.push(["1234","5ABC","234567","8BCD","345678","9CDE9","8765"]); + + inputs.forEach((input,index) => { + expect(splitForCode128(input)).toEqual(expected[index]); + }); + }); + + it('generate CODE128 barcode printing control code for Xprinter', () => { + const inputs: string[] = []; + const expected: string[] = []; + + inputs.push("AB"); + expected.push("\x04{BAB"); + + inputs.push("12"); + expected.push("\x03{C\x0c"); + + inputs.push("123456789"); + expected.push("\x09{C\x0c\x22\x38\x4e{B9"); + + inputs.push("1234ABC"); + expected.push("\x09{C\x0c\x22{BABC"); + + inputs.push("ABC1234"); + expected.push("\x09{BABC{C\x0c\x22"); + + inputs.push("AAA123456BBB"); + expected.push("\x0f{BAAA{C\x0c\x22\x38{BBBB"); + + inputs.push("12345ABC2345678BCD3456789CDE98765"); + expected.push("\x25{C\x0c\x22{B5ABC{C\x17\x2d\x43{B8BCD{C\x22\x38\x4e{B9CDE9{C\x57\x41"); + + inputs.forEach((input,index) => { + expect(genCode128forXprinter(input)).toEqual(expected[index]); + }); + + }); }) +