125 lines
5.2 KiB
JavaScript
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, "&")
|
|
.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,
|
|
};
|
|
|