portakal 0.1.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 (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +361 -0
  3. package/package.json +74 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 productdevbook
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,361 @@
1
+ <p align="center">
2
+ <br>
3
+ <img src=".github/assets/cover.png" alt="portakal — Universal printer language SDK" width="100%">
4
+ <br><br>
5
+ <b style="font-size: 2em;">portakal</b>
6
+ <br><br>
7
+ Universal printer language SDK — TSC, ZPL, EPL, ESC/POS and more.
8
+ <br>
9
+ Text, barcodes, QR codes, images, shapes — anything you can print.
10
+ <br>
11
+ One API, every thermal printer. Pure TypeScript, zero dependencies.
12
+ <br><br>
13
+ <a href="https://npmjs.com/package/portakal"><img src="https://img.shields.io/npm/v/portakal?style=flat&colorA=18181B&colorB=F97316" alt="npm version"></a>
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
+ <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
+ <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
+ </p>
18
+
19
+ > [!NOTE]
20
+ > portakal has **two ways** to print barcodes and QR codes:
21
+ >
22
+ > 1. **Printer-native** (`.barcode()` / `.qrcode()`) — sends commands to the printer's built-in encoder. Fast, zero dependencies, minimal data transfer. Works for most use cases.
23
+ > 2. **Software-rendered** (`.image()` + [`etiket`](https://github.com/productdevbook/etiket)) — renders barcodes/QR codes as images on the host, sends pixels to the printer. Pixel-perfect output, 40+ formats, styled QR codes, guaranteed consistency across all printers. You install `etiket` yourself — portakal stays zero-dependency.
24
+
25
+ ## Quick Start
26
+
27
+ ```sh
28
+ npm install portakal
29
+ ```
30
+
31
+ ### Product label (text + barcode + QR + shapes)
32
+
33
+ ```ts
34
+ import { label } from "portakal";
35
+
36
+ const cmd = label({ width: 40, height: 30, unit: "mm" })
37
+ .text("ACME Corp", { x: 10, y: 10, size: 2 })
38
+ .text("SKU: PRD-00123", { x: 10, y: 35, font: "2" })
39
+ .barcode("123456789012", { type: "ean13", x: 10, y: 60, height: 50 })
40
+ .qrcode("https://acme.com/prd/123", { x: 220, y: 60, ecc: "M", size: 5 })
41
+ .line({ x1: 5, y1: 55, x2: 310, y2: 55, thickness: 1 })
42
+ .box({ x: 5, y: 5, width: 310, height: 230, thickness: 2 })
43
+ .toTSC();
44
+ ```
45
+
46
+ ### Shipping label (multiple text fields + barcode)
47
+
48
+ ```ts
49
+ const shipping = label({ width: 100, height: 150, unit: "mm" })
50
+ .text("FROM: Warehouse A", { x: 10, y: 10 })
51
+ .text("TO: John Doe", { x: 10, y: 30, size: 2 })
52
+ .text("123 Main St, New York, NY 10001", { x: 10, y: 55 })
53
+ .barcode("SSCC00012345678901234", { type: "code128", x: 10, y: 80, height: 80 })
54
+ .qrcode("https://track.example.com/PKG123", { x: 300, y: 80, size: 8 })
55
+ .line({ x1: 5, y1: 70, x2: 780, y2: 70, thickness: 2 })
56
+ .toZPL();
57
+ ```
58
+
59
+ ### Receipt (ESC/POS — text, barcode, QR)
60
+
61
+ ```ts
62
+ const receipt = label({ width: 80, unit: "mm" })
63
+ .text("MY STORE", { align: "center", bold: true, size: 2 })
64
+ .text("123 Market St", { align: "center" })
65
+ .text("================================")
66
+ .text("Hamburger x2 $25.98")
67
+ .text("Cola x1 $3.50")
68
+ .text("================================")
69
+ .text("TOTAL $29.48", { bold: true, size: 2 })
70
+ .barcode("INV-20260402-001", { type: "code128", height: 60 })
71
+ .qrcode("https://receipt.example.com/inv/001")
72
+ .toESCPOS(); // Uint8Array (binary)
73
+ ```
74
+
75
+ ### Logo / image printing
76
+
77
+ ```ts
78
+ const cmd = label({ width: 40, height: 30, unit: "mm" })
79
+ .image(myLogoBitmap, { x: 10, y: 10, width: 100 })
80
+ .text("Company Name", { x: 120, y: 20, size: 2 })
81
+ .toTSC();
82
+ ```
83
+
84
+ ### Same label → any printer language
85
+
86
+ ```ts
87
+ const myLabel = label({ width: 40, height: 30, unit: "mm" })
88
+ .text("Hello World", { x: 10, y: 10, size: 2 })
89
+ .barcode("123456789", { type: "code128", x: 10, y: 50, height: 60 });
90
+
91
+ myLabel.toTSC(); // TSC/TSPL2 — TSC, Gprinter, Xprinter, iDPRT
92
+ myLabel.toZPL(); // Zebra ZPL II — GK420, ZT410, ZD620
93
+ myLabel.toEPL(); // Eltron EPL2 — LP/TLP 2824, GX420, ZD220
94
+ myLabel.toESCPOS(); // ESC/POS — Epson, Bixolon, Star, Citizen
95
+ ```
96
+
97
+ ### Software-rendered barcode/QR (with etiket)
98
+
99
+ For pixel-perfect output, styled QR codes, or when the printer doesn't support a format natively:
100
+
101
+ ```sh
102
+ npm install portakal etiket
103
+ ```
104
+
105
+ ```ts
106
+ import { label } from "portakal";
107
+ import { barcodePNG, qrcodePNG } from "etiket";
108
+
109
+ // etiket renders barcode/QR as PNG → portakal sends it as an image to the printer
110
+ const cmd = label({ width: 40, height: 30, unit: "mm" })
111
+ .text("Product Label", { x: 10, y: 5 })
112
+ .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
+ ```
116
+
117
+ ### When to use which?
118
+
119
+ | | Printer-native (`.barcode()` / `.qrcode()`) | Software-rendered (`.image()` + `etiket`) |
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 |
129
+
130
+ ## API
131
+
132
+ ### `label(config)`
133
+
134
+ Creates a new label builder.
135
+
136
+ ```ts
137
+ const builder = label({
138
+ width: 40, // Label width
139
+ height: 30, // Label height (omit for receipt/continuous)
140
+ unit: "mm", // "mm" | "inch" | "dot" (default: "mm")
141
+ dpi: 203, // Printer DPI (default: 203)
142
+ gap: 3, // Gap between labels in mm (default: 3)
143
+ speed: 4, // Print speed 1-10 (default: 4)
144
+ density: 8, // Darkness 0-15 (default: 8)
145
+ copies: 1, // Number of copies (default: 1)
146
+ });
147
+ ```
148
+
149
+ ### `.text(content, options?)`
150
+
151
+ ```ts
152
+ builder.text("Hello", {
153
+ x: 10, // X position in dots
154
+ y: 20, // Y position in dots
155
+ font: "2", // Font name/ID (printer-specific)
156
+ size: 2, // Magnification (1-10)
157
+ rotation: 0, // 0 | 90 | 180 | 270
158
+ bold: true, // Bold (ESC/POS only)
159
+ underline: true, // Underline (ESC/POS only)
160
+ reverse: false, // White on black
161
+ align: "center", // "left" | "center" | "right"
162
+ maxWidth: 300, // Word-wrap width in dots
163
+ });
164
+ ```
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
+ ### `.image(bitmap, options?)`
217
+
218
+ ```ts
219
+ builder.image(monochromeBitmap, {
220
+ x: 10,
221
+ y: 10, // Position
222
+ width: 200, // Target width in dots
223
+ height: 100, // Target height in dots
224
+ });
225
+ ```
226
+
227
+ The `bitmap` must be a `MonochromeBitmap`:
228
+
229
+ ```ts
230
+ interface MonochromeBitmap {
231
+ data: Uint8Array; // 1-bit packed, row-major, MSB-first
232
+ width: number; // Width in pixels
233
+ height: number; // Height in pixels
234
+ bytesPerRow: number; // ceil(width / 8)
235
+ }
236
+ ```
237
+
238
+ ### `.box(options)` / `.line(options)` / `.circle(options)`
239
+
240
+ ```ts
241
+ builder.box({ x: 0, y: 0, width: 200, height: 100, thickness: 2, radius: 5 });
242
+ builder.line({ x1: 0, y1: 50, x2: 300, y2: 50, thickness: 1 });
243
+ builder.circle({ x: 100, y: 100, diameter: 60, thickness: 2 });
244
+ ```
245
+
246
+ ### `.raw(content)`
247
+
248
+ Escape hatch for printer-specific commands:
249
+
250
+ ```ts
251
+ builder.raw("SET CUTTER ON"); // TSC
252
+ builder.raw("^FO10,10^FDCustom^FS"); // ZPL
253
+ builder.raw(new Uint8Array([0x1b, 0x70, 0x00, 0x32, 0x32])); // ESC/POS cash drawer
254
+ ```
255
+
256
+ ### Output Methods
257
+
258
+ | Method | Output | Target |
259
+ | :------------ | :----------- | :----------------------- |
260
+ | `.toTSC()` | `string` | TSC/TSPL2 label printers |
261
+ | `.toZPL()` | `string` | Zebra ZPL II printers |
262
+ | `.toEPL()` | `string` | Eltron EPL2 printers |
263
+ | `.toESCPOS()` | `Uint8Array` | ESC/POS receipt printers |
264
+
265
+ ## Supported Printer Languages
266
+
267
+ | Language | Printers | Status |
268
+ | :-------------- | :--------------------------------------------- | :----------------- |
269
+ | **TSC/TSPL2** | TSC, Gprinter, Xprinter, iDPRT, Munbyn, Polono | :white_check_mark: |
270
+ | **ZPL II** | Zebra GK420, ZT410, ZD620, ZQ series | :white_check_mark: |
271
+ | **EPL2** | Zebra LP/TLP 2824, GX420, ZD220, ZD420 | :white_check_mark: |
272
+ | **ESC/POS** | Epson, Bixolon, Citizen, Star (compat mode) | :white_check_mark: |
273
+ | **CPCL** | Zebra QLn, ZQ mobile printers | Planned |
274
+ | **DPL** | Honeywell/Datamax label printers | Planned |
275
+ | **IPL** | Intermec/Honeywell printers | Planned |
276
+ | **SBPL** | SATO label printers | Planned |
277
+ | **Star PRNT** | Star TSP100/143/600/700 (native mode) | Planned |
278
+ | **Fingerprint** | Honeywell Smart Printers | Planned |
279
+
280
+ ## Transport
281
+
282
+ portakal generates commands only — it does **not** handle printer connections. Send the output over any transport you choose:
283
+
284
+ ```ts
285
+ import { label } from "portakal";
286
+ import net from "node:net";
287
+
288
+ const commands = label({ width: 40, height: 30 }).text("Hello", { x: 10, y: 10 }).toTSC();
289
+
290
+ // TCP (port 9100)
291
+ const socket = net.createConnection({ host: "192.168.1.100", port: 9100 });
292
+ socket.write(commands);
293
+ socket.end();
294
+
295
+ // USB (via serialport)
296
+ import { SerialPort } from "serialport";
297
+ const port = new SerialPort({ path: "/dev/usb/lp0", baudRate: 9600 });
298
+ port.write(commands);
299
+
300
+ // ESC/POS (binary) over WebUSB
301
+ const escpos = label({ width: 80 }).text("Receipt").toESCPOS();
302
+ await usbDevice.transferOut(endpointNumber, escpos);
303
+ ```
304
+
305
+ ## Comparison
306
+
307
+ | Feature | portakal | [node-thermal-printer](https://github.com/Klemen1337/node-thermal-printer) | [escpos](https://github.com/node-escpos/driver) | [jszpl](https://github.com/DanieLeeuwner/JSZPL) |
308
+ | :--------------------------- | :------------------------------------: | :------------------------------------------------------------------------: | :---------------------------------------------: | :---------------------------------------------: |
309
+ | Zero dependencies | :white_check_mark: | :x: (pngjs, iconv-lite) | :x: (get-pixels, jimp) | :white_check_mark: |
310
+ | TypeScript-first | :white_check_mark: | Partial | Partial | :white_check_mark: |
311
+ | Multi-language output | :white_check_mark: TSC+ZPL+EPL+ESC/POS | :x: ESC/POS only | :x: ESC/POS only | :x: ZPL only |
312
+ | Transport-agnostic | :white_check_mark: | :x: (coupled) | :x: (coupled) | :white_check_mark: |
313
+ | Label printers (TSC/ZPL/EPL) | :white_check_mark: | :x: | :x: | ZPL only |
314
+ | Receipt printers (ESC/POS) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
315
+ | Image support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
316
+ | Barcode (printer-native) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
317
+ | QR Code (printer-native) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
318
+ | Works in browser | :white_check_mark: | :x: | :x: | :white_check_mark: |
319
+ | No native modules (no gyp) | :white_check_mark: | :x: | :x: | :white_check_mark: |
320
+ | Pure ESM | :white_check_mark: | :x: (CJS) | :x: (CJS) | :x: (CJS) |
321
+
322
+ **portakal is the only library that generates** TSC + ZPL + EPL + ESC/POS from a single API with zero dependencies.
323
+
324
+ ## Features
325
+
326
+ - Zero dependencies
327
+ - Pure ESM, edge-runtime compatible (Cloudflare Workers, Deno, Bun)
328
+ - TypeScript-first with strict types (tsgo)
329
+ - Transport-agnostic — generates commands, you handle the connection
330
+ - Multi-language — one label definition compiles to TSC, ZPL, EPL, or ESC/POS
331
+ - Fluent builder API
332
+ - Works in browser, Node.js, Deno, Bun, Electron
333
+ - No native modules — no node-gyp, no libusb, no compilation
334
+ - Barcode + QR code via printer-native commands (20+ symbologies)
335
+ - Image support via MonochromeBitmap
336
+ - Drawing primitives — box, line, circle, diagonal
337
+ - Raw command passthrough for advanced/unsupported features
338
+ - Optional [`etiket`](https://github.com/productdevbook/etiket) integration for pixel-perfect barcode/QR images
339
+
340
+ ## Contributing
341
+
342
+ Contributions are welcome! Here are areas where help is especially appreciated:
343
+
344
+ - **CPCL, DPL, IPL, SBPL, Star PRNT** compiler implementations
345
+ - Image processing pipeline (dithering, compression)
346
+ - Character encoding engine (UTF-8 auto code page selection)
347
+ - Arabic/Hebrew RTL support (bidi + shaping)
348
+ - Receipt layout engine (tables, same-line alignment)
349
+ - Printer capability profiles
350
+ - Transport layer implementations (WebUSB, WebSerial, Web Bluetooth)
351
+
352
+ ```bash
353
+ pnpm install # Install dependencies
354
+ pnpm dev # Run tests in watch mode
355
+ pnpm test # Lint + typecheck + test
356
+ pnpm build # Build for production
357
+ ```
358
+
359
+ ## License
360
+
361
+ Published under the [MIT](https://github.com/productdevbook/portakal/blob/main/LICENSE) license.
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "portakal",
3
+ "version": "0.1.0",
4
+ "description": "Universal printer language SDK — TSC, ZPL, EPL, ESC/POS, CPCL, DPL, IPL, SBPL and more. Text, barcode, QR, images. Pure TypeScript, zero dependencies.",
5
+ "keywords": [
6
+ "barcode",
7
+ "cpcl",
8
+ "dpl",
9
+ "epl",
10
+ "esc-pos",
11
+ "esm",
12
+ "ipl",
13
+ "label",
14
+ "printer",
15
+ "qr",
16
+ "sbpl",
17
+ "thermal-printer",
18
+ "tsc",
19
+ "typescript",
20
+ "zero-dependency",
21
+ "zpl"
22
+ ],
23
+ "homepage": "https://github.com/productdevbook/portakal",
24
+ "bugs": {
25
+ "url": "https://github.com/productdevbook/portakal/issues"
26
+ },
27
+ "license": "MIT",
28
+ "author": "productdevbook",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/productdevbook/portakal.git"
32
+ },
33
+ "funding": "https://github.com/sponsors/productdevbook",
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "type": "module",
38
+ "module": "./dist/index.mjs",
39
+ "types": "./dist/index.d.mts",
40
+ "exports": {
41
+ ".": {
42
+ "types": "./dist/index.d.mts",
43
+ "default": "./dist/index.mjs"
44
+ }
45
+ },
46
+ "scripts": {
47
+ "build": "obuild",
48
+ "dev": "vitest",
49
+ "lint": "oxlint . && oxfmt --check .",
50
+ "lint:fix": "oxlint . --fix && oxfmt .",
51
+ "fmt": "oxfmt .",
52
+ "test": "pnpm lint && pnpm typecheck && vitest run",
53
+ "typecheck": "tsgo --noEmit",
54
+ "release": "pnpm test && pnpm build && bumpp --commit --tag --push --all"
55
+ },
56
+ "devDependencies": {
57
+ "@typescript/native-preview": "7.0.0-dev.20260316.1",
58
+ "@vitest/coverage-v8": "^4.1.1",
59
+ "bumpp": "^11.0.1",
60
+ "obuild": "^0.4.32",
61
+ "oxfmt": "^0.42.0",
62
+ "oxlint": "^1.57.0",
63
+ "typescript": "^6.0.2",
64
+ "vitest": "^4.1.1"
65
+ },
66
+ "peerDependencies": {
67
+ "etiket": ">=0.1.0"
68
+ },
69
+ "peerDependenciesMeta": {
70
+ "etiket": {
71
+ "optional": true
72
+ }
73
+ }
74
+ }