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
JavaScript
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
};