libniimai 0.0.0-stage → 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anam Ahmed
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 CHANGED
@@ -1,3 +1,88 @@
1
- # Temporary Holding Version
1
+ # libniimai
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Print to NIIMBOT label printers and other Bluetooth thermal printers from the browser. libniimai is the printer layer of [niimai](https://niimai.app), the label designer, published on its own.
4
+
5
+ - **One connect call** for NIIMBOT, TSPL label printers, ESC/POS mini printers and cat printers. The printer family is found from the device the user picks.
6
+ - **Print any canvas.** Pages are cropped to the printhead, rotated and encoded for the printer they go to.
7
+ - **Live status**: battery, the loaded roll on NIIMBOT printers, and print progress.
8
+ - **No server.** Everything runs in the page over Web Bluetooth or Web Serial.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm i libniimai
14
+ ```
15
+
16
+ ## Print a label
17
+
18
+ ```ts
19
+ import { createPrinter } from 'libniimai';
20
+
21
+ const printer = createPrinter();
22
+ printer.subscribe((status) => console.log(status.state, status.deviceName, status.progress));
23
+
24
+ // Must run from a user gesture, such as a click.
25
+ button.onclick = async () => {
26
+ await printer.connect('bluetooth');
27
+
28
+ // A 40 × 12 mm label at 203 dpi: 8 dots per mm.
29
+ const canvas = document.createElement('canvas');
30
+ canvas.width = 320;
31
+ canvas.height = 96;
32
+ const ctx = canvas.getContext('2d')!;
33
+ ctx.fillStyle = '#fff';
34
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
35
+ ctx.fillStyle = '#000';
36
+ ctx.font = 'bold 48px sans-serif';
37
+ ctx.fillText('Hello', 20, 66);
38
+
39
+ await printer.print(canvas, { density: 3, copies: 1 });
40
+ };
41
+ ```
42
+
43
+ Draw black on white at the printer's resolution, `status.profile.dpi`. Thermal printers print each dot black or white, so dither photos and greys first.
44
+
45
+ ## API
46
+
47
+ ### `createPrinter()`
48
+
49
+ Returns a `PrinterService`, which owns one printer connection.
50
+
51
+ | Member | What it does |
52
+ | --- | --- |
53
+ | `connect(kind)` | Opens the browser's device picker and connects. `kind` is `'bluetooth'`, `'serial'` (NIIMBOT over USB) or `'demo'` (a printer in memory). |
54
+ | `disconnect()` | Closes the connection. |
55
+ | `print(source, options)` | Prints a canvas, or a `{ count, page(i) }` source for a batch of different labels. Options: `density`, `copies`, `labelType` (paper type, see `PAPER_TYPES`) and `offset` in mm. |
56
+ | `getStatus()` | The current `PrinterStatus`. |
57
+ | `subscribe(fn)` | Calls `fn` on every status change. Returns a function that unsubscribes. |
58
+ | `setManualProfile(id)` | Picks a printer type from `MANUAL_PROFILES` for NIIMBOT models niimbluelib does not recognise yet. |
59
+
60
+ `PrinterStatus` holds `state` (`disconnected`, `connecting`, `ready` or `printing`), `deviceName`, `battery`, `profile` (dpi, printhead width, density range), `label` (the loaded roll) and `progress`.
61
+
62
+ ### Lower level
63
+
64
+ The protocol pieces are exported too, for building your own flow:
65
+
66
+ - `createClient(kind)` returns a raw client for one connection.
67
+ - `TsplPrinterClient`, `EscPosPrinterClient`, `CatPrinterClient` and `ThermalPrinterClient` take any `ThermalTransport`.
68
+ - `tsplJob`, `escposJob` and `catJob` build the bytes for one page.
69
+ - `TSPL_MODELS`, `ESCPOS_MODELS` and `CAT_MODELS` list the known models, with `tsplModelFor`, `escposModelFor` and `catModelFor` to match a Bluetooth name.
70
+ - `unsupportedReason(kind)` says in a few words why a connection type cannot be used in the current browser.
71
+
72
+ ## Printers
73
+
74
+ NIIMBOT printers go through [niimbluelib](https://github.com/MultiMote/niimbluelib), which loads only when one connects. The other families and their models are listed under [Supported printers](https://github.com/theanam/niimai#supported-printers), with how well each model is tested in [thermal-printers.md](https://github.com/theanam/niimai/blob/main/docs/thermal-printers.md).
75
+
76
+ ## Browsers
77
+
78
+ Needs Web Bluetooth or Web Serial: Chrome or Edge on desktop, or Chrome on Android. Connecting has to start from a user gesture.
79
+
80
+ ## Feedback
81
+
82
+ Found a bug or a printer that should work? [Open an issue](https://github.com/theanam/niimai/issues). For anything else, write to [anam.ahmed.a@gmail.com](mailto:anam.ahmed.a@gmail.com).
83
+
84
+ libniimai is an independent project and is not affiliated with NIIMBOT.
85
+
86
+ ## License
87
+
88
+ [MIT](LICENSE)
package/dist/ble.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import type { NiimbotAbstractClient } from '@mmote/niimbluelib';
2
+ type Lib = typeof import('@mmote/niimbluelib');
3
+ /** The standard NIIMBOT service, followed by serial-over-BLE services common on budget printers. */
4
+ export declare const CANDIDATE_SERVICES: string[];
5
+ /** Name prefixes of NIIMBOT printers; the chooser lists only these and known cat printers. */
6
+ export declare const NAME_PREFIXES: string[];
7
+ /** Opens the browser's printer chooser. */
8
+ export declare function requestPrinter(extraPrefixes?: string[]): Promise<BluetoothDevice>;
9
+ interface CharLike {
10
+ uuid: string;
11
+ properties: {
12
+ notify: boolean;
13
+ indicate?: boolean;
14
+ write: boolean;
15
+ writeWithoutResponse: boolean;
16
+ };
17
+ }
18
+ export interface ServiceLike<C extends CharLike = CharLike> {
19
+ uuid: string;
20
+ getCharacteristics(): Promise<C[]>;
21
+ }
22
+ export interface Channel<C extends CharLike = CharLike> {
23
+ write: C;
24
+ notify: C;
25
+ }
26
+ /**
27
+ * Picks the characteristics to talk through. A single characteristic that both
28
+ * notifies and accepts writes wins (the NIIMBOT layout); otherwise a write and
29
+ * a notify characteristic from the same service are paired.
30
+ */
31
+ export declare function pickChannel<C extends CharLike>(services: ServiceLike<C>[]): Promise<Channel<C> | null>;
32
+ /** Turns Chrome's terse GATT errors into something a person can act on. */
33
+ export declare function describeBleError(err: unknown, found?: string[]): Error;
34
+ /**
35
+ * A Web Bluetooth client that accepts more service layouts than niimbluelib's
36
+ * own, which only knows the standard NIIMBOT service. The protocol handling
37
+ * (packets, negotiation, heartbeat) is inherited unchanged.
38
+ */
39
+ export declare function createBleClient(lib: Lib, chosen?: BluetoothDevice): NiimbotAbstractClient;
40
+ export {};
@@ -0,0 +1,57 @@
1
+ import type { ClientLike } from './client.js';
2
+ type Lib = typeof import('@mmote/niimbluelib');
3
+ /**
4
+ * The Bluetooth printer behind the Connect button. One chooser lists NIIMBOT
5
+ * printers and the known extra thermal printers. A known cat printer offering
6
+ * AE30 gets the cat client, a known ESC/POS printer offering 18F0 the ESC/POS
7
+ * client, a known TSPL label printer offering 18F0 the TSPL client, and
8
+ * everything else the NIIMBOT client exactly as before. Known names never
9
+ * overlap, so each printer matches one client at most.
10
+ */
11
+ export declare class BluetoothPrinter implements ClientLike {
12
+ private lib;
13
+ private inner;
14
+ constructor(lib: () => Promise<Lib>);
15
+ private get client();
16
+ connect(): Promise<{
17
+ deviceName?: string;
18
+ }>;
19
+ disconnect(): Promise<void>;
20
+ isConnected(): boolean;
21
+ on(event: string, fn: (e: any) => void): unknown;
22
+ off(event: string, fn: (e: any) => void): unknown;
23
+ getPrinterInfo(): {
24
+ batteryPercents?: number;
25
+ modelId?: number;
26
+ printheadWidth?: number;
27
+ protocolVersion?: number;
28
+ };
29
+ getModelMetadata(): import("@mmote/niimbluelib").PrinterModelMeta | undefined;
30
+ getPrintTaskType(): "B1" | "B21_L2B" | "B21_V1" | "D110" | "D110M_V4" | "D11_V1" | "H1S" | undefined;
31
+ getRfidInfo(): {
32
+ labelRfidInfo?: {
33
+ tagPresent?: boolean;
34
+ barCode?: string;
35
+ allPaper?: number;
36
+ usedPaper?: number;
37
+ consumablesType?: number;
38
+ };
39
+ paperInfo?: {
40
+ valid?: boolean;
41
+ paperWidth?: number;
42
+ paperHeight?: number;
43
+ paperType?: number;
44
+ };
45
+ };
46
+ fetchRfidInfo(): Promise<unknown>;
47
+ startHeartbeat(): void;
48
+ stopHeartbeat(): void;
49
+ isHeartbeatStarted(): boolean;
50
+ get protocol(): {
51
+ newPrintTask(name: import("@mmote/niimbluelib").PrintTaskName, options: Record<string, unknown>): import("./client.js").PrintTaskLike;
52
+ printEnd(): Promise<boolean>;
53
+ };
54
+ get encodePage(): ((canvas: HTMLCanvasElement) => unknown) | undefined;
55
+ get rollKey(): (() => string) | undefined;
56
+ }
57
+ export {};
package/dist/cat.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * "Cat printers": the small Bluetooth thermal printers sold as GB01, MX06 and
3
+ * the like. See thermal.ts for what they share with other extras.
4
+ *
5
+ * Protocol, from rbaron/catprinter and NaitLee/Cat-Printer: packets are
6
+ * 51 78 <cmd> 00 <len> 00 <payload> <crc8(payload)> FF over service AE30,
7
+ * written to AE01. The printer sends flow control on AE02.
8
+ */
9
+ import { ThermalPrinterClient, type Confidence, type Pace, type ThermalModel, type ThermalTransport } from './thermal.js';
10
+ export declare const CAT_SERVICE = "0000ae30-0000-1000-8000-00805f9b34fb";
11
+ export declare const CAT_TX = "0000ae01-0000-1000-8000-00805f9b34fb";
12
+ export declare const CAT_RX = "0000ae02-0000-1000-8000-00805f9b34fb";
13
+ export type { Confidence };
14
+ export interface CatModel extends ThermalModel {
15
+ /** Takes the newer "start printing" command. */
16
+ newKind?: boolean;
17
+ /** Mishandles the feed command, so the paper is fed with blank rows. */
18
+ feedWithRows?: boolean;
19
+ }
20
+ /**
21
+ * Models known to speak this protocol. "high": supported by both reference
22
+ * projects and widely used. "medium": listed by Cat-Printer, fewer reports.
23
+ * "tested": printed with niimai on real hardware.
24
+ */
25
+ export declare const CAT_MODELS: CatModel[];
26
+ /** Look-alikes that use other protocols. Listed in Help and docs/thermal-printers.md. */
27
+ export declare const CAT_UNSUPPORTED: {
28
+ name: string;
29
+ note: string;
30
+ }[];
31
+ export declare const catModelFor: (name: string | undefined) => CatModel | null;
32
+ export declare const crc8: (data: ArrayLike<number>) => number;
33
+ export declare function packet(cmd: number, payload: ArrayLike<number>): number[];
34
+ /** Flow control the printer sends on AE02 when its buffer fills and drains. */
35
+ export declare const FLOW_PAUSE: number[];
36
+ export declare const FLOW_RESUME: number[];
37
+ /** Rows for a cat printer: leftmost dot in the lowest bit. */
38
+ export declare const encodeRows: (data: ArrayLike<number>, width: number, height: number) => Uint8Array[];
39
+ /** Everything sent for one job: setup, the rows for each copy, then feed and finish. */
40
+ export declare function printJob(rows: Uint8Array[], { density, copies, model }: {
41
+ density: number;
42
+ copies: number;
43
+ model: CatModel | null;
44
+ }): Uint8Array;
45
+ /** A cat printer: the shared thermal client with cat packets and flow control. */
46
+ export declare class CatPrinterClient extends ThermalPrinterClient {
47
+ readonly model: CatModel | null;
48
+ protected readonly densityLevels: number;
49
+ protected readonly modelName: string;
50
+ constructor(transport: ThermalTransport, model: CatModel | null, pace?: Pace);
51
+ protected encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array<ArrayBufferLike>[];
52
+ protected job(rows: Uint8Array[], density: number, copies: number): Uint8Array<ArrayBufferLike>;
53
+ }
@@ -0,0 +1,70 @@
1
+ import type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';
2
+ /**
3
+ * The slice of niimbluelib's client the app uses. Keeping it narrow lets the
4
+ * mock printer and the tests stand in for real hardware.
5
+ */
6
+ export interface PrintTaskLike {
7
+ printInit(): Promise<void>;
8
+ printPage(image: unknown, quantity?: number): Promise<void>;
9
+ waitForFinished(): Promise<void>;
10
+ printEnd(): Promise<boolean>;
11
+ }
12
+ export interface ClientLike {
13
+ connect(): Promise<{
14
+ deviceName?: string;
15
+ }>;
16
+ disconnect(): Promise<void>;
17
+ isConnected(): boolean;
18
+ on(event: string, fn: (e: any) => void): unknown;
19
+ off(event: string, fn: (e: any) => void): unknown;
20
+ getPrinterInfo(): {
21
+ batteryPercents?: number;
22
+ modelId?: number;
23
+ printheadWidth?: number;
24
+ protocolVersion?: number;
25
+ };
26
+ getModelMetadata(): PrinterModelMeta | undefined;
27
+ getPrintTaskType(): PrintTaskName | undefined;
28
+ getRfidInfo(): {
29
+ labelRfidInfo?: {
30
+ tagPresent?: boolean;
31
+ barCode?: string;
32
+ allPaper?: number;
33
+ usedPaper?: number;
34
+ consumablesType?: number;
35
+ };
36
+ paperInfo?: {
37
+ valid?: boolean;
38
+ paperWidth?: number;
39
+ paperHeight?: number;
40
+ paperType?: number;
41
+ };
42
+ };
43
+ fetchRfidInfo(): Promise<unknown>;
44
+ startHeartbeat(): void;
45
+ stopHeartbeat(): void;
46
+ isHeartbeatStarted?(): boolean;
47
+ protocol: {
48
+ newPrintTask(name: PrintTaskName, options: Record<string, unknown>): PrintTaskLike;
49
+ /** True once the printer has finished every page. */
50
+ printEnd(): Promise<boolean>;
51
+ };
52
+ /** Clients that encode pages themselves (cat printers); NIIMBOT pages go through niimbluelib's encoder. */
53
+ encodePage?(canvas: HTMLCanvasElement): unknown;
54
+ /**
55
+ * Label printers that cannot sense the roll: a key to remember the roll
56
+ * size the user sets, per printer. NIIMBOT printers read their rolls instead.
57
+ */
58
+ rollKey?(): string;
59
+ }
60
+ export type ConnectionKind = 'bluetooth' | 'serial' | 'demo';
61
+ export declare const supportsBluetooth: () => boolean;
62
+ export declare const supportsSerial: () => boolean;
63
+ export type Platform = 'ios' | 'android' | 'desktop';
64
+ export declare function detectPlatform(ua?: string, touchPoints?: number): Platform;
65
+ /**
66
+ * Why a connection type cannot be used in this browser, in a few words, or
67
+ * null when it can. iOS browsers all run on WebKit, which has neither Web
68
+ * Bluetooth nor Web Serial; Bluefy adds Web Bluetooth there.
69
+ */
70
+ export declare function unsupportedReason(kind: ConnectionKind, platform?: Platform): string | null;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ESC/POS mini printers: 58 mm Bluetooth receipt printers such as the MP583
3
+ * (YAEN MPT-II). They take standard raster images (GS v 0) over the 18F0
4
+ * printer service. See thermal.ts for what they share with other extras.
5
+ */
6
+ import { ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal.js';
7
+ export declare const ESCPOS_SERVICE = "000018f0-0000-1000-8000-00805f9b34fb";
8
+ export declare const ESCPOS_WRITE = "00002af1-0000-1000-8000-00805f9b34fb";
9
+ /**
10
+ * Models known to take ESC/POS raster images over 18F0. "tested": printed
11
+ * with niimai on real hardware. "medium": the same chipset and service are
12
+ * widely reported, untested here.
13
+ */
14
+ export declare const ESCPOS_MODELS: ThermalModel[];
15
+ export declare const escposModelFor: (name: string | undefined) => ThermalModel | null;
16
+ /** Rows for an ESC/POS printer: leftmost dot in the highest bit. */
17
+ export declare const encodeRows: (data: ArrayLike<number>, width: number, height: number) => Uint8Array[];
18
+ /** ESC @, then each copy as GS v 0 bands with a gap between, then a feed. */
19
+ export declare function escposJob(rows: Uint8Array[], copies: number): Uint8Array;
20
+ /**
21
+ * An ESC/POS printer: the shared thermal client with raster commands. These
22
+ * printers send no flow control, so writes are paced. Heat settings vary by
23
+ * vendor, so darkness is left to the printer.
24
+ */
25
+ export declare class EscPosPrinterClient extends ThermalPrinterClient {
26
+ readonly model: ThermalModel | null;
27
+ protected readonly densityLevels = 1;
28
+ protected readonly modelName: string;
29
+ constructor(transport: ThermalTransport, model: ThermalModel | null, pace?: Pace);
30
+ protected encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array<ArrayBufferLike>[];
31
+ protected job(rows: Uint8Array[], _density: number, copies: number): Uint8Array<ArrayBufferLike>;
32
+ }
@@ -0,0 +1,29 @@
1
+ /// <reference types="web-bluetooth" preserve="true" />
2
+ /// <reference types="w3c-web-serial" preserve="true" />
3
+ import type { ClientLike, ConnectionKind } from './client.js';
4
+ import { PrinterService } from './service.js';
5
+ /**
6
+ * A client for one kind of connection. Bluetooth finds the printer family
7
+ * from the device the user picks; serial is NIIMBOT only; demo is a printer
8
+ * in memory.
9
+ */
10
+ export declare function createClient(kind: ConnectionKind): Promise<ClientLike>;
11
+ /** A ready printer service: connect, watch its status, print canvases. */
12
+ export declare function createPrinter(): PrinterService;
13
+ export { PrinterService, DEMO_NAME, friendlyError, fitToPrinthead, shiftCanvas } from './service.js';
14
+ export type { ClientFactory, PageSource, PrinterState, PrinterStatus, PrintOffset, PrintOptions } from './service.js';
15
+ export { detectPlatform, supportsBluetooth, supportsSerial, unsupportedReason } from './client.js';
16
+ export type { ClientLike, ConnectionKind, Platform, PrintTaskLike } from './client.js';
17
+ export { correctTask, findManualProfile, inkOutsideHead, MANUAL_PROFILES, printheadCrop, printheadMm, resolveProfile } from './profiles.js';
18
+ export type { LabelSize, PrintDirection, PrinterProfile } from './profiles.js';
19
+ export { effectivePaper, labelTypeOf, PAPER_TYPES, paperFromLabelType, paperName } from './paper.js';
20
+ export type { PaperType } from './paper.js';
21
+ export { BluetoothPrinter } from './bluetooth.js';
22
+ export { describeBleError } from './ble.js';
23
+ export { DEMO_META, MockClient } from './mock.js';
24
+ export { modelFor, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, ThermalPrinterClient, webTransport } from './thermal.js';
25
+ export type { Confidence, Pace, ThermalModel, ThermalTransport } from './thermal.js';
26
+ export { CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, catModelFor, printJob as catJob } from './cat.js';
27
+ export type { CatModel } from './cat.js';
28
+ export { ESCPOS_MODELS, EscPosPrinterClient, escposJob, escposModelFor } from './escpos.js';
29
+ export { TSPL_MODELS, TSPL_SIZES, TsplPrinterClient, tsplJob, tsplModelFor } from './tspl.js';