retail-print-kit 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -2
  3. package/bin/retail-print-kit.js +318 -0
  4. package/dist/browser/retail-print-kit.js +26 -0
  5. package/dist/browser/retail-print-kit.js.map +7 -0
  6. package/dist/browser/retail-print-kit.mjs +25 -0
  7. package/dist/browser.d.ts +15 -0
  8. package/dist/browser.js +35 -0
  9. package/dist/core/barcode1d.d.ts +12 -0
  10. package/dist/core/barcode1d.js +192 -0
  11. package/dist/core/bitmap.d.ts +31 -0
  12. package/dist/core/bitmap.js +126 -0
  13. package/dist/core/builder.d.ts +58 -0
  14. package/dist/core/builder.js +177 -0
  15. package/dist/core/encode.d.ts +8 -0
  16. package/dist/core/encode.js +17 -0
  17. package/dist/core/escpos-encoder.d.ts +4 -0
  18. package/dist/core/escpos-encoder.js +150 -0
  19. package/dist/core/identify.d.ts +22 -0
  20. package/dist/core/identify.js +71 -0
  21. package/dist/core/image.d.ts +13 -0
  22. package/dist/core/image.js +32 -0
  23. package/dist/core/ops.d.ts +78 -0
  24. package/dist/core/ops.js +2 -0
  25. package/dist/core/printer.d.ts +35 -0
  26. package/dist/core/printer.js +96 -0
  27. package/dist/core/rasterizer.d.ts +29 -0
  28. package/dist/core/rasterizer.js +199 -0
  29. package/dist/core/star-graphic-encoder.d.ts +12 -0
  30. package/dist/core/star-graphic-encoder.js +48 -0
  31. package/dist/core/star-line-encoder.d.ts +8 -0
  32. package/dist/core/star-line-encoder.js +119 -0
  33. package/dist/core/status.d.ts +22 -0
  34. package/dist/core/status.js +52 -0
  35. package/dist/index.d.ts +26 -0
  36. package/dist/index.js +49 -0
  37. package/dist/label/builder.d.ts +39 -0
  38. package/dist/label/builder.js +37 -0
  39. package/dist/label/ezpl.d.ts +6 -0
  40. package/dist/label/ezpl.js +78 -0
  41. package/dist/label/index.d.ts +12 -0
  42. package/dist/label/index.js +42 -0
  43. package/dist/label/svg.d.ts +3 -0
  44. package/dist/label/svg.js +46 -0
  45. package/dist/label/template.d.ts +61 -0
  46. package/dist/label/template.js +33 -0
  47. package/dist/label/tspl.d.ts +3 -0
  48. package/dist/label/tspl.js +83 -0
  49. package/dist/label/types.d.ts +69 -0
  50. package/dist/label/types.js +10 -0
  51. package/dist/label/zpl.d.ts +3 -0
  52. package/dist/label/zpl.js +57 -0
  53. package/dist/preview/html.d.ts +14 -0
  54. package/dist/preview/html.js +132 -0
  55. package/dist/preview/index.d.ts +8 -0
  56. package/dist/preview/index.js +14 -0
  57. package/dist/preview/png.d.ts +3 -0
  58. package/dist/preview/png.js +89 -0
  59. package/dist/profiles/epson.d.ts +10 -0
  60. package/dist/profiles/epson.js +54 -0
  61. package/dist/profiles/index.d.ts +7 -0
  62. package/dist/profiles/index.js +36 -0
  63. package/dist/profiles/star.d.ts +7 -0
  64. package/dist/profiles/star.js +39 -0
  65. package/dist/profiles/types.d.ts +41 -0
  66. package/dist/profiles/types.js +2 -0
  67. package/dist/template/expr.d.ts +16 -0
  68. package/dist/template/expr.js +85 -0
  69. package/dist/template/index.d.ts +4 -0
  70. package/dist/template/index.js +27 -0
  71. package/dist/template/render.d.ts +8 -0
  72. package/dist/template/render.js +119 -0
  73. package/dist/template/types.d.ts +81 -0
  74. package/dist/template/types.js +2 -0
  75. package/dist/transports/discover.d.ts +21 -0
  76. package/dist/transports/discover.js +73 -0
  77. package/dist/transports/net.d.ts +20 -0
  78. package/dist/transports/net.js +72 -0
  79. package/dist/transports/serial.d.ts +20 -0
  80. package/dist/transports/serial.js +71 -0
  81. package/dist/transports/types.d.ts +16 -0
  82. package/dist/transports/types.js +41 -0
  83. package/dist/transports/usb.d.ts +30 -0
  84. package/dist/transports/usb.js +128 -0
  85. package/dist/util/base64.d.ts +2 -0
  86. package/dist/util/base64.js +38 -0
  87. package/package.json +133 -4
