Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ <h4>${titleHtml}</h4>
const printer = this.printers[printerIdx];
const textarea = this.labelForm.querySelector('#labelFormText') as HTMLTextAreaElement;
const rawReceiptline = textarea.value;
const doc = WebReceipt.parseReceiptLineToDocument(rawReceiptline, printer.printerOptions);
const doc = await WebReceipt.parseReceiptLineToDocument(rawReceiptline, printer.printerOptions);
await printer.sendDocument(doc);
});
document.getElementById(`printer_${idx}`)!
Expand Down
2 changes: 1 addition & 1 deletion demo/test_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ class BasicDocumentPrinterApp {
const printer = this.printers[printerIdx];
const textarea = this.labelForm.querySelector('#labelFormText') as HTMLTextAreaElement;
const rawReceiptline = textarea.value;
const doc = WebReceipt.parseReceiptLineToDocument(rawReceiptline, printer.printerOptions);
const doc = await WebReceipt.parseReceiptLineToDocument(rawReceiptline, printer.printerOptions);
await printer.sendDocument(doc);
});
document.getElementById(`printer_${idx}`)!
Expand Down
4 changes: 2 additions & 2 deletions src/Commands/BasicCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class ImageCommand implements IPrinterBasicCommand {
effectFlags = new CommandEffectFlags(["feedsPaper"]);
toDisplay() { return this.name; }

constructor(public readonly imgData: string) {}
constructor(public readonly img: Util.BitmapGRF) {}
}

