libniimai 0.2.0 → 0.3.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
@@ -1,6 +1,6 @@
1
1
  # libniimai
2
2
 
3
- Print from the browser on four families of thermal printers: **NIIMBOT**, **TSPL label printers**, **ESC/POS mini printers** and **cat printers**. One API connects, detects the printer and prints any canvas, over Web Bluetooth or Web Serial. No server, no drivers, no app.
3
+ Print from the browser on four families of thermal printers: **NIIMBOT**, **TSPL label printers**, **ESC/POS receipt printers** and **cat printers**. One API connects, detects the printer and prints any canvas, over Web Bluetooth or Web Serial. No server, no drivers, no app.
4
4
 
5
5
  libniimai powers [niimai](https://niimai.app), the label designer.
6
6
 
@@ -9,11 +9,11 @@ libniimai powers [niimai](https://niimai.app), the label designer.
9
9
  | Family | Paper | Print width | Connection | Models |
10
10
  |---|---|---|---|---|
11
11
  | **NIIMBOT** | Labels with gaps, black mark, continuous, transparent | 12 mm and up, by model | Bluetooth, USB | D11, D110, D101, B1, B18, B21, B3S and more |
12
- | **TSPL label printers** | Labels with gaps, black mark, continuous | Up to 80 mm | Bluetooth | QR380A (sold as CC390) |
13
- | **ESC/POS mini printers** | 58 mm continuous | 48 mm | Bluetooth | MP583, MPT-II, PT-210, RPP02, ZJ-58 and more |
14
- | **Cat printers** | 58 mm continuous | 48 mm | Bluetooth | GB01, GB02, GB03, MX05, MX06, MX10 and more |
12
+ | **TSPL label printers** | Labels with gaps, black mark, continuous | Up to 80 mm | Bluetooth | QR380A (sold as CC390), Gprinter GP-M322 |
13
+ | **ESC/POS receipt printers** | 58 or 80 mm, continuous or stickers with gaps | 48 or 72 mm | Bluetooth | MP583, MPT-II, Xprinter XP-58IIL, PT-210, RPP02, MPT-3, RPP300 and more |
14
+ | **Cat printers** | 58 mm continuous, stickers with gaps | 48 mm | Bluetooth | GB01, GB02, GB03, MX05, MX06, MX10 and more |
15
15
 
16
- The family is found from the device the user picks: by its Bluetooth name, then checked against the services it offers. Every family gets the same treatment:
16
+ The family is found from the device the user picks: by its Bluetooth name in `PRINTER_CATALOG`, then checked against the services it offers. A name that matches no family is treated as a NIIMBOT, unless a type from `PRINTER_TYPES` is set for unrecognised printers. Every family gets the same treatment:
17
17
 
18
18
  - **Detection**: the right client and settings, picked for you.
19
19
  - **Page handling**: each page is cropped to the head, rotated where the printer needs it, and encoded in the printer's own format.
@@ -29,13 +29,13 @@ Label printers in many widths, from 12 mm pocket models up. Protocol by [niimblu
29
29
 
30
30
  Desktop label printers up to 80 mm wide that take TSC's TSPL label language. The printer finds each label's edge itself, and confirms each job. When a job goes unconfirmed, libniimai asks the printer why, and an open cover, jam or empty roll comes back as an error. Common 2- to 3-inch label sizes are listed in `profile.sizes`, 3 × 4 inch included. These printers cannot sense the roll, so `status.label.barcode` gives a stable key to remember its size per printer.
31
31
 
32
- ### ESC/POS mini printers
32
+ ### ESC/POS receipt printers
33
33
 
34
- Pocket receipt printers on 58 mm paper with a 384-dot head. Pages print as one raster image at any length, for receipts, lists and long labels.
34
+ Receipt printers on 58 mm paper with a 384-dot head, and 80 mm models with a 576-dot head. Pages print as one raster image at any length, for receipts, lists and long labels. `feed(mm)` lines up the paper; these printers have no reverse feed. For sticker rolls, `gapMm` keeps each print on its sticker.
35
35
 
36
36
  ### Cat printers
37
37
 
38
- The small "cat" printers sold under many names, on 58 mm paper with a 384-dot head. Five darkness levels, flow control so long prints never overrun the printer, and the newer GB03 and MX-series variants handled per model.
38
+ The small "cat" printers sold under many names, on 58 mm paper with a 384-dot head. Five darkness levels, flow control so long prints never overrun the printer, and the newer GB03 and MX-series variants handled per model. `feed(mm)` moves the paper either way to line it up, and `gapMm` keeps prints on sticker rolls.
39
39
 
40
40
  Each model is marked by how well it is tested in `TSPL_MODELS`, `ESCPOS_MODELS` and `CAT_MODELS`; see also [thermal-printers.md](https://github.com/theanam/niimai/blob/main/docs/thermal-printers.md).
41
41
 
@@ -95,7 +95,8 @@ Returns a `PrinterService`, which owns one printer connection.
95
95
  | `print(source, options)` | Prints a canvas, or a `{ count, page(i) }` source for a batch of different labels. |
96
96
  | `getStatus()` | The current `PrinterStatus`. |
97
97
  | `subscribe(fn)` | Calls `fn` on every status change. Returns a function that unsubscribes. |
98
- | `setManualProfile(id)` | Picks a printer type from `MANUAL_PROFILES` for NIIMBOT models niimbluelib does not know yet. |
98
+ | `feed(mm)` | Moves the paper by `mm` to line it up, back into the printer when negative. For printers that cannot sense the paper, where `status.canFeed` is true. |
99
+ | `setManualProfile(id)` | Picks a printer type for a printer that is not recognised: a NIIMBOT model from `MANUAL_PROFILES`, or a receipt, label or cat printer from `PRINTER_TYPES`. With one of those, the Bluetooth chooser lists every device. |
99
100
 
100
101
  **Print options**
101
102
 
@@ -105,6 +106,7 @@ Returns a `PrinterService`, which owns one printer connection.
105
106
  | `copies` | Copies of each page. |
106
107
  | `labelType` | Paper type, from `PAPER_TYPES`: 1 labels with gaps, 2 black mark, 3 continuous. |
107
108
  | `offset` | Nudges the print, in mm: `{ x, y }`. |
109
+ | `gapMm` | Stickers with gaps on printers that cannot sense them (`status.canFeed`): each label is followed by exactly this much feed, in place of the tear-off feed. |
108
110
 
109
111
  **`PrinterStatus`**
110
112
 
@@ -116,6 +118,7 @@ Returns a `PrinterService`, which owns one printer connection.
116
118
  | `label` | The loaded roll, where the printer reports one. |
117
119
  | `battery` | Battery level, where the printer reports one. |
118
120
  | `progress` | `{ page, total, sending }`; `sending` is 0–100 for the page going over the link. |
121
+ | `canFeed` | True when `feed(mm)` can move the paper: ESC/POS and cat printers. |
119
122
 
120
123
  ### Your own clients
121
124
 
@@ -154,8 +157,8 @@ Every piece is exported, for building your own flow:
154
157
 
155
158
  - `createClient(kind)` returns a raw client for one connection.
156
159
  - `TsplPrinterClient`, `EscPosPrinterClient` and `CatPrinterClient` take any `ThermalTransport`, so they also work over other links.
157
- - `tsplJob`, `escposJob` and `catJob` build the bytes for one page.
158
- - `tsplModelFor`, `escposModelFor` and `catModelFor` match a Bluetooth name to a known model.
160
+ - `tsplJob`, `escposJob` and `catJob` build the bytes for one page; `escposFeed` and `catFeed` the bytes that move the paper.
161
+ - `identifyPrinter(name)` matches a Bluetooth name to a known model across every family, from `PRINTER_CATALOG`; `tsplModelFor`, `escposModelFor` and `catModelFor` look in one family.
159
162
  - `unsupportedReason(kind)` says in a few words why a connection type cannot be used in the current browser.
160
163
 
161
164
  ## Browsers
package/dist/ble.d.ts CHANGED
@@ -4,8 +4,8 @@ type Lib = typeof import('@mmote/niimbluelib');
4
4
  export declare const CANDIDATE_SERVICES: string[];
5
5
  /** Name prefixes of NIIMBOT printers; the chooser lists only these and known cat printers. */
6
6
  export declare const NAME_PREFIXES: string[];
7
- /** Opens the browser's printer chooser. */
8
- export declare function requestPrinter(extraPrefixes?: string[]): Promise<BluetoothDevice>;
7
+ /** Opens the browser's printer chooser. `all` lists every device, for printers whose names niimai does not know. */
8
+ export declare function requestPrinter(extraPrefixes?: string[], all?: boolean): Promise<BluetoothDevice>;
9
9
  interface CharLike {
10
10
  uuid: string;
11
11
  properties: {
@@ -3,16 +3,20 @@ type Lib = typeof import('@mmote/niimbluelib');
3
3
  /**
4
4
  * The Bluetooth printer behind the Connect button. One chooser lists NIIMBOT
5
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.
6
+ * AE30 gets the cat client; a known ESC/POS printer the ESC/POS client, over
7
+ * 18F0, the shared E781 service (as Xprinters use), or any serial service
8
+ * with a writable characteristic; a known TSPL label printer offering 18F0
9
+ * the TSPL client; and everything else the NIIMBOT client exactly as before.
10
+ * Known names never overlap, so each printer matches one client at most.
10
11
  */
11
12
  export declare class BluetoothPrinter implements ClientLike {
12
13
  private lib;
13
14
  private inner;
14
15
  constructor(lib: () => Promise<Lib>);
15
16
  private get client();
17
+ /** The kind of printer to treat an unrecognised one as; null leaves it to NIIMBOT. */
18
+ private fallback;
19
+ unrecognisedAs(typeId: string | null): void;
16
20
  connect(): Promise<{
17
21
  deviceName?: string;
18
22
  }>;
@@ -53,5 +57,7 @@ export declare class BluetoothPrinter implements ClientLike {
53
57
  };
54
58
  get encodePage(): ((canvas: HTMLCanvasElement) => unknown) | undefined;
55
59
  get rollKey(): (() => string) | undefined;
60
+ get feedPaper(): ((dots: number) => Promise<void>) | undefined;
61
+ get feedsBack(): boolean | undefined;
56
62
  }
57
63
  export {};
package/dist/cat.d.ts CHANGED
@@ -36,12 +36,19 @@ export declare const FLOW_PAUSE: number[];
36
36
  export declare const FLOW_RESUME: number[];
37
37
  /** Rows for a cat printer: leftmost dot in the lowest bit. */
38
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 }: {
39
+ /**
40
+ * Everything sent for one job: setup, the rows for each copy, then feed and
41
+ * finish. With `gapDots`, for labels with gaps, every label is followed by
42
+ * exactly that feed, so the next print starts on the next label.
43
+ */
44
+ export declare function printJob(rows: Uint8Array[], { density, copies, model, gapDots }: {
41
45
  density: number;
42
46
  copies: number;
43
47
  model: CatModel | null;
48
+ gapDots?: number;
44
49
  }): Uint8Array;
50
+ /** Moves the paper by `dots`, back when negative. Models that mishandle the feed command get blank rows. */
51
+ export declare function catFeed(dots: number, model: CatModel | null): Uint8Array;
45
52
  /** A cat printer: the shared thermal client with cat packets and flow control. */
46
53
  export declare class CatPrinterClient extends ThermalPrinterClient {
47
54
  readonly model: CatModel | null;
@@ -49,5 +56,8 @@ export declare class CatPrinterClient extends ThermalPrinterClient {
49
56
  protected readonly modelName: string;
50
57
  constructor(transport: ThermalTransport, model: CatModel | null, pace?: Pace);
51
58
  protected encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array<ArrayBufferLike>[];
52
- protected job(rows: Uint8Array[], density: number, copies: number): Uint8Array<ArrayBufferLike>;
59
+ protected job(rows: Uint8Array[], density: number, copies: number, _labelType: number, gapDots?: number): Uint8Array<ArrayBufferLike>;
60
+ readonly feedsBack = true;
61
+ /** These printers cannot sense the paper, so the user lines it up by hand. */
62
+ feedPaper(dots: number): Promise<void>;
53
63
  }
@@ -0,0 +1,17 @@
1
+ import { type ThermalModel } from './thermal.js';
2
+ export type PrinterFamily = 'escpos' | 'tspl' | 'cat';
3
+ export interface CatalogEntry {
4
+ family: PrinterFamily;
5
+ model: ThermalModel;
6
+ }
7
+ export declare const PRINTER_CATALOG: CatalogEntry[];
8
+ /** The known printer a Bluetooth name belongs to, longest prefix first across every family. */
9
+ export declare function identifyPrinter(name: string | undefined): CatalogEntry | null;
10
+ /** A kind of printer to pick for one niimai does not recognise. */
11
+ export interface PrinterType extends CatalogEntry {
12
+ id: string;
13
+ name: string;
14
+ }
15
+ /** The kinds of printer an unrecognised one can be set to, alongside NIIMBOT's MANUAL_PROFILES. */
16
+ export declare const PRINTER_TYPES: PrinterType[];
17
+ export declare const findPrinterType: (id: string | null | undefined) => PrinterType | null;
package/dist/client.d.ts CHANGED
@@ -56,6 +56,16 @@ export interface ClientLike {
56
56
  * size the user sets, per printer. NIIMBOT printers read their rolls instead.
57
57
  */
58
58
  rollKey?(): string;
59
+ /** Printers that cannot sense the paper: moves it by `dots`, back into the printer when negative. */
60
+ feedPaper?(dots: number): Promise<void>;
61
+ /** feedPaper can also move the paper back. */
62
+ feedsBack?: boolean;
63
+ /**
64
+ * Bluetooth: the kind of printer to treat one with an unknown name as, set
65
+ * before connecting: an id from PRINTER_TYPES, or a NIIMBOT profile id the
66
+ * client ignores.
67
+ */
68
+ unrecognisedAs?(typeId: string | null): void;
59
69
  }
60
70
  export type ConnectionKind = 'bluetooth' | 'serial' | 'demo';
61
71
  export declare const supportsBluetooth: () => boolean;
package/dist/escpos.d.ts CHANGED
@@ -6,17 +6,35 @@
6
6
  import { ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal.js';
7
7
  export declare const ESCPOS_SERVICE = "000018f0-0000-1000-8000-00805f9b34fb";
8
8
  export declare const ESCPOS_WRITE = "00002af1-0000-1000-8000-00805f9b34fb";
9
+ /** The serial service many budget printers offer, Xprinter among them; NIIMBOT printers use it too. */
10
+ export declare const ESCPOS_ALT_SERVICE = "e7810a71-73ae-499d-8c15-faa9aef0c3f2";
11
+ export declare const ESCPOS_ALT_WRITE = "bef8d6c9-9c21-4c9e-b632-bd58c1009f9f";
9
12
  /**
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
+ * Models known to take ESC/POS raster images, by Bluetooth name. "tested":
14
+ * printed with niimai on real hardware. "high": supported by several
15
+ * open-source printer projects. "medium": the name is reported by one or two
16
+ * of them, untested here. Names from receipt-printer projects such as
17
+ * WebBluetoothReceiptPrinter and bitbank2/Thermal_Printer; see
18
+ * docs/thermal-printers.md for sources. Most of these printers share one
19
+ * Bluetooth module, so its services tell nothing apart: the name decides.
13
20
  */
14
21
  export declare const ESCPOS_MODELS: ThermalModel[];
15
22
  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;
23
+ /** Rows for an ESC/POS printer: leftmost dot in the highest bit, centred on a head `head` dots wide. */
24
+ export declare const encodeRows: (data: ArrayLike<number>, width: number, height: number, head?: number) => Uint8Array[];
25
+ /**
26
+ * ESC @, then each copy as GS v 0 bands with a gap between, then a feed past
27
+ * the tear bar. With `gapDots`, for labels with gaps, every label is followed
28
+ * by exactly that feed, so the next print starts on the next label.
29
+ */
30
+ export declare function escposJob(rows: Uint8Array[], copies: number, gapDots?: number): Uint8Array;
31
+ /**
32
+ * Moves the paper forward by `dots`: line feeds with the line spacing set to
33
+ * the distance (ESC 3 n, LF), then the default spacing back (ESC 2). The
34
+ * MP583 ignores ESC J, the usual dot feed, but takes line feeds. These
35
+ * printers have no reverse feed.
36
+ */
37
+ export declare function escposFeed(dots: number): Uint8Array;
20
38
  /**
21
39
  * An ESC/POS printer: the shared thermal client with raster commands. These
22
40
  * printers send no flow control, so writes are paced. Heat settings vary by
@@ -26,7 +44,10 @@ export declare class EscPosPrinterClient extends ThermalPrinterClient {
26
44
  readonly model: ThermalModel | null;
27
45
  protected readonly densityLevels = 1;
28
46
  protected readonly modelName: string;
47
+ protected readonly headDots: number;
29
48
  constructor(transport: ThermalTransport, model: ThermalModel | null, pace?: Pace);
30
49
  protected encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array<ArrayBufferLike>[];
31
- protected job(rows: Uint8Array[], _density: number, copies: number): Uint8Array<ArrayBufferLike>;
50
+ protected job(rows: Uint8Array[], _density: number, copies: number, _labelType: number, gapDots?: number): Uint8Array<ArrayBufferLike>;
51
+ /** These printers cannot sense the paper, so the user lines it up by hand. */
52
+ feedPaper(dots: number): Promise<void>;
32
53
  }
package/dist/index.d.ts CHANGED
@@ -25,12 +25,14 @@ export { correctTask, findManualProfile, inkOutsideHead, MANUAL_PROFILES, printh
25
25
  export type { LabelSize, PrintDirection, PrinterProfile } from './profiles.js';
26
26
  export { effectivePaper, labelTypeOf, PAPER_TYPES, paperFromLabelType, paperName } from './paper.js';
27
27
  export type { PaperType } from './paper.js';
28
+ export { findPrinterType, identifyPrinter, PRINTER_CATALOG, PRINTER_TYPES } from './catalog.js';
29
+ export type { CatalogEntry, PrinterFamily, PrinterType } from './catalog.js';
28
30
  export { BluetoothPrinter } from './bluetooth.js';
29
31
  export { describeBleError } from './ble.js';
30
32
  export { DEMO_META, MockClient } from './mock.js';
31
33
  export { modelFor, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, ThermalPrinterClient, webTransport } from './thermal.js';
32
34
  export type { Confidence, Pace, ThermalModel, ThermalTransport } from './thermal.js';
33
- export { CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, catModelFor, printJob as catJob } from './cat.js';
35
+ export { CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, catFeed, catModelFor, printJob as catJob } from './cat.js';
34
36
  export type { CatModel } from './cat.js';
35
- export { ESCPOS_MODELS, EscPosPrinterClient, escposJob, escposModelFor } from './escpos.js';
37
+ export { ESCPOS_MODELS, EscPosPrinterClient, escposFeed, escposJob, escposModelFor } from './escpos.js';
36
38
  export { TSPL_MODELS, TSPL_SIZES, TsplPrinterClient, tsplJob, tsplModelFor } from './tspl.js';