You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
55 lines
2.0 KiB
TypeScript
55 lines
2.0 KiB
TypeScript
/**
|
|
* Raster (bit-image) receipt bytes. ESC/POS text mode can't render Devanagari
|
|
* or Malayalam (09-UX §5.3), so non-Latin receipts print as a 1-bit raster via
|
|
* GS v 0. This module is the pure part — thresholding pixels and packing bits;
|
|
* the canvas that draws the glyphs lives in the app (apps/pos/src/raster.ts),
|
|
* because canvas is a browser API and byte-packing must stay testable.
|
|
*/
|
|
const GS = 0x1d
|
|
|
|
/** 1 bit per pixel, row-major; `bits[y*width + x]` is 1 for a printed (black) dot. */
|
|
export interface Bitmap {
|
|
width: number
|
|
height: number
|
|
bits: Uint8Array
|
|
}
|
|
|
|
/**
|
|
* Pack one pixel row (0/1 values) into bytes, most-significant bit = leftmost
|
|
* pixel, padded with zeros to a whole byte. 9 pixels → 2 bytes.
|
|
*/
|
|
export function packRasterRow(row: ArrayLike<number>): number[] {
|
|
const bytes = new Array<number>(Math.ceil(row.length / 8)).fill(0)
|
|
for (let i = 0; i < row.length; i++) {
|
|
if (row[i]) bytes[i >> 3]! |= 0x80 >> (i & 7)
|
|
}
|
|
return bytes
|
|
}
|
|
|
|
/**
|
|
* Threshold RGBA pixel data (canvas `ImageData.data`) into a 1-bpp bitmap.
|
|
* A dot prints when it is opaque and darker than `threshold` (Rec.601 luma).
|
|
*/
|
|
export function bitonalFromRGBA(
|
|
data: ArrayLike<number>, width: number, height: number, threshold = 128,
|
|
): Bitmap {
|
|
const bits = new Uint8Array(width * height)
|
|
for (let i = 0; i < width * height; i++) {
|
|
const r = data[i * 4]!, g = data[i * 4 + 1]!, b = data[i * 4 + 2]!, a = data[i * 4 + 3]!
|
|
const luma = 0.299 * r + 0.587 * g + 0.114 * b
|
|
bits[i] = a >= 128 && luma < threshold ? 1 : 0
|
|
}
|
|
return { width, height, bits }
|
|
}
|
|
|
|
/** GS v 0 raster bit-image command for a bitmap (m=0, normal density). */
|
|
export function rasterToEscPos(bmp: Bitmap): Uint8Array {
|
|
const bytesPerRow = Math.ceil(bmp.width / 8)
|
|
const out: number[] = [GS, 0x76, 0x30, 0, bytesPerRow & 0xff, (bytesPerRow >> 8) & 0xff, bmp.height & 0xff, (bmp.height >> 8) & 0xff]
|
|
for (let y = 0; y < bmp.height; y++) {
|
|
const row = bmp.bits.subarray(y * bmp.width, (y + 1) * bmp.width)
|
|
out.push(...packRasterRow(row))
|
|
}
|
|
return Uint8Array.from(out)
|
|
}
|