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.
- package/LICENSE +21 -0
- package/README.md +361 -0
- 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
|
+
}
|