portakal-lite 1.0.0 → 2.0.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 CHANGED
@@ -5,7 +5,7 @@ Integrates [etiket](https://github.com/productdevbook/etiket) package for seamle
5
5
 
6
6
  - Fluent `label()` builder — text, boxes, lines, circles, ellipses, reverse/erase regions, images, raw commands
7
7
  - `.barcode()` / `.qrcode()` — 40+ symbologies via [etiket](https://github.com/productdevbook/etiket)
8
- - `tsc.compile()` / `zpl.compile()` → **printer-ready string** (no transport, no connection — you send it)
8
+ - `tsc.compile()` / `zpl.compile()` → **printer-ready output** — TSC as a `Uint8Array` (binary bitmap payload), ZPL as a string. No transport, no connection — you send it.
9
9
  - `tsc.preview()` / `zpl.preview()` → SVG rendering with per-language font metrics
10
10
  - Receipt layout helpers: `formatPair`, `formatRow`, `formatTable`, `separator`, `wordWrap`
11
11
  - One runtime dependency (`etiket`, itself zero-dep), pure ESM, works in Node, browsers, Deno, Bun
@@ -72,25 +72,59 @@ Barcode options: `x`, `y`, `height`, `moduleWidth`, `ratio`, `rotation`, `readab
72
72
 
73
73
  QR options: `x`, `y`, `cellSize`, `ecc` (`L`/`M`/`Q`/`H`), `rotation`, `version`, `mode`, `mask`, `eci`, `gs1`.
74
74
 
75
- ### Send the compiled file to a printer
75
+ ### Sending to a printer
76
76
 
77
- The package only produces the string wire it to your printer however you like:
77
+ `tsc.compile()` returns a **`Uint8Array`**, not a string: the text commands are
78
+ ASCII, but a `BITMAP` payload is raw packed pixels. Send those bytes as-is — the
79
+ compiler has already concatenated the whole stream for you. `zpl.compile()`
80
+ returns a string (ZPL has no binary payload).
78
81
 
79
82
  ```ts
80
83
  import { label, tsc } from "portakal-lite";
81
84
  import net from "node:net";
82
85
 
83
- const commands = tsc.compile(
86
+ const bytes = tsc.compile(
84
87
  label({ width: 40, height: 30 }).text("Hello", { x: 10, y: 10 }),
85
88
  );
86
89
 
87
90
  // TCP label printers usually listen on port 9100
88
91
  const socket = net.createConnection({ host: "192.168.1.100", port: 9100 });
89
- socket.write(commands);
92
+ socket.write(bytes);
90
93
  socket.end();
91
94
 
92
95
  // ...or write to a file for a print spooler
93
- // fs.writeFileSync("label.prn", commands);
96
+ // fs.writeFileSync("label.prn", bytes);
97
+ ```
98
+
99
+ Do **not** decode the output to text before sending. `new TextDecoder().decode(bytes)`,
100
+ `String.fromCharCode(...bytes)`, `bytes.toString()` and `JSON.stringify(bytes)` all
101
+ mangle bytes ≥ `0x80` and change the total length, so the printer reads the wrong
102
+ byte count — images print as noise, while text-only labels still look fine
103
+ because those bytes are pure ASCII.
104
+
105
+ For HTTP, post the bytes directly (no encoding needed):
106
+
107
+ ```ts
108
+ await fetch("/print", {
109
+ method: "POST",
110
+ headers: { "Content-Type": "application/octet-stream" },
111
+ body: bytes, // React Native: new Blob([bytes], { type: "application/octet-stream" })
112
+ });
113
+ ```
114
+
115
+ If a transport only accepts a `string` (some BLE and Expo bridges), wrap the
116
+ bytes losslessly with base64 and let the transport decode them:
117
+
118
+ ```ts
119
+ import { bytesToBase64, chunkBytes } from "portakal-lite";
120
+
121
+ await ble.write(deviceId, characteristicId, bytesToBase64(bytes));
122
+
123
+ // MTU-limited link? Split the BYTES, then encode each chunk — never split
124
+ // an encoded string.
125
+ for (const chunk of chunkBytes(bytes, 180)) {
126
+ await ble.write(deviceId, characteristicId, bytesToBase64(chunk));
127
+ }
94
128
  ```
95
129
 
96
130
  TSC output uses LF (`\n`) line endings by default. If your printer firmware
@@ -103,6 +137,21 @@ const commands = tsc.compile(
103
137
  );
104
138
  ```
105
139
 
140
+ ### Showing the output
141
+
142
+ `formatTSCBytes` (or `tsc.text(builder)`) renders the stream for display — ASCII
143
+ commands verbatim, with any binary `BITMAP` payload elided. Use it for a UI, a
144
+ log, or a "show compiled commands" panel; never send it in place of the bytes.
145
+
146
+ ```ts
147
+ import { formatTSCBytes, tsc } from "portakal-lite";
148
+
149
+ formatTSCBytes(bytes);
150
+ // 'SIZE 40 mm,30 mm\nCLS\nTEXT 10,10,"2",0,1,1,"Hello"\nPRINT 1\n'
151
+
152
+ tsc.text(builder); // an image label shows: '… BITMAP 10,10,2,16,0,<32 bytes of bitmap data>\nPRINT 1\n'
153
+ ```
154
+
106
155
  ### Receipt-style aligned lines
107
156
 
108
157
  ```ts
@@ -150,6 +199,29 @@ const b = label({ width: 40, height: 30 })
150
199
 
151
200
  `MonochromeBitmap` is a 1-bit packed `Uint8Array`: `{ data, width, height, bytesPerRow }` with `bytesPerRow === Math.ceil(width / 8)`.
152
201
 
202
+ ### Images
203
+
204
+ Raster graphics need packed 1-bit pixels. `toMonochromeBitmap` converts raw
205
+ grayscale or RGB/RGBA pixels (e.g. a decoded photo) into that shape, with
206
+ threshold or error-diffusion dithering:
207
+
208
+ ```ts
209
+ import { label, tsc, toMonochromeBitmap } from "portakal-lite";
210
+
211
+ // pixels: row-major, 1 (grayscale), 3 (RGB) or 4 (RGBA) bytes per pixel.
212
+ const bitmap = toMonochromeBitmap(pixels, width, height, {
213
+ dither: "floyd-steinberg", // "threshold" | "floyd-steinberg" | "atkinson" | "ordered"
214
+ threshold: 128, // luminance cutoff (default 128)
215
+ invert: false,
216
+ });
217
+
218
+ const myLabel = label({ width: 40, height: 30 }).image(bitmap, { x: 20, y: 60 });
219
+ ```
220
+
221
+ RGBA is composited over white, so transparent areas print as unmarked paper.
222
+ Note that TSC `BITMAP` cannot scale — emit the bitmap at the size you want
223
+ printed (ZPL `^GFA` is fixed-size too). See [`examples/image-label.js`](./examples/image-label.js).
224
+
153
225
  ## Examples
154
226
 
155
227
  Runnable examples live in [`examples/`](./examples) — build the package first (`npm run build`), then run any:
@@ -159,6 +231,7 @@ node examples/basic-label.js # text + box + Code 128 + QR, both language
159
231
  node examples/shipping-label.js # shipping label with tracking barcode + QR
160
232
  node examples/receipt-label.js # receipt-style label with order barcode
161
233
  node examples/max-symbologies.js # native + rasterized (EAN-13, UPC-A, Code 39, ITF, DataMatrix, PDF417, Aztec)
234
+ node examples/image-label.js # dithered bitmap image (BITMAP / ^GFA)
162
235
  ```
163
236
 
164
237
  ## Security
@@ -177,7 +250,7 @@ The compilers are hardened against command injection — the most important thin
177
250
 
178
251
  ## Differences from portakal
179
252
 
180
- `portakal-lite` keeps the label builder, TSC/ZPL compilers, per-language preview, receipt helpers, and adds barcode/QR support backed by etiket. It drops the other 7 languages, parsers, `validate()`, cross-compiler, image dithering, encoding engine, and the transport layer. Behavior of the generated TSC/ZPL commands is identical to portakal's, plus the hardening above.
253
+ `portakal-lite` keeps the label builder, TSC/ZPL compilers, per-language preview, receipt helpers, and adds barcode/QR support backed by etiket plus image dithering via `toMonochromeBitmap()`. It drops the other 7 languages, parsers, `validate()`, cross-compiler, encoding engine, and the transport layer. Behavior of the generated TSC/ZPL commands is identical to portakal's, plus the hardening above.
181
254
 
182
255
  ## License
183
256