@angadie/chittie-text 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 +24 -0
- package/README.md +60 -0
- package/dist/index.d.mts +62 -0
- package/dist/index.mjs +82 -0
- package/package.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Asyncdot Engineering
|
|
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
|
+
Portions of this software are vendored from MIT-licensed projects and retain
|
|
16
|
+
their original copyright notices; see VENDOR.md.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @angadie/chittie-text
|
|
2
|
+
|
|
3
|
+
Smart text for chittie: print **code-page text** when the characters are representable, **auto-rasterize** complex scripts (Sinhala, Tamil, Thai, Devanagari, Arabic, …) via an **injected rasterizer**, and **never silently print `?`**.
|
|
4
|
+
|
|
5
|
+
Platform-neutral and dependency-light — it never imports canvas or skia. You supply the rasterizer; this package decides *when* to use it.
|
|
6
|
+
|
|
7
|
+
> Why: thermal printers have no font ROM or code page for Indic/complex scripts, and those scripts need shaping/reordering. The only correct path is to render the text to a bitmap on the host and send it as an image. chittie-text automates the decision and guards against the silent-`?` failure.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @angadie/chittie-text
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
### `needsRaster(text, codepage = 'cp437'): boolean`
|
|
18
|
+
True if any character can't be encoded in `codepage` (it would print as `?`).
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { needsRaster } from '@angadie/chittie-text';
|
|
22
|
+
needsRaster('Rs. 250'); // false
|
|
23
|
+
needsRaster('ආයුබෝවන්'); // true
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### `TextRasterizer`
|
|
27
|
+
The interface **you** implement with a platform rasterizer:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
interface TextRasterizer {
|
|
31
|
+
rasterize(text: string, options: RasterOptions): ImageData;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A web implementation, for example, draws to a `<canvas>` and returns `ctx.getImageData(...)`. On React Native, use `react-native-skia` or a pre-rendered PNG decoded to `ImageData`.
|
|
36
|
+
|
|
37
|
+
### `smartText(encoder, text, options): void`
|
|
38
|
+
Prints `text` the right way onto a chittie-core encoder:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { smartText } from '@angadie/chittie-text';
|
|
42
|
+
|
|
43
|
+
smartText(encoder, 'මිල: Rs.250', {
|
|
44
|
+
rasterizer, // optional TextRasterizer
|
|
45
|
+
codepage: 'cp437', // optional
|
|
46
|
+
raster: { fontSize: 24, maxWidth: 576 }, // passed to your rasterizer
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- encodable → `encoder.text(text)`
|
|
51
|
+
- not encodable + rasterizer → `encoder.image(...)`
|
|
52
|
+
- not encodable + **no** rasterizer → **throws** (no silent `?`)
|
|
53
|
+
|
|
54
|
+
## With chittie-react
|
|
55
|
+
|
|
56
|
+
`render(tree, { rasterizer, codepage })` wires this into every `<Text>` automatically — see [`@angadie/chittie-react`](../chittie-react).
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import CodepageEncoder from "@angadie/chittie-codepage";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
/** Code-page name accepted by the encoder — derived from the engine, not duplicated. */
|
|
5
|
+
type Codepage = Parameters<typeof CodepageEncoder.encode>[1];
|
|
6
|
+
/**
|
|
7
|
+
* True if `text` contains characters the target code page cannot represent
|
|
8
|
+
* (they would print as "?"). Sinhala, Tamil, and other Indic/complex scripts
|
|
9
|
+
* have no thermal-printer code page, so they always return true — those runs
|
|
10
|
+
* must be printed as a raster image (see VENDOR research: non-latin-printing).
|
|
11
|
+
*/
|
|
12
|
+
declare function needsRaster(text: string, codepage?: Codepage): boolean;
|
|
13
|
+
/** Options handed to the injected rasterizer. */
|
|
14
|
+
interface RasterOptions {
|
|
15
|
+
fontSize?: number;
|
|
16
|
+
bold?: boolean;
|
|
17
|
+
font?: string;
|
|
18
|
+
/** Max pixel width (printer dot width, e.g. 384 for 58mm, 576 for 80mm). */
|
|
19
|
+
maxWidth?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* You implement this with a platform rasterizer (web canvas, react-native-skia,
|
|
23
|
+
* or a pre-rendered PNG decoded to ImageData). chittie-text stays platform-neutral
|
|
24
|
+
* and dependency-free; it never imports canvas/skia.
|
|
25
|
+
*/
|
|
26
|
+
interface TextRasterizer {
|
|
27
|
+
rasterize(text: string, options: RasterOptions): ImageData;
|
|
28
|
+
}
|
|
29
|
+
/** Minimal slice of the encoder smartText drives (avoids depending on chittie-core). */
|
|
30
|
+
interface EncoderLike {
|
|
31
|
+
text(value: string): unknown;
|
|
32
|
+
image(image: ImageData, width: number, height: number): unknown;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* ESC/POS raster requires dimensions that are multiples of 8. Pad right/bottom
|
|
36
|
+
* with white so any image "just works". Platform-neutral: reuses the input's own
|
|
37
|
+
* ImageData constructor (browser or @canvas/image-data) — no import. Exported so
|
|
38
|
+
* chittie-react's <Image> (and others) reuse the same handling.
|
|
39
|
+
*/
|
|
40
|
+
declare function padTo8(img: ImageData): ImageData;
|
|
41
|
+
interface SmartTextOptions {
|
|
42
|
+
rasterizer?: TextRasterizer;
|
|
43
|
+
codepage?: Codepage;
|
|
44
|
+
raster?: RasterOptions;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Print `text` the right way: as code-page text when it's representable, or as a
|
|
48
|
+
* rasterized image when it isn't (Sinhala/Tamil/…). If a raster is needed but no
|
|
49
|
+
* rasterizer was supplied, it throws a clear error instead of silently printing "?".
|
|
50
|
+
*/
|
|
51
|
+
declare function smartText(encoder: EncoderLike, text: string, options?: SmartTextOptions): void;
|
|
52
|
+
/**
|
|
53
|
+
* Render a two-column row (left flush-left, right flush-right) to one
|
|
54
|
+
* printer-width image — the correct way to print non-Latin text inside a
|
|
55
|
+
* table column, since a per-cell image can't sit in a text column. chittie
|
|
56
|
+
* computes the layout; the injected rasterizer shapes each fragment.
|
|
57
|
+
*/
|
|
58
|
+
declare function rasterizeRow(rasterizer: TextRasterizer, left: string, right: string, options: {
|
|
59
|
+
dotWidth: number;
|
|
60
|
+
} & RasterOptions): ImageData;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { Codepage, RasterOptions, SmartTextOptions, TextRasterizer, needsRaster, padTo8, rasterizeRow, smartText };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import CodepageEncoder from "@angadie/chittie-codepage";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* True if `text` contains characters the target code page cannot represent
|
|
5
|
+
* (they would print as "?"). Sinhala, Tamil, and other Indic/complex scripts
|
|
6
|
+
* have no thermal-printer code page, so they always return true — those runs
|
|
7
|
+
* must be printed as a raster image (see VENDOR research: non-latin-printing).
|
|
8
|
+
*/
|
|
9
|
+
function needsRaster(text, codepage = "cp437") {
|
|
10
|
+
for (const ch of text) {
|
|
11
|
+
if (ch === "?") continue;
|
|
12
|
+
const bytes = CodepageEncoder.encode(ch, codepage);
|
|
13
|
+
if (bytes.length > 0 && Array.from(bytes).every((b) => b === 63)) return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* ESC/POS raster requires dimensions that are multiples of 8. Pad right/bottom
|
|
19
|
+
* with white so any image "just works". Platform-neutral: reuses the input's own
|
|
20
|
+
* ImageData constructor (browser or @canvas/image-data) — no import. Exported so
|
|
21
|
+
* chittie-react's <Image> (and others) reuse the same handling.
|
|
22
|
+
*/
|
|
23
|
+
function padTo8(img) {
|
|
24
|
+
const { width: w, height: h } = img;
|
|
25
|
+
const w8 = Math.ceil(w / 8) * 8;
|
|
26
|
+
const h8 = Math.ceil(h / 8) * 8;
|
|
27
|
+
if (w8 === w && h8 === h) return img;
|
|
28
|
+
const out = new Uint8ClampedArray(w8 * h8 * 4).fill(255);
|
|
29
|
+
for (let y = 0; y < h; y++) out.set(img.data.subarray(y * w * 4, y * w * 4 + w * 4), y * w8 * 4);
|
|
30
|
+
const Ctor = img.constructor;
|
|
31
|
+
return new Ctor(out, w8, h8);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Print `text` the right way: as code-page text when it's representable, or as a
|
|
35
|
+
* rasterized image when it isn't (Sinhala/Tamil/…). If a raster is needed but no
|
|
36
|
+
* rasterizer was supplied, it throws a clear error instead of silently printing "?".
|
|
37
|
+
*/
|
|
38
|
+
function smartText(encoder, text, options = {}) {
|
|
39
|
+
if (!needsRaster(text, options.codepage ?? "cp437")) {
|
|
40
|
+
encoder.text(text);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!options.rasterizer) throw new Error(`chittie-text: "${text}" contains characters with no code page (e.g. Sinhala/Tamil). Provide a rasterizer to print it as an image, or remove the unsupported characters. See https://github.com/octalpixel/chittie — non-Latin scripts must be rastered.`);
|
|
44
|
+
const img = padTo8(options.rasterizer.rasterize(text, options.raster ?? {}));
|
|
45
|
+
encoder.image(img, img.width, img.height);
|
|
46
|
+
}
|
|
47
|
+
function blank(template, width, height) {
|
|
48
|
+
const Ctor = template.constructor;
|
|
49
|
+
return new Ctor(new Uint8ClampedArray(width * height * 4).fill(255), width, height);
|
|
50
|
+
}
|
|
51
|
+
function blit(dst, src, dx, dy) {
|
|
52
|
+
for (let y = 0; y < src.height; y++) {
|
|
53
|
+
const drow = ((dy + y) * dst.width + dx) * 4;
|
|
54
|
+
dst.data.set(src.data.subarray(y * src.width * 4, (y + 1) * src.width * 4), drow);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Render a two-column row (left flush-left, right flush-right) to one
|
|
59
|
+
* printer-width image — the correct way to print non-Latin text inside a
|
|
60
|
+
* table column, since a per-cell image can't sit in a text column. chittie
|
|
61
|
+
* computes the layout; the injected rasterizer shapes each fragment.
|
|
62
|
+
*/
|
|
63
|
+
function rasterizeRow(rasterizer, left, right, options) {
|
|
64
|
+
const { dotWidth, ...raster } = options;
|
|
65
|
+
const l = left ? rasterizer.rasterize(left, raster) : void 0;
|
|
66
|
+
const r = right ? rasterizer.rasterize(right, raster) : void 0;
|
|
67
|
+
const template = l ?? r;
|
|
68
|
+
if (!template) return padTo8(blankImage(8, 8));
|
|
69
|
+
const height = Math.max(l?.height ?? 0, r?.height ?? 0) || 1;
|
|
70
|
+
const width = Math.max(dotWidth, (l?.width ?? 0) + (r?.width ?? 0));
|
|
71
|
+
const out = blank(template, width, height);
|
|
72
|
+
if (l) blit(out, l, 0, 0);
|
|
73
|
+
if (r) blit(out, r, width - r.width, 0);
|
|
74
|
+
return padTo8(out);
|
|
75
|
+
}
|
|
76
|
+
function blankImage(w, h) {
|
|
77
|
+
const Ctor = globalThis.ImageData;
|
|
78
|
+
if (!Ctor) throw new Error("chittie-text: no ImageData available for an empty row");
|
|
79
|
+
return new Ctor(new Uint8ClampedArray(w * h * 4).fill(255), w, h);
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { needsRaster, padTo8, rasterizeRow, smartText };
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@angadie/chittie-text",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Smart text for chittie: print code-page text when representable, auto-rasterize complex scripts (Sinhala/Tamil/…) via an injected rasterizer, never silently print '?'. Platform-neutral.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "./dist/index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@angadie/chittie-codepage": "0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@canvas/image-data": "^1.1.0",
|
|
22
|
+
"@angadie/chittie-core": "0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsdown",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"test": "tsx spikes/text.spike.ts"
|
|
31
|
+
}
|
|
32
|
+
}
|