UNPKG

node-thermal-printer

Version:

Print on Epson, Star, Tranca, Daruma, Brother and Custom thermal printers with Node.js

610 lines (527 loc) 17.8 kB
const { PNG } = require('pngjs'); const iconv = require('iconv-lite'); const PrinterTypes = { EPSON: 'epson', TANCA: 'tanca', STAR: 'star', DARUMA: 'daruma', BROTHER: 'brother', CUSTOM: 'custom', }; const BreakLine = { NONE: 'NONE', CHARACTER: 'CHARACTER', WORD: 'WORD', }; const CharacterSet = { PC437_USA: 'PC437_USA', PC850_MULTILINGUAL: 'PC850_MULTILINGUAL', PC860_PORTUGUESE: 'PC860_PORTUGUESE', PC863_CANADIAN_FRENCH: 'PC863_CANADIAN_FRENCH', PC865_NORDIC: 'PC865_NORDIC', PC851_GREEK: 'PC851_GREEK', PC857_TURKISH: 'PC857_TURKISH', PC737_GREEK: 'PC737_GREEK', ISO8859_7_GREEK: 'ISO8859_7_GREEK', WPC1252: 'WPC1252', PC866_CYRILLIC2: 'PC866_CYRILLIC2', PC852_LATIN2: 'PC852_LATIN2', SLOVENIA: 'SLOVENIA', PC858_EURO: 'PC858_EURO', WPC775_BALTIC_RIM: 'WPC775_BALTIC_RIM', PC855_CYRILLIC: 'PC855_CYRILLIC', PC861_ICELANDIC: 'PC861_ICELANDIC', PC862_HEBREW: 'PC862_HEBREW', PC864_ARABIC: 'PC864_ARABIC', PC869_GREEK: 'PC869_GREEK', ISO8859_2_LATIN2: 'ISO8859_2_LATIN2', ISO8859_15_LATIN9: 'ISO8859_15_LATIN9', PC1125_UKRANIAN: 'PC1125_UKRANIAN', WPC1250_LATIN2: 'WPC1250_LATIN2', WPC1251_CYRILLIC: 'WPC1251_CYRILLIC', WPC1253_GREEK: 'WPC1253_GREEK', WPC1254_TURKISH: 'WPC1254_TURKISH', WPC1255_HEBREW: 'WPC1255_HEBREW', WPC1256_ARABIC: 'WPC1256_ARABIC', WPC1257_BALTIC_RIM: 'WPC1257_BALTIC_RIM', WPC1258_VIETNAMESE: 'WPC1258_VIETNAMESE', KZ1048_KAZAKHSTAN: 'KZ1048_KAZAKHSTAN', JAPAN: 'JAPAN', KOREA: 'KOREA', CHINA: 'CHINA', HK_TW: 'HK_TW', TCVN_VIETNAMESE: 'TCVN_VIETNAMESE', }; class ThermalPrinter { constructor (initConfig) { if (initConfig.interface) { const getInterface = require('./interfaces'); this.Interface = getInterface( initConfig.interface, initConfig.options, initConfig.driver ); } if (!this.Interface) throw new Error("No interface! Please set 'interface' in the config."); this.buffer = null; this.config = null; this.printer = null; this.types = PrinterTypes; switch (initConfig.type) { case this.types.EPSON: const Epson = require('./types/epson'); this.printer = new Epson(); break; case this.types.STAR: const Star = require('./types/star'); this.printer = new Star(); break; case this.types.TANCA: const Tanca = require('./types/tanca'); this.printer = new Tanca(); break; case this.types.DARUMA: const Daruma = require('./types/daruma'); this.printer = new Daruma(); break; case this.types.BROTHER: const Brother = require('./types/brother'); this.printer = new Brother(); break; case this.types.CUSTOM: const Custom = require('./types/custom'); this.printer = new Custom(); break; } if (!this.printer) { throw new Error(`Printer type '${initConfig.type}' not recognized!`); } this.config = { type: initConfig.type, width: parseInt(initConfig.width) || 48, characterSet: initConfig.characterSet, removeSpecialCharacters: initConfig.removeSpecialCharacters || false, lineCharacter: initConfig.lineCharacter || '-', breakLine: initConfig.breakLine || BreakLine.WORD, options: initConfig.options, }; // Set initial code page. if (this.config.characterSet) this.setCharacterSet(this.config.characterSet); } setPrinterDriver (driver) { if (!this.Interface) throw new Error("No interface!"); this.Interface.driver = driver; } async execute (options = {}) { if (!this.Interface) throw new Error("No interface!"); try { return await this.Interface.execute(this.buffer, options); } catch (error) { throw error; } } cut ({ verticalTabAmount = 2 } = {}) { for (let i = 0; i < verticalTabAmount; i++) { this.append(this.printer.config.CTL_VT); } this.append(this.printer.config.PAPER_FULL_CUT); this.initHardware(); } partialCut ({ verticalTabAmount = 2 } = {}) { for (let i = 0; i < verticalTabAmount; i++) { this.append(this.printer.config.CTL_VT); } this.append(this.printer.config.PAPER_PART_CUT); this.initHardware(); } initHardware () { this.append(this.printer.config.HW_INIT); } getWidth () { return parseInt(this.config.width); } getText () { return this.buffer.toString(); } getBuffer () { return this.buffer; } setBuffer (newBuffer) { this.buffer = Buffer.from(newBuffer); } clear () { this.buffer = null; if (this.config.characterSet) this.setCharacterSet(this.config.characterSet); } add (buffer) { this.append(buffer); } print (text) { text = text || ''; if (this.config.breakLine != BreakLine.NONE) { // Break lines text = this._fold(text, this.config.width, this.config.breakLine == BreakLine.CHARACTER).join("\n"); } this.append(text.toString()); } println (text) { this.print(text); this.append('\n'); } printVerticalTab () { this.append(this.printer.config.CTL_VT); } bold (enabled) { if (enabled) this.append(this.printer.config.TXT_BOLD_ON); else this.append(this.printer.config.TXT_BOLD_OFF); } underline (enabled) { if (enabled) this.append(this.printer.config.TXT_UNDERL_ON); else this.append(this.printer.config.TXT_UNDERL_OFF); } underlineThick (enabled) { if (enabled) this.append(this.printer.config.TXT_UNDERL2_ON); else this.append(this.printer.config.TXT_UNDERL_OFF); } upsideDown (enabled) { if (enabled) this.append(this.printer.config.UPSIDE_DOWN_ON); else this.append(this.printer.config.UPSIDE_DOWN_OFF); } invert (enabled) { if (enabled) this.append(this.printer.config.TXT_INVERT_ON); else this.append(this.printer.config.TXT_INVERT_OFF); } openCashDrawer () { if (this.config.type === this.types.STAR) { this.append(this.printer.config.CD_KICK); } else { this.append(this.printer.config.CD_KICK_2); this.append(this.printer.config.CD_KICK_5); } } alignCenter () { this.append(this.printer.config.TXT_ALIGN_CT); } alignLeft () { this.append(this.printer.config.TXT_ALIGN_LT); } alignRight () { this.append(this.printer.config.TXT_ALIGN_RT); } setTypeFontA () { this.append(this.printer.config.TXT_FONT_A); } setTypeFontB () { this.append(this.printer.config.TXT_FONT_B); } setTextNormal () { this.append(this.printer.config.TXT_NORMAL); } setTextDoubleHeight () { this.append(this.printer.config.TXT_2HEIGHT); } setTextDoubleWidth () { this.append(this.printer.config.TXT_2WIDTH); } setTextQuadArea () { this.append(this.printer.config.TXT_4SQUARE); } setTextSize (height, width) { this.append(this.printer.setTextSize(height, width)); } // ----------------------------------------------------- NEW LINE ----------------------------------------------------- newLine () { this.append(this.printer.config.CTL_LF); } // ----------------------------------------------------- DRAW LINE ----------------------------------------------------- drawLine (character = this.config.lineCharacter) { for (let i = 0; i < this.config.width; i++) { this.append(Buffer.from(character)); } this.newLine(); } // ----------------------------------------------------- LEFT RIGHT ----------------------------------------------------- leftRight (left, right) { this.append(left.toString()); const width = this.config.width - left.toString().length - right.toString().length; for (let i = 0; i < width; i++) { this.append(Buffer.from(' ')); } this.append(right.toString()); this.newLine(); } // ----------------------------------------------------- TABLE ----------------------------------------------------- table (data) { const cellWidth = this.config.width / data.length; for (let i = 0; i < data.length; i++) { this.append(data[i].toString()); const spaces = cellWidth - data[i].toString().length; for (let j = 0; j < spaces; j++) { this.append(Buffer.from(' ')); } } this.newLine(); } // ----------------------------------------------------- TABLE CUSTOM ----------------------------------------------------- // Options: text, align, width, bold tableCustom (data) { let cellWidth = this.config.width / data.length; const secondLine = []; let secondLineEnabled = false; for (let i = 0; i < data.length; i++) { let tooLong = false; const obj = data[i]; obj.text = obj.text.toString(); if (obj.width) { cellWidth = this.config.width * obj.width; } else if (obj.cols) { cellWidth = obj.cols; } if (obj.bold) { this.bold(true); } // If text is too wide go to next line if (cellWidth < obj.text.length) { tooLong = true; obj.originalText = obj.text; obj.text = obj.text.substring(0, cellWidth - 1); } if (obj.align == 'CENTER') { const spaces = (cellWidth - obj.text.toString().length) / 2; for (let j = 0; j < spaces; j++) { this.append(Buffer.from(' ')); } if (obj.text != '') this.append(obj.text); for (let j = 0; j < spaces - 1; j++) { this.append(Buffer.from(' ')); } } else if (obj.align == 'RIGHT') { const spaces = cellWidth - obj.text.toString().length; for (let j = 0; j < spaces; j++) { this.append(Buffer.from(' ')); } if (obj.text != '') this.append(obj.text); } else { if (obj.text != '') this.append(obj.text); const spaces = cellWidth - obj.text.toString().length; for (let j = 0; j < spaces; j++) { this.append(Buffer.from(' ')); } } if (obj.bold) { this.bold(false); } if (tooLong) { secondLineEnabled = true; obj.text = obj.originalText.substring(cellWidth - 1); secondLine.push(obj); } else { obj.text = ''; secondLine.push(obj); } } this.newLine(); // Print the second line if (secondLineEnabled) { this.tableCustom(secondLine); } } // ----------------------------------------------------- IS PRINTER CONNECTED ----------------------------------------------------- async isPrinterConnected (exists) { return this.Interface.isPrinterConnected(exists); } // ----------------------------------------------------- GET PRINTER STATUS ----------------------------------------------------- async getStatus () { this.append(this.printer.getStatus()); } // ----------------------------------------------------- BEEP ----------------------------------------------------- beep (numberOfBeeps, lengthOfTheSound) { this.append(this.printer.beep(numberOfBeeps, lengthOfTheSound)); } // ----------------------------------------------------- PRINT QR ----------------------------------------------------- printQR (data, settings) { this.append(this.printer.printQR(data, settings)); } // ----------------------------------------------------- PRINT BARCODE ----------------------------------------------------- printBarcode (data, type, settings) { this.append(this.printer.printBarcode(data, type, settings)); } // ----------------------------------------------------- PRINT MAXICODE ----------------------------------------------------- maxiCode (data, settings) { this.append(this.printer.maxiCode(data, settings)); } // ----------------------------------------------------- PRINT CODE128 ----------------------------------------------------- code128 (data, settings) { this.append(this.printer.code128(data, settings)); } // ----------------------------------------------------- PRINT PDF417 ----------------------------------------------------- pdf417 (data, settings) { this.append(this.printer.pdf417(data, settings)); } // ----------------------------------------------------- PRINT IMAGE ----------------------------------------------------- async printImage (image) { try { const fs = require('fs'); // Check if file exists fs.accessSync(image); // Check for file type if (image.slice(-4) === '.png') { try { const response = await this.printer.printImage(image); this.append(response); return response; } catch (error) { throw error; } } else { throw new Error('Image printing supports only PNG files.'); } } catch (error) { throw error; } } // ----------------------------------------------------- PRINT IMAGE BUFFER ----------------------------------------------------- async printImageBuffer (buffer) { try { const png = PNG.sync.read(buffer); const buff = this.printer.printImageBuffer(png.width, png.height, png.data); this.append(buff); return buff; } catch (error) { throw error; } } // ------------------------------ RAW ------------------------------ async raw (text) { try { return await this.Interface.execute(text); } catch (error) { throw error; } } // ------------------------------ Merge objects ------------------------------ mergeObjects (obj1, obj2) { const obj3 = {}; for (const attrname in obj1) { obj3[attrname] = obj1[attrname]; } for (const attrname in obj2) { obj3[attrname] = obj2[attrname]; } return obj3; } // ------------------------------ Set character set ------------------------------ setCharacterSet (characterSet) { const buffer = this.printer.config[`CODE_PAGE_${characterSet}`]; if (buffer) { this.append(buffer); this.config.codePage = characterSet; } else { throw new Error(`Code page not recognized: '${characterSet}'`); } } // ------------------------------ Append ------------------------------ append (text) { if (typeof text === 'string') { // Remove special characters. if (this.config.removeSpecialCharacters) { const unorm = require('unorm'); const combining = /[\u0300-\u036F]/g; text = unorm.nfkd(text).replace(combining, ''); } let endBuff = null; for (const char of text) { let code = char; if (!/^[\x00-\x7F]$/.test(char)) { // Test if the active code page can print the current character. try { code = iconv.encode(char, this.printer.config.CODE_PAGES[this.config.codePage]); } catch (e) { // Probably encoding not recognized. console.error(e); code = '?'; } if (code.toString() === '?') { // Character not available in active code page, now try all other code pages. for (const tmpCodePageKey of Object.keys(this.printer.config.CODE_PAGES)) { const tmpCodePage = this.printer.config.CODE_PAGES[tmpCodePageKey]; try { code = iconv.encode(char, tmpCodePage); } catch (e) { // Probably encoding not recognized. console.error(e); } if (code.toString() !== '?') { // We found a match, change active code page. this.config.codePage = tmpCodePageKey; code = Buffer.concat([this.printer.config[`CODE_PAGE_${tmpCodePageKey}`], code]); break; } } } } endBuff = endBuff ? Buffer.concat([endBuff, Buffer.from(code)]) : Buffer.from(code); } text = endBuff; } // Append buffer if (text) { if (this.buffer) { this.buffer = Buffer.concat([this.buffer, text]); } else { this.buffer = text; } } } // ------------------------------ Fold ------------------------------ /** * This function splits text input into multiple lines. Returns array of lines * @param {string} text - text to split into lines * @param {number} lineSize - maximum allowed character count in one line * @param {boolean} breakWord - Break word or character * @param {array} lineArray - Array of lines passed for recursion * @returns {array} Array of lines */ _fold (text, lineSize, breakWord, lineArray = []) { text = String(text); // Split the text by newlines first const lines = text.split('\n'); for (let i = 0; i < lines.length; i++) { let currentLine = lines[i]; while (currentLine.length > lineSize) { let line = currentLine.substring(0, lineSize); if (!breakWord) { // Insert newlines anywhere lineArray.push(line); currentLine = currentLine.substring(lineSize); } else { // Attempt to insert newlines after whitespace const lastSpaceRgx = /\s(?!.*\s)/; const idx = line.search(lastSpaceRgx); let nextIdx = lineSize; if (idx > 0) { line = line.substring(0, idx); nextIdx = idx; } lineArray.push(line); currentLine = currentLine.substring(nextIdx); } } // Push the remaining part of the line if (currentLine.length > 0) { lineArray.push(currentLine); } } return lineArray; } } module.exports = { printer: ThermalPrinter, types: PrinterTypes, printerTypes: PrinterTypes, breakLine: BreakLine, characterSet: CharacterSet, ThermalPrinter, PrinterTypes, BreakLine, CharacterSet };