export class Barcode implements IPrinterBasicCommand {
Expand Down Expand Up @@ -134,7 +134,7 @@ export class RawCommand<TOutput> implements IPrinterBasicCommand {
export type Underline = "None" | "Single" | "Double";
export type Bold = "None" | "Enable";
export type Invert = "None" | "Enable";
export type Alignment = "Left" | "Center" | "Right";
export type Alignment = "Left" | "Center" | "Right" | "Toggle";
export type Width = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export type Height = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;

Expand Down
90 changes: 82 additions & 8 deletions src/Languages/EscPos/BasicCommands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as Util from '../../Util/index.js';
import * as Cmds from '../../Commands/index.js';
import * as Conf from '../../Configs/index.js';
import { codepageNumberForEscPos, codepageSwitchCmd } from './Codepages.js';

/** Encode a single character, for readability of command sequences. */
Expand Down Expand Up @@ -51,7 +52,7 @@ export function setTextFormatting(f: Cmds.TextFormat, docState: Cmds.TranspiledD
const buffer: number[] = [];

if (f.underline !== undefined || f.resetToDefault) { // ESC - // FS -
docState.textFormat.underline = f.underline;
docState.textFormat.underline = f.underline ?? 'None';
let op: number;
switch (f.underline) {
default:
Expand All @@ -75,11 +76,20 @@ export function setTextFormatting(f: Cmds.TextFormat, docState: Cmds.TranspiledD
}

if (f.alignment !== undefined || f.resetToDefault) { // ESC a
docState.textFormat.alignment = f.alignment;
let newAlign = f.alignment ?? 'Center';
if (newAlign === 'Toggle') {
switch (docState.textFormat.alignment) {
case 'Left': newAlign = 'Right'; break;
case 'Right': newAlign = 'Left'; break;
default: newAlign = 'Center'; break;
}
}

docState.textFormat.alignment = newAlign;
let op: number;
switch (f.alignment) {
switch (newAlign) {
default: Util.exhaustiveMatchGuard(newAlign); break;
case 'Left': op = 0x00; break;
default:
case 'Center': op = 0x01; break;
case 'Right': op = 0x02; break;
}
Expand Down Expand Up @@ -115,10 +125,10 @@ export function setCodepage(
docState.codepage = gotCode;
}

return [new Uint8Array([
return new Uint8Array([
// ESC t <codepage ID>, then any other weird commands we might need to set.
Util.AsciiCodeNumbers.ESC, enc('t')]), code
];
Util.AsciiCodeNumbers.ESC, enc('t'),
...code]);
}

export function offsetPrintPosition(
Expand Down Expand Up @@ -203,12 +213,76 @@ export function text(
const fragments = Util.CodepageEncoder
.autoEncode(text, candidateCodepages)
.flatMap(f => [
...setCodepage(f.codepage, docState),
setCodepage(f.codepage, docState),
f.bytes
]);
return fragments;
}

export function image(
cmd: Cmds.ImageCommand,
docState: Cmds.TranspiledDocumentState
) {
const buffer: number[] = [];
const invert = docState.initialConfig.printOrientation === Conf.PrintOrientation.inverted;

if (invert) {
// When printing inverted we must also invert the print area and alignment.
buffer.push(...setPrintArea(new Cmds.SetPrintArea(
docState.margin.rightChars,
docState.currentPrintWidth,
docState.margin.leftChars
), docState),
...setTextFormatting({alignment: "Toggle"}, docState)
);
// let r = docState.initialConfig.printOrientation == Conf.PrintOrientation.inverted
// ? this.area(right + this.marginRight - this.margin, width, left) + this.align(2 - align)
// : '';
}

// Number of rows of pixels to split the image on into multiple commands.
const maxImgLength = 512;

// ESCPOS treats colors as print element enable. 1 means black, 0 means white.
const bitmap = invert
? cmd.img.toInvertedGRF().rotate(180)
: cmd.img.toInvertedGRF();
const imgWidth = bitmap.boundingBox.width;
const imgByteWidth = bitmap.boundingBox.width + 7 >> 3;
const imgData = bitmap.toBinaryGRF();

for (let z = 0; z < bitmap.boundingBox.height; z += maxImgLength) {
const imgSplitHeight = Math.min(maxImgLength, bitmap.boundingBox.height - z);
const imgDataLength = imgByteWidth * imgSplitHeight + 10; // TODO: why 10?

buffer.push(
Util.AsciiCodeNumbers.GS, enc('8'), enc('L'), // Direct load raster img
imgDataLength & 255, // p1
imgDataLength >> 8 & 255, // p2
imgDataLength >> 16 & 255, // p3
imgDataLength >> 24 & 255, // p4

48, 112, // raster img data command
48, // monocolor, 52 = grayscale
1, // bx, horizontal scale, 1 or 2
1, // by, vertical scale, 1 or 2
49, // c, color, 49 for mono. Not all printers support other colors.

imgWidth & 255, // xL
imgWidth >> 8 & 255, // xH
imgSplitHeight & 255, // yL
imgSplitHeight >> 8 & 255, // yH

...imgData.slice((z + 7 >> 3), imgByteWidth * imgSplitHeight),

Util.AsciiCodeNumbers.GS, enc('('), enc('L'), // Print raster img
2, 0, 48, 50
);
}

return new Uint8Array(buffer);
}

export function setFormattingCodepage() {
// FS C 0 to disable kanji
// FS . to reset katakana mode?
Expand Down
4 changes: 2 additions & 2 deletions src/Languages/EscPos/EscPos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ export class EscPos extends Cmds.RawCommandSet {
},
Codepage: {
commandType: 'Codepage',
transpile: (c, d) => this.combineCommands(...Basic.setCodepage((c as Cmds.SetCodepage).codepage, d)),
transpile: (c, d) => Basic.setCodepage((c as Cmds.SetCodepage).codepage, d),
},
HorizontalRule: {
commandType: 'HorizontalRule',
transpile: (c, d) => this.combineCommands(...Basic.horizontalRule((c as Cmds.HorizontalRule), d))
},
Image: {
commandType: 'Image',
transpile: (c) => { throw new Cmds.TranspileDocumentError(`Command not implemented: ${c.constructor.name}`) },
transpile: (c, d) => Basic.image(c as Cmds.ImageCommand, d),
},
SetLineSpacing: {
commandType: 'SetLineSpacing',
Expand Down
161 changes: 0 additions & 161 deletions src/ReceiptLine/ESCPOS.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,6 @@ import Flatten from 'canvas-flatten';

const codepageMappings = {
epson: {
'cp437': 0x00,
'shiftjis': 0x01,
'cp850': 0x02,
'cp860': 0x03,
'cp863': 0x04,
'cp865': 0x05,
'cp851': 0x0b,
'cp853': 0x0c,
'cp857': 0x0d,
'cp737': 0x0e,
'iso88597': 0x0f,
'windows1252': 0x10,
'cp866': 0x11,
'cp852': 0x12,
'cp858': 0x13,
'cp720': 0x20,
'cp775': 0x21,
'cp855': 0x22,
'cp861': 0x23,
'cp862': 0x24,
'cp864': 0x25,
'cp869': 0x26,
'iso88592': 0x27,
'iso885915': 0x28,
'cp1098': 0x29,
'cp1118': 0x2a,
'cp1119': 0x2b,
'cp1125': 0x2c,
'windows1250': 0x2d,
'windows1251': 0x2e,
'windows1253': 0x2f,
'windows1254': 0x30,
'windows1255': 0x31,
'windows1256': 0x32,
'windows1257': 0x33,
'windows1258': 0x34,
'rk1048': 0x35,
},

zjiang: {
Expand Down Expand Up @@ -607,128 +570,4 @@ class EscPosEncoder {

return this;
}

/**
* Image
*
* @param {object} element an element, like a canvas or image that needs to be printed
* @param {number} width width of the image on the printer
* @param {number} height height of the image on the printer
* @param {string} algorithm the dithering algorithm for making the image black and white
* @param {number} threshold threshold for the dithering algorithm
* @return {object} Return the object, for easy chaining commands
*
*/
image(element, width, height, algorithm, threshold) {
if (this._embedded) {
throw new Error('Images are not supported in table cells or boxes');
}

if (width % 8 !== 0) {
throw new Error('Width must be a multiple of 8');
}

if (height % 8 !== 0) {
throw new Error('Height must be a multiple of 8');
}

if (typeof algorithm === 'undefined') {
algorithm = 'threshold';
}

if (typeof threshold === 'undefined') {
threshold = 128;
}

const canvas = createCanvas(width, height);
const context = canvas.getContext('2d');
context.drawImage(element, 0, 0, width, height);
let image = context.getImageData(0, 0, width, height);

image = Flatten.flatten(image, [0xff, 0xff, 0xff]);

switch (algorithm) {
case 'threshold': image = Dither.threshold(image, threshold); break;
case 'bayer': image = Dither.bayer(image, threshold); break;
case 'floydsteinberg': image = Dither.floydsteinberg(image); break;
case 'atkinson': image = Dither.atkinson(image); break;
}

const getPixel = (x, y) => x < width && y < height ? (image.data[((width * y) + x) * 4] > 0 ? 0 : 1) : 0;

const getColumnData = (width, height) => {
const data = [];

for (let s = 0; s < Math.ceil(height / 24); s++) {
const bytes = new Uint8Array(width * 3);

for (let x = 0; x < width; x++) {
for (let c = 0; c < 3; c++) {
for (let b = 0; b < 8; b++) {
bytes[(x * 3) + c] |= getPixel(x, (s * 24) + b + (8 * c)) << (7 - b);
}
}
}

data.push(bytes);
}

return data;
};

const getRowData = (width, height) => {
const bytes = new Uint8Array((width * height) >> 3);

for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x = x + 8) {
for (let b = 0; b < 8; b++) {
bytes[(y * (width >> 3)) + (x >> 3)] |= getPixel(x + b, y) << (7 - b);
}
}
}

return bytes;
};


if (this._cursor != 0) {
this.newline();
}

/* Encode images with ESC * */

if (this._options.imageMode == 'column') {
this._queue([
0x1b, 0x33, 0x24,
]);

getColumnData(width, height).forEach((bytes) => {
this._queue([
0x1b, 0x2a, 0x21,
(width) & 0xff, (((width) >> 8) & 0xff),
bytes,
0x0a,
]);
});

this._queue([
0x1b, 0x32,
]);
}

/* Encode images with GS v */

if (this._options.imageMode == 'raster') {
this._queue([
0x1d, 0x76, 0x30, 0x00,
(width >> 3) & 0xff, (((width >> 3) >> 8) & 0xff),
height & 0xff, ((height >> 8) & 0xff),
getRowData(width, height),
]);
}

this._flush();

return this;
}
}
Loading