Files
printerCentral/src/eposBuilder.js

125 lines
5.2 KiB
JavaScript

// Server-side minimal ePOS-Print XML builder for Epson printers
// Focused on printer-only tags. Builds the inner message for <epos-print>.
function escapeXml(str) {
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&apos;");
}
class EposMessageBuilder {
constructor() {
this.parts = [];
}
// Text and styles
text(content) { this.parts.push(`<text>${escapeXml(content)}</text>`); return this; }
textLang(lang) { this.parts.push(`<text lang="${lang}"/>`); return this; }
textAlign(align) { this.parts.push(`<text align="${align}"/>`); return this; }
textRotate(rotate) { this.parts.push(`<text rotate="${rotate ? 'true' : 'false'}"/>`); return this; }
textLineSpace(linespc) { this.parts.push(`<text linespc="${linespc}"/>`); return this; }
textFont(font) { this.parts.push(`<text font="${font}"/>`); return this; }
textSmooth(smooth) { this.parts.push(`<text smooth="${smooth ? 'true' : 'false'}"/>`); return this; }
textDouble(dw, dh) {
const attrs = [dw !== undefined ? `dw="${dw ? 'true' : 'false'}"` : '', dh !== undefined ? `dh="${dh ? 'true' : 'false'}"` : ''].filter(Boolean).join(' ');
this.parts.push(`<text ${attrs}/>`.trim());
return this;
}
textSize(width, height) {
const attrs = [width !== undefined ? `width="${width}"` : '', height !== undefined ? `height="${height}"` : ''].filter(Boolean).join(' ');
this.parts.push(`<text ${attrs}/>`.trim());
return this;
}
textStyle({ reverse, ul, em, color } = {}) {
const attrs = [];
if (reverse !== undefined) attrs.push(`reverse="${reverse ? 'true' : 'false'}"`);
if (ul !== undefined) attrs.push(`ul="${ul ? 'true' : 'false'}"`);
if (em !== undefined) attrs.push(`em="${em ? 'true' : 'false'}"`);
if (color !== undefined) attrs.push(`color="${color}"`);
this.parts.push(`<text ${attrs.join(' ')}/>`.trim());
return this;
}
textPosition(x) { this.parts.push(`<text x="${x}"/>`); return this; }
textVPosition(y) { this.parts.push(`<text y="${y}"/>`); return this; }
// Feed
feed() { this.parts.push('<feed/>'); return this; }
feedUnit(unit) { this.parts.push(`<feed unit="${unit}"/>`); return this; }
feedLine(line) { this.parts.push(`<feed line="${line}"/>`); return this; }
feedPosition(pos) { this.parts.push(`<feed pos="${pos}"/>`); return this; }
// Cut
cut(type) { this.parts.push(`<cut type="${type}"/>`); return this; }
// Drawer
pulse({ drawer = 'drawer_1', time = 'pulse_200' } = {}) { this.parts.push(`<pulse drawer="${drawer}" time="${time}"/>`); return this; }
// Barcode and symbols (minimal)
barcode(data, { type, hri, font, width, height } = {}) {
const attrs = [];
if (type) attrs.push(`type="${type}"`);
if (hri) attrs.push(`hri="${hri}"`);
if (font) attrs.push(`font="${font}"`);
if (width) attrs.push(`width="${width}"`);
if (height) attrs.push(`height="${height}"`);
this.parts.push(`<barcode ${attrs.join(' ')}>${escapeXml(data)}</barcode>`.trim());
return this;
}
qrcode(data, { model = 'qrcode_model_2', level = 'level_m', size } = {}) {
const attrs = [`type="${model}"`, `level="${level}"`];
if (size) attrs.push(`size="${size}"`);
this.parts.push(`<symbol ${attrs.join(' ')}>${escapeXml(data)}</symbol>`);
return this;
}
// Logo (NV) and image are omitted for now; can be added later.
build() {
return this.parts.join('');
}
}
// Accepts a list of operations: [{ op: 'text', args: {...} | [] }, ...]
function buildFromOperations(ops) {
const b = new EposMessageBuilder();
for (const item of ops || []) {
const { op } = item;
if (!op) continue;
switch (op) {
case 'text': b.text(item.value ?? ''); break;
case 'textLang': b.textLang(item.lang); break;
case 'textAlign': b.textAlign(item.align); break;
case 'textRotate': b.textRotate(!!item.rotate); break;
case 'textLineSpace': b.textLineSpace(item.linespc); break;
case 'textFont': b.textFont(item.font); break;
case 'textSmooth': b.textSmooth(!!item.smooth); break;
case 'textDouble': b.textDouble(item.dw, item.dh); break;
case 'textSize': b.textSize(item.width, item.height); break;
case 'textStyle': b.textStyle({ reverse: item.reverse, ul: item.ul, em: item.em, color: item.color }); break;
case 'textPosition': b.textPosition(item.x); break;
case 'textVPosition': b.textVPosition(item.y); break;
case 'feed': b.feed(); break;
case 'feedUnit': b.feedUnit(item.unit); break;
case 'feedLine': b.feedLine(item.line); break;
case 'feedPosition': b.feedPosition(item.pos); break;
case 'cut': b.cut(item.type || 'feed'); break;
case 'pulse': b.pulse({ drawer: item.drawer, time: item.time }); break;
case 'barcode': b.barcode(item.data || '', { type: item.type, hri: item.hri, font: item.font, width: item.width, height: item.height }); break;
case 'qrcode': b.qrcode(item.data || '', { model: item.model, level: item.level, size: item.size }); break;
default:
// ignore unknown for now
break;
}
}
return b.build();
}
module.exports = {
EposMessageBuilder,
buildFromOperations,
};