Initial commit: Epson ePOS Node backend + Vue3 UI (printer matricial2)
This commit is contained in:
124
src/eposBuilder.js
Normal file
124
src/eposBuilder.js
Normal file
@@ -0,0 +1,124 @@
|
||||
// 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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user