portakal 0.2.0 → 0.4.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.
- package/README.md +155 -143
- package/dist/_chunks/types.d.mts +202 -0
- package/dist/encoding.d.mts +42 -0
- package/dist/encoding.mjs +1 -0
- package/dist/image.d.mts +22 -0
- package/dist/image.mjs +1 -0
- package/dist/index.d.mts +757 -431
- package/dist/index.mjs +5 -16
- package/dist/languages/cpcl.d.mts +7 -0
- package/dist/languages/cpcl.mjs +3 -0
- package/dist/languages/dpl.d.mts +7 -0
- package/dist/languages/dpl.mjs +3 -0
- package/dist/languages/epl.d.mts +7 -0
- package/dist/languages/epl.mjs +3 -0
- package/dist/languages/escpos.d.mts +7 -0
- package/dist/languages/escpos.mjs +1 -0
- package/dist/languages/ipl.d.mts +7 -0
- package/dist/languages/ipl.mjs +3 -0
- package/dist/languages/sbpl.d.mts +7 -0
- package/dist/languages/sbpl.mjs +3 -0
- package/dist/languages/starprnt.d.mts +7 -0
- package/dist/languages/starprnt.mjs +1 -0
- package/dist/languages/tsc.d.mts +7 -0
- package/dist/languages/tsc.mjs +3 -0
- package/dist/languages/zpl.d.mts +7 -0
- package/dist/languages/zpl.mjs +3 -0
- package/dist/preview.d.mts +7 -0
- package/dist/preview.mjs +2 -0
- package/dist/profiles.d.mts +49 -0
- package/dist/profiles.mjs +1 -0
- package/dist/receipt.d.mts +20 -0
- package/dist/receipt.mjs +1 -0
- package/dist/transport.d.mts +106 -0
- package/dist/transport.mjs +1 -0
- package/package.json +63 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<br><br>
|
|
5
5
|
<b style="font-size: 2em;">portakal</b>
|
|
6
6
|
<br><br>
|
|
7
|
-
Universal printer language SDK —
|
|
7
|
+
Universal printer language SDK — 9 languages, one API.
|
|
8
8
|
<br>
|
|
9
9
|
Text, barcodes, QR codes, images, shapes — anything you can print.
|
|
10
10
|
<br>
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
<a href="https://npmjs.com/package/portakal"><img src="https://img.shields.io/npm/dm/portakal?style=flat&colorA=18181B&colorB=F97316" alt="npm downloads"></a>
|
|
15
15
|
<a href="https://bundlephobia.com/result?p=portakal"><img src="https://img.shields.io/bundlephobia/minzip/portakal?style=flat&colorA=18181B&colorB=F97316" alt="bundle size"></a>
|
|
16
16
|
<a href="https://github.com/productdevbook/portakal/blob/main/LICENSE"><img src="https://img.shields.io/github/license/productdevbook/portakal?style=flat&colorA=18181B&colorB=F97316" alt="license"></a>
|
|
17
|
+
<br><br>
|
|
18
|
+
<a href="https://portakal.productdevbook.com">Playground</a> · <a href="https://github.com/productdevbook/portakal">GitHub</a> · <a href="https://npmjs.com/package/portakal">npm</a>
|
|
17
19
|
</p>
|
|
18
20
|
|
|
19
21
|
> [!NOTE]
|
|
@@ -28,37 +30,51 @@
|
|
|
28
30
|
npm install portakal
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
### Product label
|
|
33
|
+
### Product label
|
|
32
34
|
|
|
33
35
|
```ts
|
|
34
|
-
import { label } from "portakal";
|
|
36
|
+
import { label } from "portakal/core";
|
|
37
|
+
import { tsc } from "portakal/lang/tsc";
|
|
35
38
|
|
|
36
|
-
const
|
|
39
|
+
const myLabel = label({ width: 40, height: 30, unit: "mm" })
|
|
37
40
|
.text("ACME Corp", { x: 10, y: 10, size: 2 })
|
|
38
|
-
.text("SKU: PRD-00123", { x: 10, y: 35
|
|
39
|
-
.
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
.text("SKU: PRD-00123", { x: 10, y: 35 })
|
|
42
|
+
.line({ x1: 5, y1: 55, x2: 310, y2: 55 })
|
|
43
|
+
.box({ x: 5, y: 5, width: 310, height: 230, thickness: 2 });
|
|
44
|
+
|
|
45
|
+
const code = tsc.compile(myLabel); // TSC/TSPL2 commands
|
|
46
|
+
const svg = tsc.preview(myLabel); // SVG preview with TSC font metrics
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
###
|
|
49
|
+
### Same label → any language
|
|
47
50
|
|
|
48
51
|
```ts
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
import { label } from "portakal/core";
|
|
53
|
+
import { tsc } from "portakal/lang/tsc";
|
|
54
|
+
import { zpl } from "portakal/lang/zpl";
|
|
55
|
+
import { epl } from "portakal/lang/epl";
|
|
56
|
+
import { escpos } from "portakal/lang/escpos";
|
|
57
|
+
|
|
58
|
+
const myLabel = label({ width: 40, height: 30, unit: "mm" }).text("Hello World", {
|
|
59
|
+
x: 10,
|
|
60
|
+
y: 10,
|
|
61
|
+
size: 2,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
tsc.compile(myLabel); // TSC/TSPL2 — TSC, Gprinter, Xprinter, iDPRT
|
|
65
|
+
zpl.compile(myLabel); // Zebra ZPL II — GK420, ZT410, ZD620
|
|
66
|
+
epl.compile(myLabel); // Eltron EPL2 — LP/TLP 2824, GX420, ZD220
|
|
67
|
+
escpos.compile(myLabel); // ESC/POS — Epson, Bixolon, Star, Citizen (Uint8Array)
|
|
57
68
|
```
|
|
58
69
|
|
|
59
|
-
|
|
70
|
+
Only the imported languages enter your bundle — 100% tree-shakeable.
|
|
71
|
+
|
|
72
|
+
### Receipt (ESC/POS)
|
|
60
73
|
|
|
61
74
|
```ts
|
|
75
|
+
import { label } from "portakal/core";
|
|
76
|
+
import { escpos } from "portakal/lang/escpos";
|
|
77
|
+
|
|
62
78
|
const receipt = label({ width: 80, unit: "mm" })
|
|
63
79
|
.text("MY STORE", { align: "center", bold: true, size: 2 })
|
|
64
80
|
.text("123 Market St", { align: "center" })
|
|
@@ -66,66 +82,50 @@ const receipt = label({ width: 80, unit: "mm" })
|
|
|
66
82
|
.text("Hamburger x2 $25.98")
|
|
67
83
|
.text("Cola x1 $3.50")
|
|
68
84
|
.text("================================")
|
|
69
|
-
.text("TOTAL $29.48", { bold: true, size: 2 })
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
.text("TOTAL $29.48", { bold: true, size: 2 });
|
|
86
|
+
|
|
87
|
+
const bytes = escpos.compile(receipt); // Uint8Array
|
|
88
|
+
const svg = escpos.preview(receipt); // Receipt-style SVG
|
|
73
89
|
```
|
|
74
90
|
|
|
75
|
-
###
|
|
91
|
+
### Each module: compile + parse + preview + validate
|
|
76
92
|
|
|
77
93
|
```ts
|
|
78
|
-
|
|
79
|
-
.image(myLogoBitmap, { x: 10, y: 10, width: 100 })
|
|
80
|
-
.text("Company Name", { x: 120, y: 20, size: 2 })
|
|
81
|
-
.toTSC();
|
|
82
|
-
```
|
|
94
|
+
import { tsc } from "portakal/lang/tsc";
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
// Compile: label → printer commands
|
|
97
|
+
tsc.compile(myLabel);
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
// Preview: label → SVG (per-language font metrics)
|
|
100
|
+
tsc.preview(myLabel);
|
|
101
|
+
|
|
102
|
+
// Parse: printer commands → structured data
|
|
103
|
+
tsc.parse(tscCode); // { commands, elements, widthDots, ... }
|
|
90
104
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
myLabel.toEPL(); // Eltron EPL2 — LP/TLP 2824, GX420, ZD220
|
|
94
|
-
myLabel.toESCPOS(); // ESC/POS — Epson, Bixolon, Star, Citizen
|
|
105
|
+
// Validate: check for errors
|
|
106
|
+
tsc.validate(tscCode); // { valid, errors, issues }
|
|
95
107
|
```
|
|
96
108
|
|
|
97
|
-
|
|
109
|
+
Available: `tsc`, `zpl`, `epl`, `cpcl`, `dpl`, `sbpl`, `escpos`, `starprnt`, `ipl`
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
### Barcode/QR via etiket
|
|
100
112
|
|
|
101
|
-
|
|
102
|
-
npm install portakal etiket
|
|
103
|
-
```
|
|
113
|
+
Use [`etiket`](https://github.com/productdevbook/etiket) for barcode/QR generation, then embed as image:
|
|
104
114
|
|
|
105
115
|
```ts
|
|
106
|
-
import { label } from "portakal";
|
|
116
|
+
import { label } from "portakal/core";
|
|
117
|
+
import { tsc } from "portakal/lang/tsc";
|
|
107
118
|
import { barcodePNG, qrcodePNG } from "etiket";
|
|
108
119
|
|
|
109
|
-
|
|
110
|
-
const cmd = label({ width: 40, height: 30, unit: "mm" })
|
|
120
|
+
const myLabel = label({ width: 40, height: 30, unit: "mm" })
|
|
111
121
|
.text("Product Label", { x: 10, y: 5 })
|
|
112
122
|
.image(barcodePNG("123456789", { type: "code128" }), { x: 10, y: 40, width: 200 })
|
|
113
|
-
.image(qrcodePNG("https://example.com"), { x: 220, y: 40, width: 80 })
|
|
114
|
-
.toZPL();
|
|
115
|
-
```
|
|
123
|
+
.image(qrcodePNG("https://example.com"), { x: 220, y: 40, width: 80 });
|
|
116
124
|
|
|
117
|
-
|
|
125
|
+
const code = tsc.compile(myLabel);
|
|
126
|
+
```
|
|
118
127
|
|
|
119
|
-
|
|
|
120
|
-
| :------------------------------------ | :------------------------------------------ | :---------------------------------------- |
|
|
121
|
-
| **Dependencies** | None | `etiket` (you install it) |
|
|
122
|
-
| **Speed** | Fast (only sends command text) | Slower (sends pixel data) |
|
|
123
|
-
| **Data size** | Small (~50 bytes) | Larger (bitmap data) |
|
|
124
|
-
| **Consistency** | Varies by printer model | Identical on every printer |
|
|
125
|
-
| **Format support** | Depends on printer (10-20 types) | 40+ barcode types, styled QR |
|
|
126
|
-
| **Styled QR** (dots, gradients, logo) | Not possible | Full support via etiket |
|
|
127
|
-
| **Works on cheap printers** | May not support QR/PDF417 | Always works (it's just an image) |
|
|
128
|
-
| **Best for** | Simple labels, fast printing | Pixel-perfect, guaranteed output |
|
|
128
|
+
| **Best for** | Simple labels, fast printing | Pixel-perfect, guaranteed output |
|
|
129
129
|
|
|
130
130
|
## API
|
|
131
131
|
|
|
@@ -163,56 +163,6 @@ builder.text("Hello", {
|
|
|
163
163
|
});
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
### `.barcode(data, options)`
|
|
167
|
-
|
|
168
|
-
Printer-native barcode (uses the printer's built-in encoder):
|
|
169
|
-
|
|
170
|
-
```ts
|
|
171
|
-
builder.barcode("123456789", {
|
|
172
|
-
type: "code128", // Symbology (see table below)
|
|
173
|
-
x: 10,
|
|
174
|
-
y: 50, // Position
|
|
175
|
-
height: 80, // Bar height in dots
|
|
176
|
-
narrowWidth: 2, // Narrow bar width
|
|
177
|
-
wideWidth: 4, // Wide bar width
|
|
178
|
-
readable: true, // Show human-readable text
|
|
179
|
-
readablePosition: "below", // "none" | "above" | "below" | "both"
|
|
180
|
-
rotation: 0, // 0 | 90 | 180 | 270
|
|
181
|
-
});
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
**Supported barcode types:**
|
|
185
|
-
|
|
186
|
-
| Type | Description |
|
|
187
|
-
| :----------------------------------- | :--------------------- |
|
|
188
|
-
| `code128` | Code 128 (auto subset) |
|
|
189
|
-
| `code128a` / `code128b` / `code128c` | Code 128 subsets |
|
|
190
|
-
| `code39` | Code 39 |
|
|
191
|
-
| `code93` | Code 93 |
|
|
192
|
-
| `ean13` / `ean8` | EAN-13 / EAN-8 |
|
|
193
|
-
| `upca` / `upce` | UPC-A / UPC-E |
|
|
194
|
-
| `itf` / `itf14` | Interleaved 2 of 5 |
|
|
195
|
-
| `codabar` | Codabar |
|
|
196
|
-
| `msi` | MSI Plessey |
|
|
197
|
-
| `plessey` | Plessey |
|
|
198
|
-
| `code11` | Code 11 |
|
|
199
|
-
| `postnet` / `planet` | USPS Postnet / Planet |
|
|
200
|
-
| `gs1_128` | GS1-128 |
|
|
201
|
-
| `gs1_databar` | GS1 DataBar |
|
|
202
|
-
|
|
203
|
-
### `.qrcode(data, options?)`
|
|
204
|
-
|
|
205
|
-
```ts
|
|
206
|
-
builder.qrcode("https://example.com", {
|
|
207
|
-
x: 10,
|
|
208
|
-
y: 100, // Position
|
|
209
|
-
ecc: "M", // "L" | "M" | "Q" | "H"
|
|
210
|
-
size: 6, // Module size 1-10
|
|
211
|
-
model: 2, // QR model 1 | 2
|
|
212
|
-
rotation: 0, // 0 | 90 | 180 | 270
|
|
213
|
-
});
|
|
214
|
-
```
|
|
215
|
-
|
|
216
166
|
### `.image(bitmap, options?)`
|
|
217
167
|
|
|
218
168
|
```ts
|
|
@@ -253,20 +203,16 @@ builder.raw("^FO10,10^FDCustom^FS"); // ZPL
|
|
|
253
203
|
builder.raw(new Uint8Array([0x1b, 0x70, 0x00, 0x32, 0x32])); // ESC/POS cash drawer
|
|
254
204
|
```
|
|
255
205
|
|
|
256
|
-
###
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
|
|
|
263
|
-
|
|
|
264
|
-
|
|
|
265
|
-
|
|
|
266
|
-
| `.toESCPOS()` | `Uint8Array` | ESC/POS receipt printers |
|
|
267
|
-
| `.toStarPRNT()` | `Uint8Array` | Star Micronics printers |
|
|
268
|
-
| `.toIPL()` | `string` | Intermec/Honeywell printers |
|
|
269
|
-
| `.toPreview()` | `string` | SVG preview (no printer needed) |
|
|
206
|
+
### Language Module Methods
|
|
207
|
+
|
|
208
|
+
Each language module (`tsc`, `zpl`, `epl`, `cpcl`, `dpl`, `sbpl`, `escpos`, `starprnt`, `ipl`) has:
|
|
209
|
+
|
|
210
|
+
| Method | Output | Description |
|
|
211
|
+
| :-------------------- | :----------------------- | :--------------------------------------- |
|
|
212
|
+
| `lang.compile(label)` | `string` or `Uint8Array` | Compile to printer commands |
|
|
213
|
+
| `lang.preview(label)` | `string` | SVG preview with language-specific fonts |
|
|
214
|
+
| `lang.parse(code)` | `object` | Parse printer commands → structured data |
|
|
215
|
+
| `lang.validate(code)` | `object` | Validate commands for errors/warnings |
|
|
270
216
|
|
|
271
217
|
### Image Processing
|
|
272
218
|
|
|
@@ -279,7 +225,7 @@ const bitmap = imageToMonochrome(rgbaPixels, width, height, {
|
|
|
279
225
|
dither: "floyd-steinberg", // "threshold" | "floyd-steinberg" | "atkinson" | "ordered"
|
|
280
226
|
});
|
|
281
227
|
|
|
282
|
-
label({ width: 40, height: 30 }).image(bitmap, { x: 10, y: 10 })
|
|
228
|
+
tsc.compile(label({ width: 40, height: 30 }).image(bitmap, { x: 10, y: 10 }));
|
|
283
229
|
```
|
|
284
230
|
|
|
285
231
|
### Receipt Layout
|
|
@@ -310,6 +256,60 @@ formatTable(
|
|
|
310
256
|
);
|
|
311
257
|
```
|
|
312
258
|
|
|
259
|
+
### Cross-Compiler
|
|
260
|
+
|
|
261
|
+
Convert between any printer languages — world's first thermal printer translator:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { convert } from "portakal";
|
|
265
|
+
|
|
266
|
+
// TSC → ZPL
|
|
267
|
+
const { output } = convert(tscCode, "tsc", "zpl");
|
|
268
|
+
|
|
269
|
+
// ZPL → ESC/POS
|
|
270
|
+
const { output } = convert(zplCode, "zpl", "escpos");
|
|
271
|
+
|
|
272
|
+
// EPL → CPCL
|
|
273
|
+
const { output } = convert(eplCode, "epl", "cpcl");
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
7 source × 9 target = **63 conversion paths**.
|
|
277
|
+
|
|
278
|
+
### Validation
|
|
279
|
+
|
|
280
|
+
Check printer commands for errors before sending to printer:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { validate } from "portakal";
|
|
284
|
+
|
|
285
|
+
const result = validate(code, "tsc");
|
|
286
|
+
// { valid: false, errors: 1, warnings: 2, issues: [
|
|
287
|
+
// { level: "error", message: "CLS must appear before label elements" },
|
|
288
|
+
// { level: "warning", message: "No PRINT command found" },
|
|
289
|
+
// ]}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
TSC validation: SIZE order, CLS before elements, PRINT required, DENSITY 0-15, SPEED 1-18.
|
|
293
|
+
ZPL validation: ^XA/^XZ required, ^FD without ^FO, ^PW range.
|
|
294
|
+
|
|
295
|
+
### Printer Profiles
|
|
296
|
+
|
|
297
|
+
Auto-configure DPI, paper width, and capabilities based on printer model:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import { label, getProfile, findByVendorId } from "portakal";
|
|
301
|
+
|
|
302
|
+
// Auto-DPI from profile
|
|
303
|
+
label({ width: 40, height: 30, printer: "tsc-te310" }); // 300 DPI
|
|
304
|
+
label({ width: 80, printer: "epson-tm-t88vi" }); // 203 DPI
|
|
305
|
+
|
|
306
|
+
// Lookup profiles
|
|
307
|
+
getProfile("zebra-zd420"); // { name, dpi, paperWidth, ... }
|
|
308
|
+
findByVendorId(0x04b8); // All Epson printers
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
20 built-in profiles: Epson, Star, Bixolon, Citizen, TSC, Zebra, SATO, Honeywell, Generic.
|
|
312
|
+
|
|
313
313
|
## Supported Printer Languages
|
|
314
314
|
|
|
315
315
|
| Language | Printers | Status |
|
|
@@ -331,24 +331,23 @@ formatTable(
|
|
|
331
331
|
portakal generates commands only — it does **not** handle printer connections. Send the output over any transport you choose:
|
|
332
332
|
|
|
333
333
|
```ts
|
|
334
|
-
import { label } from "portakal";
|
|
334
|
+
import { label } from "portakal/core";
|
|
335
|
+
import { tsc } from "portakal/lang/tsc";
|
|
336
|
+
import { escpos } from "portakal/lang/escpos";
|
|
335
337
|
import net from "node:net";
|
|
336
338
|
|
|
337
|
-
const
|
|
339
|
+
const myLabel = label({ width: 40, height: 30 }).text("Hello", { x: 10, y: 10 });
|
|
340
|
+
const commands = tsc.compile(myLabel);
|
|
338
341
|
|
|
339
342
|
// TCP (port 9100)
|
|
340
343
|
const socket = net.createConnection({ host: "192.168.1.100", port: 9100 });
|
|
341
344
|
socket.write(commands);
|
|
342
345
|
socket.end();
|
|
343
346
|
|
|
344
|
-
// USB (via serialport)
|
|
345
|
-
import { SerialPort } from "serialport";
|
|
346
|
-
const port = new SerialPort({ path: "/dev/usb/lp0", baudRate: 9600 });
|
|
347
|
-
port.write(commands);
|
|
348
|
-
|
|
349
347
|
// ESC/POS (binary) over WebUSB
|
|
350
|
-
const
|
|
351
|
-
|
|
348
|
+
const receipt = label({ width: 80 }).text("Receipt");
|
|
349
|
+
const bytes = escpos.compile(receipt);
|
|
350
|
+
await usbDevice.transferOut(endpointNumber, bytes);
|
|
352
351
|
```
|
|
353
352
|
|
|
354
353
|
## Comparison
|
|
@@ -366,6 +365,10 @@ await usbDevice.transferOut(endpointNumber, escpos);
|
|
|
366
365
|
| Image dithering | :white_check_mark: | :x: | :x: | :x: |
|
|
367
366
|
| Receipt layout engine | :white_check_mark: | Partial | :x: | :x: |
|
|
368
367
|
| SVG preview | :white_check_mark: | :x: | :x: | :x: |
|
|
368
|
+
| Command parser (reverse) | :white_check_mark: 9 parsers | :x: | :x: | :x: |
|
|
369
|
+
| Cross-compiler (translate) | :white_check_mark: 63 paths | :x: | :x: | :x: |
|
|
370
|
+
| Command validation | :white_check_mark: | :x: | :x: | :x: |
|
|
371
|
+
| Printer profiles | :white_check_mark: 20 | :x: | :x: | :x: |
|
|
369
372
|
| Works in browser | :white_check_mark: | :x: | :x: | :white_check_mark: |
|
|
370
373
|
| No native modules (no gyp) | :white_check_mark: | :x: | :x: | :white_check_mark: |
|
|
371
374
|
| Pure ESM | :white_check_mark: | :x: (CJS) | :x: (CJS) | :x: (CJS) |
|
|
@@ -376,30 +379,39 @@ await usbDevice.transferOut(endpointNumber, escpos);
|
|
|
376
379
|
|
|
377
380
|
- Zero dependencies — pure computation, no native modules, no node-gyp
|
|
378
381
|
- **9 printer languages** — TSC, ZPL, EPL, CPCL, DPL, SBPL, ESC/POS, Star PRNT, IPL
|
|
382
|
+
- **Tree-shakeable** — sub-path exports for every module (`portakal/tsc`, `portakal/image`, etc.)
|
|
379
383
|
- Pure ESM, edge-runtime compatible (Cloudflare Workers, Deno, Bun)
|
|
380
384
|
- TypeScript-first with strict types (tsgo)
|
|
381
385
|
- Transport-agnostic — generates commands, you handle the connection
|
|
382
386
|
- Fluent builder API — one label definition compiles to any language
|
|
383
387
|
- **Image processing** — RGBA → monochrome with 4 dithering algorithms (Floyd-Steinberg, Atkinson, ordered, threshold)
|
|
384
388
|
- **Receipt layout engine** — same-line left+right alignment, tables, word-wrap, separators
|
|
385
|
-
- **SVG preview** —
|
|
389
|
+
- **SVG preview** — `lang.preview(label)` renders labels without a physical printer
|
|
390
|
+
- **9 parsers** — reverse-parse printer commands back to structured data (TSC, ZPL, EPL, CPCL, ESC/POS, DPL, SBPL, Star PRNT, IPL)
|
|
386
391
|
- Drawing primitives — box, line, circle, diagonal
|
|
387
392
|
- Raw command passthrough for advanced/unsupported features
|
|
388
393
|
- Optional [`etiket`](https://github.com/productdevbook/etiket) integration for barcode/QR images (40+ formats)
|
|
389
394
|
- Works in browser, Node.js, Deno, Bun, Electron
|
|
390
395
|
- **UTF-8 encoding engine** — auto code page selection (CP437, CP858, CP1252, CP866, CP857)
|
|
391
|
-
-
|
|
396
|
+
- **Cross-compiler** — convert between any languages (63 paths: TSC↔ZPL↔EPL↔CPCL↔DPL↔SBPL↔IPL↔ESC/POS↔Star)
|
|
397
|
+
- **Real validation** — parameter ranges, command order, structure checks
|
|
398
|
+
- **20 printer profiles** — auto-DPI, auto-width by model (Epson, Star, Zebra, TSC, SATO, etc.)
|
|
399
|
+
- **Language modules** — each language is a standalone module (compile + parse + preview + validate)
|
|
400
|
+
- **Per-language SVG preview** — TSC fonts differ from ZPL fonts, ESC/POS renders receipt-style
|
|
401
|
+
- 447 tests across 28 test files
|
|
392
402
|
|
|
393
403
|
## Contributing
|
|
394
404
|
|
|
395
405
|
Contributions are welcome! Here are areas where help is especially appreciated:
|
|
396
406
|
|
|
397
|
-
- **
|
|
398
|
-
-
|
|
399
|
-
-
|
|
400
|
-
-
|
|
401
|
-
-
|
|
402
|
-
-
|
|
407
|
+
- **Arabic/Hebrew RTL** support (bidi algorithm + Arabic shaping)
|
|
408
|
+
- **GS1/UDI label standards** (SSCC, GTIN, FMD, DSCSA templates)
|
|
409
|
+
- **Star TSP100** raster-only text rendering
|
|
410
|
+
- **CJK encoding** (GB18030, Shift_JIS, Big5, EUC-KR)
|
|
411
|
+
- **Fingerprint** (Honeywell BASIC-like) compiler
|
|
412
|
+
- **WebUSB/WebSerial/Web Bluetooth** transport adapters
|
|
413
|
+
- Additional printer profiles
|
|
414
|
+
- Parser validation rules for more languages
|
|
403
415
|
|
|
404
416
|
```bash
|
|
405
417
|
pnpm install # Install dependencies
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/** Unit of measurement for label dimensions */
|
|
3
|
+
type Unit = "mm" | "inch" | "dot";
|
|
4
|
+
/** Print orientation / rotation */
|
|
5
|
+
type Rotation = 0 | 90 | 180 | 270;
|
|
6
|
+
/** Text alignment */
|
|
7
|
+
type Alignment = "left" | "center" | "right";
|
|
8
|
+
/** Dithering algorithm for image processing */
|
|
9
|
+
type DitherAlgorithm = "threshold" | "floyd-steinberg" | "atkinson" | "ordered";
|
|
10
|
+
/** Label configuration */
|
|
11
|
+
interface LabelConfig {
|
|
12
|
+
/** Printer profile ID (e.g., "epson-tm-t88vi", "zebra-zd420"). Overrides width, dpi, etc. */
|
|
13
|
+
printer?: string;
|
|
14
|
+
/** Label width */
|
|
15
|
+
width: number;
|
|
16
|
+
/** Label height (optional for receipt/continuous mode) */
|
|
17
|
+
height?: number;
|
|
18
|
+
/** Unit of measurement (default: "mm") */
|
|
19
|
+
unit?: Unit;
|
|
20
|
+
/** Printer DPI (default: 203) */
|
|
21
|
+
dpi?: number;
|
|
22
|
+
/** Gap between labels in mm (label printers only) */
|
|
23
|
+
gap?: number;
|
|
24
|
+
/** Print speed (1-10, printer-dependent) */
|
|
25
|
+
speed?: number;
|
|
26
|
+
/** Print darkness/density (0-15) */
|
|
27
|
+
density?: number;
|
|
28
|
+
/** Print direction */
|
|
29
|
+
direction?: 0 | 1;
|
|
30
|
+
/** Number of copies */
|
|
31
|
+
copies?: number;
|
|
32
|
+
}
|
|
33
|
+
/** Text element options */
|
|
34
|
+
interface TextOptions {
|
|
35
|
+
/** X position */
|
|
36
|
+
x?: number;
|
|
37
|
+
/** Y position */
|
|
38
|
+
y?: number;
|
|
39
|
+
/** Font name or ID */
|
|
40
|
+
font?: string;
|
|
41
|
+
/** Font size or magnification */
|
|
42
|
+
size?: number;
|
|
43
|
+
/** Horizontal magnification (1-10) */
|
|
44
|
+
xScale?: number;
|
|
45
|
+
/** Vertical magnification (1-10) */
|
|
46
|
+
yScale?: number;
|
|
47
|
+
/** Rotation */
|
|
48
|
+
rotation?: Rotation;
|
|
49
|
+
/** Bold */
|
|
50
|
+
bold?: boolean;
|
|
51
|
+
/** Underline */
|
|
52
|
+
underline?: boolean;
|
|
53
|
+
/** Reverse (white on black) */
|
|
54
|
+
reverse?: boolean;
|
|
55
|
+
/** Alignment */
|
|
56
|
+
align?: Alignment;
|
|
57
|
+
/** Max width for word-wrap (in dots) */
|
|
58
|
+
maxWidth?: number;
|
|
59
|
+
/** Line spacing (in dots) */
|
|
60
|
+
lineSpacing?: number;
|
|
61
|
+
}
|
|
62
|
+
/** Image element options */
|
|
63
|
+
interface ImageOptions {
|
|
64
|
+
/** X position */
|
|
65
|
+
x?: number;
|
|
66
|
+
/** Y position */
|
|
67
|
+
y?: number;
|
|
68
|
+
/** Target width in dots (auto-scale if set) */
|
|
69
|
+
width?: number;
|
|
70
|
+
/** Target height in dots (auto-scale if set) */
|
|
71
|
+
height?: number;
|
|
72
|
+
/** Dithering algorithm (default: "threshold") */
|
|
73
|
+
dither?: DitherAlgorithm;
|
|
74
|
+
/** Threshold for monochrome conversion (0-255, default: 128) */
|
|
75
|
+
threshold?: number;
|
|
76
|
+
}
|
|
77
|
+
/** Box/rectangle element options */
|
|
78
|
+
interface BoxOptions {
|
|
79
|
+
/** X position */
|
|
80
|
+
x: number;
|
|
81
|
+
/** Y position */
|
|
82
|
+
y: number;
|
|
83
|
+
/** Width */
|
|
84
|
+
width: number;
|
|
85
|
+
/** Height */
|
|
86
|
+
height: number;
|
|
87
|
+
/** Border thickness in dots (default: 1) */
|
|
88
|
+
thickness?: number;
|
|
89
|
+
/** Corner radius in dots */
|
|
90
|
+
radius?: number;
|
|
91
|
+
}
|
|
92
|
+
/** Line element options */
|
|
93
|
+
interface LineOptions {
|
|
94
|
+
/** Start X */
|
|
95
|
+
x1: number;
|
|
96
|
+
/** Start Y */
|
|
97
|
+
y1: number;
|
|
98
|
+
/** End X */
|
|
99
|
+
x2: number;
|
|
100
|
+
/** End Y */
|
|
101
|
+
y2: number;
|
|
102
|
+
/** Line thickness in dots (default: 1) */
|
|
103
|
+
thickness?: number;
|
|
104
|
+
}
|
|
105
|
+
/** Circle element options */
|
|
106
|
+
interface CircleOptions {
|
|
107
|
+
/** Center X */
|
|
108
|
+
x: number;
|
|
109
|
+
/** Center Y */
|
|
110
|
+
y: number;
|
|
111
|
+
/** Diameter in dots */
|
|
112
|
+
diameter: number;
|
|
113
|
+
/** Border thickness in dots (default: 1) */
|
|
114
|
+
thickness?: number;
|
|
115
|
+
}
|
|
116
|
+
/** 1-bit monochrome bitmap (universal intermediate format for images) */
|
|
117
|
+
interface MonochromeBitmap {
|
|
118
|
+
/** Packed 1-bit pixel data, row-major, MSB-first */
|
|
119
|
+
data: Uint8Array;
|
|
120
|
+
/** Width in pixels */
|
|
121
|
+
width: number;
|
|
122
|
+
/** Height in pixels */
|
|
123
|
+
height: number;
|
|
124
|
+
/** Bytes per row = ceil(width / 8) */
|
|
125
|
+
bytesPerRow: number;
|
|
126
|
+
}
|
|
127
|
+
/** Ellipse element options */
|
|
128
|
+
interface EllipseOptions {
|
|
129
|
+
x: number;
|
|
130
|
+
y: number;
|
|
131
|
+
width: number;
|
|
132
|
+
height: number;
|
|
133
|
+
thickness?: number;
|
|
134
|
+
}
|
|
135
|
+
/** Reverse region options (invert black/white) */
|
|
136
|
+
interface ReverseOptions {
|
|
137
|
+
x: number;
|
|
138
|
+
y: number;
|
|
139
|
+
width: number;
|
|
140
|
+
height: number;
|
|
141
|
+
}
|
|
142
|
+
/** Erase region options (clear to white) */
|
|
143
|
+
interface EraseOptions {
|
|
144
|
+
x: number;
|
|
145
|
+
y: number;
|
|
146
|
+
width: number;
|
|
147
|
+
height: number;
|
|
148
|
+
}
|
|
149
|
+
/** Internal label element union type */
|
|
150
|
+
type LabelElement = {
|
|
151
|
+
type: "text";
|
|
152
|
+
content: string;
|
|
153
|
+
options: TextOptions;
|
|
154
|
+
} | {
|
|
155
|
+
type: "image";
|
|
156
|
+
bitmap: MonochromeBitmap;
|
|
157
|
+
options: ImageOptions;
|
|
158
|
+
} | {
|
|
159
|
+
type: "box";
|
|
160
|
+
options: BoxOptions;
|
|
161
|
+
} | {
|
|
162
|
+
type: "line";
|
|
163
|
+
options: LineOptions;
|
|
164
|
+
} | {
|
|
165
|
+
type: "circle";
|
|
166
|
+
options: CircleOptions;
|
|
167
|
+
} | {
|
|
168
|
+
type: "ellipse";
|
|
169
|
+
options: EllipseOptions;
|
|
170
|
+
} | {
|
|
171
|
+
type: "reverse";
|
|
172
|
+
options: ReverseOptions;
|
|
173
|
+
} | {
|
|
174
|
+
type: "erase";
|
|
175
|
+
options: EraseOptions;
|
|
176
|
+
} | {
|
|
177
|
+
type: "raw";
|
|
178
|
+
content: string | Uint8Array;
|
|
179
|
+
};
|
|
180
|
+
/** Resolved label configuration with computed values */
|
|
181
|
+
interface ResolvedLabel {
|
|
182
|
+
/** Width in dots */
|
|
183
|
+
widthDots: number;
|
|
184
|
+
/** Height in dots (0 for continuous/receipt) */
|
|
185
|
+
heightDots: number;
|
|
186
|
+
/** DPI */
|
|
187
|
+
dpi: number;
|
|
188
|
+
/** Gap in dots */
|
|
189
|
+
gapDots: number;
|
|
190
|
+
/** Speed */
|
|
191
|
+
speed: number;
|
|
192
|
+
/** Density */
|
|
193
|
+
density: number;
|
|
194
|
+
/** Direction */
|
|
195
|
+
direction: 0 | 1;
|
|
196
|
+
/** Copies */
|
|
197
|
+
copies: number;
|
|
198
|
+
/** All elements to render */
|
|
199
|
+
elements: LabelElement[];
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
export { EllipseOptions as a, LabelConfig as c, MonochromeBitmap as d, ResolvedLabel as f, Unit as g, TextOptions as h, DitherAlgorithm as i, LabelElement as l, Rotation as m, BoxOptions as n, EraseOptions as o, ReverseOptions as p, CircleOptions as r, ImageOptions as s, Alignment as t, LineOptions as u };
|