@@ -0,0 +1,39 @@
1
+ import { BarcodeType } from '../core/ops';
2
+ import { Bitmap } from '../core/bitmap';
3
+ import { Label, LabelOp, Rotation } from './types';
4
+ export interface LabelSize {
5
+ /** Width and height in millimetres. */
6
+ widthMm: number;
7
+ heightMm: number;
8
+ dpi?: 203 | 300 | 600;
9
+ gapMm?: number;
10
+ }
11
+ /** Records label elements in dots; `mm()` converts millimetres at the label's dpi. */
12
+ export declare class LabelBuilder {
13
+ readonly label: Label;
14
+ constructor(size: LabelSize);
15
+ mm(mm: number): number;
16
+ copies(n: number): this;
17
+ darkness(n: number): this;
18
+ speed(ips: number): this;
19
+ push(op: LabelOp): this;
20
+ text(x: number, y: number, text: string, height: number, opts?: {
21
+ width?: number;
22
+ rotation?: Rotation;
23
+ bold?: boolean;
24
+ invert?: boolean;
25
+ }): this;
26
+ barcode(x: number, y: number, data: string, type: BarcodeType, height: number, opts?: {
27
+ module?: number;
28
+ rotation?: Rotation;
29
+ text?: boolean;
30
+ }): this;
31
+ qr(x: number, y: number, data: string, opts?: {
32
+ module?: number;
33
+ correction?: 'L' | 'M' | 'Q' | 'H';
34
+ rotation?: Rotation;
35
+ }): this;
36
+ box(x: number, y: number, width: number, height: number, thickness?: number): this;
37
+ rect(x: number, y: number, width: number, height: number): this;
38
+ image(x: number, y: number, bitmap: Bitmap): this;
39
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LabelBuilder = void 0;
4
+ const types_1 = require("./types");
5
+ /** Records label elements in dots; `mm()` converts millimetres at the label's dpi. */
6
+ class LabelBuilder {
7
+ constructor(size) {
8
+ const dpi = size.dpi ?? 203;
9
+ this.label = { width: (0, types_1.mmToDots)(size.widthMm, dpi), height: (0, types_1.mmToDots)(size.heightMm, dpi), dpi, gap: size.gapMm ?? 3, copies: 1, ops: [] };
10
+ }
11
+ mm(mm) {
12
+ return (0, types_1.mmToDots)(mm, this.label.dpi);
13
+ }
14
+ copies(n) { this.label.copies = Math.max(1, Math.floor(n)); return this; }
15
+ darkness(n) { this.label.darkness = n; return this; }
16
+ speed(ips) { this.label.speed = ips; return this; }
17
+ push(op) { this.label.ops.push(op); return this; }
18
+ text(x, y, text, height, opts = {}) {
19
+ return this.push({ kind: 'text', x, y, text, height, ...opts });
20
+ }
21
+ barcode(x, y, data, type, height, opts = {}) {
22
+ return this.push({ kind: 'barcode', x, y, data, type, height, ...opts });
23
+ }
24
+ qr(x, y, data, opts = {}) {
25
+ return this.push({ kind: 'qr', x, y, data, ...opts });
26
+ }
27
+ box(x, y, width, height, thickness = 2) {
28
+ return this.push({ kind: 'box', x, y, width, height, thickness });
29
+ }
30
+ rect(x, y, width, height) {
31
+ return this.push({ kind: 'rect', x, y, width, height });
32
+ }
33
+ image(x, y, bitmap) {
34
+ return this.push({ kind: 'image', x, y, bitmap });
35
+ }
36
+ }
37
+ exports.LabelBuilder = LabelBuilder;
@@ -0,0 +1,6 @@
1
+ import { Label } from './types';
2
+ /**
3
+ * Godex EZPL. The header and text/barcode shapes match the field format the POS sends to Godex
4
+ * units (^Q/^W/^H/^P/^S/^L ... E). Text uses internal fonts chosen by height.
5
+ */
6
+ export declare function encodeEzpl(label: Label): string;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeEzpl = encodeEzpl;
4
+ const types_1 = require("./types");
5
+ /**
6
+ * Godex internal fonts by approximate cap height in dots at 203 dpi. Used when a text op does not
7
+ * name a font; sizes between entries round down. Approximate: check against a printed sample.
8
+ */
9
+ const FONTS = [['A', 12], ['B', 16], ['C', 20], ['D', 24], ['E', 32], ['F', 40], ['G', 48], ['H', 64]];
10
+ /** Godex barcode letters; H (UPC-A) is field-proven, the rest follow the EZPL manual. */
11
+ const BARCODE = {
12
+ CODE39: 'A', CODE128: 'C', EAN13: 'E', EAN8: 'F', UPC_E: 'G', UPC_A: 'H', ITF: 'I', CODABAR: 'J',
13
+ };
14
+ function fontFor(height) {
15
+ let font = FONTS[0][0];
16
+ let size = FONTS[0][1];
17
+ for (const [f, h] of FONTS)
18
+ if (h <= height) {
19
+ font = f;
20
+ size = h;
21
+ }
22
+ const mul = Math.max(1, Math.min(9, Math.floor(height / size)));
23
+ return { font, mul };
24
+ }
25
+ function op(o) {
26
+ const x = Math.round(o.x);
27
+ const y = Math.round(o.y);
28
+ const rot = o.rotation ?? 0;
29
+ switch (o.kind) {
30
+ case 'text': {
31
+ const { font, mul } = fontFor(o.height);
32
+ return `A${font},${x},${y},${mul},${mul},${rot / 90},0${o.invert ? 'R' : 'E'},${o.text}`;
33
+ }
34
+ case 'barcode': {
35
+ const code = BARCODE[o.type];
36
+ if (!code)
37
+ throw new Error(`retail-print-kit: EZPL has no ${o.type}`);
38
+ const narrow = o.module ?? 2;
39
+ return `B${code},${x},${y},${narrow},${narrow * 2},${Math.round(o.height)},${rot / 90},${o.text === false ? 0 : 1},${o.data}`;
40
+ }
41
+ case 'qr': {
42
+ const ec = { L: 'L', M: 'M', Q: 'Q', H: 'H' }[o.correction ?? 'M'];
43
+ return `W${x},${y},5,2,${ec},${o.module ?? 4},${rot / 90},${o.data.length}\n${o.data}`;
44
+ }
45
+ case 'box':
46
+ return `R${x},${y},${x + Math.round(o.width)},${y + Math.round(o.height)},${o.thickness ?? 2},${o.thickness ?? 2}`;
47
+ case 'rect':
48
+ return `R${x},${y},${x + Math.round(o.width)},${y + Math.round(o.height)},${Math.round(o.width)},${Math.round(o.height)}`;
49
+ case 'image':
50
+ throw new Error('retail-print-kit: EZPL images are not implemented yet');
51
+ }
52
+ }
53
+ /**
54
+ * Godex EZPL. The header and text/barcode shapes match the field format the POS sends to Godex
55
+ * units (^Q/^W/^H/^P/^S/^L ... E). Text uses internal fonts chosen by height.
56
+ */
57
+ function encodeEzpl(label) {
58
+ const lines = [
59
+ `^Q${(0, types_1.dotsToMm)(label.height, label.dpi).toFixed(2)},${label.gap.toFixed(0)}`,
60
+ `^W${(0, types_1.dotsToMm)(label.width, label.dpi).toFixed(2)}`,
61
+ `^H${label.darkness ?? 10}`,
62
+ `^P${label.copies}`,
63
+ `^S${label.speed ?? 2}`,
64
+ '^AD',
65
+ '^C1',
66
+ '^R0',
67
+ '~Q+0',
68
+ '^O0',
69
+ '^D0',
70
+ '^E18',
71
+ '~R255',
72
+ '^L',
73
+ ];
74
+ for (const o of label.ops)
75
+ lines.push(op(o));
76
+ lines.push('E');
77
+ return lines.join('\n') + '\n';
78
+ }
@@ -0,0 +1,12 @@
1
+ import { Label, LabelLanguage } from './types';
2
+ export * from './types';
3
+ export { LabelBuilder } from './builder';
4
+ export type { LabelSize } from './builder';
5
+ export { encodeZpl } from './zpl';
6
+ export { encodeTspl } from './tspl';
7
+ export { encodeEzpl } from './ezpl';
8
+ export { labelToSvg } from './svg';
9
+ export { renderLabel } from './template';
10
+ export type { LabelTemplate, LabelElement } from './template';
11
+ /** Bytes for the label in the given language; ZPL and EZPL are plain text, TSPL may carry binary images. */
12
+ export declare function encodeLabel(label: Label, language: LabelLanguage): Uint8Array;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.renderLabel = exports.labelToSvg = exports.encodeEzpl = exports.encodeTspl = exports.encodeZpl = exports.LabelBuilder = void 0;
18
+ exports.encodeLabel = encodeLabel;
19
+ const zpl_1 = require("./zpl");
20
+ const tspl_1 = require("./tspl");
21
+ const ezpl_1 = require("./ezpl");
22
+ __exportStar(require("./types"), exports);
23
+ var builder_1 = require("./builder");
24
+ Object.defineProperty(exports, "LabelBuilder", { enumerable: true, get: function () { return builder_1.LabelBuilder; } });
25
+ var zpl_2 = require("./zpl");
26
+ Object.defineProperty(exports, "encodeZpl", { enumerable: true, get: function () { return zpl_2.encodeZpl; } });
27
+ var tspl_2 = require("./tspl");
28
+ Object.defineProperty(exports, "encodeTspl", { enumerable: true, get: function () { return tspl_2.encodeTspl; } });
29
+ var ezpl_2 = require("./ezpl");
30
+ Object.defineProperty(exports, "encodeEzpl", { enumerable: true, get: function () { return ezpl_2.encodeEzpl; } });
31
+ var svg_1 = require("./svg");
32
+ Object.defineProperty(exports, "labelToSvg", { enumerable: true, get: function () { return svg_1.labelToSvg; } });
33
+ var template_1 = require("./template");
34
+ Object.defineProperty(exports, "renderLabel", { enumerable: true, get: function () { return template_1.renderLabel; } });
35
+ /** Bytes for the label in the given language; ZPL and EZPL are plain text, TSPL may carry binary images. */
36
+ function encodeLabel(label, language) {
37
+ switch (language) {
38
+ case 'zpl': return new TextEncoder().encode((0, zpl_1.encodeZpl)(label));
39
+ case 'ezpl': return new TextEncoder().encode((0, ezpl_1.encodeEzpl)(label));
40
+ case 'tspl': return (0, tspl_1.encodeTspl)(label);
41
+ }
42
+ }
@@ -0,0 +1,3 @@
1
+ import { Label } from './types';
2
+ /** SVG of the label at 1 unit per dot; `viewBox` is the label size so it scales anywhere. */
3
+ export declare function labelToSvg(label: Label): string;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.labelToSvg = labelToSvg;
4
+ const barcode1d_1 = require("../core/barcode1d");
5
+ const bitmap_1 = require("../core/bitmap");
6
+ const png_1 = require("../preview/png");
7
+ const base64_1 = require("../util/base64");
8
+ function esc(s) {
9
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
10
+ }
11
+ function rotate(o) {
12
+ return o.rotation ? ` transform="rotate(${o.rotation} ${o.x} ${o.y})"` : '';
13
+ }
14
+ function png(b) {
15
+ return `data:image/png;base64,${(0, base64_1.toBase64)((0, png_1.bitmapToPng)(b))}`;
16
+ }
17
+ function op(o) {
18
+ switch (o.kind) {
19
+ case 'text': {
20
+ const w = o.width ?? o.height * 0.9;
21
+ const len = o.text.length * w * 0.62;
22
+ const bg = o.invert ? `<rect x="${o.x}" y="${o.y}" width="${len}" height="${o.height}" fill="#000"/>` : '';
23
+ return `${bg}<text x="${o.x}" y="${o.y + o.height * 0.82}" font-size="${o.height}" textLength="${len}" lengthAdjust="spacingAndGlyphs" font-family="Helvetica, Arial, sans-serif"${o.bold ? ' font-weight="bold"' : ''} fill="${o.invert ? '#fff' : '#000'}"${rotate(o)}>${esc(o.text)}</text>`;
24
+ }
25
+ case 'barcode': {
26
+ const b = (0, barcode1d_1.barcodeBitmap)(o.type, o.data, o.module ?? 2, Math.round(o.height));
27
+ const hri = o.text === false ? '' : `<text x="${o.x + b.width / 2}" y="${o.y + o.height + 14}" font-size="14" text-anchor="middle" font-family="Helvetica, Arial, sans-serif">${esc(o.data)}</text>`;
28
+ return `<image x="${o.x}" y="${o.y}" width="${b.width}" height="${b.height}" href="${png(b)}" style="image-rendering:pixelated"${rotate(o)}/>${hri}`;
29
+ }
30
+ case 'qr': {
31
+ const b = (0, bitmap_1.qrBitmap)(o.data, { size: o.module ?? 4, correction: o.correction });
32
+ return `<image x="${o.x}" y="${o.y}" width="${b.width}" height="${b.height}" href="${png(b)}" style="image-rendering:pixelated"${rotate(o)}/>`;
33
+ }
34
+ case 'box':
35
+ return `<rect x="${o.x}" y="${o.y}" width="${o.width}" height="${o.height}" fill="none" stroke="#000" stroke-width="${o.thickness ?? 2}"/>`;
36
+ case 'rect':
37
+ return `<rect x="${o.x}" y="${o.y}" width="${o.width}" height="${o.height}" fill="#000"/>`;
38
+ case 'image':
39
+ return `<image x="${o.x}" y="${o.y}" width="${o.bitmap.width}" height="${o.bitmap.height}" href="${png(o.bitmap)}" style="image-rendering:pixelated"/>`;
40
+ }
41
+ }
42
+ /** SVG of the label at 1 unit per dot; `viewBox` is the label size so it scales anywhere. */
43
+ function labelToSvg(label) {
44
+ const body = label.ops.map(op).join('');
45
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${label.width} ${label.height}" width="${label.width}" height="${label.height}"><rect width="${label.width}" height="${label.height}" fill="#fff"/>${body}</svg>`;
46
+ }
@@ -0,0 +1,61 @@
1
+ import { BarcodeType } from '../core/ops';
2
+ import { Label, Rotation } from './types';
3
+ export type LabelElement = {
4
+ type: 'text';
5
+ x: number;
6
+ y: number;
7
+ value: string;
8
+ height: number;
9
+ width?: number;
10
+ rotation?: Rotation;
11
+ bold?: boolean;
12
+ invert?: boolean;
13
+ when?: string;
14
+ } | {
15
+ type: 'barcode';
16
+ x: number;
17
+ y: number;
18
+ value: string;
19
+ format?: BarcodeType;
20
+ height: number;
21
+ module?: number;
22
+ rotation?: Rotation;
23
+ text?: boolean;
24
+ when?: string;
25
+ } | {
26
+ type: 'qr';
27
+ x: number;
28
+ y: number;
29
+ value: string;
30
+ module?: number;
31
+ correction?: 'L' | 'M' | 'Q' | 'H';
32
+ rotation?: Rotation;
33
+ when?: string;
34
+ } | {
35
+ type: 'box';
36
+ x: number;
37
+ y: number;
38
+ width: number;
39
+ height: number;
40
+ thickness?: number;
41
+ when?: string;
42
+ } | {
43
+ type: 'rect';
44
+ x: number;
45
+ y: number;
46
+ width: number;
47
+ height: number;
48
+ when?: string;
49
+ };
50
+ /** A label layout: positions in dots at `dpi`, values are `{{path | filter}}` strings. */
51
+ export interface LabelTemplate {
52
+ name: string;
53
+ version: 1;
54
+ kind: 'label';
55
+ widthMm: number;
56
+ heightMm: number;
57
+ dpi?: 203 | 300 | 600;
58
+ gapMm?: number;
59
+ elements: LabelElement[];
60
+ }
61
+ export declare function renderLabel(template: LabelTemplate, data: unknown): Label;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderLabel = renderLabel;
4
+ const builder_1 = require("./builder");
5
+ const expr_1 = require("../template/expr");
6
+ function renderLabel(template, data) {
7
+ if (template.version !== 1 || template.kind !== 'label')
8
+ throw new Error('retail-print-kit: not a version 1 label template');
9
+ const b = new builder_1.LabelBuilder({ widthMm: template.widthMm, heightMm: template.heightMm, dpi: template.dpi, gapMm: template.gapMm });
10
+ const scope = { root: data, current: data };
11
+ for (const e of template.elements) {
12
+ if (e.when && !(0, expr_1.truthy)(e.when, scope))
13
+ continue;
14
+ switch (e.type) {
15
+ case 'text':
16
+ b.text(e.x, e.y, (0, expr_1.interpolate)(e.value, scope), e.height, { width: e.width, rotation: e.rotation, bold: e.bold, invert: e.invert });
17
+ break;
18
+ case 'barcode':
19
+ b.barcode(e.x, e.y, (0, expr_1.interpolate)(e.value, scope), e.format ?? 'CODE128', e.height, { module: e.module, rotation: e.rotation, text: e.text });
20
+ break;
21
+ case 'qr':
22
+ b.qr(e.x, e.y, (0, expr_1.interpolate)(e.value, scope), { module: e.module, correction: e.correction, rotation: e.rotation });
23
+ break;
24
+ case 'box':
25
+ b.box(e.x, e.y, e.width, e.height, e.thickness);
26
+ break;
27
+ case 'rect':
28
+ b.rect(e.x, e.y, e.width, e.height);
29
+ break;
30
+ }
31
+ }
32
+ return b.label;
33
+ }
@@ -0,0 +1,3 @@
1
+ import { Label } from './types';
2
+ /** TSC / TSPL2 (TSC, Xprinter and Rongta label units). Images are binary, so the result is bytes. */
3
+ export declare function encodeTspl(label: Label): Uint8Array;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeTspl = encodeTspl;
4
+ const types_1 = require("./types");
5
+ const BARCODE = {
6
+ CODE128: '128', EAN13: 'EAN13', EAN8: 'EAN8', UPC_A: 'UPCA', UPC_E: 'UPCE', CODE39: '39', ITF: 'ITF', CODABAR: 'CODA',
7
+ };
8
+ function q(text) {
9
+ return `"${text.replace(/"/g, '\\["]')}"`;
10
+ }
11
+ function mm(dots, dpi) {
12
+ return (0, types_1.dotsToMm)(dots, dpi).toFixed(2);
13
+ }
14
+ /** TSPL BITMAP data: 1 bits are white, so the bitmap is inverted. */
15
+ function bitmapBytes(b) {
16
+ const out = new Uint8Array(b.bits.length);
17
+ for (let i = 0; i < b.bits.length; i++)
18
+ out[i] = ~b.bits[i] & 0xff;
19
+ return out;
20
+ }
21
+ function op(o, dpi) {
22
+ const x = Math.round(o.x);
23
+ const y = Math.round(o.y);
24
+ const rot = (r) => r ?? 0;
25
+ switch (o.kind) {
26
+ case 'text': {
27
+ const h = Math.round(o.height);
28
+ const w = Math.round(o.width ?? o.height * 0.9);
29
+ const line = `TEXT ${x},${y},"0",${rot(o.rotation)},${w},${h},${q(o.text)}`;
30
+ return o.invert ? `${line}\nREVERSE ${x},${y},${Math.round(w * o.text.length * 0.6)},${h}` : line;
31
+ }
32
+ case 'barcode': {
33
+ const code = BARCODE[o.type];
34
+ if (!code)
35
+ throw new Error(`retail-print-kit: TSPL has no ${o.type}`);
36
+ const narrow = o.module ?? 2;
37
+ return `BARCODE ${x},${y},"${code}",${Math.round(o.height)},${o.text === false ? 0 : 1},${rot(o.rotation)},${narrow},${narrow * 2},${q(o.data)}`;
38
+ }
39
+ case 'qr':
40
+ return `QRCODE ${x},${y},${o.correction ?? 'M'},${o.module ?? 4},A,${rot(o.rotation)},${q(o.data)}`;
41
+ case 'box':
42
+ return `BOX ${x},${y},${x + Math.round(o.width)},${y + Math.round(o.height)},${o.thickness ?? 2}`;
43
+ case 'rect':
44
+ return `BAR ${x},${y},${Math.round(o.width)},${Math.round(o.height)}`;
45
+ case 'image': {
46
+ const rowBytes = Math.ceil(o.bitmap.width / 8);
47
+ const head = `BITMAP ${x},${y},${rowBytes},${o.bitmap.height},0,`;
48
+ const bytes = bitmapBytes(o.bitmap);
49
+ const out = new Uint8Array(head.length + bytes.length + 1);
50
+ for (let i = 0; i < head.length; i++)
51
+ out[i] = head.charCodeAt(i);
52
+ out.set(bytes, head.length);
53
+ out[out.length - 1] = 0x0a;
54
+ return out;
55
+ }
56
+ }
57
+ void dpi;
58
+ }
59
+ /** TSC / TSPL2 (TSC, Xprinter and Rongta label units). Images are binary, so the result is bytes. */
60
+ function encodeTspl(label) {
61
+ const parts = [
62
+ `SIZE ${mm(label.width, label.dpi)} mm,${mm(label.height, label.dpi)} mm`,
63
+ `GAP ${label.gap.toFixed(2)} mm,0 mm`,
64
+ 'DIRECTION 1,0',
65
+ 'REFERENCE 0,0',
66
+ ];
67
+ if (label.speed !== undefined)
68
+ parts.push(`SPEED ${label.speed}`);
69
+ if (label.darkness !== undefined)
70
+ parts.push(`DENSITY ${Math.round(label.darkness)}`);
71
+ parts.push('CLS');
72
+ for (const o of label.ops)
73
+ parts.push(op(o, label.dpi));
74
+ parts.push(`PRINT ${label.copies},1`);
75
+ const chunks = parts.map((p) => (typeof p === 'string' ? new TextEncoder().encode(p + '\n') : p));
76
+ const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
77
+ let o = 0;
78
+ for (const c of chunks) {
79
+ out.set(c, o);
80
+ o += c.length;
81
+ }
82
+ return out;
83
+ }
@@ -0,0 +1,69 @@
1
+ import { BarcodeType } from '../core/ops';
2
+ import { Bitmap } from '../core/bitmap';
3
+ export type Rotation = 0 | 90 | 180 | 270;
4
+ export type LabelLanguage = 'zpl' | 'tspl' | 'ezpl';
5
+ /** Everything is in dots at the label's dpi; (0,0) is the top-left corner. */
6
+ export type LabelOp = {
7
+ kind: 'text';
8
+ x: number;
9
+ y: number;
10
+ text: string;
11
+ height: number;
12
+ width?: number;
13
+ rotation?: Rotation;
14
+ bold?: boolean;
15
+ invert?: boolean;
16
+ } | {
17
+ kind: 'barcode';
18
+ x: number;
19
+ y: number;
20
+ data: string;
21
+ type: BarcodeType;
22
+ height: number;
23
+ module?: number;
24
+ rotation?: Rotation;
25
+ text?: boolean;
26
+ } | {
27
+ kind: 'qr';
28
+ x: number;
29
+ y: number;
30
+ data: string;
31
+ module?: number;
32
+ correction?: 'L' | 'M' | 'Q' | 'H';
33
+ rotation?: Rotation;
34
+ } | {
35
+ kind: 'box';
36
+ x: number;
37
+ y: number;
38
+ width: number;
39
+ height: number;
40
+ thickness?: number;
41
+ } | {
42
+ kind: 'rect';
43
+ x: number;
44
+ y: number;
45
+ width: number;
46
+ height: number;
47
+ } | {
48
+ kind: 'image';
49
+ x: number;
50
+ y: number;
51
+ bitmap: Bitmap;
52
+ };
53
+ export interface Label {
54
+ /** Dots across the label. */
55
+ width: number;
56
+ /** Dots down the label. */
57
+ height: number;
58
+ dpi: 203 | 300 | 600;
59
+ /** Gap between labels in millimetres (0 for continuous stock). */
60
+ gap: number;
61
+ copies: number;
62
+ /** Print darkness 0..15 where the language has it. */
63
+ darkness?: number;
64
+ /** Print speed in inches per second where the language has it. */
65
+ speed?: number;
66
+ ops: LabelOp[];
67
+ }
68
+ export declare function mmToDots(mm: number, dpi: number): number;
69
+ export declare function dotsToMm(dots: number, dpi: number): number;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mmToDots = mmToDots;
4
+ exports.dotsToMm = dotsToMm;
5
+ function mmToDots(mm, dpi) {
6
+ return Math.round((mm / 25.4) * dpi);
7
+ }
8
+ function dotsToMm(dots, dpi) {
9
+ return (dots / dpi) * 25.4;
10
+ }
@@ -0,0 +1,3 @@
1
+ import { Label } from './types';
2
+ /** Zebra ZPL II. Verified against the field format the POS sends to Zebra units (^A0N text, ^BUN barcodes). */
3
+ export declare function encodeZpl(label: Label): string;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeZpl = encodeZpl;
4
+ const ROT = { 0: 'N', 90: 'R', 180: 'I', 270: 'B' };
5
+ const BARCODE = {
6
+ CODE128: 'BC', EAN13: 'BE', EAN8: 'B8', UPC_A: 'BU', UPC_E: 'B9', CODE39: 'B3', ITF: 'B2', CODABAR: 'BK',
7
+ };
8
+ function fd(text) {
9
+ return text.replace(/[\^~\\]/g, (c) => `\\${c}`);
10
+ }
11
+ function hexRows(b) {
12
+ const rowBytes = Math.ceil(b.width / 8);
13
+ let out = '';
14
+ for (let i = 0; i < b.bits.length; i++)
15
+ out += b.bits[i].toString(16).padStart(2, '0').toUpperCase();
16
+ return `${rowBytes * b.height},${rowBytes * b.height},${rowBytes},${out}`;
17
+ }
18
+ function op(o) {
19
+ const at = `^FO${Math.round(o.x)},${Math.round(o.y)}`;
20
+ switch (o.kind) {
21
+ case 'text': {
22
+ const w = Math.round(o.width ?? o.height * 0.9);
23
+ const rev = o.invert ? '^FR' : '';
24
+ return `${at}${rev}^A0${ROT[o.rotation ?? 0]},${Math.round(o.height)},${w}^FD${fd(o.text)}^FS`;
25
+ }
26
+ case 'barcode': {
27
+ const code = BARCODE[o.type];
28
+ if (!code)
29
+ throw new Error(`retail-print-kit: ZPL has no ${o.type}`);
30
+ const hri = o.text === false ? 'N' : 'Y';
31
+ const extra = o.type === 'CODE128' ? `,${hri},N,N` : `,${hri},N`;
32
+ return `${at}^BY${o.module ?? 2},2,${Math.round(o.height)}^${code}${ROT[o.rotation ?? 0]},${Math.round(o.height)}${extra}^FD${fd(o.data)}^FS`;
33
+ }
34
+ case 'qr': {
35
+ const ec = o.correction ?? 'M';
36
+ return `${at}^BQ${ROT[o.rotation ?? 0]},2,${o.module ?? 4}^FD${ec}A,${fd(o.data)}^FS`;
37
+ }
38
+ case 'box':
39
+ return `${at}^GB${Math.round(o.width)},${Math.round(o.height)},${o.thickness ?? 2}^FS`;
40
+ case 'rect':
41
+ return `${at}^GB${Math.round(o.width)},${Math.round(o.height)},${Math.round(Math.min(o.width, o.height))}^FS`;
42
+ case 'image':
43
+ return `${at}^GFA,${hexRows(o.bitmap)}^FS`;
44
+ }
45
+ }
46
+ /** Zebra ZPL II. Verified against the field format the POS sends to Zebra units (^A0N text, ^BUN barcodes). */
47
+ function encodeZpl(label) {
48
+ const lines = ['^XA', '^CI28', `^PW${label.width}`, `^LL${label.height}`, '^LH0,0'];
49
+ if (label.speed !== undefined)
50
+ lines.push(`^PR${Math.round(label.speed)}`);
51
+ if (label.darkness !== undefined)
52
+ lines.push(`^MD${Math.round(label.darkness * 2 - 15)}`);
53
+ for (const o of label.ops)
54
+ lines.push(op(o));
55
+ lines.push(`^PQ${label.copies},0,1,Y`, '^XZ');
56
+ return lines.join('\n') + '\n';
57
+ }
@@ -0,0 +1,14 @@
1
+ import { Op } from '../core/ops';
2
+ import { Profile } from '../profiles/types';
3
+ export interface HtmlPreviewOptions {
4
+ /** Wrap in a full document with the receipt styles. Default true. */
5
+ document?: boolean;
6
+ /** CSS pixels per character cell of font A. Default 8. */
7
+ cellWidth?: number;
8
+ }
9
+ /**
10
+ * Paper-like HTML of the ops: monospaced lines at the profile's column count, with size, style,
11
+ * alignment and font honoured, and QR / barcodes / images inlined as PNG. Meant for on-screen
12
+ * preview and for a designer; not byte-exact with any printer.
13
+ */
14
+ export declare function opsToHtml(ops: Op[], profile: Profile, options?: HtmlPreviewOptions): string;