// Server-side minimal ePOS-Print XML builder for Epson printers // Focused on printer-only tags. Builds the inner message for . function escapeXml(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/\"/g, """) .replace(/'/g, "'"); } class EposMessageBuilder { constructor() { this.parts = []; } // Text and styles text(content) { this.parts.push(`${escapeXml(content)}`); return this; } textLang(lang) { this.parts.push(``); return this; } textAlign(align) { this.parts.push(``); return this; } textRotate(rotate) { this.parts.push(``); return this; } textLineSpace(linespc) { this.parts.push(``); return this; } textFont(font) { this.parts.push(``); return this; } textSmooth(smooth) { this.parts.push(``); 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(``.trim()); return this; } textSize(width, height) { const attrs = [width !== undefined ? `width="${width}"` : '', height !== undefined ? `height="${height}"` : ''].filter(Boolean).join(' '); this.parts.push(``.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(``.trim()); return this; } textPosition(x) { this.parts.push(``); return this; } textVPosition(y) { this.parts.push(``); return this; } // Feed feed() { this.parts.push(''); return this; } feedUnit(unit) { this.parts.push(``); return this; } feedLine(line) { this.parts.push(``); return this; } feedPosition(pos) { this.parts.push(``); return this; } // Cut cut(type) { this.parts.push(``); return this; } // Drawer pulse({ drawer = 'drawer_1', time = 'pulse_200' } = {}) { this.parts.push(``); 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(`${escapeXml(data)}`.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(`${escapeXml(data)}`); 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, };