import { EscPos, bitonalFromRGBA, rasterToEscPos } from '@sims/printing' /** * Indic raster receipt (09-UX §5.3): ESC/POS text mode can't render Devanagari * or Malayalam, so non-Latin receipts are drawn to an offscreen canvas and sent * as a GS v 0 bit-image. Canvas is a browser API, so this lives in the app; the * thresholding + bit-packing are the pure @sims/printing package (raster.ts). * * v1 caveat (documented): browsers fall back to the OS fonts for canvas text, so * Devanagari/Malayalam coverage depends on the shop PC having a suitable font * (Windows ships Nirmala UI). The raster *pipeline* is what S2 delivers; bundled * canvas webfonts are a follow-up. Latin/ASCII always renders. */ /** Print-head pixel width: 384 dots for 2" (32 col), 576 for 3" (42 col). */ export function headWidth(cols: 32 | 42): number { return cols === 32 ? 384 : 576 } export function renderRasterReceipt(lines: string[], opts: { width: 32 | 42; kickDrawer?: boolean }): Uint8Array { const W = headWidth(opts.width) const fontPx = opts.width === 32 ? 20 : 22 const lineH = Math.round(fontPx * 1.35) const padTop = 8 const padBottom = 24 const H = padTop + lines.length * lineH + padBottom const canvas = document.createElement('canvas') canvas.width = W canvas.height = H const ctx = canvas.getContext('2d') if (ctx === null) throw new Error('Canvas 2D unavailable — cannot rasterize receipt') ctx.fillStyle = '#fff' ctx.fillRect(0, 0, W, H) ctx.fillStyle = '#000' ctx.textBaseline = 'top' // A font stack the browser can fall back to for Indic scripts on the shop PC. ctx.font = `${fontPx}px "Noto Sans", "Noto Sans Devanagari", "Nirmala UI", "Segoe UI", monospace` lines.forEach((text, i) => { const y = padTop + i * lineH if (i === 0) { ctx.textAlign = 'center' ctx.fillText(text, W / 2, y) } else { ctx.textAlign = 'left' ctx.fillText(text, 4, y) } }) const img = ctx.getImageData(0, 0, W, H) const body = rasterToEscPos(bitonalFromRGBA(img.data, W, H)) const head = new EscPos().init() if (opts.kickDrawer === true) head.drawerKick() const prefix = head.align('left').build() const tail = new EscPos().feed(4).cut().build() const out = new Uint8Array(prefix.length + body.length + tail.length) out.set(prefix, 0) out.set(body, prefix.length) out.set(tail, prefix.length + body.length) return out }