libniimai 0.1.1 → 0.2.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
@@ -80,10 +80,14 @@ Draw black on white. Thermal printers print each dot black or white, so dither p
80
80
 
81
81
  ## API
82
82
 
83
- ### `createPrinter()`
83
+ ### `createPrinter(options?)`
84
84
 
85
85
  Returns a `PrinterService`, which owns one printer connection.
86
86
 
87
+ | Option | What it does |
88
+ | --- | --- |
89
+ | `createClient` | Makes the client for each connection. Defaults to `createClient`. |
90
+
87
91
  | Member | What it does |
88
92
  | --- | --- |
89
93
  | `connect(kind)` | Opens the browser's device picker and connects. `kind` is `'bluetooth'` (every family), `'serial'` (NIIMBOT over USB) or `'demo'` (a printer in memory, for trying things out). |
@@ -113,6 +117,37 @@ Returns a `PrinterService`, which owns one printer connection.
113
117
  | `battery` | Battery level, where the printer reports one. |
114
118
  | `progress` | `{ page, total, sending }`; `sending` is 0–100 for the page going over the link. |
115
119
 
120
+ ### Your own clients
121
+
122
+ Pass `createClient` to change how one kind of connection is made and keep the others. This demo prints like a 58 mm ESC/POS printer into memory, while Bluetooth and USB use the built-in clients:
123
+
124
+ ```ts
125
+ import { createClient, createPrinter, DEMO_NAME, EscPosPrinterClient } from 'libniimai';
126
+
127
+ const output: Uint8Array[] = [];
128
+ const printer = createPrinter({
129
+ createClient: (kind) =>
130
+ kind === 'demo'
131
+ ? Promise.resolve(
132
+ new EscPosPrinterClient(
133
+ {
134
+ name: DEMO_NAME,
135
+ write: async (chunk) => void output.push(chunk.slice()),
136
+ onNotify: () => {},
137
+ onDisconnect: () => {},
138
+ disconnect: async () => {},
139
+ },
140
+ null,
141
+ // No Bluetooth to pace for: send at once.
142
+ { chunkMs: 0, pauseMs: 1 },
143
+ ),
144
+ )
145
+ : createClient(kind),
146
+ });
147
+ ```
148
+
149
+ A printer connected as `'demo'` takes the stand-in's paper, head and density range, and always shows as `DEMO_NAME`. Thermal clients encode their own pages, so niimbluelib never loads for them.
150
+
116
151
  ### Lower level
117
152
 
118
153
  Every piece is exported, for building your own flow:
package/dist/index.d.ts CHANGED
@@ -1,15 +1,22 @@
1
1
  /// <reference types="web-bluetooth" preserve="true" />
2
2
  /// <reference types="w3c-web-serial" preserve="true" />
3
3
  import type { ClientLike, ConnectionKind } from './client.js';
4
- import { PrinterService } from './service.js';
4
+ import { PrinterService, type ClientFactory } from './service.js';
5
5
  /**
6
6
  * A client for one kind of connection. Bluetooth finds the printer family
7
7
  * from the device the user picks; serial is NIIMBOT only; demo is a printer
8
8
  * in memory.
9
9
  */
10
10
  export declare function createClient(kind: ConnectionKind): Promise<ClientLike>;
11
+ export interface CreatePrinterOptions {
12
+ /**
13
+ * Makes the client for each connection. Defaults to createClient; wrap it
14
+ * to change one kind of connection and keep the others.
15
+ */
16
+ createClient?: ClientFactory;
17
+ }
11
18
  /** A ready printer service: connect, watch its status, print canvases. */
12
- export declare function createPrinter(): PrinterService;
19
+ export declare function createPrinter(options?: CreatePrinterOptions): PrinterService;
13
20
  export { PrinterService, DEMO_NAME, friendlyError, fitToPrinthead, shiftCanvas } from './service.js';
14
21
  export type { ClientFactory, PageSource, PrinterState, PrinterStatus, PrintOffset, PrintOptions } from './service.js';
15
22
  export { detectPlatform, supportsBluetooth, supportsSerial, unsupportedReason } from './client.js';
package/dist/libniimai.js CHANGED
@@ -1595,8 +1595,8 @@ async function createClient(kind) {
1595
1595
  return new (await (niimbot())).NiimbotSerialClient();
1596
1596
  }
1597
1597
  /** A ready printer service: connect, watch its status, print canvases. */
1598
- function createPrinter() {
1599
- return new PrinterService(createClient, async (canvas, direction) => (await niimbot()).ImageEncoder.encodeCanvas(canvas, 0, direction));
1598
+ function createPrinter(options = {}) {
1599
+ return new PrinterService(options.createClient ?? createClient, async (canvas, direction) => (await niimbot()).ImageEncoder.encodeCanvas(canvas, 0, direction));
1600
1600
  }
1601
1601
  export { BluetoothPrinter, CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, DEMO_META, DEMO_NAME, ESCPOS_MODELS, EscPosPrinterClient, MANUAL_PROFILES, MockClient, PAPER_TYPES, PrinterService, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, TSPL_MODELS, TSPL_SIZES, ThermalPrinterClient, TsplPrinterClient, printJob as catJob, catModelFor, correctTask, createClient, createPrinter, describeBleError, detectPlatform, effectivePaper, escposJob, escposModelFor, findManualProfile, fitToPrinthead, friendlyError, inkOutsideHead, labelTypeOf, modelFor, paperFromLabelType, paperName, printheadCrop, printheadMm, resolveProfile, shiftCanvas, supportsBluetooth, supportsSerial, tsplJob, tsplModelFor, unsupportedReason, webTransport };
1602
1602
 
@@ -1 +1 @@
1
- {"version":3,"file":"libniimai.js","names":["sleep","ROW_BYTES","encodeRows","encodeRows"],"sources":["../src/ble.ts","../src/thermal.ts","../src/cat.ts","../src/escpos.ts","../src/tspl.ts","../src/bluetooth.ts","../src/mock.ts","../src/paper.ts","../src/profiles.ts","../src/service.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import type { ConnectionInfo, NiimbotAbstractClient } from '@mmote/niimbluelib';\n\ntype Lib = typeof import('@mmote/niimbluelib');\n\n/** The standard NIIMBOT service, followed by serial-over-BLE services common on budget printers. */\nexport const CANDIDATE_SERVICES = [\n 'e7810a71-73ae-499d-8c15-faa9aef0c3f2', // NIIMBOT\n '0000e0ff-3c17-d293-8e48-14fe2e4da212', // NIIMBOT B21 C2B (model id 771), channel ffe1\n '0000ae30-0000-1000-8000-00805f9b34fb', // AE30 printer UART\n '0000ff00-0000-1000-8000-00805f9b34fb',\n '0000ffe0-0000-1000-8000-00805f9b34fb', // HM-10 style\n '0000fff0-0000-1000-8000-00805f9b34fb',\n '000018f0-0000-1000-8000-00805f9b34fb', // generic printer\n '49535343-fe7d-4ae5-8fa9-9fafd205e455', // Microchip transparent UART\n '6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART\n];\n\n/** Name prefixes of NIIMBOT printers; the chooser lists only these and known cat printers. */\nexport const NAME_PREFIXES = ['A', 'B', 'C', 'D', 'E', 'F', 'H', 'J', 'K', 'M', 'N', 'P', 'S', 'T', 'Z'];\n\n/** Opens the browser's printer chooser. */\nexport function requestPrinter(extraPrefixes: string[] = []): Promise<BluetoothDevice> {\n return navigator.bluetooth.requestDevice({\n filters: [...[...NAME_PREFIXES, ...extraPrefixes].map((namePrefix) => ({ namePrefix })), { services: [CANDIDATE_SERVICES[0]] }],\n optionalServices: CANDIDATE_SERVICES,\n });\n}\n\ninterface CharLike {\n uuid: string;\n properties: { notify: boolean; indicate?: boolean; write: boolean; writeWithoutResponse: boolean };\n}\n\nexport interface ServiceLike<C extends CharLike = CharLike> {\n uuid: string;\n getCharacteristics(): Promise<C[]>;\n}\n\nexport interface Channel<C extends CharLike = CharLike> {\n write: C;\n notify: C;\n}\n\n/**\n * Picks the characteristics to talk through. A single characteristic that both\n * notifies and accepts writes wins (the NIIMBOT layout); otherwise a write and\n * a notify characteristic from the same service are paired.\n */\nexport async function pickChannel<C extends CharLike>(services: ServiceLike<C>[]): Promise<Channel<C> | null> {\n const all: { service: string; chars: C[] }[] = [];\n for (const service of services) all.push({ service: service.uuid, chars: await service.getCharacteristics() });\n\n const canWrite = (c: C) => c.properties.writeWithoutResponse || c.properties.write;\n const canNotify = (c: C) => c.properties.notify || !!c.properties.indicate;\n\n for (const { chars } of all) {\n const both = chars.find((c) => c.properties.writeWithoutResponse && c.properties.notify);\n if (both) return { write: both, notify: both };\n }\n for (const { chars } of all) {\n const write = chars.find(canWrite);\n const notify = chars.find(canNotify);\n if (write && notify) return { write, notify };\n }\n return null;\n}\n\n/** Turns Chrome's terse GATT errors into something a person can act on. */\nexport function describeBleError(err: unknown, found: string[] = []): Error {\n const msg = err instanceof Error ? err.message : String(err);\n if (/no services found/i.test(msg) || (found.length === 0 && /characteristic/i.test(msg))) {\n return new Error('This printer uses an unknown Bluetooth service. See Help → Printer not recognised.');\n }\n if (/characteristic/i.test(msg)) return new Error(`No usable channel. Services: ${found.join(', ')}`);\n return err instanceof Error ? err : new Error(msg);\n}\n\n/**\n * A Web Bluetooth client that accepts more service layouts than niimbluelib's\n * own, which only knows the standard NIIMBOT service. The protocol handling\n * (packets, negotiation, heartbeat) is inherited unchanged.\n */\nexport function createBleClient(lib: Lib, chosen?: BluetoothDevice): NiimbotAbstractClient {\n class FlexibleBleClient extends lib.NiimbotAbstractClient {\n private server?: BluetoothRemoteGATTServer;\n private channel?: Channel<BluetoothRemoteGATTCharacteristic>;\n\n async connect(): Promise<ConnectionInfo> {\n await this.disconnect();\n const device = chosen ?? (await requestPrinter());\n if (!device.gatt) throw new Error('Device has no GATT server');\n\n const onDisconnect = () => {\n this.server = undefined;\n this.channel = undefined;\n this.emit('disconnect', new lib.DisconnectEvent());\n device.removeEventListener('gattserverdisconnected', onDisconnect);\n };\n device.addEventListener('gattserverdisconnected', onDisconnect);\n\n const server = await device.gatt.connect();\n let found: string[] = [];\n try {\n const services = await server.getPrimaryServices();\n found = services.map((s) => s.uuid);\n console.info('[niimai] services', found);\n const channel = await pickChannel(services);\n if (!channel) throw new Error('Suitable characteristic not found');\n console.info('[niimai] channel', channel.write.uuid, channel.notify.uuid);\n channel.notify.addEventListener('characteristicvaluechanged', (e) => {\n const value = (e.target as BluetoothRemoteGATTCharacteristic).value;\n if (value) this.processRawPacket(value);\n });\n await channel.notify.startNotifications();\n this.server = server;\n this.channel = channel;\n } catch (err) {\n server.disconnect();\n throw describeBleError(err, found);\n }\n\n await this.negotiateAndGetPrinterInfo(() => this.disconnect());\n const result: ConnectionInfo = {\n deviceName: device.name,\n result: this.printerInfo.connectResult ?? lib.ConnectResult.FirmwareErrors,\n };\n this.emit('connect', new lib.ConnectEvent(result));\n return result;\n }\n\n isConnected(): boolean {\n return !!this.server && !!this.channel;\n }\n\n async disconnect(): Promise<void> {\n this.stopHeartbeat();\n this.server?.disconnect();\n this.server = undefined;\n this.channel = undefined;\n }\n\n async sendRaw(data: Uint8Array, force?: boolean): Promise<void> {\n const send = async () => {\n const c = this.channel?.write;\n if (!c) throw new Error('Channel is closed');\n await lib.Utils.sleep(this.packetIntervalMs);\n const buf = data.slice().buffer;\n if (c.properties.writeWithoutResponse) await c.writeValueWithoutResponse(buf);\n else await c.writeValueWithResponse(buf);\n this.emit('rawpacketsent', new lib.RawPacketSentEvent(data));\n };\n if (force) await send();\n else await this.mutex.runExclusive(send);\n }\n\n getType() {\n return 'bluetooth' as const;\n }\n }\n return new FlexibleBleClient();\n}\n","/**\n * Shared ground for the extra Bluetooth thermal printers: cat printers and\n * ESC/POS mini printers (58 mm continuous paper, 384-dot head) and TSPL\n * label printers (labels with gaps, up to 80 mm). niimai's focus is NIIMBOT;\n * these clients are kept apart so NIIMBOT printing never goes through them.\n */\nimport type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\nimport type { LabelSize } from './profiles';\nimport type { ClientLike, PrintTaskLike } from './client';\n\n/** Dots across the head: 48 mm at 203 dpi. */\nexport const THERMAL_HEAD_DOTS = 384;\n/** The print width in whole millimetres, the widest a design on continuous paper can be. */\nexport const THERMAL_HEAD_MM = 48;\n\nexport type Confidence = 'tested' | 'high' | 'medium';\n\nexport interface ThermalModel {\n /** Bluetooth name prefix. */\n prefix: string;\n confidence: Confidence;\n note?: string;\n}\n\n/** The known model a Bluetooth name belongs to, longest prefix first. */\nexport function modelFor<M extends ThermalModel>(models: M[], name: string | undefined): M | null {\n if (!name) return null;\n return [...models].sort((a, b) => b.prefix.length - a.prefix.length).find((m) => name.startsWith(m.prefix)) ?? null;\n}\n\n/** Sizes that suit 58 mm continuous paper: the full 48 mm print width at common lengths. */\nexport const THERMAL_SIZES: LabelSize[] = [15, 20, 30, 48, 80].map((heightMm) => ({ widthMm: 48, heightMm }));\nexport const THERMAL_DEFAULT_SIZE: LabelSize = { widthMm: 48, heightMm: 30 };\n\n/**\n * Packs a 1-bit page (RGBA, dark = black) into rows as wide as the head, centred across\n * the head. Cat printers want the leftmost dot in the lowest bit, ESC/POS in\n * the highest.\n */\nexport function packRows(data: ArrayLike<number>, width: number, height: number, order: 'lsb' | 'msb', head = THERMAL_HEAD_DOTS): Uint8Array[] {\n const offset = Math.max(0, Math.floor((head - width) / 2));\n const rows: Uint8Array[] = [];\n for (let y = 0; y < height; y++) {\n const row = new Uint8Array(head / 8);\n for (let x = 0; x < width && x + offset < head; x++) {\n if (data[(y * width + x) * 4] < 128) {\n const dot = x + offset;\n row[dot >> 3] |= order === 'lsb' ? 1 << (dot & 7) : 0x80 >> (dot & 7);\n }\n }\n rows.push(row);\n }\n return rows;\n}\n\n/** The Bluetooth link, kept narrow so tests can stand in for it. */\nexport interface ThermalTransport {\n name?: string;\n write(chunk: Uint8Array): Promise<void>;\n onNotify(fn: (data: Uint8Array) => void): void;\n onDisconnect(fn: () => void): void;\n disconnect(): Promise<void>;\n}\n\nexport interface Pace {\n /** Pause after each write. */\n chunkMs: number;\n /** How often to look again while the printer asks to wait. */\n pauseMs: number;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * A thermal printer behind the same interface as a NIIMBOT client, so the\n * printer service drives both. It reports itself through model metadata with\n * kind \"thermal\"; it has no roll tags, battery or heartbeat to offer.\n * Subclasses supply the row encoding and the bytes for a job.\n */\nexport abstract class ThermalPrinterClient implements ClientLike {\n private listeners = new Map<string, Set<(e: unknown) => void>>();\n private connected = false;\n /** Set while the printer asks to wait (cat printers' flow control). */\n protected paused = false;\n /** Bytes per write. Starts large and halves if the link refuses it. */\n private chunk: number;\n\n protected abstract readonly densityLevels: number;\n /** The model name shown for this printer. */\n protected abstract readonly modelName: string;\n /** Dots across the head. */\n protected readonly headDots: number = THERMAL_HEAD_DOTS;\n /** \"left\": the label runs sideways through the head, as on NIIMBOT D-series printers. */\n protected readonly printDirection: 'top' | 'left' = 'top';\n /** Continuous-paper printers report \"thermal\"; label printers report nothing and get the roll UI. */\n protected readonly kind: 'thermal' | undefined = 'thermal';\n /** Label sizes common for this printer, offered in place of NIIMBOT roll sizes. */\n protected readonly labelSizes: LabelSize[] | undefined = undefined;\n protected abstract encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array[];\n protected abstract job(rows: Uint8Array[], density: number, copies: number, labelType: number): Uint8Array;\n /** Called after each page is sent; printers that confirm a job wait here. */\n protected async afterPage(): Promise<void> {}\n\n constructor(\n protected transport: ThermalTransport,\n protected pace: Pace,\n firstChunk: number,\n ) {\n this.chunk = firstChunk;\n transport.onDisconnect(() => {\n this.connected = false;\n this.emit('disconnect', {});\n });\n }\n\n async connect() {\n this.connected = true;\n return { deviceName: this.transport.name };\n }\n\n async disconnect() {\n this.connected = false;\n await this.transport.disconnect();\n }\n\n isConnected() {\n return this.connected;\n }\n\n on(event: string, fn: (e: unknown) => void) {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\n this.listeners.get(event)!.add(fn);\n }\n\n off(event: string, fn: (e: unknown) => void) {\n this.listeners.get(event)?.delete(fn);\n }\n\n private emit(event: string, e: unknown) {\n this.listeners.get(event)?.forEach((fn) => fn(e));\n }\n\n getPrinterInfo() {\n return {};\n }\n\n getModelMetadata(): PrinterModelMeta {\n return {\n model: this.modelName,\n ...(this.kind ? { kind: this.kind } : {}),\n ...(this.labelSizes ? { labelSizes: this.labelSizes } : {}),\n dpi: 203,\n printheadPixels: this.headDots,\n printDirection: this.printDirection,\n densityMin: 1,\n densityMax: this.densityLevels,\n densityDefault: Math.ceil(this.densityLevels / 2),\n } as unknown as PrinterModelMeta;\n }\n\n getPrintTaskType(): PrintTaskName {\n // The service needs a task name; these clients print the same way whatever it is.\n return 'B1' as PrintTaskName;\n }\n\n getRfidInfo() {\n return {};\n }\n\n async fetchRfidInfo() {}\n startHeartbeat() {}\n stopHeartbeat() {}\n\n /** Pages arrive as 1-bit canvases; the service hands them here instead of to niimbluelib's encoder. */\n encodePage(canvas: HTMLCanvasElement): Uint8Array[] {\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('Canvas is unavailable');\n return this.encodeRows(ctx.getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height);\n }\n\n /** Sends bytes in chunks, waiting while the printer asks for a pause. `onSent` hears the share sent, 0–1. */\n async send(bytes: Uint8Array, onSent?: (share: number) => void): Promise<void> {\n for (let i = 0; i < bytes.length; ) {\n while (this.paused) await sleep(this.pace.pauseMs);\n const part = bytes.subarray(i, i + this.chunk);\n try {\n await this.transport.write(part);\n } catch (err) {\n if (this.chunk <= 20) throw err;\n this.chunk = Math.max(20, this.chunk >> 1);\n continue;\n }\n i += part.length;\n onSent?.(i / bytes.length);\n await sleep(this.pace.chunkMs);\n }\n }\n\n protocol = {\n printEnd: async () => true,\n newPrintTask: (_name: PrintTaskName, options: Record<string, unknown>): PrintTaskLike => {\n const density = Number(options.density ?? 1);\n const labelType = Number(options.labelType ?? 1);\n // A batch sends several pages to one task; progress counts across them.\n let printed = 0;\n return {\n printInit: async () => {},\n printPage: async (image, quantity = 1) => {\n const total = () => Math.max(printed, Number(options.totalPages ?? printed));\n // Sending is the slow part over Bluetooth, so report it as it goes, in whole percents.\n let shown = -1;\n await this.send(this.job(image as Uint8Array[], density, Math.max(1, quantity), labelType), (share) => {\n const sending = Math.floor(share * 100);\n if (sending === shown) return;\n shown = sending;\n this.emit('printprogress', { page: printed, pagesTotal: total(), pagePrintProgress: 0, pageFeedProgress: 0, sending });\n });\n await this.afterPage();\n printed += Math.max(1, quantity);\n this.emit('printprogress', { page: printed, pagesTotal: total(), pagePrintProgress: 100, pageFeedProgress: 100 });\n },\n waitForFinished: async () => {},\n printEnd: async () => true,\n };\n },\n };\n}\n\n/** A Web Bluetooth link through one service: a write characteristic and, if present, a notify one. */\nexport async function webTransport(\n device: BluetoothDevice,\n server: BluetoothRemoteGATTServer,\n ids: { service: string; write: string; notify?: string },\n): Promise<ThermalTransport> {\n const service = await server.getPrimaryService(ids.service);\n const tx = await service.getCharacteristic(ids.write);\n const notify: ((d: Uint8Array) => void)[] = [];\n if (ids.notify) {\n const rx = await service.getCharacteristic(ids.notify);\n rx.addEventListener('characteristicvaluechanged', (e) => {\n const v = (e.target as BluetoothRemoteGATTCharacteristic).value;\n if (v) notify.forEach((fn) => fn(new Uint8Array(v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength))));\n });\n await rx.startNotifications();\n }\n return {\n name: device.name,\n write: async (chunk) => {\n const buf = chunk.slice().buffer;\n if (tx.properties.writeWithoutResponse) await tx.writeValueWithoutResponse(buf);\n else await tx.writeValueWithResponse(buf);\n },\n onNotify: (fn) => notify.push(fn),\n onDisconnect: (fn) => device.addEventListener('gattserverdisconnected', fn),\n disconnect: async () => server.disconnect(),\n };\n}\n","/**\n * \"Cat printers\": the small Bluetooth thermal printers sold as GB01, MX06 and\n * the like. See thermal.ts for what they share with other extras.\n *\n * Protocol, from rbaron/catprinter and NaitLee/Cat-Printer: packets are\n * 51 78 <cmd> 00 <len> 00 <payload> <crc8(payload)> FF over service AE30,\n * written to AE01. The printer sends flow control on AE02.\n */\nimport { modelFor, packRows, THERMAL_HEAD_DOTS, ThermalPrinterClient, type Confidence, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const CAT_SERVICE = '0000ae30-0000-1000-8000-00805f9b34fb';\nexport const CAT_TX = '0000ae01-0000-1000-8000-00805f9b34fb';\nexport const CAT_RX = '0000ae02-0000-1000-8000-00805f9b34fb';\n\nconst ROW_BYTES = THERMAL_HEAD_DOTS / 8;\n\nexport type { Confidence };\n\nexport interface CatModel extends ThermalModel {\n /** Takes the newer \"start printing\" command. */\n newKind?: boolean;\n /** Mishandles the feed command, so the paper is fed with blank rows. */\n feedWithRows?: boolean;\n}\n\n/**\n * Models known to speak this protocol. \"high\": supported by both reference\n * projects and widely used. \"medium\": listed by Cat-Printer, fewer reports.\n * \"tested\": printed with niimai on real hardware.\n */\nexport const CAT_MODELS: CatModel[] = [\n { prefix: 'GB01', confidence: 'high' },\n { prefix: 'GB02', confidence: 'high' },\n { prefix: 'GB03', confidence: 'high', newKind: true },\n { prefix: 'MX05', confidence: 'high', feedWithRows: true },\n { prefix: 'MX06', confidence: 'high', feedWithRows: true },\n { prefix: 'MX10', confidence: 'high', feedWithRows: true },\n { prefix: 'MX08', confidence: 'medium', feedWithRows: true },\n { prefix: 'MX09', confidence: 'medium', feedWithRows: true },\n { prefix: 'MX11', confidence: 'medium' },\n { prefix: 'GT01', confidence: 'medium' },\n { prefix: 'YT01', confidence: 'medium' },\n { prefix: 'PD01', confidence: 'medium' },\n { prefix: 'MXTP', confidence: 'medium' },\n { prefix: 'SC03h', confidence: 'medium' },\n];\n\n/** Look-alikes that use other protocols. Listed in Help and docs/thermal-printers.md. */\nexport const CAT_UNSUPPORTED: { name: string; note: string }[] = [\n { name: 'MXW01', note: 'Newer protocol' },\n { name: 'PeriPage', note: 'Different protocol' },\n { name: 'Phomemo M02 / T02', note: 'Different protocol' },\n];\n\nexport const catModelFor = (name: string | undefined): CatModel | null => modelFor(CAT_MODELS, name);\n\n// CRC-8, polynomial 0x07, as the printers expect.\nconst CRC_TABLE = Array.from({ length: 256 }, (_, i) => {\n let c = i;\n for (let b = 0; b < 8; b++) c = c & 0x80 ? ((c << 1) ^ 0x07) & 0xff : (c << 1) & 0xff;\n return c;\n});\nexport const crc8 = (data: ArrayLike<number>): number => {\n let c = 0;\n for (let i = 0; i < data.length; i++) c = CRC_TABLE[(c ^ data[i]) & 0xff];\n return c;\n};\n\nexport function packet(cmd: number, payload: ArrayLike<number>): number[] {\n if (payload.length > 0xff) throw new Error('Payload too long');\n return [0x51, 0x78, cmd, 0x00, payload.length, 0x00, ...Array.from(payload), crc8(payload), 0xff];\n}\n\nconst u16 = (v: number) => [v & 0xff, (v >> 8) & 0xff];\n\nconst CMD = {\n feed: 0xa1,\n row: 0xa2,\n state: 0xa3,\n quality: 0xa4,\n lattice: 0xa6,\n update: 0xa9,\n energy: 0xaf,\n speed: 0xbd,\n applyEnergy: 0xbe,\n} as const;\n\nconst LATTICE_START = [0xaa, 0x55, 0x17, 0x38, 0x44, 0x5f, 0x5f, 0x5f, 0x44, 0x38, 0x2c];\nconst LATTICE_END = [0xaa, 0x55, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17];\n\n/** Flow control the printer sends on AE02 when its buffer fills and drains. */\nexport const FLOW_PAUSE = [0x51, 0x78, 0xae, 0x01, 0x01, 0x00, 0x10, 0x70, 0xff];\nexport const FLOW_RESUME = [0x51, 0x78, 0xae, 0x01, 0x01, 0x00, 0x00, 0x00, 0xff];\n\n/** Heat energy for each darkness step; Cat-Printer prints images at 0x4000. */\nconst ENERGY = [0x2000, 0x3000, 0x4000, 0x6000, 0x9000];\n/** Rows fed after the last copy, so the print clears the tear bar. */\nconst FEED_AFTER = 128;\n/** Blank rows between copies. */\nconst GAP_BETWEEN = 24;\n\n/** Rows for a cat printer: leftmost dot in the lowest bit. */\nexport const encodeRows = (data: ArrayLike<number>, width: number, height: number): Uint8Array[] => packRows(data, width, height, 'lsb');\n\n/** Everything sent for one job: setup, the rows for each copy, then feed and finish. */\nexport function printJob(rows: Uint8Array[], { density, copies, model }: { density: number; copies: number; model: CatModel | null }): Uint8Array {\n const out: number[] = [];\n const push = (cmd: number, payload: ArrayLike<number>) => out.push(...packet(cmd, payload));\n push(CMD.state, [0]);\n if (model?.newKind) out.push(0x12);\n push(CMD.state, [0]);\n push(CMD.quality, [50]);\n push(CMD.speed, [32]);\n push(CMD.energy, u16(ENERGY[Math.min(ENERGY.length, Math.max(1, Math.round(density))) - 1]));\n push(CMD.applyEnergy, [1]);\n push(CMD.update, [0]);\n push(CMD.lattice, LATTICE_START);\n const blank = new Uint8Array(ROW_BYTES);\n for (let c = 0; c < copies; c++) {\n if (c > 0) for (let i = 0; i < GAP_BETWEEN; i++) push(CMD.row, blank);\n for (const row of rows) push(CMD.row, row);\n }\n push(CMD.lattice, LATTICE_END);\n push(CMD.speed, [8]);\n if (model?.feedWithRows) for (let i = 0; i < FEED_AFTER; i++) push(CMD.row, blank);\n else push(CMD.feed, u16(FEED_AFTER));\n push(CMD.state, [0]);\n return Uint8Array.from(out);\n}\n\n/** A cat printer: the shared thermal client with cat packets and flow control. */\nexport class CatPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = ENERGY.length;\n protected readonly modelName: string;\n\n constructor(\n transport: ThermalTransport,\n readonly model: CatModel | null,\n pace: Pace = { chunkMs: 20, pauseMs: 100 },\n ) {\n super(transport, pace, 180);\n this.modelName = model?.prefix ?? (transport.name || 'Cat printer');\n transport.onNotify((data) => {\n if (same(data, FLOW_PAUSE)) this.paused = true;\n else if (same(data, FLOW_RESUME)) this.paused = false;\n });\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n protected job(rows: Uint8Array[], density: number, copies: number) {\n return printJob(rows, { density, copies, model: this.model });\n }\n}\n\nconst same = (a: ArrayLike<number>, b: number[]) => a.length === b.length && b.every((v, i) => a[i] === v);\n","/**\n * ESC/POS mini printers: 58 mm Bluetooth receipt printers such as the MP583\n * (YAEN MPT-II). They take standard raster images (GS v 0) over the 18F0\n * printer service. See thermal.ts for what they share with other extras.\n */\nimport { modelFor, packRows, THERMAL_HEAD_DOTS, ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const ESCPOS_SERVICE = '000018f0-0000-1000-8000-00805f9b34fb';\nexport const ESCPOS_WRITE = '00002af1-0000-1000-8000-00805f9b34fb';\n\n/**\n * Models known to take ESC/POS raster images over 18F0. \"tested\": printed\n * with niimai on real hardware. \"medium\": the same chipset and service are\n * widely reported, untested here.\n */\nexport const ESCPOS_MODELS: ThermalModel[] = [\n { prefix: 'MP583', confidence: 'tested', note: 'Sold as YAEN MPT-II' },\n { prefix: 'MPT-II', confidence: 'high' },\n { prefix: 'MP58', confidence: 'medium' },\n { prefix: 'MTP-II', confidence: 'medium' },\n { prefix: 'PT-210', confidence: 'medium' },\n { prefix: 'PT-200', confidence: 'medium' },\n { prefix: 'RPP02', confidence: 'medium' },\n { prefix: 'ZJ-58', confidence: 'medium' },\n];\n\nexport const escposModelFor = (name: string | undefined): ThermalModel | null => modelFor(ESCPOS_MODELS, name);\n\nconst ROW_BYTES = THERMAL_HEAD_DOTS / 8;\n/** Rows per raster command; small bands keep the printer's buffer from overflowing. */\nconst BAND = 24;\n/** Dots fed between copies. */\nconst GAP_DOTS = 24;\n/** Lines fed after the last copy, so the print clears the tear bar. */\nconst FEED_LINES = 4;\n\n/** Rows for an ESC/POS printer: leftmost dot in the highest bit. */\nexport const encodeRows = (data: ArrayLike<number>, width: number, height: number): Uint8Array[] => packRows(data, width, height, 'msb');\n\n/** ESC @, then each copy as GS v 0 bands with a gap between, then a feed. */\nexport function escposJob(rows: Uint8Array[], copies: number): Uint8Array {\n const out: number[] = [0x1b, 0x40];\n for (let c = 0; c < copies; c++) {\n if (c > 0) out.push(0x1b, 0x4a, GAP_DOTS);\n for (let i = 0; i < rows.length; i += BAND) {\n const band = rows.slice(i, i + BAND);\n out.push(0x1d, 0x76, 0x30, 0x00, ROW_BYTES & 0xff, ROW_BYTES >> 8, band.length & 0xff, band.length >> 8);\n for (const row of band) out.push(...row);\n }\n }\n out.push(0x1b, 0x64, FEED_LINES);\n return Uint8Array.from(out);\n}\n\n/**\n * An ESC/POS printer: the shared thermal client with raster commands. These\n * printers send no flow control, so writes are paced. Heat settings vary by\n * vendor, so darkness is left to the printer.\n */\nexport class EscPosPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = 1;\n protected readonly modelName: string;\n\n constructor(\n transport: ThermalTransport,\n readonly model: ThermalModel | null,\n pace: Pace = { chunkMs: 30, pauseMs: 100 },\n ) {\n super(transport, pace, 100);\n this.modelName = model?.prefix ?? (transport.name || 'Thermal printer');\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n protected job(rows: Uint8Array[], _density: number, copies: number) {\n return escposJob(rows, copies);\n }\n}\n","/**\n * TSPL label printers: 2- and 3-inch desktop label printers that take TSC's\n * label language over the 18F0 printer service. A job is a few text commands\n * (SIZE, GAP, DENSITY, CLS) around one BITMAP of the page, then PRINT. The\n * printer finds each label's edge itself. It answers \"OK\" on 2AF0 when it has\n * taken a job, and a status byte to ESC ! ?.\n */\nimport type { LabelSize } from './profiles';\nimport { modelFor, ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const TSPL_SERVICE = '000018f0-0000-1000-8000-00805f9b34fb';\nexport const TSPL_WRITE = '00002af1-0000-1000-8000-00805f9b34fb';\nexport const TSPL_NOTIFY = '00002af0-0000-1000-8000-00805f9b34fb';\n\n/** Dots across the head: 80 mm at 203 dpi. */\nexport const TSPL_HEAD_DOTS = 640;\nconst DOTS_PER_MM = 8;\n\n/**\n * Models known to take TSPL over 18F0. \"tested\": printed with niimai on real\n * hardware.\n */\nexport const TSPL_MODELS: ThermalModel[] = [{ prefix: 'QR380A', confidence: 'tested', note: 'Sold as CC390' }];\n\nexport const tsplModelFor = (name: string | undefined): ThermalModel | null => modelFor(TSPL_MODELS, name);\n\n/** Common 2- to 3-inch label sizes, width × height in mm, smallest first. */\nexport const TSPL_SIZES: LabelSize[] = [\n [40, 30],\n [50, 25],\n [50, 30],\n [50, 40],\n [57, 32],\n [60, 40],\n [50, 50],\n [70, 50],\n [75, 50],\n [80, 50],\n [60, 60],\n [80, 60],\n [80, 80],\n [75, 100],\n].map(([widthMm, heightMm]) => ({ widthMm, heightMm }));\n\n/** TSPL's darkness steps, 0 to 15; niimai offers 1 to 15. */\nconst DENSITY_LEVELS = 15;\n/** The gap between labels, for printers that look for it. */\nconst GAP_MM = 2;\n\n/** NIIMBOT label types, as the print dialog passes them. */\nconst LABEL_TYPE = { blackmark: 2, continuous: 3 } as const;\n\n/** The status query, and the bits of its answer that stop a print. */\nexport const STATUS_QUERY = [0x1b, 0x21, 0x3f];\nconst STATUS_FAULTS: [number, string][] = [\n [0x01, 'Printer cover is open'],\n [0x02, 'Paper jam'],\n [0x04, 'Out of paper'],\n [0x08, 'Out of ribbon'],\n];\n\n/** Replies that mean a job was taken: \"OK\". */\nconst OK = [0x4f, 0x4b];\n/** How long to wait for \"OK\" before asking the printer what is wrong. */\nconst ACK_TIMEOUT_MS = 20_000;\n/** How long to wait for the answer to a status query. */\nconst STATUS_TIMEOUT_MS = 2_000;\n\n/**\n * Rows for a TSPL BITMAP: as wide as the page, leftmost dot in the highest\n * bit, and 0 for a dark dot.\n */\nexport function encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array[] {\n const bytes = Math.ceil(width / 8);\n const rows: Uint8Array[] = [];\n for (let y = 0; y < height; y++) {\n const row = new Uint8Array(bytes).fill(0xff);\n for (let x = 0; x < width; x++) if (data[(y * width + x) * 4] < 128) row[x >> 3] &= ~(0x80 >> (x & 7));\n rows.push(row);\n }\n return rows;\n}\n\nconst ascii = (s: string) => Array.from(s, (c) => c.charCodeAt(0));\nconst mm = (dots: number) => `${Math.round((dots / DOTS_PER_MM) * 10) / 10} mm`;\n\n/** How the printer finds the start of each label. */\nfunction paperCommand(labelType: number): string {\n if (labelType === LABEL_TYPE.continuous) return 'GAP 0 mm,0 mm';\n if (labelType === LABEL_TYPE.blackmark) return `BLINE ${GAP_MM} mm,0 mm`;\n return `GAP ${GAP_MM} mm,0 mm`;\n}\n\n/** One label: setup, the page as one bitmap at the top left, then print it `copies` times. */\nexport function tsplJob(rows: Uint8Array[], { density, copies, labelType }: { density: number; copies: number; labelType: number }): Uint8Array {\n const bytes = rows[0]?.length ?? 0;\n const height = rows.length;\n const head = [\n `SIZE ${mm(bytes * 8)},${mm(height)}`,\n paperCommand(labelType),\n 'DIRECTION 0',\n `DENSITY ${Math.min(DENSITY_LEVELS, Math.max(0, Math.round(density)))}`,\n 'CLS',\n ];\n const out = ascii(head.map((l) => `${l}\\r\\n`).join('') + `BITMAP 0,0,${bytes},${height},0,`);\n for (const row of rows) for (const b of row) out.push(b);\n out.push(...ascii(`\\r\\nPRINT ${Math.max(1, copies)},1\\r\\n`));\n return Uint8Array.from(out);\n}\n\n/**\n * A TSPL label printer: the shared thermal client with TSPL jobs. It shows\n * as a label printer (roll sizes, paper types). Writes are paced at the size\n * these printers' BLE modules take without dropping data.\n */\nexport class TsplPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = DENSITY_LEVELS;\n protected readonly modelName: string;\n protected readonly headDots = TSPL_HEAD_DOTS;\n protected readonly kind = undefined;\n protected readonly labelSizes = TSPL_SIZES;\n\n private oks = 0;\n private status: number | null = null;\n private wake: (() => void) | null = null;\n\n constructor(\n transport: ThermalTransport,\n readonly model: ThermalModel | null,\n pace: Pace = { chunkMs: 30, pauseMs: 100 },\n private ackTimeoutMs = ACK_TIMEOUT_MS,\n ) {\n super(transport, pace, 100);\n this.modelName = model?.prefix ?? (transport.name || 'Label printer');\n transport.onNotify((data) => {\n if (data.length >= 2 && data[0] === OK[0] && data[1] === OK[1]) this.oks++;\n else if (data.length === 1) this.status = data[0];\n else return;\n this.wake?.();\n });\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n /** These printers cannot sense the roll; its size is remembered per printer. */\n rollKey() {\n return `printer:${this.transport.name || this.modelName}`;\n }\n\n protected job(rows: Uint8Array[], density: number, copies: number, labelType: number) {\n this.oks = 0;\n return tsplJob(rows, { density, copies, labelType });\n }\n\n /** Resolves when `done` holds or the time runs out. */\n private async until(done: () => boolean, ms: number) {\n const deadline = Date.now() + ms;\n while (!done() && Date.now() < deadline) {\n await new Promise<void>((resolve) => {\n const t = setTimeout(resolve, deadline - Date.now());\n this.wake = () => (clearTimeout(t), resolve());\n });\n }\n this.wake = null;\n }\n\n /**\n * Waits for the printer's \"OK\", so the next label never lands on a busy\n * printer. Without one, asks for its status: a fault fails the print, and a\n * printer that is merely quiet is left to carry on.\n */\n protected async afterPage() {\n await this.until(() => this.oks > 0, this.ackTimeoutMs);\n if (this.oks > 0) return;\n this.status = null;\n await this.send(Uint8Array.from(STATUS_QUERY));\n await this.until(() => this.status !== null, STATUS_TIMEOUT_MS);\n const fault = STATUS_FAULTS.find(([bit]) => (this.status ?? 0) & bit);\n if (fault) throw new Error(fault[1]);\n console.warn('[niimai] Label printer did not confirm the job');\n }\n}\n","import { createBleClient, requestPrinter } from './ble';\nimport { CAT_MODELS, CAT_RX, CAT_SERVICE, CAT_TX, CatPrinterClient, catModelFor } from './cat';\nimport { ESCPOS_MODELS, ESCPOS_SERVICE, ESCPOS_WRITE, EscPosPrinterClient, escposModelFor } from './escpos';\nimport { TSPL_MODELS, TSPL_NOTIFY, TSPL_SERVICE, TSPL_WRITE, TsplPrinterClient, tsplModelFor } from './tspl';\nimport { webTransport } from './thermal';\nimport type { ClientLike } from './client';\n\ntype Lib = typeof import('@mmote/niimbluelib');\n\n/**\n * The Bluetooth printer behind the Connect button. One chooser lists NIIMBOT\n * printers and the known extra thermal printers. A known cat printer offering\n * AE30 gets the cat client, a known ESC/POS printer offering 18F0 the ESC/POS\n * client, a known TSPL label printer offering 18F0 the TSPL client, and\n * everything else the NIIMBOT client exactly as before. Known names never\n * overlap, so each printer matches one client at most.\n */\nexport class BluetoothPrinter implements ClientLike {\n private inner: ClientLike | null = null;\n\n constructor(private lib: () => Promise<Lib>) {}\n\n private get client(): ClientLike {\n if (!this.inner) throw new Error('Not connected');\n return this.inner;\n }\n\n async connect() {\n const device = await requestPrinter([...CAT_MODELS, ...ESCPOS_MODELS, ...TSPL_MODELS].map((m) => m.prefix));\n const cat = catModelFor(device.name);\n const escpos = escposModelFor(device.name);\n const tspl = tsplModelFor(device.name);\n if ((cat || escpos || tspl) && device.gatt) {\n const server = await device.gatt.connect();\n const services = (await server.getPrimaryServices().catch(() => [])).map((s) => s.uuid);\n if (cat && services.includes(CAT_SERVICE)) {\n this.inner = new CatPrinterClient(await webTransport(device, server, { service: CAT_SERVICE, write: CAT_TX, notify: CAT_RX }), cat);\n } else if (escpos && services.includes(ESCPOS_SERVICE)) {\n this.inner = new EscPosPrinterClient(await webTransport(device, server, { service: ESCPOS_SERVICE, write: ESCPOS_WRITE }), escpos);\n } else if (tspl && services.includes(TSPL_SERVICE)) {\n this.inner = new TsplPrinterClient(await webTransport(device, server, { service: TSPL_SERVICE, write: TSPL_WRITE, notify: TSPL_NOTIFY }), tspl);\n }\n if (this.inner) return this.inner.connect();\n }\n this.inner = createBleClient(await this.lib(), device) as unknown as ClientLike;\n return this.inner.connect();\n }\n\n async disconnect() {\n await this.inner?.disconnect();\n }\n\n isConnected() {\n return !!this.inner?.isConnected();\n }\n\n on(event: string, fn: (e: any) => void) {\n return this.client.on(event, fn);\n }\n\n off(event: string, fn: (e: any) => void) {\n return this.inner?.off(event, fn);\n }\n\n getPrinterInfo() {\n return this.inner?.getPrinterInfo() ?? {};\n }\n\n getModelMetadata() {\n return this.inner?.getModelMetadata();\n }\n\n getPrintTaskType() {\n return this.inner?.getPrintTaskType();\n }\n\n getRfidInfo() {\n return this.inner?.getRfidInfo() ?? {};\n }\n\n fetchRfidInfo() {\n return this.client.fetchRfidInfo();\n }\n\n startHeartbeat() {\n this.inner?.startHeartbeat();\n }\n\n stopHeartbeat() {\n this.inner?.stopHeartbeat();\n }\n\n isHeartbeatStarted() {\n return !!this.inner?.isHeartbeatStarted?.();\n }\n\n get protocol() {\n return this.client.protocol;\n }\n\n get encodePage() {\n return this.inner?.encodePage?.bind(this.inner);\n }\n\n get rollKey() {\n return this.inner?.rollKey?.bind(this.inner);\n }\n}\n","import type { PrinterModelMeta } from '@mmote/niimbluelib';\nimport type { ClientLike, PrintTaskLike } from './client';\n\ntype Listener = (e: any) => void;\n\n/** A printer that only exists in memory. Used by the demo mode and the tests. */\nexport class MockClient implements ClientLike {\n connected = false;\n printed: { image: unknown; quantity: number; heartbeat: boolean }[] = [];\n failNextPrint: string | null = null;\n private listeners = new Map<string, Set<Listener>>();\n\n /** Pass null to act as a model the library does not recognise. */\n constructor(private meta: PrinterModelMeta | null = DEMO_META) {}\n\n async connect() {\n this.connected = true;\n this.emit('connect', { info: { deviceName: 'Demo printer' } });\n return { deviceName: 'Demo printer' };\n }\n async disconnect() {\n this.connected = false;\n this.emit('disconnect', {});\n }\n isConnected() {\n return this.connected;\n }\n on(event: string, fn: Listener) {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\n this.listeners.get(event)!.add(fn);\n return this;\n }\n off(event: string, fn: Listener) {\n this.listeners.get(event)?.delete(fn);\n return this;\n }\n emit(event: string, e: unknown) {\n this.listeners.get(event)?.forEach((fn) => fn(e));\n }\n protocolVersion = 1;\n getPrinterInfo() {\n return { batteryPercents: 80, modelId: 512, printheadWidth: 96, protocolVersion: this.protocolVersion };\n }\n getModelMetadata() {\n return this.meta ?? undefined;\n }\n getPrintTaskType() {\n return this.meta ? ('D11_V1' as const) : undefined;\n }\n /** Set false to act like printers that report only the roll's barcode. */\n reportsPaperSize = true;\n\n getRfidInfo() {\n return {\n labelRfidInfo: { tagPresent: true, barCode: '6971501227941', allPaper: 210, usedPaper: 12, consumablesType: 1 },\n paperInfo: this.reportsPaperSize ? { valid: true, paperWidth: 40, paperHeight: 12 } : undefined,\n };\n }\n async fetchRfidInfo() {\n return this.getRfidInfo();\n }\n startHeartbeat() {\n this.heartbeat = true;\n this.emit('heartbeat', { data: { batteryPercents: 80, lidClosed: true, paperInserted: true } });\n }\n heartbeat = false;\n stopHeartbeat() {\n this.heartbeat = false;\n }\n /** How many \"print end\" polls answer false before the printer reports done. */\n printEndPending = 0;\n protocol = {\n printEnd: async () => {\n if (this.printEndPending > 0) {\n this.printEndPending--;\n return false;\n }\n return true;\n },\n newPrintTask: (_name: string, options: Record<string, unknown>): PrintTaskLike => {\n const total = Number(options.totalPages ?? 1);\n return {\n printInit: async () => {\n if (this.failNextPrint) {\n const msg = this.failNextPrint;\n this.failNextPrint = null;\n throw new Error(msg);\n }\n },\n printPage: async (image, quantity = 1) => {\n this.printed.push({ image, quantity, heartbeat: this.heartbeat });\n },\n waitForFinished: async () => {\n for (let page = 1; page <= total; page++) {\n this.emit('printprogress', { page, pagesTotal: total, pagePrintProgress: 100, pageFeedProgress: 100 });\n }\n },\n printEnd: async () => true,\n };\n },\n };\n}\n\nexport const DEMO_META = {\n model: 'D11',\n id: [512],\n dpi: 203,\n printDirection: 'left',\n printheadPixels: 96,\n paperTypes: [1],\n densityMin: 1,\n densityMax: 3,\n densityDefault: 2,\n} as unknown as PrinterModelMeta;\n","/**\n * Paper types, as niimbluelib's LabelType numbers. The printer uses this to\n * find the start of each label, so the wrong one mis-feeds the roll.\n */\nexport type PaperType = 'gaps' | 'blackmark' | 'continuous' | 'perforated' | 'transparent';\n\nexport const PAPER_TYPES: { value: PaperType; label: string; labelType: number }[] = [\n { value: 'gaps', label: 'Labels with gaps', labelType: 1 },\n { value: 'blackmark', label: 'Black mark', labelType: 2 },\n { value: 'continuous', label: 'Continuous', labelType: 3 },\n { value: 'perforated', label: 'Perforated', labelType: 4 },\n { value: 'transparent', label: 'Transparent', labelType: 5 },\n];\n\nexport const paperFromLabelType = (n: number | undefined): PaperType | undefined => PAPER_TYPES.find((p) => p.labelType === n)?.value;\n\nexport const labelTypeOf = (p: PaperType): number => PAPER_TYPES.find((x) => x.value === p)!.labelType;\n\nexport const paperName = (p: PaperType): string => PAPER_TYPES.find((x) => x.value === p)!.label;\n\n/** The paper to print on: the label's own choice, then what the roll reports, then labels with gaps. */\nexport const effectivePaper = (chosen: PaperType | undefined, fromRoll: PaperType | undefined): PaperType => chosen ?? fromRoll ?? 'gaps';\n","import type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\n\n/** A label's size in millimetres. */\nexport interface LabelSize {\n widthMm: number;\n heightMm: number;\n}\n\nexport type PrintDirection = 'left' | 'top';\n\n/** Everything the app needs to know to render and send a page to a printer. */\nexport interface PrinterProfile {\n id: string;\n name: string;\n dpi: number;\n printheadPixels: number;\n printDirection: PrintDirection;\n printTask: PrintTaskName;\n densityMin: number;\n densityMax: number;\n densityDefault: number;\n /** Extra thermal printers (cat, ESC/POS): continuous paper, no roll tags. Unset for NIIMBOT. */\n kind?: 'thermal';\n /** Label sizes common for this printer (TSPL label printers). Unset for NIIMBOT, whose sizes come from its rolls. */\n sizes?: LabelSize[];\n}\n\n/** Manual profiles for printers niimbluelib does not recognise yet. Each mirrors a known model family. */\nexport const MANUAL_PROFILES: PrinterProfile[] = [\n { id: 'd11', name: 'D11 family', dpi: 203, printheadPixels: 96, printDirection: 'left', printTask: 'D11_V1', densityMin: 1, densityMax: 3, densityDefault: 2 },\n { id: 'd110', name: 'D110 family', dpi: 203, printheadPixels: 96, printDirection: 'left', printTask: 'D110', densityMin: 1, densityMax: 3, densityDefault: 2 },\n { id: 'b1', name: 'B1 family', dpi: 203, printheadPixels: 384, printDirection: 'top', printTask: 'B1', densityMin: 1, densityMax: 5, densityDefault: 3 },\n { id: 'b21', name: 'B21 family', dpi: 203, printheadPixels: 384, printDirection: 'top', printTask: 'B21_V1', densityMin: 1, densityMax: 5, densityDefault: 3 },\n];\n\nexport const findManualProfile = (id: string | null | undefined): PrinterProfile | undefined =>\n MANUAL_PROFILES.find((p) => p.id === id);\n\n/**\n * Print tasks verified on hardware where niimbluelib's choice prints blank.\n * A B21 C2B on protocol 0 needs the original B21 packet formats; the library\n * gives every B21 C2B the B1 task, which that firmware accepts but never prints.\n */\nconst TASK_QUIRKS: { model: string; maxProtocol: number; task: PrintTaskName }[] = [{ model: 'B21_C2B', maxProtocol: 0, task: 'B21_V1' }];\n\nexport function correctTask(model: string, protocolVersion: number | undefined, task: PrintTaskName | undefined): PrintTaskName | undefined {\n const quirk = TASK_QUIRKS.find((q) => q.model === model && (protocolVersion ?? 0) <= q.maxProtocol);\n return quirk?.task ?? task;\n}\n\n/**\n * The profile to print with: the library's model data when it recognises the\n * printer, otherwise the manual choice. A detected model always wins, so a\n * choice left over from another printer cannot send the wrong settings.\n */\nexport function resolveProfile(\n meta: PrinterModelMeta | undefined,\n libraryTask: PrintTaskName | undefined,\n manual?: PrinterProfile,\n protocolVersion?: number,\n): PrinterProfile | null {\n const task = meta ? correctTask(meta.model, protocolVersion, libraryTask) : undefined;\n if (!meta || !task) return manual ?? null;\n return {\n id: `auto:${meta.model}`,\n name: meta.model.replace(/_/g, ' '),\n dpi: meta.dpi,\n printheadPixels: meta.printheadPixels,\n printDirection: meta.printDirection,\n printTask: task,\n densityMin: meta.densityMin,\n densityMax: meta.densityMax,\n densityDefault: meta.densityDefault,\n ...((meta as { kind?: string }).kind === 'thermal' ? { kind: 'thermal' as const } : {}),\n ...((meta as { labelSizes?: LabelSize[] }).labelSizes ? { sizes: (meta as { labelSizes?: LabelSize[] }).labelSizes } : {}),\n };\n}\n\n/** Widest label edge the printhead covers, in millimetres. */\nexport const printheadMm = (p: PrinterProfile): number => (p.printheadPixels / p.dpi) * 25.4;\n\n/**\n * The part of a page the printhead can print, centred across the head. Pages\n * wider than the head are cropped: printers discard rows longer than the head.\n */\nexport function printheadCrop(width: number, height: number, p: Pick<PrinterProfile, 'printheadPixels' | 'printDirection'>) {\n const acrossIsWidth = p.printDirection === 'top';\n const across = acrossIsWidth ? width : height;\n const keep = Math.min(across, p.printheadPixels);\n const offset = Math.floor((across - keep) / 2);\n return acrossIsWidth\n ? { x: offset, y: 0, width: keep, height, cropped: keep < across }\n : { x: 0, y: offset, width, height: keep, cropped: keep < across };\n}\n\n/**\n * True when any black pixel of a page (RGBA, as from getImageData) falls\n * outside what the printhead covers, so part of the design would not print.\n */\nexport function inkOutsideHead(data: ArrayLike<number>, width: number, height: number, p: Pick<PrinterProfile, 'printheadPixels' | 'printDirection'>): boolean {\n const crop = printheadCrop(width, height, p);\n if (!crop.cropped) return false;\n for (let y = 0; y < height; y++) {\n const insideRow = y >= crop.y && y < crop.y + crop.height;\n for (let x = 0; x < width; x++) {\n if (insideRow && x >= crop.x && x < crop.x + crop.width) {\n x = crop.x + crop.width - 1;\n continue;\n }\n if (data[(y * width + x) * 4] < 128) return true;\n }\n }\n return false;\n}\n","import type { ClientLike, ConnectionKind, PrintTaskLike } from './client';\nimport { paperFromLabelType, type PaperType } from './paper';\nimport { findManualProfile, printheadCrop, resolveProfile, type PrinterProfile } from './profiles';\n\nexport type PrinterState = 'disconnected' | 'connecting' | 'ready' | 'printing';\n\nexport interface PrinterStatus {\n state: PrinterState;\n deviceName?: string;\n battery?: number;\n /** Null when the printer is unknown and no manual profile is chosen. */\n profile: PrinterProfile | null;\n detectedModel?: string;\n /**\n * The loaded roll. Size comes from the printer's paper info where it reports\n * one; the barcode identifies the roll when it does not (the B21 C2B, for one).\n */\n label?: { widthMm?: number; heightMm?: number; remaining?: number; capacity?: number; barcode?: string; paper?: PaperType; manual?: boolean };\n lidClosed?: boolean;\n paperInserted?: boolean;\n /** Pages done of the total; `sending` is how much of the current page has gone over the link, 0–100, for printers that report it. */\n progress?: { page: number; total: number; sending?: number };\n}\n\n/** Nudges the printed image, in mm: positive x moves right, positive y moves down. */\nexport interface PrintOffset {\n x: number;\n y: number;\n}\n\nexport interface PrintOptions {\n density: number;\n /** Copies of each page. */\n copies: number;\n labelType?: number;\n offset?: PrintOffset;\n}\n\n/** Different labels in one job, drawn one at a time so a long batch never holds every page at once. */\nexport interface PageSource {\n count: number;\n page: (index: number) => Promise<HTMLCanvasElement>;\n}\n\ntype Encoder = (canvas: HTMLCanvasElement, direction: PrinterProfile['printDirection']) => unknown | Promise<unknown>;\nexport type ClientFactory = (kind: ConnectionKind) => Promise<ClientLike>;\n\nconst DISCONNECTED: PrinterStatus = { state: 'disconnected', profile: null };\n\n/** What the demo printer is called wherever a printer's name shows. */\nexport const DEMO_NAME = 'Demo printer';\n\nconst PRINT_TIMEOUT_MS = 20_000;\nconst PAGE_TIMEOUT_MS = 10_000;\nconst PRINT_END_POLL_MS = 300;\nconst mmToPx = (mm: number, dpi: number): number => (mm / 25.4) * dpi;\n\n/** The page moved by whole pixels on a white canvas of the same size. */\nexport function shiftCanvas(canvas: HTMLCanvasElement, dx: number, dy: number): HTMLCanvasElement {\n dx = Math.round(dx);\n dy = Math.round(dy);\n if (!dx && !dy) return canvas;\n const out = document.createElement('canvas');\n out.width = canvas.width;\n out.height = canvas.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.fillStyle = '#fff';\n ctx.fillRect(0, 0, out.width, out.height);\n ctx.drawImage(canvas, dx, dy);\n return out;\n}\n\n/** Crops the page to what the printhead covers. Returns the canvas itself when it already fits. */\nexport function fitToPrinthead(canvas: HTMLCanvasElement, profile: PrinterProfile): HTMLCanvasElement {\n const crop = printheadCrop(canvas.width, canvas.height, profile);\n if (!crop.cropped) return canvas;\n const out = document.createElement('canvas');\n out.width = crop.width;\n out.height = crop.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.drawImage(canvas, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height);\n return out;\n}\n\n/** Black pixels on the page, for the print log. A zero here means the page itself was blank. */\nfunction inkPixels(canvas: HTMLCanvasElement): number | null {\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);\n let n = 0;\n for (let i = 0; i < data.length; i += 4) if (data[i] < 128) n++;\n return n;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nfunction withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(() => reject(new Error('Timeout waiting for the printer to finish')), ms);\n p.then(\n (v) => (clearTimeout(t), resolve(v)),\n (e) => (clearTimeout(t), reject(e)),\n );\n });\n}\n\n/** Translates the library's error codes and messages into short, readable ones. */\nexport function friendlyError(err: unknown): string {\n const msg = err instanceof Error ? err.message : String(err);\n if (/user cancelled|no device selected|chooser/i.test(msg)) return 'Connection cancelled';\n if (/timeout waiting for the printer to finish/i.test(msg)) return 'The printer did not confirm the print';\n if (/timeout/i.test(msg)) return 'Printer stopped responding';\n if (/lid|cover/i.test(msg)) return 'Close the printer lid';\n if (/paper|label/i.test(msg) && /no|out|missing/i.test(msg)) return 'Load a label roll';\n if (/unknown Bluetooth service|No usable channel/i.test(msg)) return msg;\n if (/disconnected|gatt/i.test(msg)) return 'Printer disconnected';\n return msg || 'Something went wrong';\n}\n\n/**\n * Owns the printer connection. The UI subscribes to status updates and calls\n * connect/print; the raw protocol stays inside niimbluelib.\n */\nexport class PrinterService {\n private client: ClientLike | null = null;\n private status: PrinterStatus = DISCONNECTED;\n private listeners = new Set<(s: PrinterStatus) => void>();\n private manualProfileId: string | null = null;\n private detach: (() => void) | null = null;\n\n /** How the current printer is connected. */\n private kind: ConnectionKind | null = null;\n\n constructor(\n private createClient: ClientFactory,\n private encode: Encoder,\n ) {}\n\n getStatus(): PrinterStatus {\n return this.status;\n }\n\n subscribe(fn: (s: PrinterStatus) => void): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n\n private set(patch: Partial<PrinterStatus>): void {\n this.replace({ ...this.status, ...patch });\n }\n\n /** Swaps the whole status, so fields from an earlier connection never linger. */\n private replace(status: PrinterStatus): void {\n this.status = status;\n this.listeners.forEach((fn) => fn(this.status));\n }\n\n setManualProfile(id: string | null): void {\n this.manualProfileId = id;\n if (this.client) this.set({ profile: this.currentProfile() });\n }\n\n private currentProfile(): PrinterProfile | null {\n const c = this.client;\n const profile = resolveProfile(c?.getModelMetadata(), c?.getPrintTaskType(), findManualProfile(this.manualProfileId), c?.getPrinterInfo().protocolVersion);\n // The demo behaves like a D11 underneath, but never passes for one.\n return profile && this.kind === 'demo' ? { ...profile, name: DEMO_NAME } : profile;\n }\n\n private readRfid(): PrinterStatus['label'] {\n // Printers that cannot sense the roll keep the size the user sets, under a key of their own.\n const manual = this.client?.rollKey?.();\n if (manual) return { barcode: manual, manual: true };\n const info = this.client?.getRfidInfo();\n if (!info) return undefined;\n const paper = info.paperInfo?.valid ? info.paperInfo : undefined;\n const tag = info.labelRfidInfo?.tagPresent ? info.labelRfidInfo : undefined;\n if (!paper && !tag) return undefined;\n return {\n widthMm: paper?.paperWidth,\n heightMm: paper?.paperHeight,\n remaining: tag && tag.allPaper !== undefined && tag.allPaper >= 0 ? Math.max(0, tag.allPaper - (tag.usedPaper ?? 0)) : undefined,\n capacity: tag && tag.allPaper !== undefined && tag.allPaper > 0 ? tag.allPaper : undefined,\n barcode: tag?.barCode || undefined,\n paper: paperFromLabelType(tag?.consumablesType ?? paper?.paperType),\n };\n }\n\n async connect(kind: ConnectionKind): Promise<void> {\n if (this.client) await this.disconnect();\n this.kind = kind;\n this.replace({ ...DISCONNECTED, state: 'connecting' });\n let client: ClientLike;\n try {\n client = await this.createClient(kind);\n const info = await client.connect();\n this.client = client;\n this.set({ deviceName: info.deviceName });\n } catch (err) {\n this.client = null;\n this.replace(DISCONNECTED);\n throw new Error(friendlyError(err));\n }\n\n const onDisconnect = () => this.handleDisconnect();\n const onHeartbeat = (e: { data: { batteryPercents?: number; lidClosed?: boolean; paperInserted?: boolean } }) =>\n this.set({ battery: e.data.batteryPercents ?? this.status.battery, lidClosed: e.data.lidClosed, paperInserted: e.data.paperInserted });\n const onRfid = () => this.set({ label: this.readRfid() });\n const onProgress = (e: { page: number; pagesTotal: number; sending?: number }) =>\n this.set({ progress: { page: e.page, total: e.pagesTotal, ...(e.sending !== undefined ? { sending: e.sending } : {}) } });\n client.on('disconnect', onDisconnect);\n client.on('heartbeat', onHeartbeat);\n client.on('rfidinfofetched', onRfid);\n client.on('printprogress', onProgress);\n this.detach = () => {\n client.off('disconnect', onDisconnect);\n client.off('heartbeat', onHeartbeat);\n client.off('rfidinfofetched', onRfid);\n client.off('printprogress', onProgress);\n };\n\n const meta = client.getModelMetadata();\n this.set({\n state: 'ready',\n battery: client.getPrinterInfo().batteryPercents,\n detectedModel: meta?.model,\n profile: this.currentProfile(),\n label: this.readRfid(),\n });\n client.startHeartbeat();\n }\n\n private handleDisconnect(): void {\n this.detach?.();\n this.detach = null;\n this.client = null;\n this.replace(DISCONNECTED);\n }\n\n async disconnect(): Promise<void> {\n const c = this.client;\n if (!c) return;\n c.stopHeartbeat();\n try {\n await c.disconnect();\n } finally {\n this.handleDisconnect();\n }\n }\n\n /**\n * B21_V1 finishes by polling \"print end\", but niimbluelib's poll cancels its\n * own timer on the first reply, so it never resolves unless the printer is\n * already done. That task polls here instead.\n */\n private async waitForFinished(client: ClientLike, task: PrintTaskLike, name: string, copies: number): Promise<void> {\n if (name !== 'B21_V1') return task.waitForFinished();\n this.set({ progress: { page: 0, total: copies } });\n while (!(await client.protocol.printEnd())) await sleep(PRINT_END_POLL_MS);\n this.set({ progress: { page: copies, total: copies } });\n }\n\n async print(source: HTMLCanvasElement | PageSource, opts: PrintOptions): Promise<void> {\n const client = this.client;\n const profile = this.status.profile;\n if (!client) throw new Error('Connect a printer first');\n if (!profile) throw new Error('Choose your printer type');\n if (this.status.state === 'printing') throw new Error('Already printing');\n\n const pages: PageSource = 'page' in source ? source : { count: 1, page: async () => source };\n const copies = Math.max(1, Math.floor(opts.copies));\n const total = copies * Math.max(1, pages.count);\n const density = Math.min(profile.densityMax, Math.max(profile.densityMin, Math.round(opts.density)));\n const task = client.protocol.newPrintTask(profile.printTask, {\n totalPages: total,\n density,\n labelType: opts.labelType ?? 1,\n pageColor: 0,\n statusPollIntervalMs: 100,\n statusTimeoutMs: 8000,\n });\n\n // Heartbeat queries interleaved with page data spoil prints on some models.\n client.stopHeartbeat();\n this.set({ state: 'printing', progress: { page: 0, total } });\n try {\n const offset = opts.offset ?? { x: 0, y: 0 };\n for (let i = 0; i < pages.count; i++) {\n const canvas = await pages.page(i);\n const shifted = shiftCanvas(canvas, mmToPx(offset.x, profile.dpi), mmToPx(offset.y, profile.dpi));\n const page = fitToPrinthead(shifted, profile);\n const image = client.encodePage ? await client.encodePage(page) : await this.encode(page, profile.printDirection);\n if (i === 0) {\n console.info('[niimai] print', {\n profile: profile.id,\n task: profile.printTask,\n direction: profile.printDirection,\n printhead: profile.printheadPixels,\n page: `${canvas.width}x${canvas.height}`,\n sent: `${page.width}x${page.height}`,\n density,\n copies,\n pages: pages.count,\n ink: inkPixels(page),\n });\n await task.printInit();\n }\n await task.printPage(image, copies);\n }\n await withTimeout(this.waitForFinished(client, task, profile.printTask, total), PRINT_TIMEOUT_MS + total * PAGE_TIMEOUT_MS);\n } catch (err) {\n throw new Error(friendlyError(err));\n } finally {\n try {\n await task.printEnd();\n } catch {\n // The page already failed; the original error is the useful one.\n }\n if (this.client) {\n client.startHeartbeat();\n this.set({ state: 'ready', progress: undefined });\n void client.fetchRfidInfo().then(() => this.set({ label: this.readRfid() }), () => {});\n }\n }\n }\n}\n","import type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\n\n/**\n * The slice of niimbluelib's client the app uses. Keeping it narrow lets the\n * mock printer and the tests stand in for real hardware.\n */\nexport interface PrintTaskLike {\n printInit(): Promise<void>;\n printPage(image: unknown, quantity?: number): Promise<void>;\n waitForFinished(): Promise<void>;\n printEnd(): Promise<boolean>;\n}\n\nexport interface ClientLike {\n connect(): Promise<{ deviceName?: string }>;\n disconnect(): Promise<void>;\n isConnected(): boolean;\n on(event: string, fn: (e: any) => void): unknown;\n off(event: string, fn: (e: any) => void): unknown;\n getPrinterInfo(): { batteryPercents?: number; modelId?: number; printheadWidth?: number; protocolVersion?: number };\n getModelMetadata(): PrinterModelMeta | undefined;\n getPrintTaskType(): PrintTaskName | undefined;\n getRfidInfo(): {\n labelRfidInfo?: { tagPresent?: boolean; barCode?: string; allPaper?: number; usedPaper?: number; consumablesType?: number };\n paperInfo?: { valid?: boolean; paperWidth?: number; paperHeight?: number; paperType?: number };\n };\n fetchRfidInfo(): Promise<unknown>;\n startHeartbeat(): void;\n stopHeartbeat(): void;\n isHeartbeatStarted?(): boolean;\n protocol: {\n newPrintTask(name: PrintTaskName, options: Record<string, unknown>): PrintTaskLike;\n /** True once the printer has finished every page. */\n printEnd(): Promise<boolean>;\n };\n /** Clients that encode pages themselves (cat printers); NIIMBOT pages go through niimbluelib's encoder. */\n encodePage?(canvas: HTMLCanvasElement): unknown;\n /**\n * Label printers that cannot sense the roll: a key to remember the roll\n * size the user sets, per printer. NIIMBOT printers read their rolls instead.\n */\n rollKey?(): string;\n}\n\nexport type ConnectionKind = 'bluetooth' | 'serial' | 'demo';\n\nexport const supportsBluetooth = (): boolean => typeof navigator !== 'undefined' && 'bluetooth' in navigator;\nexport const supportsSerial = (): boolean => typeof navigator !== 'undefined' && 'serial' in navigator;\n\nexport type Platform = 'ios' | 'android' | 'desktop';\n\nexport function detectPlatform(ua = typeof navigator === 'undefined' ? '' : navigator.userAgent, touchPoints = typeof navigator === 'undefined' ? 0 : navigator.maxTouchPoints): Platform {\n // iPadOS reports itself as a Mac; touch support gives it away.\n if (/iPhone|iPad|iPod/.test(ua) || (/Macintosh/.test(ua) && touchPoints > 1)) return 'ios';\n if (/Android/.test(ua)) return 'android';\n return 'desktop';\n}\n\n/**\n * Why a connection type cannot be used in this browser, in a few words, or\n * null when it can. iOS browsers all run on WebKit, which has neither Web\n * Bluetooth nor Web Serial; Bluefy adds Web Bluetooth there.\n */\nexport function unsupportedReason(kind: ConnectionKind, platform: Platform = detectPlatform()): string | null {\n if (kind === 'demo') return null;\n if (kind === 'bluetooth' ? supportsBluetooth() : supportsSerial()) return null;\n if (platform === 'ios') return kind === 'bluetooth' ? 'On iPhone and iPad, use the Bluefy browser' : 'Not available on iPhone and iPad';\n if (platform === 'android') return 'Open niimai in Chrome';\n return 'Needs Chrome or Edge';\n}\n","/**\n * libniimai: connect to NIIMBOT, TSPL, ESC/POS and cat printers from the\n * browser over Web Bluetooth or Web Serial, and print a canvas on them.\n */\n/// <reference types=\"web-bluetooth\" preserve=\"true\" />\n/// <reference types=\"w3c-web-serial\" preserve=\"true\" />\nimport { BluetoothPrinter } from './bluetooth';\nimport type { ClientLike, ConnectionKind } from './client';\nimport { MockClient } from './mock';\nimport { PrinterService } from './service';\n\n// niimbluelib loads only when it is needed, so pages that never meet a NIIMBOT stay light.\nconst niimbot = () => import('@mmote/niimbluelib');\n\n/**\n * A client for one kind of connection. Bluetooth finds the printer family\n * from the device the user picks; serial is NIIMBOT only; demo is a printer\n * in memory.\n */\nexport async function createClient(kind: ConnectionKind): Promise<ClientLike> {\n if (kind === 'demo') return new MockClient();\n if (kind === 'bluetooth') return new BluetoothPrinter(niimbot);\n const lib = await niimbot();\n return new lib.NiimbotSerialClient() as unknown as ClientLike;\n}\n\n/** A ready printer service: connect, watch its status, print canvases. */\nexport function createPrinter(): PrinterService {\n return new PrinterService(createClient, async (canvas, direction) => (await niimbot()).ImageEncoder.encodeCanvas(canvas, 0, direction));\n}\n\nexport { PrinterService, DEMO_NAME, friendlyError, fitToPrinthead, shiftCanvas } from './service';\nexport type { ClientFactory, PageSource, PrinterState, PrinterStatus, PrintOffset, PrintOptions } from './service';\n\nexport { detectPlatform, supportsBluetooth, supportsSerial, unsupportedReason } from './client';\nexport type { ClientLike, ConnectionKind, Platform, PrintTaskLike } from './client';\n\nexport { correctTask, findManualProfile, inkOutsideHead, MANUAL_PROFILES, printheadCrop, printheadMm, resolveProfile } from './profiles';\nexport type { LabelSize, PrintDirection, PrinterProfile } from './profiles';\n\nexport { effectivePaper, labelTypeOf, PAPER_TYPES, paperFromLabelType, paperName } from './paper';\nexport type { PaperType } from './paper';\n\nexport { BluetoothPrinter } from './bluetooth';\nexport { describeBleError } from './ble';\nexport { DEMO_META, MockClient } from './mock';\n\nexport { modelFor, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, ThermalPrinterClient, webTransport } from './thermal';\nexport type { Confidence, Pace, ThermalModel, ThermalTransport } from './thermal';\n\nexport { CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, catModelFor, printJob as catJob } from './cat';\nexport type { CatModel } from './cat';\nexport { ESCPOS_MODELS, EscPosPrinterClient, escposJob, escposModelFor } from './escpos';\nexport { TSPL_MODELS, TSPL_SIZES, TsplPrinterClient, tsplJob, tsplModelFor } from './tspl';\n"],"mappings":"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"}
1
+ {"version":3,"file":"libniimai.js","names":["sleep","ROW_BYTES","encodeRows","encodeRows"],"sources":["../src/ble.ts","../src/thermal.ts","../src/cat.ts","../src/escpos.ts","../src/tspl.ts","../src/bluetooth.ts","../src/mock.ts","../src/paper.ts","../src/profiles.ts","../src/service.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import type { ConnectionInfo, NiimbotAbstractClient } from '@mmote/niimbluelib';\n\ntype Lib = typeof import('@mmote/niimbluelib');\n\n/** The standard NIIMBOT service, followed by serial-over-BLE services common on budget printers. */\nexport const CANDIDATE_SERVICES = [\n 'e7810a71-73ae-499d-8c15-faa9aef0c3f2', // NIIMBOT\n '0000e0ff-3c17-d293-8e48-14fe2e4da212', // NIIMBOT B21 C2B (model id 771), channel ffe1\n '0000ae30-0000-1000-8000-00805f9b34fb', // AE30 printer UART\n '0000ff00-0000-1000-8000-00805f9b34fb',\n '0000ffe0-0000-1000-8000-00805f9b34fb', // HM-10 style\n '0000fff0-0000-1000-8000-00805f9b34fb',\n '000018f0-0000-1000-8000-00805f9b34fb', // generic printer\n '49535343-fe7d-4ae5-8fa9-9fafd205e455', // Microchip transparent UART\n '6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART\n];\n\n/** Name prefixes of NIIMBOT printers; the chooser lists only these and known cat printers. */\nexport const NAME_PREFIXES = ['A', 'B', 'C', 'D', 'E', 'F', 'H', 'J', 'K', 'M', 'N', 'P', 'S', 'T', 'Z'];\n\n/** Opens the browser's printer chooser. */\nexport function requestPrinter(extraPrefixes: string[] = []): Promise<BluetoothDevice> {\n return navigator.bluetooth.requestDevice({\n filters: [...[...NAME_PREFIXES, ...extraPrefixes].map((namePrefix) => ({ namePrefix })), { services: [CANDIDATE_SERVICES[0]] }],\n optionalServices: CANDIDATE_SERVICES,\n });\n}\n\ninterface CharLike {\n uuid: string;\n properties: { notify: boolean; indicate?: boolean; write: boolean; writeWithoutResponse: boolean };\n}\n\nexport interface ServiceLike<C extends CharLike = CharLike> {\n uuid: string;\n getCharacteristics(): Promise<C[]>;\n}\n\nexport interface Channel<C extends CharLike = CharLike> {\n write: C;\n notify: C;\n}\n\n/**\n * Picks the characteristics to talk through. A single characteristic that both\n * notifies and accepts writes wins (the NIIMBOT layout); otherwise a write and\n * a notify characteristic from the same service are paired.\n */\nexport async function pickChannel<C extends CharLike>(services: ServiceLike<C>[]): Promise<Channel<C> | null> {\n const all: { service: string; chars: C[] }[] = [];\n for (const service of services) all.push({ service: service.uuid, chars: await service.getCharacteristics() });\n\n const canWrite = (c: C) => c.properties.writeWithoutResponse || c.properties.write;\n const canNotify = (c: C) => c.properties.notify || !!c.properties.indicate;\n\n for (const { chars } of all) {\n const both = chars.find((c) => c.properties.writeWithoutResponse && c.properties.notify);\n if (both) return { write: both, notify: both };\n }\n for (const { chars } of all) {\n const write = chars.find(canWrite);\n const notify = chars.find(canNotify);\n if (write && notify) return { write, notify };\n }\n return null;\n}\n\n/** Turns Chrome's terse GATT errors into something a person can act on. */\nexport function describeBleError(err: unknown, found: string[] = []): Error {\n const msg = err instanceof Error ? err.message : String(err);\n if (/no services found/i.test(msg) || (found.length === 0 && /characteristic/i.test(msg))) {\n return new Error('This printer uses an unknown Bluetooth service. See Help → Printer not recognised.');\n }\n if (/characteristic/i.test(msg)) return new Error(`No usable channel. Services: ${found.join(', ')}`);\n return err instanceof Error ? err : new Error(msg);\n}\n\n/**\n * A Web Bluetooth client that accepts more service layouts than niimbluelib's\n * own, which only knows the standard NIIMBOT service. The protocol handling\n * (packets, negotiation, heartbeat) is inherited unchanged.\n */\nexport function createBleClient(lib: Lib, chosen?: BluetoothDevice): NiimbotAbstractClient {\n class FlexibleBleClient extends lib.NiimbotAbstractClient {\n private server?: BluetoothRemoteGATTServer;\n private channel?: Channel<BluetoothRemoteGATTCharacteristic>;\n\n async connect(): Promise<ConnectionInfo> {\n await this.disconnect();\n const device = chosen ?? (await requestPrinter());\n if (!device.gatt) throw new Error('Device has no GATT server');\n\n const onDisconnect = () => {\n this.server = undefined;\n this.channel = undefined;\n this.emit('disconnect', new lib.DisconnectEvent());\n device.removeEventListener('gattserverdisconnected', onDisconnect);\n };\n device.addEventListener('gattserverdisconnected', onDisconnect);\n\n const server = await device.gatt.connect();\n let found: string[] = [];\n try {\n const services = await server.getPrimaryServices();\n found = services.map((s) => s.uuid);\n console.info('[niimai] services', found);\n const channel = await pickChannel(services);\n if (!channel) throw new Error('Suitable characteristic not found');\n console.info('[niimai] channel', channel.write.uuid, channel.notify.uuid);\n channel.notify.addEventListener('characteristicvaluechanged', (e) => {\n const value = (e.target as BluetoothRemoteGATTCharacteristic).value;\n if (value) this.processRawPacket(value);\n });\n await channel.notify.startNotifications();\n this.server = server;\n this.channel = channel;\n } catch (err) {\n server.disconnect();\n throw describeBleError(err, found);\n }\n\n await this.negotiateAndGetPrinterInfo(() => this.disconnect());\n const result: ConnectionInfo = {\n deviceName: device.name,\n result: this.printerInfo.connectResult ?? lib.ConnectResult.FirmwareErrors,\n };\n this.emit('connect', new lib.ConnectEvent(result));\n return result;\n }\n\n isConnected(): boolean {\n return !!this.server && !!this.channel;\n }\n\n async disconnect(): Promise<void> {\n this.stopHeartbeat();\n this.server?.disconnect();\n this.server = undefined;\n this.channel = undefined;\n }\n\n async sendRaw(data: Uint8Array, force?: boolean): Promise<void> {\n const send = async () => {\n const c = this.channel?.write;\n if (!c) throw new Error('Channel is closed');\n await lib.Utils.sleep(this.packetIntervalMs);\n const buf = data.slice().buffer;\n if (c.properties.writeWithoutResponse) await c.writeValueWithoutResponse(buf);\n else await c.writeValueWithResponse(buf);\n this.emit('rawpacketsent', new lib.RawPacketSentEvent(data));\n };\n if (force) await send();\n else await this.mutex.runExclusive(send);\n }\n\n getType() {\n return 'bluetooth' as const;\n }\n }\n return new FlexibleBleClient();\n}\n","/**\n * Shared ground for the extra Bluetooth thermal printers: cat printers and\n * ESC/POS mini printers (58 mm continuous paper, 384-dot head) and TSPL\n * label printers (labels with gaps, up to 80 mm). niimai's focus is NIIMBOT;\n * these clients are kept apart so NIIMBOT printing never goes through them.\n */\nimport type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\nimport type { LabelSize } from './profiles';\nimport type { ClientLike, PrintTaskLike } from './client';\n\n/** Dots across the head: 48 mm at 203 dpi. */\nexport const THERMAL_HEAD_DOTS = 384;\n/** The print width in whole millimetres, the widest a design on continuous paper can be. */\nexport const THERMAL_HEAD_MM = 48;\n\nexport type Confidence = 'tested' | 'high' | 'medium';\n\nexport interface ThermalModel {\n /** Bluetooth name prefix. */\n prefix: string;\n confidence: Confidence;\n note?: string;\n}\n\n/** The known model a Bluetooth name belongs to, longest prefix first. */\nexport function modelFor<M extends ThermalModel>(models: M[], name: string | undefined): M | null {\n if (!name) return null;\n return [...models].sort((a, b) => b.prefix.length - a.prefix.length).find((m) => name.startsWith(m.prefix)) ?? null;\n}\n\n/** Sizes that suit 58 mm continuous paper: the full 48 mm print width at common lengths. */\nexport const THERMAL_SIZES: LabelSize[] = [15, 20, 30, 48, 80].map((heightMm) => ({ widthMm: 48, heightMm }));\nexport const THERMAL_DEFAULT_SIZE: LabelSize = { widthMm: 48, heightMm: 30 };\n\n/**\n * Packs a 1-bit page (RGBA, dark = black) into rows as wide as the head, centred across\n * the head. Cat printers want the leftmost dot in the lowest bit, ESC/POS in\n * the highest.\n */\nexport function packRows(data: ArrayLike<number>, width: number, height: number, order: 'lsb' | 'msb', head = THERMAL_HEAD_DOTS): Uint8Array[] {\n const offset = Math.max(0, Math.floor((head - width) / 2));\n const rows: Uint8Array[] = [];\n for (let y = 0; y < height; y++) {\n const row = new Uint8Array(head / 8);\n for (let x = 0; x < width && x + offset < head; x++) {\n if (data[(y * width + x) * 4] < 128) {\n const dot = x + offset;\n row[dot >> 3] |= order === 'lsb' ? 1 << (dot & 7) : 0x80 >> (dot & 7);\n }\n }\n rows.push(row);\n }\n return rows;\n}\n\n/** The Bluetooth link, kept narrow so tests can stand in for it. */\nexport interface ThermalTransport {\n name?: string;\n write(chunk: Uint8Array): Promise<void>;\n onNotify(fn: (data: Uint8Array) => void): void;\n onDisconnect(fn: () => void): void;\n disconnect(): Promise<void>;\n}\n\nexport interface Pace {\n /** Pause after each write. */\n chunkMs: number;\n /** How often to look again while the printer asks to wait. */\n pauseMs: number;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * A thermal printer behind the same interface as a NIIMBOT client, so the\n * printer service drives both. It reports itself through model metadata with\n * kind \"thermal\"; it has no roll tags, battery or heartbeat to offer.\n * Subclasses supply the row encoding and the bytes for a job.\n */\nexport abstract class ThermalPrinterClient implements ClientLike {\n private listeners = new Map<string, Set<(e: unknown) => void>>();\n private connected = false;\n /** Set while the printer asks to wait (cat printers' flow control). */\n protected paused = false;\n /** Bytes per write. Starts large and halves if the link refuses it. */\n private chunk: number;\n\n protected abstract readonly densityLevels: number;\n /** The model name shown for this printer. */\n protected abstract readonly modelName: string;\n /** Dots across the head. */\n protected readonly headDots: number = THERMAL_HEAD_DOTS;\n /** \"left\": the label runs sideways through the head, as on NIIMBOT D-series printers. */\n protected readonly printDirection: 'top' | 'left' = 'top';\n /** Continuous-paper printers report \"thermal\"; label printers report nothing and get the roll UI. */\n protected readonly kind: 'thermal' | undefined = 'thermal';\n /** Label sizes common for this printer, offered in place of NIIMBOT roll sizes. */\n protected readonly labelSizes: LabelSize[] | undefined = undefined;\n protected abstract encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array[];\n protected abstract job(rows: Uint8Array[], density: number, copies: number, labelType: number): Uint8Array;\n /** Called after each page is sent; printers that confirm a job wait here. */\n protected async afterPage(): Promise<void> {}\n\n constructor(\n protected transport: ThermalTransport,\n protected pace: Pace,\n firstChunk: number,\n ) {\n this.chunk = firstChunk;\n transport.onDisconnect(() => {\n this.connected = false;\n this.emit('disconnect', {});\n });\n }\n\n async connect() {\n this.connected = true;\n return { deviceName: this.transport.name };\n }\n\n async disconnect() {\n this.connected = false;\n await this.transport.disconnect();\n }\n\n isConnected() {\n return this.connected;\n }\n\n on(event: string, fn: (e: unknown) => void) {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\n this.listeners.get(event)!.add(fn);\n }\n\n off(event: string, fn: (e: unknown) => void) {\n this.listeners.get(event)?.delete(fn);\n }\n\n private emit(event: string, e: unknown) {\n this.listeners.get(event)?.forEach((fn) => fn(e));\n }\n\n getPrinterInfo() {\n return {};\n }\n\n getModelMetadata(): PrinterModelMeta {\n return {\n model: this.modelName,\n ...(this.kind ? { kind: this.kind } : {}),\n ...(this.labelSizes ? { labelSizes: this.labelSizes } : {}),\n dpi: 203,\n printheadPixels: this.headDots,\n printDirection: this.printDirection,\n densityMin: 1,\n densityMax: this.densityLevels,\n densityDefault: Math.ceil(this.densityLevels / 2),\n } as unknown as PrinterModelMeta;\n }\n\n getPrintTaskType(): PrintTaskName {\n // The service needs a task name; these clients print the same way whatever it is.\n return 'B1' as PrintTaskName;\n }\n\n getRfidInfo() {\n return {};\n }\n\n async fetchRfidInfo() {}\n startHeartbeat() {}\n stopHeartbeat() {}\n\n /** Pages arrive as 1-bit canvases; the service hands them here instead of to niimbluelib's encoder. */\n encodePage(canvas: HTMLCanvasElement): Uint8Array[] {\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('Canvas is unavailable');\n return this.encodeRows(ctx.getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height);\n }\n\n /** Sends bytes in chunks, waiting while the printer asks for a pause. `onSent` hears the share sent, 0–1. */\n async send(bytes: Uint8Array, onSent?: (share: number) => void): Promise<void> {\n for (let i = 0; i < bytes.length; ) {\n while (this.paused) await sleep(this.pace.pauseMs);\n const part = bytes.subarray(i, i + this.chunk);\n try {\n await this.transport.write(part);\n } catch (err) {\n if (this.chunk <= 20) throw err;\n this.chunk = Math.max(20, this.chunk >> 1);\n continue;\n }\n i += part.length;\n onSent?.(i / bytes.length);\n await sleep(this.pace.chunkMs);\n }\n }\n\n protocol = {\n printEnd: async () => true,\n newPrintTask: (_name: PrintTaskName, options: Record<string, unknown>): PrintTaskLike => {\n const density = Number(options.density ?? 1);\n const labelType = Number(options.labelType ?? 1);\n // A batch sends several pages to one task; progress counts across them.\n let printed = 0;\n return {\n printInit: async () => {},\n printPage: async (image, quantity = 1) => {\n const total = () => Math.max(printed, Number(options.totalPages ?? printed));\n // Sending is the slow part over Bluetooth, so report it as it goes, in whole percents.\n let shown = -1;\n await this.send(this.job(image as Uint8Array[], density, Math.max(1, quantity), labelType), (share) => {\n const sending = Math.floor(share * 100);\n if (sending === shown) return;\n shown = sending;\n this.emit('printprogress', { page: printed, pagesTotal: total(), pagePrintProgress: 0, pageFeedProgress: 0, sending });\n });\n await this.afterPage();\n printed += Math.max(1, quantity);\n this.emit('printprogress', { page: printed, pagesTotal: total(), pagePrintProgress: 100, pageFeedProgress: 100 });\n },\n waitForFinished: async () => {},\n printEnd: async () => true,\n };\n },\n };\n}\n\n/** A Web Bluetooth link through one service: a write characteristic and, if present, a notify one. */\nexport async function webTransport(\n device: BluetoothDevice,\n server: BluetoothRemoteGATTServer,\n ids: { service: string; write: string; notify?: string },\n): Promise<ThermalTransport> {\n const service = await server.getPrimaryService(ids.service);\n const tx = await service.getCharacteristic(ids.write);\n const notify: ((d: Uint8Array) => void)[] = [];\n if (ids.notify) {\n const rx = await service.getCharacteristic(ids.notify);\n rx.addEventListener('characteristicvaluechanged', (e) => {\n const v = (e.target as BluetoothRemoteGATTCharacteristic).value;\n if (v) notify.forEach((fn) => fn(new Uint8Array(v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength))));\n });\n await rx.startNotifications();\n }\n return {\n name: device.name,\n write: async (chunk) => {\n const buf = chunk.slice().buffer;\n if (tx.properties.writeWithoutResponse) await tx.writeValueWithoutResponse(buf);\n else await tx.writeValueWithResponse(buf);\n },\n onNotify: (fn) => notify.push(fn),\n onDisconnect: (fn) => device.addEventListener('gattserverdisconnected', fn),\n disconnect: async () => server.disconnect(),\n };\n}\n","/**\n * \"Cat printers\": the small Bluetooth thermal printers sold as GB01, MX06 and\n * the like. See thermal.ts for what they share with other extras.\n *\n * Protocol, from rbaron/catprinter and NaitLee/Cat-Printer: packets are\n * 51 78 <cmd> 00 <len> 00 <payload> <crc8(payload)> FF over service AE30,\n * written to AE01. The printer sends flow control on AE02.\n */\nimport { modelFor, packRows, THERMAL_HEAD_DOTS, ThermalPrinterClient, type Confidence, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const CAT_SERVICE = '0000ae30-0000-1000-8000-00805f9b34fb';\nexport const CAT_TX = '0000ae01-0000-1000-8000-00805f9b34fb';\nexport const CAT_RX = '0000ae02-0000-1000-8000-00805f9b34fb';\n\nconst ROW_BYTES = THERMAL_HEAD_DOTS / 8;\n\nexport type { Confidence };\n\nexport interface CatModel extends ThermalModel {\n /** Takes the newer \"start printing\" command. */\n newKind?: boolean;\n /** Mishandles the feed command, so the paper is fed with blank rows. */\n feedWithRows?: boolean;\n}\n\n/**\n * Models known to speak this protocol. \"high\": supported by both reference\n * projects and widely used. \"medium\": listed by Cat-Printer, fewer reports.\n * \"tested\": printed with niimai on real hardware.\n */\nexport const CAT_MODELS: CatModel[] = [\n { prefix: 'GB01', confidence: 'high' },\n { prefix: 'GB02', confidence: 'high' },\n { prefix: 'GB03', confidence: 'high', newKind: true },\n { prefix: 'MX05', confidence: 'high', feedWithRows: true },\n { prefix: 'MX06', confidence: 'high', feedWithRows: true },\n { prefix: 'MX10', confidence: 'high', feedWithRows: true },\n { prefix: 'MX08', confidence: 'medium', feedWithRows: true },\n { prefix: 'MX09', confidence: 'medium', feedWithRows: true },\n { prefix: 'MX11', confidence: 'medium' },\n { prefix: 'GT01', confidence: 'medium' },\n { prefix: 'YT01', confidence: 'medium' },\n { prefix: 'PD01', confidence: 'medium' },\n { prefix: 'MXTP', confidence: 'medium' },\n { prefix: 'SC03h', confidence: 'medium' },\n];\n\n/** Look-alikes that use other protocols. Listed in Help and docs/thermal-printers.md. */\nexport const CAT_UNSUPPORTED: { name: string; note: string }[] = [\n { name: 'MXW01', note: 'Newer protocol' },\n { name: 'PeriPage', note: 'Different protocol' },\n { name: 'Phomemo M02 / T02', note: 'Different protocol' },\n];\n\nexport const catModelFor = (name: string | undefined): CatModel | null => modelFor(CAT_MODELS, name);\n\n// CRC-8, polynomial 0x07, as the printers expect.\nconst CRC_TABLE = Array.from({ length: 256 }, (_, i) => {\n let c = i;\n for (let b = 0; b < 8; b++) c = c & 0x80 ? ((c << 1) ^ 0x07) & 0xff : (c << 1) & 0xff;\n return c;\n});\nexport const crc8 = (data: ArrayLike<number>): number => {\n let c = 0;\n for (let i = 0; i < data.length; i++) c = CRC_TABLE[(c ^ data[i]) & 0xff];\n return c;\n};\n\nexport function packet(cmd: number, payload: ArrayLike<number>): number[] {\n if (payload.length > 0xff) throw new Error('Payload too long');\n return [0x51, 0x78, cmd, 0x00, payload.length, 0x00, ...Array.from(payload), crc8(payload), 0xff];\n}\n\nconst u16 = (v: number) => [v & 0xff, (v >> 8) & 0xff];\n\nconst CMD = {\n feed: 0xa1,\n row: 0xa2,\n state: 0xa3,\n quality: 0xa4,\n lattice: 0xa6,\n update: 0xa9,\n energy: 0xaf,\n speed: 0xbd,\n applyEnergy: 0xbe,\n} as const;\n\nconst LATTICE_START = [0xaa, 0x55, 0x17, 0x38, 0x44, 0x5f, 0x5f, 0x5f, 0x44, 0x38, 0x2c];\nconst LATTICE_END = [0xaa, 0x55, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17];\n\n/** Flow control the printer sends on AE02 when its buffer fills and drains. */\nexport const FLOW_PAUSE = [0x51, 0x78, 0xae, 0x01, 0x01, 0x00, 0x10, 0x70, 0xff];\nexport const FLOW_RESUME = [0x51, 0x78, 0xae, 0x01, 0x01, 0x00, 0x00, 0x00, 0xff];\n\n/** Heat energy for each darkness step; Cat-Printer prints images at 0x4000. */\nconst ENERGY = [0x2000, 0x3000, 0x4000, 0x6000, 0x9000];\n/** Rows fed after the last copy, so the print clears the tear bar. */\nconst FEED_AFTER = 128;\n/** Blank rows between copies. */\nconst GAP_BETWEEN = 24;\n\n/** Rows for a cat printer: leftmost dot in the lowest bit. */\nexport const encodeRows = (data: ArrayLike<number>, width: number, height: number): Uint8Array[] => packRows(data, width, height, 'lsb');\n\n/** Everything sent for one job: setup, the rows for each copy, then feed and finish. */\nexport function printJob(rows: Uint8Array[], { density, copies, model }: { density: number; copies: number; model: CatModel | null }): Uint8Array {\n const out: number[] = [];\n const push = (cmd: number, payload: ArrayLike<number>) => out.push(...packet(cmd, payload));\n push(CMD.state, [0]);\n if (model?.newKind) out.push(0x12);\n push(CMD.state, [0]);\n push(CMD.quality, [50]);\n push(CMD.speed, [32]);\n push(CMD.energy, u16(ENERGY[Math.min(ENERGY.length, Math.max(1, Math.round(density))) - 1]));\n push(CMD.applyEnergy, [1]);\n push(CMD.update, [0]);\n push(CMD.lattice, LATTICE_START);\n const blank = new Uint8Array(ROW_BYTES);\n for (let c = 0; c < copies; c++) {\n if (c > 0) for (let i = 0; i < GAP_BETWEEN; i++) push(CMD.row, blank);\n for (const row of rows) push(CMD.row, row);\n }\n push(CMD.lattice, LATTICE_END);\n push(CMD.speed, [8]);\n if (model?.feedWithRows) for (let i = 0; i < FEED_AFTER; i++) push(CMD.row, blank);\n else push(CMD.feed, u16(FEED_AFTER));\n push(CMD.state, [0]);\n return Uint8Array.from(out);\n}\n\n/** A cat printer: the shared thermal client with cat packets and flow control. */\nexport class CatPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = ENERGY.length;\n protected readonly modelName: string;\n\n constructor(\n transport: ThermalTransport,\n readonly model: CatModel | null,\n pace: Pace = { chunkMs: 20, pauseMs: 100 },\n ) {\n super(transport, pace, 180);\n this.modelName = model?.prefix ?? (transport.name || 'Cat printer');\n transport.onNotify((data) => {\n if (same(data, FLOW_PAUSE)) this.paused = true;\n else if (same(data, FLOW_RESUME)) this.paused = false;\n });\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n protected job(rows: Uint8Array[], density: number, copies: number) {\n return printJob(rows, { density, copies, model: this.model });\n }\n}\n\nconst same = (a: ArrayLike<number>, b: number[]) => a.length === b.length && b.every((v, i) => a[i] === v);\n","/**\n * ESC/POS mini printers: 58 mm Bluetooth receipt printers such as the MP583\n * (YAEN MPT-II). They take standard raster images (GS v 0) over the 18F0\n * printer service. See thermal.ts for what they share with other extras.\n */\nimport { modelFor, packRows, THERMAL_HEAD_DOTS, ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const ESCPOS_SERVICE = '000018f0-0000-1000-8000-00805f9b34fb';\nexport const ESCPOS_WRITE = '00002af1-0000-1000-8000-00805f9b34fb';\n\n/**\n * Models known to take ESC/POS raster images over 18F0. \"tested\": printed\n * with niimai on real hardware. \"medium\": the same chipset and service are\n * widely reported, untested here.\n */\nexport const ESCPOS_MODELS: ThermalModel[] = [\n { prefix: 'MP583', confidence: 'tested', note: 'Sold as YAEN MPT-II' },\n { prefix: 'MPT-II', confidence: 'high' },\n { prefix: 'MP58', confidence: 'medium' },\n { prefix: 'MTP-II', confidence: 'medium' },\n { prefix: 'PT-210', confidence: 'medium' },\n { prefix: 'PT-200', confidence: 'medium' },\n { prefix: 'RPP02', confidence: 'medium' },\n { prefix: 'ZJ-58', confidence: 'medium' },\n];\n\nexport const escposModelFor = (name: string | undefined): ThermalModel | null => modelFor(ESCPOS_MODELS, name);\n\nconst ROW_BYTES = THERMAL_HEAD_DOTS / 8;\n/** Rows per raster command; small bands keep the printer's buffer from overflowing. */\nconst BAND = 24;\n/** Dots fed between copies. */\nconst GAP_DOTS = 24;\n/** Lines fed after the last copy, so the print clears the tear bar. */\nconst FEED_LINES = 4;\n\n/** Rows for an ESC/POS printer: leftmost dot in the highest bit. */\nexport const encodeRows = (data: ArrayLike<number>, width: number, height: number): Uint8Array[] => packRows(data, width, height, 'msb');\n\n/** ESC @, then each copy as GS v 0 bands with a gap between, then a feed. */\nexport function escposJob(rows: Uint8Array[], copies: number): Uint8Array {\n const out: number[] = [0x1b, 0x40];\n for (let c = 0; c < copies; c++) {\n if (c > 0) out.push(0x1b, 0x4a, GAP_DOTS);\n for (let i = 0; i < rows.length; i += BAND) {\n const band = rows.slice(i, i + BAND);\n out.push(0x1d, 0x76, 0x30, 0x00, ROW_BYTES & 0xff, ROW_BYTES >> 8, band.length & 0xff, band.length >> 8);\n for (const row of band) out.push(...row);\n }\n }\n out.push(0x1b, 0x64, FEED_LINES);\n return Uint8Array.from(out);\n}\n\n/**\n * An ESC/POS printer: the shared thermal client with raster commands. These\n * printers send no flow control, so writes are paced. Heat settings vary by\n * vendor, so darkness is left to the printer.\n */\nexport class EscPosPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = 1;\n protected readonly modelName: string;\n\n constructor(\n transport: ThermalTransport,\n readonly model: ThermalModel | null,\n pace: Pace = { chunkMs: 30, pauseMs: 100 },\n ) {\n super(transport, pace, 100);\n this.modelName = model?.prefix ?? (transport.name || 'Thermal printer');\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n protected job(rows: Uint8Array[], _density: number, copies: number) {\n return escposJob(rows, copies);\n }\n}\n","/**\n * TSPL label printers: 2- and 3-inch desktop label printers that take TSC's\n * label language over the 18F0 printer service. A job is a few text commands\n * (SIZE, GAP, DENSITY, CLS) around one BITMAP of the page, then PRINT. The\n * printer finds each label's edge itself. It answers \"OK\" on 2AF0 when it has\n * taken a job, and a status byte to ESC ! ?.\n */\nimport type { LabelSize } from './profiles';\nimport { modelFor, ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const TSPL_SERVICE = '000018f0-0000-1000-8000-00805f9b34fb';\nexport const TSPL_WRITE = '00002af1-0000-1000-8000-00805f9b34fb';\nexport const TSPL_NOTIFY = '00002af0-0000-1000-8000-00805f9b34fb';\n\n/** Dots across the head: 80 mm at 203 dpi. */\nexport const TSPL_HEAD_DOTS = 640;\nconst DOTS_PER_MM = 8;\n\n/**\n * Models known to take TSPL over 18F0. \"tested\": printed with niimai on real\n * hardware.\n */\nexport const TSPL_MODELS: ThermalModel[] = [{ prefix: 'QR380A', confidence: 'tested', note: 'Sold as CC390' }];\n\nexport const tsplModelFor = (name: string | undefined): ThermalModel | null => modelFor(TSPL_MODELS, name);\n\n/** Common 2- to 3-inch label sizes, width × height in mm, smallest first. */\nexport const TSPL_SIZES: LabelSize[] = [\n [40, 30],\n [50, 25],\n [50, 30],\n [50, 40],\n [57, 32],\n [60, 40],\n [50, 50],\n [70, 50],\n [75, 50],\n [80, 50],\n [60, 60],\n [80, 60],\n [80, 80],\n [75, 100],\n].map(([widthMm, heightMm]) => ({ widthMm, heightMm }));\n\n/** TSPL's darkness steps, 0 to 15; niimai offers 1 to 15. */\nconst DENSITY_LEVELS = 15;\n/** The gap between labels, for printers that look for it. */\nconst GAP_MM = 2;\n\n/** NIIMBOT label types, as the print dialog passes them. */\nconst LABEL_TYPE = { blackmark: 2, continuous: 3 } as const;\n\n/** The status query, and the bits of its answer that stop a print. */\nexport const STATUS_QUERY = [0x1b, 0x21, 0x3f];\nconst STATUS_FAULTS: [number, string][] = [\n [0x01, 'Printer cover is open'],\n [0x02, 'Paper jam'],\n [0x04, 'Out of paper'],\n [0x08, 'Out of ribbon'],\n];\n\n/** Replies that mean a job was taken: \"OK\". */\nconst OK = [0x4f, 0x4b];\n/** How long to wait for \"OK\" before asking the printer what is wrong. */\nconst ACK_TIMEOUT_MS = 20_000;\n/** How long to wait for the answer to a status query. */\nconst STATUS_TIMEOUT_MS = 2_000;\n\n/**\n * Rows for a TSPL BITMAP: as wide as the page, leftmost dot in the highest\n * bit, and 0 for a dark dot.\n */\nexport function encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array[] {\n const bytes = Math.ceil(width / 8);\n const rows: Uint8Array[] = [];\n for (let y = 0; y < height; y++) {\n const row = new Uint8Array(bytes).fill(0xff);\n for (let x = 0; x < width; x++) if (data[(y * width + x) * 4] < 128) row[x >> 3] &= ~(0x80 >> (x & 7));\n rows.push(row);\n }\n return rows;\n}\n\nconst ascii = (s: string) => Array.from(s, (c) => c.charCodeAt(0));\nconst mm = (dots: number) => `${Math.round((dots / DOTS_PER_MM) * 10) / 10} mm`;\n\n/** How the printer finds the start of each label. */\nfunction paperCommand(labelType: number): string {\n if (labelType === LABEL_TYPE.continuous) return 'GAP 0 mm,0 mm';\n if (labelType === LABEL_TYPE.blackmark) return `BLINE ${GAP_MM} mm,0 mm`;\n return `GAP ${GAP_MM} mm,0 mm`;\n}\n\n/** One label: setup, the page as one bitmap at the top left, then print it `copies` times. */\nexport function tsplJob(rows: Uint8Array[], { density, copies, labelType }: { density: number; copies: number; labelType: number }): Uint8Array {\n const bytes = rows[0]?.length ?? 0;\n const height = rows.length;\n const head = [\n `SIZE ${mm(bytes * 8)},${mm(height)}`,\n paperCommand(labelType),\n 'DIRECTION 0',\n `DENSITY ${Math.min(DENSITY_LEVELS, Math.max(0, Math.round(density)))}`,\n 'CLS',\n ];\n const out = ascii(head.map((l) => `${l}\\r\\n`).join('') + `BITMAP 0,0,${bytes},${height},0,`);\n for (const row of rows) for (const b of row) out.push(b);\n out.push(...ascii(`\\r\\nPRINT ${Math.max(1, copies)},1\\r\\n`));\n return Uint8Array.from(out);\n}\n\n/**\n * A TSPL label printer: the shared thermal client with TSPL jobs. It shows\n * as a label printer (roll sizes, paper types). Writes are paced at the size\n * these printers' BLE modules take without dropping data.\n */\nexport class TsplPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = DENSITY_LEVELS;\n protected readonly modelName: string;\n protected readonly headDots = TSPL_HEAD_DOTS;\n protected readonly kind = undefined;\n protected readonly labelSizes = TSPL_SIZES;\n\n private oks = 0;\n private status: number | null = null;\n private wake: (() => void) | null = null;\n\n constructor(\n transport: ThermalTransport,\n readonly model: ThermalModel | null,\n pace: Pace = { chunkMs: 30, pauseMs: 100 },\n private ackTimeoutMs = ACK_TIMEOUT_MS,\n ) {\n super(transport, pace, 100);\n this.modelName = model?.prefix ?? (transport.name || 'Label printer');\n transport.onNotify((data) => {\n if (data.length >= 2 && data[0] === OK[0] && data[1] === OK[1]) this.oks++;\n else if (data.length === 1) this.status = data[0];\n else return;\n this.wake?.();\n });\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n /** These printers cannot sense the roll; its size is remembered per printer. */\n rollKey() {\n return `printer:${this.transport.name || this.modelName}`;\n }\n\n protected job(rows: Uint8Array[], density: number, copies: number, labelType: number) {\n this.oks = 0;\n return tsplJob(rows, { density, copies, labelType });\n }\n\n /** Resolves when `done` holds or the time runs out. */\n private async until(done: () => boolean, ms: number) {\n const deadline = Date.now() + ms;\n while (!done() && Date.now() < deadline) {\n await new Promise<void>((resolve) => {\n const t = setTimeout(resolve, deadline - Date.now());\n this.wake = () => (clearTimeout(t), resolve());\n });\n }\n this.wake = null;\n }\n\n /**\n * Waits for the printer's \"OK\", so the next label never lands on a busy\n * printer. Without one, asks for its status: a fault fails the print, and a\n * printer that is merely quiet is left to carry on.\n */\n protected async afterPage() {\n await this.until(() => this.oks > 0, this.ackTimeoutMs);\n if (this.oks > 0) return;\n this.status = null;\n await this.send(Uint8Array.from(STATUS_QUERY));\n await this.until(() => this.status !== null, STATUS_TIMEOUT_MS);\n const fault = STATUS_FAULTS.find(([bit]) => (this.status ?? 0) & bit);\n if (fault) throw new Error(fault[1]);\n console.warn('[niimai] Label printer did not confirm the job');\n }\n}\n","import { createBleClient, requestPrinter } from './ble';\nimport { CAT_MODELS, CAT_RX, CAT_SERVICE, CAT_TX, CatPrinterClient, catModelFor } from './cat';\nimport { ESCPOS_MODELS, ESCPOS_SERVICE, ESCPOS_WRITE, EscPosPrinterClient, escposModelFor } from './escpos';\nimport { TSPL_MODELS, TSPL_NOTIFY, TSPL_SERVICE, TSPL_WRITE, TsplPrinterClient, tsplModelFor } from './tspl';\nimport { webTransport } from './thermal';\nimport type { ClientLike } from './client';\n\ntype Lib = typeof import('@mmote/niimbluelib');\n\n/**\n * The Bluetooth printer behind the Connect button. One chooser lists NIIMBOT\n * printers and the known extra thermal printers. A known cat printer offering\n * AE30 gets the cat client, a known ESC/POS printer offering 18F0 the ESC/POS\n * client, a known TSPL label printer offering 18F0 the TSPL client, and\n * everything else the NIIMBOT client exactly as before. Known names never\n * overlap, so each printer matches one client at most.\n */\nexport class BluetoothPrinter implements ClientLike {\n private inner: ClientLike | null = null;\n\n constructor(private lib: () => Promise<Lib>) {}\n\n private get client(): ClientLike {\n if (!this.inner) throw new Error('Not connected');\n return this.inner;\n }\n\n async connect() {\n const device = await requestPrinter([...CAT_MODELS, ...ESCPOS_MODELS, ...TSPL_MODELS].map((m) => m.prefix));\n const cat = catModelFor(device.name);\n const escpos = escposModelFor(device.name);\n const tspl = tsplModelFor(device.name);\n if ((cat || escpos || tspl) && device.gatt) {\n const server = await device.gatt.connect();\n const services = (await server.getPrimaryServices().catch(() => [])).map((s) => s.uuid);\n if (cat && services.includes(CAT_SERVICE)) {\n this.inner = new CatPrinterClient(await webTransport(device, server, { service: CAT_SERVICE, write: CAT_TX, notify: CAT_RX }), cat);\n } else if (escpos && services.includes(ESCPOS_SERVICE)) {\n this.inner = new EscPosPrinterClient(await webTransport(device, server, { service: ESCPOS_SERVICE, write: ESCPOS_WRITE }), escpos);\n } else if (tspl && services.includes(TSPL_SERVICE)) {\n this.inner = new TsplPrinterClient(await webTransport(device, server, { service: TSPL_SERVICE, write: TSPL_WRITE, notify: TSPL_NOTIFY }), tspl);\n }\n if (this.inner) return this.inner.connect();\n }\n this.inner = createBleClient(await this.lib(), device) as unknown as ClientLike;\n return this.inner.connect();\n }\n\n async disconnect() {\n await this.inner?.disconnect();\n }\n\n isConnected() {\n return !!this.inner?.isConnected();\n }\n\n on(event: string, fn: (e: any) => void) {\n return this.client.on(event, fn);\n }\n\n off(event: string, fn: (e: any) => void) {\n return this.inner?.off(event, fn);\n }\n\n getPrinterInfo() {\n return this.inner?.getPrinterInfo() ?? {};\n }\n\n getModelMetadata() {\n return this.inner?.getModelMetadata();\n }\n\n getPrintTaskType() {\n return this.inner?.getPrintTaskType();\n }\n\n getRfidInfo() {\n return this.inner?.getRfidInfo() ?? {};\n }\n\n fetchRfidInfo() {\n return this.client.fetchRfidInfo();\n }\n\n startHeartbeat() {\n this.inner?.startHeartbeat();\n }\n\n stopHeartbeat() {\n this.inner?.stopHeartbeat();\n }\n\n isHeartbeatStarted() {\n return !!this.inner?.isHeartbeatStarted?.();\n }\n\n get protocol() {\n return this.client.protocol;\n }\n\n get encodePage() {\n return this.inner?.encodePage?.bind(this.inner);\n }\n\n get rollKey() {\n return this.inner?.rollKey?.bind(this.inner);\n }\n}\n","import type { PrinterModelMeta } from '@mmote/niimbluelib';\nimport type { ClientLike, PrintTaskLike } from './client';\n\ntype Listener = (e: any) => void;\n\n/** A printer that only exists in memory. Used by the demo mode and the tests. */\nexport class MockClient implements ClientLike {\n connected = false;\n printed: { image: unknown; quantity: number; heartbeat: boolean }[] = [];\n failNextPrint: string | null = null;\n private listeners = new Map<string, Set<Listener>>();\n\n /** Pass null to act as a model the library does not recognise. */\n constructor(private meta: PrinterModelMeta | null = DEMO_META) {}\n\n async connect() {\n this.connected = true;\n this.emit('connect', { info: { deviceName: 'Demo printer' } });\n return { deviceName: 'Demo printer' };\n }\n async disconnect() {\n this.connected = false;\n this.emit('disconnect', {});\n }\n isConnected() {\n return this.connected;\n }\n on(event: string, fn: Listener) {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\n this.listeners.get(event)!.add(fn);\n return this;\n }\n off(event: string, fn: Listener) {\n this.listeners.get(event)?.delete(fn);\n return this;\n }\n emit(event: string, e: unknown) {\n this.listeners.get(event)?.forEach((fn) => fn(e));\n }\n protocolVersion = 1;\n getPrinterInfo() {\n return { batteryPercents: 80, modelId: 512, printheadWidth: 96, protocolVersion: this.protocolVersion };\n }\n getModelMetadata() {\n return this.meta ?? undefined;\n }\n getPrintTaskType() {\n return this.meta ? ('D11_V1' as const) : undefined;\n }\n /** Set false to act like printers that report only the roll's barcode. */\n reportsPaperSize = true;\n\n getRfidInfo() {\n return {\n labelRfidInfo: { tagPresent: true, barCode: '6971501227941', allPaper: 210, usedPaper: 12, consumablesType: 1 },\n paperInfo: this.reportsPaperSize ? { valid: true, paperWidth: 40, paperHeight: 12 } : undefined,\n };\n }\n async fetchRfidInfo() {\n return this.getRfidInfo();\n }\n startHeartbeat() {\n this.heartbeat = true;\n this.emit('heartbeat', { data: { batteryPercents: 80, lidClosed: true, paperInserted: true } });\n }\n heartbeat = false;\n stopHeartbeat() {\n this.heartbeat = false;\n }\n /** How many \"print end\" polls answer false before the printer reports done. */\n printEndPending = 0;\n protocol = {\n printEnd: async () => {\n if (this.printEndPending > 0) {\n this.printEndPending--;\n return false;\n }\n return true;\n },\n newPrintTask: (_name: string, options: Record<string, unknown>): PrintTaskLike => {\n const total = Number(options.totalPages ?? 1);\n return {\n printInit: async () => {\n if (this.failNextPrint) {\n const msg = this.failNextPrint;\n this.failNextPrint = null;\n throw new Error(msg);\n }\n },\n printPage: async (image, quantity = 1) => {\n this.printed.push({ image, quantity, heartbeat: this.heartbeat });\n },\n waitForFinished: async () => {\n for (let page = 1; page <= total; page++) {\n this.emit('printprogress', { page, pagesTotal: total, pagePrintProgress: 100, pageFeedProgress: 100 });\n }\n },\n printEnd: async () => true,\n };\n },\n };\n}\n\nexport const DEMO_META = {\n model: 'D11',\n id: [512],\n dpi: 203,\n printDirection: 'left',\n printheadPixels: 96,\n paperTypes: [1],\n densityMin: 1,\n densityMax: 3,\n densityDefault: 2,\n} as unknown as PrinterModelMeta;\n","/**\n * Paper types, as niimbluelib's LabelType numbers. The printer uses this to\n * find the start of each label, so the wrong one mis-feeds the roll.\n */\nexport type PaperType = 'gaps' | 'blackmark' | 'continuous' | 'perforated' | 'transparent';\n\nexport const PAPER_TYPES: { value: PaperType; label: string; labelType: number }[] = [\n { value: 'gaps', label: 'Labels with gaps', labelType: 1 },\n { value: 'blackmark', label: 'Black mark', labelType: 2 },\n { value: 'continuous', label: 'Continuous', labelType: 3 },\n { value: 'perforated', label: 'Perforated', labelType: 4 },\n { value: 'transparent', label: 'Transparent', labelType: 5 },\n];\n\nexport const paperFromLabelType = (n: number | undefined): PaperType | undefined => PAPER_TYPES.find((p) => p.labelType === n)?.value;\n\nexport const labelTypeOf = (p: PaperType): number => PAPER_TYPES.find((x) => x.value === p)!.labelType;\n\nexport const paperName = (p: PaperType): string => PAPER_TYPES.find((x) => x.value === p)!.label;\n\n/** The paper to print on: the label's own choice, then what the roll reports, then labels with gaps. */\nexport const effectivePaper = (chosen: PaperType | undefined, fromRoll: PaperType | undefined): PaperType => chosen ?? fromRoll ?? 'gaps';\n","import type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\n\n/** A label's size in millimetres. */\nexport interface LabelSize {\n widthMm: number;\n heightMm: number;\n}\n\nexport type PrintDirection = 'left' | 'top';\n\n/** Everything the app needs to know to render and send a page to a printer. */\nexport interface PrinterProfile {\n id: string;\n name: string;\n dpi: number;\n printheadPixels: number;\n printDirection: PrintDirection;\n printTask: PrintTaskName;\n densityMin: number;\n densityMax: number;\n densityDefault: number;\n /** Extra thermal printers (cat, ESC/POS): continuous paper, no roll tags. Unset for NIIMBOT. */\n kind?: 'thermal';\n /** Label sizes common for this printer (TSPL label printers). Unset for NIIMBOT, whose sizes come from its rolls. */\n sizes?: LabelSize[];\n}\n\n/** Manual profiles for printers niimbluelib does not recognise yet. Each mirrors a known model family. */\nexport const MANUAL_PROFILES: PrinterProfile[] = [\n { id: 'd11', name: 'D11 family', dpi: 203, printheadPixels: 96, printDirection: 'left', printTask: 'D11_V1', densityMin: 1, densityMax: 3, densityDefault: 2 },\n { id: 'd110', name: 'D110 family', dpi: 203, printheadPixels: 96, printDirection: 'left', printTask: 'D110', densityMin: 1, densityMax: 3, densityDefault: 2 },\n { id: 'b1', name: 'B1 family', dpi: 203, printheadPixels: 384, printDirection: 'top', printTask: 'B1', densityMin: 1, densityMax: 5, densityDefault: 3 },\n { id: 'b21', name: 'B21 family', dpi: 203, printheadPixels: 384, printDirection: 'top', printTask: 'B21_V1', densityMin: 1, densityMax: 5, densityDefault: 3 },\n];\n\nexport const findManualProfile = (id: string | null | undefined): PrinterProfile | undefined =>\n MANUAL_PROFILES.find((p) => p.id === id);\n\n/**\n * Print tasks verified on hardware where niimbluelib's choice prints blank.\n * A B21 C2B on protocol 0 needs the original B21 packet formats; the library\n * gives every B21 C2B the B1 task, which that firmware accepts but never prints.\n */\nconst TASK_QUIRKS: { model: string; maxProtocol: number; task: PrintTaskName }[] = [{ model: 'B21_C2B', maxProtocol: 0, task: 'B21_V1' }];\n\nexport function correctTask(model: string, protocolVersion: number | undefined, task: PrintTaskName | undefined): PrintTaskName | undefined {\n const quirk = TASK_QUIRKS.find((q) => q.model === model && (protocolVersion ?? 0) <= q.maxProtocol);\n return quirk?.task ?? task;\n}\n\n/**\n * The profile to print with: the library's model data when it recognises the\n * printer, otherwise the manual choice. A detected model always wins, so a\n * choice left over from another printer cannot send the wrong settings.\n */\nexport function resolveProfile(\n meta: PrinterModelMeta | undefined,\n libraryTask: PrintTaskName | undefined,\n manual?: PrinterProfile,\n protocolVersion?: number,\n): PrinterProfile | null {\n const task = meta ? correctTask(meta.model, protocolVersion, libraryTask) : undefined;\n if (!meta || !task) return manual ?? null;\n return {\n id: `auto:${meta.model}`,\n name: meta.model.replace(/_/g, ' '),\n dpi: meta.dpi,\n printheadPixels: meta.printheadPixels,\n printDirection: meta.printDirection,\n printTask: task,\n densityMin: meta.densityMin,\n densityMax: meta.densityMax,\n densityDefault: meta.densityDefault,\n ...((meta as { kind?: string }).kind === 'thermal' ? { kind: 'thermal' as const } : {}),\n ...((meta as { labelSizes?: LabelSize[] }).labelSizes ? { sizes: (meta as { labelSizes?: LabelSize[] }).labelSizes } : {}),\n };\n}\n\n/** Widest label edge the printhead covers, in millimetres. */\nexport const printheadMm = (p: PrinterProfile): number => (p.printheadPixels / p.dpi) * 25.4;\n\n/**\n * The part of a page the printhead can print, centred across the head. Pages\n * wider than the head are cropped: printers discard rows longer than the head.\n */\nexport function printheadCrop(width: number, height: number, p: Pick<PrinterProfile, 'printheadPixels' | 'printDirection'>) {\n const acrossIsWidth = p.printDirection === 'top';\n const across = acrossIsWidth ? width : height;\n const keep = Math.min(across, p.printheadPixels);\n const offset = Math.floor((across - keep) / 2);\n return acrossIsWidth\n ? { x: offset, y: 0, width: keep, height, cropped: keep < across }\n : { x: 0, y: offset, width, height: keep, cropped: keep < across };\n}\n\n/**\n * True when any black pixel of a page (RGBA, as from getImageData) falls\n * outside what the printhead covers, so part of the design would not print.\n */\nexport function inkOutsideHead(data: ArrayLike<number>, width: number, height: number, p: Pick<PrinterProfile, 'printheadPixels' | 'printDirection'>): boolean {\n const crop = printheadCrop(width, height, p);\n if (!crop.cropped) return false;\n for (let y = 0; y < height; y++) {\n const insideRow = y >= crop.y && y < crop.y + crop.height;\n for (let x = 0; x < width; x++) {\n if (insideRow && x >= crop.x && x < crop.x + crop.width) {\n x = crop.x + crop.width - 1;\n continue;\n }\n if (data[(y * width + x) * 4] < 128) return true;\n }\n }\n return false;\n}\n","import type { ClientLike, ConnectionKind, PrintTaskLike } from './client';\nimport { paperFromLabelType, type PaperType } from './paper';\nimport { findManualProfile, printheadCrop, resolveProfile, type PrinterProfile } from './profiles';\n\nexport type PrinterState = 'disconnected' | 'connecting' | 'ready' | 'printing';\n\nexport interface PrinterStatus {\n state: PrinterState;\n deviceName?: string;\n battery?: number;\n /** Null when the printer is unknown and no manual profile is chosen. */\n profile: PrinterProfile | null;\n detectedModel?: string;\n /**\n * The loaded roll. Size comes from the printer's paper info where it reports\n * one; the barcode identifies the roll when it does not (the B21 C2B, for one).\n */\n label?: { widthMm?: number; heightMm?: number; remaining?: number; capacity?: number; barcode?: string; paper?: PaperType; manual?: boolean };\n lidClosed?: boolean;\n paperInserted?: boolean;\n /** Pages done of the total; `sending` is how much of the current page has gone over the link, 0–100, for printers that report it. */\n progress?: { page: number; total: number; sending?: number };\n}\n\n/** Nudges the printed image, in mm: positive x moves right, positive y moves down. */\nexport interface PrintOffset {\n x: number;\n y: number;\n}\n\nexport interface PrintOptions {\n density: number;\n /** Copies of each page. */\n copies: number;\n labelType?: number;\n offset?: PrintOffset;\n}\n\n/** Different labels in one job, drawn one at a time so a long batch never holds every page at once. */\nexport interface PageSource {\n count: number;\n page: (index: number) => Promise<HTMLCanvasElement>;\n}\n\ntype Encoder = (canvas: HTMLCanvasElement, direction: PrinterProfile['printDirection']) => unknown | Promise<unknown>;\nexport type ClientFactory = (kind: ConnectionKind) => Promise<ClientLike>;\n\nconst DISCONNECTED: PrinterStatus = { state: 'disconnected', profile: null };\n\n/** What the demo printer is called wherever a printer's name shows. */\nexport const DEMO_NAME = 'Demo printer';\n\nconst PRINT_TIMEOUT_MS = 20_000;\nconst PAGE_TIMEOUT_MS = 10_000;\nconst PRINT_END_POLL_MS = 300;\nconst mmToPx = (mm: number, dpi: number): number => (mm / 25.4) * dpi;\n\n/** The page moved by whole pixels on a white canvas of the same size. */\nexport function shiftCanvas(canvas: HTMLCanvasElement, dx: number, dy: number): HTMLCanvasElement {\n dx = Math.round(dx);\n dy = Math.round(dy);\n if (!dx && !dy) return canvas;\n const out = document.createElement('canvas');\n out.width = canvas.width;\n out.height = canvas.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.fillStyle = '#fff';\n ctx.fillRect(0, 0, out.width, out.height);\n ctx.drawImage(canvas, dx, dy);\n return out;\n}\n\n/** Crops the page to what the printhead covers. Returns the canvas itself when it already fits. */\nexport function fitToPrinthead(canvas: HTMLCanvasElement, profile: PrinterProfile): HTMLCanvasElement {\n const crop = printheadCrop(canvas.width, canvas.height, profile);\n if (!crop.cropped) return canvas;\n const out = document.createElement('canvas');\n out.width = crop.width;\n out.height = crop.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.drawImage(canvas, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height);\n return out;\n}\n\n/** Black pixels on the page, for the print log. A zero here means the page itself was blank. */\nfunction inkPixels(canvas: HTMLCanvasElement): number | null {\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);\n let n = 0;\n for (let i = 0; i < data.length; i += 4) if (data[i] < 128) n++;\n return n;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nfunction withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(() => reject(new Error('Timeout waiting for the printer to finish')), ms);\n p.then(\n (v) => (clearTimeout(t), resolve(v)),\n (e) => (clearTimeout(t), reject(e)),\n );\n });\n}\n\n/** Translates the library's error codes and messages into short, readable ones. */\nexport function friendlyError(err: unknown): string {\n const msg = err instanceof Error ? err.message : String(err);\n if (/user cancelled|no device selected|chooser/i.test(msg)) return 'Connection cancelled';\n if (/timeout waiting for the printer to finish/i.test(msg)) return 'The printer did not confirm the print';\n if (/timeout/i.test(msg)) return 'Printer stopped responding';\n if (/lid|cover/i.test(msg)) return 'Close the printer lid';\n if (/paper|label/i.test(msg) && /no|out|missing/i.test(msg)) return 'Load a label roll';\n if (/unknown Bluetooth service|No usable channel/i.test(msg)) return msg;\n if (/disconnected|gatt/i.test(msg)) return 'Printer disconnected';\n return msg || 'Something went wrong';\n}\n\n/**\n * Owns the printer connection. The UI subscribes to status updates and calls\n * connect/print; the raw protocol stays inside niimbluelib.\n */\nexport class PrinterService {\n private client: ClientLike | null = null;\n private status: PrinterStatus = DISCONNECTED;\n private listeners = new Set<(s: PrinterStatus) => void>();\n private manualProfileId: string | null = null;\n private detach: (() => void) | null = null;\n\n /** How the current printer is connected. */\n private kind: ConnectionKind | null = null;\n\n constructor(\n private createClient: ClientFactory,\n private encode: Encoder,\n ) {}\n\n getStatus(): PrinterStatus {\n return this.status;\n }\n\n subscribe(fn: (s: PrinterStatus) => void): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n\n private set(patch: Partial<PrinterStatus>): void {\n this.replace({ ...this.status, ...patch });\n }\n\n /** Swaps the whole status, so fields from an earlier connection never linger. */\n private replace(status: PrinterStatus): void {\n this.status = status;\n this.listeners.forEach((fn) => fn(this.status));\n }\n\n setManualProfile(id: string | null): void {\n this.manualProfileId = id;\n if (this.client) this.set({ profile: this.currentProfile() });\n }\n\n private currentProfile(): PrinterProfile | null {\n const c = this.client;\n const profile = resolveProfile(c?.getModelMetadata(), c?.getPrintTaskType(), findManualProfile(this.manualProfileId), c?.getPrinterInfo().protocolVersion);\n // Whatever printer stands in for the demo, it never passes for a real model.\n return profile && this.kind === 'demo' ? { ...profile, name: DEMO_NAME } : profile;\n }\n\n private readRfid(): PrinterStatus['label'] {\n // Printers that cannot sense the roll keep the size the user sets, under a key of their own.\n const manual = this.client?.rollKey?.();\n if (manual) return { barcode: manual, manual: true };\n const info = this.client?.getRfidInfo();\n if (!info) return undefined;\n const paper = info.paperInfo?.valid ? info.paperInfo : undefined;\n const tag = info.labelRfidInfo?.tagPresent ? info.labelRfidInfo : undefined;\n if (!paper && !tag) return undefined;\n return {\n widthMm: paper?.paperWidth,\n heightMm: paper?.paperHeight,\n remaining: tag && tag.allPaper !== undefined && tag.allPaper >= 0 ? Math.max(0, tag.allPaper - (tag.usedPaper ?? 0)) : undefined,\n capacity: tag && tag.allPaper !== undefined && tag.allPaper > 0 ? tag.allPaper : undefined,\n barcode: tag?.barCode || undefined,\n paper: paperFromLabelType(tag?.consumablesType ?? paper?.paperType),\n };\n }\n\n async connect(kind: ConnectionKind): Promise<void> {\n if (this.client) await this.disconnect();\n this.kind = kind;\n this.replace({ ...DISCONNECTED, state: 'connecting' });\n let client: ClientLike;\n try {\n client = await this.createClient(kind);\n const info = await client.connect();\n this.client = client;\n this.set({ deviceName: info.deviceName });\n } catch (err) {\n this.client = null;\n this.replace(DISCONNECTED);\n throw new Error(friendlyError(err));\n }\n\n const onDisconnect = () => this.handleDisconnect();\n const onHeartbeat = (e: { data: { batteryPercents?: number; lidClosed?: boolean; paperInserted?: boolean } }) =>\n this.set({ battery: e.data.batteryPercents ?? this.status.battery, lidClosed: e.data.lidClosed, paperInserted: e.data.paperInserted });\n const onRfid = () => this.set({ label: this.readRfid() });\n const onProgress = (e: { page: number; pagesTotal: number; sending?: number }) =>\n this.set({ progress: { page: e.page, total: e.pagesTotal, ...(e.sending !== undefined ? { sending: e.sending } : {}) } });\n client.on('disconnect', onDisconnect);\n client.on('heartbeat', onHeartbeat);\n client.on('rfidinfofetched', onRfid);\n client.on('printprogress', onProgress);\n this.detach = () => {\n client.off('disconnect', onDisconnect);\n client.off('heartbeat', onHeartbeat);\n client.off('rfidinfofetched', onRfid);\n client.off('printprogress', onProgress);\n };\n\n const meta = client.getModelMetadata();\n this.set({\n state: 'ready',\n battery: client.getPrinterInfo().batteryPercents,\n detectedModel: meta?.model,\n profile: this.currentProfile(),\n label: this.readRfid(),\n });\n client.startHeartbeat();\n }\n\n private handleDisconnect(): void {\n this.detach?.();\n this.detach = null;\n this.client = null;\n this.replace(DISCONNECTED);\n }\n\n async disconnect(): Promise<void> {\n const c = this.client;\n if (!c) return;\n c.stopHeartbeat();\n try {\n await c.disconnect();\n } finally {\n this.handleDisconnect();\n }\n }\n\n /**\n * B21_V1 finishes by polling \"print end\", but niimbluelib's poll cancels its\n * own timer on the first reply, so it never resolves unless the printer is\n * already done. That task polls here instead.\n */\n private async waitForFinished(client: ClientLike, task: PrintTaskLike, name: string, copies: number): Promise<void> {\n if (name !== 'B21_V1') return task.waitForFinished();\n this.set({ progress: { page: 0, total: copies } });\n while (!(await client.protocol.printEnd())) await sleep(PRINT_END_POLL_MS);\n this.set({ progress: { page: copies, total: copies } });\n }\n\n async print(source: HTMLCanvasElement | PageSource, opts: PrintOptions): Promise<void> {\n const client = this.client;\n const profile = this.status.profile;\n if (!client) throw new Error('Connect a printer first');\n if (!profile) throw new Error('Choose your printer type');\n if (this.status.state === 'printing') throw new Error('Already printing');\n\n const pages: PageSource = 'page' in source ? source : { count: 1, page: async () => source };\n const copies = Math.max(1, Math.floor(opts.copies));\n const total = copies * Math.max(1, pages.count);\n const density = Math.min(profile.densityMax, Math.max(profile.densityMin, Math.round(opts.density)));\n const task = client.protocol.newPrintTask(profile.printTask, {\n totalPages: total,\n density,\n labelType: opts.labelType ?? 1,\n pageColor: 0,\n statusPollIntervalMs: 100,\n statusTimeoutMs: 8000,\n });\n\n // Heartbeat queries interleaved with page data spoil prints on some models.\n client.stopHeartbeat();\n this.set({ state: 'printing', progress: { page: 0, total } });\n try {\n const offset = opts.offset ?? { x: 0, y: 0 };\n for (let i = 0; i < pages.count; i++) {\n const canvas = await pages.page(i);\n const shifted = shiftCanvas(canvas, mmToPx(offset.x, profile.dpi), mmToPx(offset.y, profile.dpi));\n const page = fitToPrinthead(shifted, profile);\n const image = client.encodePage ? await client.encodePage(page) : await this.encode(page, profile.printDirection);\n if (i === 0) {\n console.info('[niimai] print', {\n profile: profile.id,\n task: profile.printTask,\n direction: profile.printDirection,\n printhead: profile.printheadPixels,\n page: `${canvas.width}x${canvas.height}`,\n sent: `${page.width}x${page.height}`,\n density,\n copies,\n pages: pages.count,\n ink: inkPixels(page),\n });\n await task.printInit();\n }\n await task.printPage(image, copies);\n }\n await withTimeout(this.waitForFinished(client, task, profile.printTask, total), PRINT_TIMEOUT_MS + total * PAGE_TIMEOUT_MS);\n } catch (err) {\n throw new Error(friendlyError(err));\n } finally {\n try {\n await task.printEnd();\n } catch {\n // The page already failed; the original error is the useful one.\n }\n if (this.client) {\n client.startHeartbeat();\n this.set({ state: 'ready', progress: undefined });\n void client.fetchRfidInfo().then(() => this.set({ label: this.readRfid() }), () => {});\n }\n }\n }\n}\n","import type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\n\n/**\n * The slice of niimbluelib's client the app uses. Keeping it narrow lets the\n * mock printer and the tests stand in for real hardware.\n */\nexport interface PrintTaskLike {\n printInit(): Promise<void>;\n printPage(image: unknown, quantity?: number): Promise<void>;\n waitForFinished(): Promise<void>;\n printEnd(): Promise<boolean>;\n}\n\nexport interface ClientLike {\n connect(): Promise<{ deviceName?: string }>;\n disconnect(): Promise<void>;\n isConnected(): boolean;\n on(event: string, fn: (e: any) => void): unknown;\n off(event: string, fn: (e: any) => void): unknown;\n getPrinterInfo(): { batteryPercents?: number; modelId?: number; printheadWidth?: number; protocolVersion?: number };\n getModelMetadata(): PrinterModelMeta | undefined;\n getPrintTaskType(): PrintTaskName | undefined;\n getRfidInfo(): {\n labelRfidInfo?: { tagPresent?: boolean; barCode?: string; allPaper?: number; usedPaper?: number; consumablesType?: number };\n paperInfo?: { valid?: boolean; paperWidth?: number; paperHeight?: number; paperType?: number };\n };\n fetchRfidInfo(): Promise<unknown>;\n startHeartbeat(): void;\n stopHeartbeat(): void;\n isHeartbeatStarted?(): boolean;\n protocol: {\n newPrintTask(name: PrintTaskName, options: Record<string, unknown>): PrintTaskLike;\n /** True once the printer has finished every page. */\n printEnd(): Promise<boolean>;\n };\n /** Clients that encode pages themselves (cat printers); NIIMBOT pages go through niimbluelib's encoder. */\n encodePage?(canvas: HTMLCanvasElement): unknown;\n /**\n * Label printers that cannot sense the roll: a key to remember the roll\n * size the user sets, per printer. NIIMBOT printers read their rolls instead.\n */\n rollKey?(): string;\n}\n\nexport type ConnectionKind = 'bluetooth' | 'serial' | 'demo';\n\nexport const supportsBluetooth = (): boolean => typeof navigator !== 'undefined' && 'bluetooth' in navigator;\nexport const supportsSerial = (): boolean => typeof navigator !== 'undefined' && 'serial' in navigator;\n\nexport type Platform = 'ios' | 'android' | 'desktop';\n\nexport function detectPlatform(ua = typeof navigator === 'undefined' ? '' : navigator.userAgent, touchPoints = typeof navigator === 'undefined' ? 0 : navigator.maxTouchPoints): Platform {\n // iPadOS reports itself as a Mac; touch support gives it away.\n if (/iPhone|iPad|iPod/.test(ua) || (/Macintosh/.test(ua) && touchPoints > 1)) return 'ios';\n if (/Android/.test(ua)) return 'android';\n return 'desktop';\n}\n\n/**\n * Why a connection type cannot be used in this browser, in a few words, or\n * null when it can. iOS browsers all run on WebKit, which has neither Web\n * Bluetooth nor Web Serial; Bluefy adds Web Bluetooth there.\n */\nexport function unsupportedReason(kind: ConnectionKind, platform: Platform = detectPlatform()): string | null {\n if (kind === 'demo') return null;\n if (kind === 'bluetooth' ? supportsBluetooth() : supportsSerial()) return null;\n if (platform === 'ios') return kind === 'bluetooth' ? 'On iPhone and iPad, use the Bluefy browser' : 'Not available on iPhone and iPad';\n if (platform === 'android') return 'Open niimai in Chrome';\n return 'Needs Chrome or Edge';\n}\n","/**\n * libniimai: connect to NIIMBOT, TSPL, ESC/POS and cat printers from the\n * browser over Web Bluetooth or Web Serial, and print a canvas on them.\n */\n/// <reference types=\"web-bluetooth\" preserve=\"true\" />\n/// <reference types=\"w3c-web-serial\" preserve=\"true\" />\nimport { BluetoothPrinter } from './bluetooth';\nimport type { ClientLike, ConnectionKind } from './client';\nimport { MockClient } from './mock';\nimport { PrinterService, type ClientFactory } from './service';\n\n// niimbluelib loads only when it is needed, so pages that never meet a NIIMBOT stay light.\nconst niimbot = () => import('@mmote/niimbluelib');\n\n/**\n * A client for one kind of connection. Bluetooth finds the printer family\n * from the device the user picks; serial is NIIMBOT only; demo is a printer\n * in memory.\n */\nexport async function createClient(kind: ConnectionKind): Promise<ClientLike> {\n if (kind === 'demo') return new MockClient();\n if (kind === 'bluetooth') return new BluetoothPrinter(niimbot);\n const lib = await niimbot();\n return new lib.NiimbotSerialClient() as unknown as ClientLike;\n}\n\nexport interface CreatePrinterOptions {\n /**\n * Makes the client for each connection. Defaults to createClient; wrap it\n * to change one kind of connection and keep the others.\n */\n createClient?: ClientFactory;\n}\n\n/** A ready printer service: connect, watch its status, print canvases. */\nexport function createPrinter(options: CreatePrinterOptions = {}): PrinterService {\n return new PrinterService(options.createClient ?? createClient, async (canvas, direction) =>\n (await niimbot()).ImageEncoder.encodeCanvas(canvas, 0, direction),\n );\n}\n\nexport { PrinterService, DEMO_NAME, friendlyError, fitToPrinthead, shiftCanvas } from './service';\nexport type { ClientFactory, PageSource, PrinterState, PrinterStatus, PrintOffset, PrintOptions } from './service';\n\nexport { detectPlatform, supportsBluetooth, supportsSerial, unsupportedReason } from './client';\nexport type { ClientLike, ConnectionKind, Platform, PrintTaskLike } from './client';\n\nexport { correctTask, findManualProfile, inkOutsideHead, MANUAL_PROFILES, printheadCrop, printheadMm, resolveProfile } from './profiles';\nexport type { LabelSize, PrintDirection, PrinterProfile } from './profiles';\n\nexport { effectivePaper, labelTypeOf, PAPER_TYPES, paperFromLabelType, paperName } from './paper';\nexport type { PaperType } from './paper';\n\nexport { BluetoothPrinter } from './bluetooth';\nexport { describeBleError } from './ble';\nexport { DEMO_META, MockClient } from './mock';\n\nexport { modelFor, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, ThermalPrinterClient, webTransport } from './thermal';\nexport type { Confidence, Pace, ThermalModel, ThermalTransport } from './thermal';\n\nexport { CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, catModelFor, printJob as catJob } from './cat';\nexport type { CatModel } from './cat';\nexport { ESCPOS_MODELS, EscPosPrinterClient, escposJob, escposModelFor } from './escpos';\nexport { TSPL_MODELS, TSPL_SIZES, TsplPrinterClient, tsplJob, tsplModelFor } from './tspl';\n"],"mappings":";;AAKA,IAAa,qBAAqB;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,IAAa,gBAAgB;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG;;AAGvG,SAAgB,eAAe,gBAA0B,CAAC,GAA6B;CACrF,OAAO,UAAU,UAAU,cAAc;EACvC,SAAS,CAAC,GAAG,CAAC,GAAG,eAAe,GAAG,aAAa,CAAC,CAAC,KAAK,gBAAgB,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,CAAC,mBAAmB,EAAE,EAAE,CAAC;EAC9H,kBAAkB;CACpB,CAAC;AACH;;;;;;AAsBA,eAAsB,YAAgC,UAAwD;CAC5G,MAAM,MAAyC,CAAC;CAChD,KAAK,MAAM,WAAW,UAAU,IAAI,KAAK;EAAE,SAAS,QAAQ;EAAM,OAAO,MAAM,QAAQ,mBAAmB;CAAE,CAAC;CAE7G,MAAM,YAAY,MAAS,EAAE,WAAW,wBAAwB,EAAE,WAAW;CAC7E,MAAM,aAAa,MAAS,EAAE,WAAW,UAAU,CAAC,CAAC,EAAE,WAAW;CAElE,KAAK,MAAM,EAAE,WAAW,KAAK;EAC3B,MAAM,OAAO,MAAM,MAAM,MAAM,EAAE,WAAW,wBAAwB,EAAE,WAAW,MAAM;EACvF,IAAI,MAAM,OAAO;GAAE,OAAO;GAAM,QAAQ;EAAK;CAC/C;CACA,KAAK,MAAM,EAAE,WAAW,KAAK;EAC3B,MAAM,QAAQ,MAAM,KAAK,QAAQ;EACjC,MAAM,SAAS,MAAM,KAAK,SAAS;EACnC,IAAI,SAAS,QAAQ,OAAO;GAAE;GAAO;EAAO;CAC9C;CACA,OAAO;AACT;;AAGA,SAAgB,iBAAiB,KAAc,QAAkB,CAAC,GAAU;CAC1E,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;CAC3D,IAAI,qBAAqB,KAAK,GAAG,KAAM,MAAM,WAAW,KAAK,kBAAkB,KAAK,GAAG,GACrF,uBAAO,IAAI,MAAM,oFAAoF;CAEvG,IAAI,kBAAkB,KAAK,GAAG,GAAG,uBAAO,IAAI,MAAM,gCAAgC,MAAM,KAAK,IAAI,GAAG;CACpG,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,GAAG;AACnD;;;;;;AAOA,SAAgB,gBAAgB,KAAU,QAAiD;CACzF,MAAM,0BAA0B,IAAI,sBAAsB;EACxD;EACA;EAEA,MAAM,UAAmC;GACvC,MAAM,KAAK,WAAW;GACtB,MAAM,SAAS,UAAW,MAAM,eAAe;GAC/C,IAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,2BAA2B;GAE7D,MAAM,qBAAqB;IACzB,KAAK,SAAS,KAAA;IACd,KAAK,UAAU,KAAA;IACf,KAAK,KAAK,cAAc,IAAI,IAAI,gBAAgB,CAAC;IACjD,OAAO,oBAAoB,0BAA0B,YAAY;GACnE;GACA,OAAO,iBAAiB,0BAA0B,YAAY;GAE9D,MAAM,SAAS,MAAM,OAAO,KAAK,QAAQ;GACzC,IAAI,QAAkB,CAAC;GACvB,IAAI;IACF,MAAM,WAAW,MAAM,OAAO,mBAAmB;IACjD,QAAQ,SAAS,KAAK,MAAM,EAAE,IAAI;IAClC,QAAQ,KAAK,qBAAqB,KAAK;IACvC,MAAM,UAAU,MAAM,YAAY,QAAQ;IAC1C,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,mCAAmC;IACjE,QAAQ,KAAK,oBAAoB,QAAQ,MAAM,MAAM,QAAQ,OAAO,IAAI;IACxE,QAAQ,OAAO,iBAAiB,+BAA+B,MAAM;KACnE,MAAM,QAAS,EAAE,OAA6C;KAC9D,IAAI,OAAO,KAAK,iBAAiB,KAAK;IACxC,CAAC;IACD,MAAM,QAAQ,OAAO,mBAAmB;IACxC,KAAK,SAAS;IACd,KAAK,UAAU;GACjB,SAAS,KAAK;IACZ,OAAO,WAAW;IAClB,MAAM,iBAAiB,KAAK,KAAK;GACnC;GAEA,MAAM,KAAK,iCAAiC,KAAK,WAAW,CAAC;GAC7D,MAAM,SAAyB;IAC7B,YAAY,OAAO;IACnB,QAAQ,KAAK,YAAY,iBAAiB,IAAI,cAAc;GAC9D;GACA,KAAK,KAAK,WAAW,IAAI,IAAI,aAAa,MAAM,CAAC;GACjD,OAAO;EACT;EAEA,cAAuB;GACrB,OAAO,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,KAAK;EACjC;EAEA,MAAM,aAA4B;GAChC,KAAK,cAAc;GACnB,KAAK,QAAQ,WAAW;GACxB,KAAK,SAAS,KAAA;GACd,KAAK,UAAU,KAAA;EACjB;EAEA,MAAM,QAAQ,MAAkB,OAAgC;GAC9D,MAAM,OAAO,YAAY;IACvB,MAAM,IAAI,KAAK,SAAS;IACxB,IAAI,CAAC,GAAG,MAAM,IAAI,MAAM,mBAAmB;IAC3C,MAAM,IAAI,MAAM,MAAM,KAAK,gBAAgB;IAC3C,MAAM,MAAM,KAAK,MAAM,CAAC,CAAC;IACzB,IAAI,EAAE,WAAW,sBAAsB,MAAM,EAAE,0BAA0B,GAAG;SACvE,MAAM,EAAE,uBAAuB,GAAG;IACvC,KAAK,KAAK,iBAAiB,IAAI,IAAI,mBAAmB,IAAI,CAAC;GAC7D;GACA,IAAI,OAAO,MAAM,KAAK;QACjB,MAAM,KAAK,MAAM,aAAa,IAAI;EACzC;EAEA,UAAU;GACR,OAAO;EACT;CACF;CACA,OAAO,IAAI,kBAAkB;AAC/B;;;ACrJA,IAAa,oBAAoB;;AAEjC,IAAa,kBAAkB;;AAY/B,SAAgB,SAAiC,QAAa,MAAoC;CAChG,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,SAAS,EAAE,OAAO,MAAM,CAAC,CAAC,MAAM,MAAM,KAAK,WAAW,EAAE,MAAM,CAAC,KAAK;AACjH;;AAGA,IAAa,gBAA6B;CAAC;CAAI;CAAI;CAAI;CAAI;AAAE,CAAC,CAAC,KAAK,cAAc;CAAE,SAAS;CAAI;AAAS,EAAE;AAC5G,IAAa,uBAAkC;CAAE,SAAS;CAAI,UAAU;AAAG;;;;;;AAO3E,SAAgB,SAAS,MAAyB,OAAe,QAAgB,OAAsB,OAAA,KAAwC;CAC7I,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,OAAO,OAAO,SAAS,CAAC,CAAC;CACzD,MAAM,OAAqB,CAAC;CAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,MAAM,MAAM,IAAI,WAAW,OAAO,CAAC;EACnC,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,IAAI,SAAS,MAAM,KAC9C,IAAI,MAAM,IAAI,QAAQ,KAAK,KAAK,KAAK;GACnC,MAAM,MAAM,IAAI;GAChB,IAAI,OAAO,MAAM,UAAU,QAAQ,MAAM,MAAM,KAAK,QAAS,MAAM;EACrE;EAEF,KAAK,KAAK,GAAG;CACf;CACA,OAAO;AACT;AAkBA,IAAMA,WAAS,OAAe,IAAI,SAAS,MAAM,WAAW,GAAG,EAAE,CAAC;;;;;;;AAQlE,IAAsB,uBAAtB,MAAiE;CAyBnD;CACA;CAzBZ,4BAAoB,IAAI,IAAuC;CAC/D,YAAoB;;CAEpB,SAAmB;;CAEnB;;CAMA,WAAA;;CAEA,iBAAoD;;CAEpD,OAAiD;;CAEjD,aAAyD,KAAA;;CAIzD,MAAgB,YAA2B,CAAC;CAE5C,YACE,WACA,MACA,YACA;EAHU,KAAA,YAAA;EACA,KAAA,OAAA;EAGV,KAAK,QAAQ;EACb,UAAU,mBAAmB;GAC3B,KAAK,YAAY;GACjB,KAAK,KAAK,cAAc,CAAC,CAAC;EAC5B,CAAC;CACH;CAEA,MAAM,UAAU;EACd,KAAK,YAAY;EACjB,OAAO,EAAE,YAAY,KAAK,UAAU,KAAK;CAC3C;CAEA,MAAM,aAAa;EACjB,KAAK,YAAY;EACjB,MAAM,KAAK,UAAU,WAAW;CAClC;CAEA,cAAc;EACZ,OAAO,KAAK;CACd;CAEA,GAAG,OAAe,IAA0B;EAC1C,IAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG,KAAK,UAAU,IAAI,uBAAO,IAAI,IAAI,CAAC;EACnE,KAAK,UAAU,IAAI,KAAK,CAAC,CAAE,IAAI,EAAE;CACnC;CAEA,IAAI,OAAe,IAA0B;EAC3C,KAAK,UAAU,IAAI,KAAK,CAAC,EAAE,OAAO,EAAE;CACtC;CAEA,KAAa,OAAe,GAAY;EACtC,KAAK,UAAU,IAAI,KAAK,CAAC,EAAE,SAAS,OAAO,GAAG,CAAC,CAAC;CAClD;CAEA,iBAAiB;EACf,OAAO,CAAC;CACV;CAEA,mBAAqC;EACnC,OAAO;GACL,OAAO,KAAK;GACZ,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;GACvC,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;GACzD,KAAK;GACL,iBAAiB,KAAK;GACtB,gBAAgB,KAAK;GACrB,YAAY;GACZ,YAAY,KAAK;GACjB,gBAAgB,KAAK,KAAK,KAAK,gBAAgB,CAAC;EAClD;CACF;CAEA,mBAAkC;EAEhC,OAAO;CACT;CAEA,cAAc;EACZ,OAAO,CAAC;CACV;CAEA,MAAM,gBAAgB,CAAC;CACvB,iBAAiB,CAAC;CAClB,gBAAgB,CAAC;;CAGjB,WAAW,QAAyC;EAClD,MAAM,MAAM,OAAO,WAAW,IAAI;EAClC,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,uBAAuB;EACjD,OAAO,KAAK,WAAW,IAAI,aAAa,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,MAAM,OAAO,OAAO,OAAO,MAAM;CAC9G;;CAGA,MAAM,KAAK,OAAmB,QAAiD;EAC7E,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,SAAU;GAClC,OAAO,KAAK,QAAQ,MAAMA,QAAM,KAAK,KAAK,OAAO;GACjD,MAAM,OAAO,MAAM,SAAS,GAAG,IAAI,KAAK,KAAK;GAC7C,IAAI;IACF,MAAM,KAAK,UAAU,MAAM,IAAI;GACjC,SAAS,KAAK;IACZ,IAAI,KAAK,SAAS,IAAI,MAAM;IAC5B,KAAK,QAAQ,KAAK,IAAI,IAAI,KAAK,SAAS,CAAC;IACzC;GACF;GACA,KAAK,KAAK;GACV,SAAS,IAAI,MAAM,MAAM;GACzB,MAAMA,QAAM,KAAK,KAAK,OAAO;EAC/B;CACF;CAEA,WAAW;EACT,UAAU,YAAY;EACtB,eAAe,OAAsB,YAAoD;GACvF,MAAM,UAAU,OAAO,QAAQ,WAAW,CAAC;GAC3C,MAAM,YAAY,OAAO,QAAQ,aAAa,CAAC;GAE/C,IAAI,UAAU;GACd,OAAO;IACL,WAAW,YAAY,CAAC;IACxB,WAAW,OAAO,OAAO,WAAW,MAAM;KACxC,MAAM,cAAc,KAAK,IAAI,SAAS,OAAO,QAAQ,cAAc,OAAO,CAAC;KAE3E,IAAI,QAAQ;KACZ,MAAM,KAAK,KAAK,KAAK,IAAI,OAAuB,SAAS,KAAK,IAAI,GAAG,QAAQ,GAAG,SAAS,IAAI,UAAU;MACrG,MAAM,UAAU,KAAK,MAAM,QAAQ,GAAG;MACtC,IAAI,YAAY,OAAO;MACvB,QAAQ;MACR,KAAK,KAAK,iBAAiB;OAAE,MAAM;OAAS,YAAY,MAAM;OAAG,mBAAmB;OAAG,kBAAkB;OAAG;MAAQ,CAAC;KACvH,CAAC;KACD,MAAM,KAAK,UAAU;KACrB,WAAW,KAAK,IAAI,GAAG,QAAQ;KAC/B,KAAK,KAAK,iBAAiB;MAAE,MAAM;MAAS,YAAY,MAAM;MAAG,mBAAmB;MAAK,kBAAkB;KAAI,CAAC;IAClH;IACA,iBAAiB,YAAY,CAAC;IAC9B,UAAU,YAAY;GACxB;EACF;CACF;AACF;;AAGA,eAAsB,aACpB,QACA,QACA,KAC2B;CAC3B,MAAM,UAAU,MAAM,OAAO,kBAAkB,IAAI,OAAO;CAC1D,MAAM,KAAK,MAAM,QAAQ,kBAAkB,IAAI,KAAK;CACpD,MAAM,SAAsC,CAAC;CAC7C,IAAI,IAAI,QAAQ;EACd,MAAM,KAAK,MAAM,QAAQ,kBAAkB,IAAI,MAAM;EACrD,GAAG,iBAAiB,+BAA+B,MAAM;GACvD,MAAM,IAAK,EAAE,OAA6C;GAC1D,IAAI,GAAG,OAAO,SAAS,OAAO,GAAG,IAAI,WAAW,EAAE,OAAO,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;EAC7G,CAAC;EACD,MAAM,GAAG,mBAAmB;CAC9B;CACA,OAAO;EACL,MAAM,OAAO;EACb,OAAO,OAAO,UAAU;GACtB,MAAM,MAAM,MAAM,MAAM,CAAC,CAAC;GAC1B,IAAI,GAAG,WAAW,sBAAsB,MAAM,GAAG,0BAA0B,GAAG;QACzE,MAAM,GAAG,uBAAuB,GAAG;EAC1C;EACA,WAAW,OAAO,OAAO,KAAK,EAAE;EAChC,eAAe,OAAO,OAAO,iBAAiB,0BAA0B,EAAE;EAC1E,YAAY,YAAY,OAAO,WAAW;CAC5C;AACF;;;;;;;;;;ACtPA,IAAa,cAAc;AAC3B,IAAa,SAAS;AACtB,IAAa,SAAS;AAEtB,IAAMC,cAAAA;;;;;;AAgBN,IAAa,aAAyB;CACpC;EAAE,QAAQ;EAAQ,YAAY;CAAO;CACrC;EAAE,QAAQ;EAAQ,YAAY;CAAO;CACrC;EAAE,QAAQ;EAAQ,YAAY;EAAQ,SAAS;CAAK;CACpD;EAAE,QAAQ;EAAQ,YAAY;EAAQ,cAAc;CAAK;CACzD;EAAE,QAAQ;EAAQ,YAAY;EAAQ,cAAc;CAAK;CACzD;EAAE,QAAQ;EAAQ,YAAY;EAAQ,cAAc;CAAK;CACzD;EAAE,QAAQ;EAAQ,YAAY;EAAU,cAAc;CAAK;CAC3D;EAAE,QAAQ;EAAQ,YAAY;EAAU,cAAc;CAAK;CAC3D;EAAE,QAAQ;EAAQ,YAAY;CAAS;CACvC;EAAE,QAAQ;EAAQ,YAAY;CAAS;CACvC;EAAE,QAAQ;EAAQ,YAAY;CAAS;CACvC;EAAE,QAAQ;EAAQ,YAAY;CAAS;CACvC;EAAE,QAAQ;EAAQ,YAAY;CAAS;CACvC;EAAE,QAAQ;EAAS,YAAY;CAAS;AAC1C;;AAGA,IAAa,kBAAoD;CAC/D;EAAE,MAAM;EAAS,MAAM;CAAiB;CACxC;EAAE,MAAM;EAAY,MAAM;CAAqB;CAC/C;EAAE,MAAM;EAAqB,MAAM;CAAqB;AAC1D;AAEA,IAAa,eAAe,SAA8C,SAAS,YAAY,IAAI;AAGnG,IAAM,YAAY,MAAM,KAAK,EAAE,QAAQ,IAAI,IAAI,GAAG,MAAM;CACtD,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,IAAI,IAAI,OAAS,KAAK,IAAK,KAAQ,MAAQ,KAAK,IAAK;CACjF,OAAO;AACT,CAAC;AACD,IAAa,QAAQ,SAAoC;CACvD,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,IAAI,WAAW,IAAI,KAAK,MAAM;CACpE,OAAO;AACT;AAEA,SAAgB,OAAO,KAAa,SAAsC;CACxE,IAAI,QAAQ,SAAS,KAAM,MAAM,IAAI,MAAM,kBAAkB;CAC7D,OAAO;EAAC;EAAM;EAAM;EAAK;EAAM,QAAQ;EAAQ;EAAM,GAAG,MAAM,KAAK,OAAO;EAAG,KAAK,OAAO;EAAG;CAAI;AAClG;AAEA,IAAM,OAAO,MAAc,CAAC,IAAI,KAAO,KAAK,IAAK,GAAI;AAErD,IAAM,MAAM;CACV,MAAM;CACN,KAAK;CACL,OAAO;CACP,SAAS;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,aAAa;AACf;AAEA,IAAM,gBAAgB;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAAI;AACvF,IAAM,cAAc;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAAI;;AAGrF,IAAa,aAAa;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAAI;AAC/E,IAAa,cAAc;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAAI;;AAGhF,IAAM,SAAS;CAAC;CAAQ;CAAQ;CAAQ;CAAQ;AAAM;;AAEtD,IAAM,aAAa;;AAEnB,IAAM,cAAc;;AAGpB,IAAaC,gBAAc,MAAyB,OAAe,WAAiC,SAAS,MAAM,OAAO,QAAQ,KAAK;;AAGvI,SAAgB,SAAS,MAAoB,EAAE,SAAS,QAAQ,SAAkF;CAChJ,MAAM,MAAgB,CAAC;CACvB,MAAM,QAAQ,KAAa,YAA+B,IAAI,KAAK,GAAG,OAAO,KAAK,OAAO,CAAC;CAC1F,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC;CACnB,IAAI,OAAO,SAAS,IAAI,KAAK,EAAI;CACjC,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC;CACnB,KAAK,IAAI,SAAS,CAAC,EAAE,CAAC;CACtB,KAAK,IAAI,OAAO,CAAC,EAAE,CAAC;CACpB,KAAK,IAAI,QAAQ,IAAI,OAAO,KAAK,IAAI,OAAO,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;CAC3F,KAAK,IAAI,aAAa,CAAC,CAAC,CAAC;CACzB,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC;CACpB,KAAK,IAAI,SAAS,aAAa;CAC/B,MAAM,QAAQ,IAAI,WAAWD,WAAS;CACtC,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,IAAI,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK,KAAK,IAAI,KAAK,KAAK;EACpE,KAAK,MAAM,OAAO,MAAM,KAAK,IAAI,KAAK,GAAG;CAC3C;CACA,KAAK,IAAI,SAAS,WAAW;CAC7B,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC;CACnB,IAAI,OAAO,cAAc,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAAK,KAAK,IAAI,KAAK,KAAK;MAC5E,KAAK,IAAI,MAAM,IAAI,UAAU,CAAC;CACnC,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC;CACnB,OAAO,WAAW,KAAK,GAAG;AAC5B;;AAGA,IAAa,mBAAb,cAAsC,qBAAqB;CAM9C;CALX,gBAAmC,OAAO;CAC1C;CAEA,YACE,WACA,OACA,OAAa;EAAE,SAAS;EAAI,SAAS;CAAI,GACzC;EACA,MAAM,WAAW,MAAM,GAAG;EAHjB,KAAA,QAAA;EAIT,KAAK,YAAY,OAAO,WAAW,UAAU,QAAQ;EACrD,UAAU,UAAU,SAAS;GAC3B,IAAI,KAAK,MAAM,UAAU,GAAG,KAAK,SAAS;QACrC,IAAI,KAAK,MAAM,WAAW,GAAG,KAAK,SAAS;EAClD,CAAC;CACH;CAEA,WAAqB,MAAyB,OAAe,QAAgB;EAC3E,OAAOC,aAAW,MAAM,OAAO,MAAM;CACvC;CAEA,IAAc,MAAoB,SAAiB,QAAgB;EACjE,OAAO,SAAS,MAAM;GAAE;GAAS;GAAQ,OAAO,KAAK;EAAM,CAAC;CAC9D;AACF;AAEA,IAAM,QAAQ,GAAsB,MAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC;;;;;;;ACtJzG,IAAa,iBAAiB;AAC9B,IAAa,eAAe;;;;;;AAO5B,IAAa,gBAAgC;CAC3C;EAAE,QAAQ;EAAS,YAAY;EAAU,MAAM;CAAsB;CACrE;EAAE,QAAQ;EAAU,YAAY;CAAO;CACvC;EAAE,QAAQ;EAAQ,YAAY;CAAS;CACvC;EAAE,QAAQ;EAAU,YAAY;CAAS;CACzC;EAAE,QAAQ;EAAU,YAAY;CAAS;CACzC;EAAE,QAAQ;EAAU,YAAY;CAAS;CACzC;EAAE,QAAQ;EAAS,YAAY;CAAS;CACxC;EAAE,QAAQ;EAAS,YAAY;CAAS;AAC1C;AAEA,IAAa,kBAAkB,SAAkD,SAAS,eAAe,IAAI;AAE7G,IAAM,YAAA;;AAEN,IAAM,OAAO;;AAEb,IAAM,WAAW;;AAEjB,IAAM,aAAa;;AAGnB,IAAaC,gBAAc,MAAyB,OAAe,WAAiC,SAAS,MAAM,OAAO,QAAQ,KAAK;;AAGvI,SAAgB,UAAU,MAAoB,QAA4B;CACxE,MAAM,MAAgB,CAAC,IAAM,EAAI;CACjC,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,IAAI,IAAI,GAAG,IAAI,KAAK,IAAM,IAAM,QAAQ;EACxC,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,MAAM;GAC1C,MAAM,OAAO,KAAK,MAAM,GAAG,IAAI,IAAI;GACnC,IAAI,KAAK,IAAM,KAAM,IAAM,GAAM,YAAY,KAAM,aAAa,GAAG,KAAK,SAAS,KAAM,KAAK,UAAU,CAAC;GACvG,KAAK,MAAM,OAAO,MAAM,IAAI,KAAK,GAAG,GAAG;EACzC;CACF;CACA,IAAI,KAAK,IAAM,KAAM,UAAU;CAC/B,OAAO,WAAW,KAAK,GAAG;AAC5B;;;;;;AAOA,IAAa,sBAAb,cAAyC,qBAAqB;CAMjD;CALX,gBAAmC;CACnC;CAEA,YACE,WACA,OACA,OAAa;EAAE,SAAS;EAAI,SAAS;CAAI,GACzC;EACA,MAAM,WAAW,MAAM,GAAG;EAHjB,KAAA,QAAA;EAIT,KAAK,YAAY,OAAO,WAAW,UAAU,QAAQ;CACvD;CAEA,WAAqB,MAAyB,OAAe,QAAgB;EAC3E,OAAOA,aAAW,MAAM,OAAO,MAAM;CACvC;CAEA,IAAc,MAAoB,UAAkB,QAAgB;EAClE,OAAO,UAAU,MAAM,MAAM;CAC/B;AACF;;ACrEA,IAAa,eAAe;AAC5B,IAAa,aAAa;AAC1B,IAAa,cAAc;AAI3B,IAAM,cAAc;;;;;AAMpB,IAAa,cAA8B,CAAC;CAAE,QAAQ;CAAU,YAAY;CAAU,MAAM;AAAgB,CAAC;AAE7G,IAAa,gBAAgB,SAAkD,SAAS,aAAa,IAAI;;AAGzG,IAAa,aAA0B;CACrC,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,EAAE;CACP,CAAC,IAAI,GAAG;AACV,CAAC,CAAC,KAAK,CAAC,SAAS,eAAe;CAAE;CAAS;AAAS,EAAE;;AAGtD,IAAM,iBAAiB;;AAEvB,IAAM,SAAS;;AAGf,IAAM,aAAa;CAAE,WAAW;CAAG,YAAY;AAAE;;AAGjD,IAAa,eAAe;CAAC;CAAM;CAAM;AAAI;AAC7C,IAAM,gBAAoC;CACxC,CAAC,GAAM,uBAAuB;CAC9B,CAAC,GAAM,WAAW;CAClB,CAAC,GAAM,cAAc;CACrB,CAAC,GAAM,eAAe;AACxB;;AAGA,IAAM,KAAK,CAAC,IAAM,EAAI;;AAEtB,IAAM,iBAAiB;;AAEvB,IAAM,oBAAoB;;;;;AAM1B,SAAgB,WAAW,MAAyB,OAAe,QAA8B;CAC/F,MAAM,QAAQ,KAAK,KAAK,QAAQ,CAAC;CACjC,MAAM,OAAqB,CAAC;CAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,MAAM,MAAM,IAAI,WAAW,KAAK,CAAC,CAAC,KAAK,GAAI;EAC3C,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK,IAAI,MAAM,IAAI,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,MAAM,EAAE,QAAS,IAAI;EACnG,KAAK,KAAK,GAAG;CACf;CACA,OAAO;AACT;AAEA,IAAM,SAAS,MAAc,MAAM,KAAK,IAAI,MAAM,EAAE,WAAW,CAAC,CAAC;AACjE,IAAM,MAAM,SAAiB,GAAG,KAAK,MAAO,OAAO,cAAe,EAAE,IAAI,GAAG;;AAG3E,SAAS,aAAa,WAA2B;CAC/C,IAAI,cAAc,WAAW,YAAY,OAAO;CAChD,IAAI,cAAc,WAAW,WAAW,OAAO,SAAS,OAAO;CAC/D,OAAO,OAAO,OAAO;AACvB;;AAGA,SAAgB,QAAQ,MAAoB,EAAE,SAAS,QAAQ,aAAiF;CAC9I,MAAM,QAAQ,KAAK,EAAE,EAAE,UAAU;CACjC,MAAM,SAAS,KAAK;CAQpB,MAAM,MAAM,MAAM;EANhB,QAAQ,GAAG,QAAQ,CAAC,EAAE,GAAG,GAAG,MAAM;EAClC,aAAa,SAAS;EACtB;EACA,WAAW,KAAK,IAAI,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,CAAC;EACpE;CAEgB,CAAA,CAAK,KAAK,MAAM,GAAG,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,IAAI,cAAc,MAAM,GAAG,OAAO,IAAI;CAC3F,KAAK,MAAM,OAAO,MAAM,KAAK,MAAM,KAAK,KAAK,IAAI,KAAK,CAAC;CACvD,IAAI,KAAK,GAAG,MAAM,aAAa,KAAK,IAAI,GAAG,MAAM,EAAE,OAAO,CAAC;CAC3D,OAAO,WAAW,KAAK,GAAG;AAC5B;;;;;;AAOA,IAAa,oBAAb,cAAuC,qBAAqB;CAa/C;CAED;CAdV,gBAAmC;CACnC;CACA,WAAA;CACA,OAA0B,KAAA;CAC1B,aAAgC;CAEhC,MAAc;CACd,SAAgC;CAChC,OAAoC;CAEpC,YACE,WACA,OACA,OAAa;EAAE,SAAS;EAAI,SAAS;CAAI,GACzC,eAAuB,gBACvB;EACA,MAAM,WAAW,MAAM,GAAG;EAJjB,KAAA,QAAA;EAED,KAAA,eAAA;EAGR,KAAK,YAAY,OAAO,WAAW,UAAU,QAAQ;EACrD,UAAU,UAAU,SAAS;GAC3B,IAAI,KAAK,UAAU,KAAK,KAAK,OAAO,GAAG,MAAM,KAAK,OAAO,GAAG,IAAI,KAAK;QAChE,IAAI,KAAK,WAAW,GAAG,KAAK,SAAS,KAAK;QAC1C;GACL,KAAK,OAAO;EACd,CAAC;CACH;CAEA,WAAqB,MAAyB,OAAe,QAAgB;EAC3E,OAAO,WAAW,MAAM,OAAO,MAAM;CACvC;;CAGA,UAAU;EACR,OAAO,WAAW,KAAK,UAAU,QAAQ,KAAK;CAChD;CAEA,IAAc,MAAoB,SAAiB,QAAgB,WAAmB;EACpF,KAAK,MAAM;EACX,OAAO,QAAQ,MAAM;GAAE;GAAS;GAAQ;EAAU,CAAC;CACrD;;CAGA,MAAc,MAAM,MAAqB,IAAY;EACnD,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,OAAO,CAAC,KAAK,KAAK,KAAK,IAAI,IAAI,UAC7B,MAAM,IAAI,SAAe,YAAY;GACnC,MAAM,IAAI,WAAW,SAAS,WAAW,KAAK,IAAI,CAAC;GACnD,KAAK,cAAc,aAAa,CAAC,GAAG,QAAQ;EAC9C,CAAC;EAEH,KAAK,OAAO;CACd;;;;;;CAOA,MAAgB,YAAY;EAC1B,MAAM,KAAK,YAAY,KAAK,MAAM,GAAG,KAAK,YAAY;EACtD,IAAI,KAAK,MAAM,GAAG;EAClB,KAAK,SAAS;EACd,MAAM,KAAK,KAAK,WAAW,KAAK,YAAY,CAAC;EAC7C,MAAM,KAAK,YAAY,KAAK,WAAW,MAAM,iBAAiB;EAC9D,MAAM,QAAQ,cAAc,MAAM,CAAC,UAAU,KAAK,UAAU,KAAK,GAAG;EACpE,IAAI,OAAO,MAAM,IAAI,MAAM,MAAM,EAAE;EACnC,QAAQ,KAAK,gDAAgD;CAC/D;AACF;;;;;;;;;;ACtKA,IAAa,mBAAb,MAAoD;CAG9B;CAFpB,QAAmC;CAEnC,YAAY,KAAiC;EAAzB,KAAA,MAAA;CAA0B;CAE9C,IAAY,SAAqB;EAC/B,IAAI,CAAC,KAAK,OAAO,MAAM,IAAI,MAAM,eAAe;EAChD,OAAO,KAAK;CACd;CAEA,MAAM,UAAU;EACd,MAAM,SAAS,MAAM,eAAe;GAAC,GAAG;GAAY,GAAG;GAAe,GAAG;EAAW,CAAC,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC;EAC1G,MAAM,MAAM,YAAY,OAAO,IAAI;EACnC,MAAM,SAAS,eAAe,OAAO,IAAI;EACzC,MAAM,OAAO,aAAa,OAAO,IAAI;EACrC,KAAK,OAAO,UAAU,SAAS,OAAO,MAAM;GAC1C,MAAM,SAAS,MAAM,OAAO,KAAK,QAAQ;GACzC,MAAM,YAAY,MAAM,OAAO,mBAAmB,CAAC,CAAC,YAAY,CAAC,CAAC,EAAA,CAAG,KAAK,MAAM,EAAE,IAAI;GACtF,IAAI,OAAO,SAAS,SAAA,sCAAoB,GACtC,KAAK,QAAQ,IAAI,iBAAiB,MAAM,aAAa,QAAQ,QAAQ;IAAE,SAAS;IAAa,OAAO;IAAQ,QAAQ;GAAO,CAAC,GAAG,GAAG;QAC7H,IAAI,UAAU,SAAS,SAAA,sCAAuB,GACnD,KAAK,QAAQ,IAAI,oBAAoB,MAAM,aAAa,QAAQ,QAAQ;IAAE,SAAS;IAAgB,OAAO;GAAa,CAAC,GAAG,MAAM;QAC5H,IAAI,QAAQ,SAAS,SAAA,sCAAqB,GAC/C,KAAK,QAAQ,IAAI,kBAAkB,MAAM,aAAa,QAAQ,QAAQ;IAAE,SAAS;IAAc,OAAO;IAAY,QAAQ;GAAY,CAAC,GAAG,IAAI;GAEhJ,IAAI,KAAK,OAAO,OAAO,KAAK,MAAM,QAAQ;EAC5C;EACA,KAAK,QAAQ,gBAAgB,MAAM,KAAK,IAAI,GAAG,MAAM;EACrD,OAAO,KAAK,MAAM,QAAQ;CAC5B;CAEA,MAAM,aAAa;EACjB,MAAM,KAAK,OAAO,WAAW;CAC/B;CAEA,cAAc;EACZ,OAAO,CAAC,CAAC,KAAK,OAAO,YAAY;CACnC;CAEA,GAAG,OAAe,IAAsB;EACtC,OAAO,KAAK,OAAO,GAAG,OAAO,EAAE;CACjC;CAEA,IAAI,OAAe,IAAsB;EACvC,OAAO,KAAK,OAAO,IAAI,OAAO,EAAE;CAClC;CAEA,iBAAiB;EACf,OAAO,KAAK,OAAO,eAAe,KAAK,CAAC;CAC1C;CAEA,mBAAmB;EACjB,OAAO,KAAK,OAAO,iBAAiB;CACtC;CAEA,mBAAmB;EACjB,OAAO,KAAK,OAAO,iBAAiB;CACtC;CAEA,cAAc;EACZ,OAAO,KAAK,OAAO,YAAY,KAAK,CAAC;CACvC;CAEA,gBAAgB;EACd,OAAO,KAAK,OAAO,cAAc;CACnC;CAEA,iBAAiB;EACf,KAAK,OAAO,eAAe;CAC7B;CAEA,gBAAgB;EACd,KAAK,OAAO,cAAc;CAC5B;CAEA,qBAAqB;EACnB,OAAO,CAAC,CAAC,KAAK,OAAO,qBAAqB;CAC5C;CAEA,IAAI,WAAW;EACb,OAAO,KAAK,OAAO;CACrB;CAEA,IAAI,aAAa;EACf,OAAO,KAAK,OAAO,YAAY,KAAK,KAAK,KAAK;CAChD;CAEA,IAAI,UAAU;EACZ,OAAO,KAAK,OAAO,SAAS,KAAK,KAAK,KAAK;CAC7C;AACF;;;ACrGA,IAAa,aAAb,MAA8C;CAOxB;CANpB,YAAY;CACZ,UAAsE,CAAC;CACvE,gBAA+B;CAC/B,4BAAoB,IAAI,IAA2B;;CAGnD,YAAY,OAAwC,WAAW;EAA3C,KAAA,OAAA;CAA4C;CAEhE,MAAM,UAAU;EACd,KAAK,YAAY;EACjB,KAAK,KAAK,WAAW,EAAE,MAAM,EAAE,YAAY,eAAe,EAAE,CAAC;EAC7D,OAAO,EAAE,YAAY,eAAe;CACtC;CACA,MAAM,aAAa;EACjB,KAAK,YAAY;EACjB,KAAK,KAAK,cAAc,CAAC,CAAC;CAC5B;CACA,cAAc;EACZ,OAAO,KAAK;CACd;CACA,GAAG,OAAe,IAAc;EAC9B,IAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG,KAAK,UAAU,IAAI,uBAAO,IAAI,IAAI,CAAC;EACnE,KAAK,UAAU,IAAI,KAAK,CAAC,CAAE,IAAI,EAAE;EACjC,OAAO;CACT;CACA,IAAI,OAAe,IAAc;EAC/B,KAAK,UAAU,IAAI,KAAK,CAAC,EAAE,OAAO,EAAE;EACpC,OAAO;CACT;CACA,KAAK,OAAe,GAAY;EAC9B,KAAK,UAAU,IAAI,KAAK,CAAC,EAAE,SAAS,OAAO,GAAG,CAAC,CAAC;CAClD;CACA,kBAAkB;CAClB,iBAAiB;EACf,OAAO;GAAE,iBAAiB;GAAI,SAAS;GAAK,gBAAgB;GAAI,iBAAiB,KAAK;EAAgB;CACxG;CACA,mBAAmB;EACjB,OAAO,KAAK,QAAQ,KAAA;CACtB;CACA,mBAAmB;EACjB,OAAO,KAAK,OAAQ,WAAqB,KAAA;CAC3C;;CAEA,mBAAmB;CAEnB,cAAc;EACZ,OAAO;GACL,eAAe;IAAE,YAAY;IAAM,SAAS;IAAiB,UAAU;IAAK,WAAW;IAAI,iBAAiB;GAAE;GAC9G,WAAW,KAAK,mBAAmB;IAAE,OAAO;IAAM,YAAY;IAAI,aAAa;GAAG,IAAI,KAAA;EACxF;CACF;CACA,MAAM,gBAAgB;EACpB,OAAO,KAAK,YAAY;CAC1B;CACA,iBAAiB;EACf,KAAK,YAAY;EACjB,KAAK,KAAK,aAAa,EAAE,MAAM;GAAE,iBAAiB;GAAI,WAAW;GAAM,eAAe;EAAK,EAAE,CAAC;CAChG;CACA,YAAY;CACZ,gBAAgB;EACd,KAAK,YAAY;CACnB;;CAEA,kBAAkB;CAClB,WAAW;EACT,UAAU,YAAY;GACpB,IAAI,KAAK,kBAAkB,GAAG;IAC5B,KAAK;IACL,OAAO;GACT;GACA,OAAO;EACT;EACA,eAAe,OAAe,YAAoD;GAChF,MAAM,QAAQ,OAAO,QAAQ,cAAc,CAAC;GAC5C,OAAO;IACL,WAAW,YAAY;KACrB,IAAI,KAAK,eAAe;MACtB,MAAM,MAAM,KAAK;MACjB,KAAK,gBAAgB;MACrB,MAAM,IAAI,MAAM,GAAG;KACrB;IACF;IACA,WAAW,OAAO,OAAO,WAAW,MAAM;KACxC,KAAK,QAAQ,KAAK;MAAE;MAAO;MAAU,WAAW,KAAK;KAAU,CAAC;IAClE;IACA,iBAAiB,YAAY;KAC3B,KAAK,IAAI,OAAO,GAAG,QAAQ,OAAO,QAChC,KAAK,KAAK,iBAAiB;MAAE;MAAM,YAAY;MAAO,mBAAmB;MAAK,kBAAkB;KAAI,CAAC;IAEzG;IACA,UAAU,YAAY;GACxB;EACF;CACF;AACF;AAEA,IAAa,YAAY;CACvB,OAAO;CACP,IAAI,CAAC,GAAG;CACR,KAAK;CACL,gBAAgB;CAChB,iBAAiB;CACjB,YAAY,CAAC,CAAC;CACd,YAAY;CACZ,YAAY;CACZ,gBAAgB;AAClB;;AC3GA,IAAa,cAAwE;CACnF;EAAE,OAAO;EAAQ,OAAO;EAAoB,WAAW;CAAE;CACzD;EAAE,OAAO;EAAa,OAAO;EAAc,WAAW;CAAE;CACxD;EAAE,OAAO;EAAc,OAAO;EAAc,WAAW;CAAE;CACzD;EAAE,OAAO;EAAc,OAAO;EAAc,WAAW;CAAE;CACzD;EAAE,OAAO;EAAe,OAAO;EAAe,WAAW;CAAE;AAC7D;AAEA,IAAa,sBAAsB,MAAiD,YAAY,MAAM,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE;AAEhI,IAAa,eAAe,MAAyB,YAAY,MAAM,MAAM,EAAE,UAAU,CAAC,CAAC,CAAE;AAE7F,IAAa,aAAa,MAAyB,YAAY,MAAM,MAAM,EAAE,UAAU,CAAC,CAAC,CAAE;;AAG3F,IAAa,kBAAkB,QAA+B,aAA+C,UAAU,YAAY;;;ACOnI,IAAa,kBAAoC;CAC/C;EAAE,IAAI;EAAO,MAAM;EAAc,KAAK;EAAK,iBAAiB;EAAI,gBAAgB;EAAQ,WAAW;EAAU,YAAY;EAAG,YAAY;EAAG,gBAAgB;CAAE;CAC7J;EAAE,IAAI;EAAQ,MAAM;EAAe,KAAK;EAAK,iBAAiB;EAAI,gBAAgB;EAAQ,WAAW;EAAQ,YAAY;EAAG,YAAY;EAAG,gBAAgB;CAAE;CAC7J;EAAE,IAAI;EAAM,MAAM;EAAa,KAAK;EAAK,iBAAiB;EAAK,gBAAgB;EAAO,WAAW;EAAM,YAAY;EAAG,YAAY;EAAG,gBAAgB;CAAE;CACvJ;EAAE,IAAI;EAAO,MAAM;EAAc,KAAK;EAAK,iBAAiB;EAAK,gBAAgB;EAAO,WAAW;EAAU,YAAY;EAAG,YAAY;EAAG,gBAAgB;CAAE;AAC/J;AAEA,IAAa,qBAAqB,OAChC,gBAAgB,MAAM,MAAM,EAAE,OAAO,EAAE;;;;;;AAOzC,IAAM,cAA6E,CAAC;CAAE,OAAO;CAAW,aAAa;CAAG,MAAM;AAAS,CAAC;AAExI,SAAgB,YAAY,OAAe,iBAAqC,MAA4D;CAE1I,OADc,YAAY,MAAM,MAAM,EAAE,UAAU,UAAU,mBAAmB,MAAM,EAAE,WAChF,CAAA,EAAO,QAAQ;AACxB;;;;;;AAOA,SAAgB,eACd,MACA,aACA,QACA,iBACuB;CACvB,MAAM,OAAO,OAAO,YAAY,KAAK,OAAO,iBAAiB,WAAW,IAAI,KAAA;CAC5E,IAAI,CAAC,QAAQ,CAAC,MAAM,OAAO,UAAU;CACrC,OAAO;EACL,IAAI,QAAQ,KAAK;EACjB,MAAM,KAAK,MAAM,QAAQ,MAAM,GAAG;EAClC,KAAK,KAAK;EACV,iBAAiB,KAAK;EACtB,gBAAgB,KAAK;EACrB,WAAW;EACX,YAAY,KAAK;EACjB,YAAY,KAAK;EACjB,gBAAgB,KAAK;EACrB,GAAK,KAA2B,SAAS,YAAY,EAAE,MAAM,UAAmB,IAAI,CAAC;EACrF,GAAK,KAAsC,aAAa,EAAE,OAAQ,KAAsC,WAAW,IAAI,CAAC;CAC1H;AACF;;AAGA,IAAa,eAAe,MAA+B,EAAE,kBAAkB,EAAE,MAAO;;;;;AAMxF,SAAgB,cAAc,OAAe,QAAgB,GAA+D;CAC1H,MAAM,gBAAgB,EAAE,mBAAmB;CAC3C,MAAM,SAAS,gBAAgB,QAAQ;CACvC,MAAM,OAAO,KAAK,IAAI,QAAQ,EAAE,eAAe;CAC/C,MAAM,SAAS,KAAK,OAAO,SAAS,QAAQ,CAAC;CAC7C,OAAO,gBACH;EAAE,GAAG;EAAQ,GAAG;EAAG,OAAO;EAAM;EAAQ,SAAS,OAAO;CAAO,IAC/D;EAAE,GAAG;EAAG,GAAG;EAAQ;EAAO,QAAQ;EAAM,SAAS,OAAO;CAAO;AACrE;;;;;AAMA,SAAgB,eAAe,MAAyB,OAAe,QAAgB,GAAwE;CAC7J,MAAM,OAAO,cAAc,OAAO,QAAQ,CAAC;CAC3C,IAAI,CAAC,KAAK,SAAS,OAAO;CAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,MAAM,YAAY,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK;EACnD,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,IAAI,aAAa,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,OAAO;IACvD,IAAI,KAAK,IAAI,KAAK,QAAQ;IAC1B;GACF;GACA,IAAI,MAAM,IAAI,QAAQ,KAAK,KAAK,KAAK,OAAO;EAC9C;CACF;CACA,OAAO;AACT;;AClEA,IAAM,eAA8B;CAAE,OAAO;CAAgB,SAAS;AAAK;;AAG3E,IAAa,YAAY;AAEzB,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAC1B,IAAM,UAAU,IAAY,QAAyB,KAAK,OAAQ;;AAGlE,SAAgB,YAAY,QAA2B,IAAY,IAA+B;CAChG,KAAK,KAAK,MAAM,EAAE;CAClB,KAAK,KAAK,MAAM,EAAE;CAClB,IAAI,CAAC,MAAM,CAAC,IAAI,OAAO;CACvB,MAAM,MAAM,SAAS,cAAc,QAAQ;CAC3C,IAAI,QAAQ,OAAO;CACnB,IAAI,SAAS,OAAO;CACpB,MAAM,MAAM,IAAI,WAAW,IAAI;CAC/B,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,YAAY;CAChB,IAAI,SAAS,GAAG,GAAG,IAAI,OAAO,IAAI,MAAM;CACxC,IAAI,UAAU,QAAQ,IAAI,EAAE;CAC5B,OAAO;AACT;;AAGA,SAAgB,eAAe,QAA2B,SAA4C;CACpG,MAAM,OAAO,cAAc,OAAO,OAAO,OAAO,QAAQ,OAAO;CAC/D,IAAI,CAAC,KAAK,SAAS,OAAO;CAC1B,MAAM,MAAM,SAAS,cAAc,QAAQ;CAC3C,IAAI,QAAQ,KAAK;CACjB,IAAI,SAAS,KAAK;CAClB,MAAM,MAAM,IAAI,WAAW,IAAI;CAC/B,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,UAAU,QAAQ,KAAK,GAAG,KAAK,GAAG,KAAK,OAAO,KAAK,QAAQ,GAAG,GAAG,KAAK,OAAO,KAAK,MAAM;CAC5F,OAAO;AACT;;AAGA,SAAS,UAAU,QAA0C;CAC3D,MAAM,MAAM,OAAO,WAAW,IAAI;CAClC,IAAI,CAAC,KAAK,OAAO;CACjB,MAAM,EAAE,SAAS,IAAI,aAAa,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;CACnE,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK;CAC5D,OAAO;AACT;AAEA,IAAM,SAAS,OAAe,IAAI,SAAS,MAAM,WAAW,GAAG,EAAE,CAAC;AAElE,SAAS,YAAe,GAAe,IAAwB;CAC7D,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,IAAI,iBAAiB,uBAAO,IAAI,MAAM,2CAA2C,CAAC,GAAG,EAAE;EAC7F,EAAE,MACC,OAAO,aAAa,CAAC,GAAG,QAAQ,CAAC,KACjC,OAAO,aAAa,CAAC,GAAG,OAAO,CAAC,EACnC;CACF,CAAC;AACH;;AAGA,SAAgB,cAAc,KAAsB;CAClD,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;CAC3D,IAAI,6CAA6C,KAAK,GAAG,GAAG,OAAO;CACnE,IAAI,6CAA6C,KAAK,GAAG,GAAG,OAAO;CACnE,IAAI,WAAW,KAAK,GAAG,GAAG,OAAO;CACjC,IAAI,aAAa,KAAK,GAAG,GAAG,OAAO;CACnC,IAAI,eAAe,KAAK,GAAG,KAAK,kBAAkB,KAAK,GAAG,GAAG,OAAO;CACpE,IAAI,+CAA+C,KAAK,GAAG,GAAG,OAAO;CACrE,IAAI,qBAAqB,KAAK,GAAG,GAAG,OAAO;CAC3C,OAAO,OAAO;AAChB;;;;;AAMA,IAAa,iBAAb,MAA4B;CAWhB;CACA;CAXV,SAAoC;CACpC,SAAgC;CAChC,4BAAoB,IAAI,IAAgC;CACxD,kBAAyC;CACzC,SAAsC;;CAGtC,OAAsC;CAEtC,YACE,cACA,QACA;EAFQ,KAAA,eAAA;EACA,KAAA,SAAA;CACP;CAEH,YAA2B;EACzB,OAAO,KAAK;CACd;CAEA,UAAU,IAA4C;EACpD,KAAK,UAAU,IAAI,EAAE;EACrB,aAAa,KAAK,UAAU,OAAO,EAAE;CACvC;CAEA,IAAY,OAAqC;EAC/C,KAAK,QAAQ;GAAE,GAAG,KAAK;GAAQ,GAAG;EAAM,CAAC;CAC3C;;CAGA,QAAgB,QAA6B;EAC3C,KAAK,SAAS;EACd,KAAK,UAAU,SAAS,OAAO,GAAG,KAAK,MAAM,CAAC;CAChD;CAEA,iBAAiB,IAAyB;EACxC,KAAK,kBAAkB;EACvB,IAAI,KAAK,QAAQ,KAAK,IAAI,EAAE,SAAS,KAAK,eAAe,EAAE,CAAC;CAC9D;CAEA,iBAAgD;EAC9C,MAAM,IAAI,KAAK;EACf,MAAM,UAAU,eAAe,GAAG,iBAAiB,GAAG,GAAG,iBAAiB,GAAG,kBAAkB,KAAK,eAAe,GAAG,GAAG,eAAe,CAAC,CAAC,eAAe;EAEzJ,OAAO,WAAW,KAAK,SAAS,SAAS;GAAE,GAAG;GAAS,MAAM;EAAU,IAAI;CAC7E;CAEA,WAA2C;EAEzC,MAAM,SAAS,KAAK,QAAQ,UAAU;EACtC,IAAI,QAAQ,OAAO;GAAE,SAAS;GAAQ,QAAQ;EAAK;EACnD,MAAM,OAAO,KAAK,QAAQ,YAAY;EACtC,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,MAAM,QAAQ,KAAK,WAAW,QAAQ,KAAK,YAAY,KAAA;EACvD,MAAM,MAAM,KAAK,eAAe,aAAa,KAAK,gBAAgB,KAAA;EAClE,IAAI,CAAC,SAAS,CAAC,KAAK,OAAO,KAAA;EAC3B,OAAO;GACL,SAAS,OAAO;GAChB,UAAU,OAAO;GACjB,WAAW,OAAO,IAAI,aAAa,KAAA,KAAa,IAAI,YAAY,IAAI,KAAK,IAAI,GAAG,IAAI,YAAY,IAAI,aAAa,EAAE,IAAI,KAAA;GACvH,UAAU,OAAO,IAAI,aAAa,KAAA,KAAa,IAAI,WAAW,IAAI,IAAI,WAAW,KAAA;GACjF,SAAS,KAAK,WAAW,KAAA;GACzB,OAAO,mBAAmB,KAAK,mBAAmB,OAAO,SAAS;EACpE;CACF;CAEA,MAAM,QAAQ,MAAqC;EACjD,IAAI,KAAK,QAAQ,MAAM,KAAK,WAAW;EACvC,KAAK,OAAO;EACZ,KAAK,QAAQ;GAAE,GAAG;GAAc,OAAO;EAAa,CAAC;EACrD,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,KAAK,aAAa,IAAI;GACrC,MAAM,OAAO,MAAM,OAAO,QAAQ;GAClC,KAAK,SAAS;GACd,KAAK,IAAI,EAAE,YAAY,KAAK,WAAW,CAAC;EAC1C,SAAS,KAAK;GACZ,KAAK,SAAS;GACd,KAAK,QAAQ,YAAY;GACzB,MAAM,IAAI,MAAM,cAAc,GAAG,CAAC;EACpC;EAEA,MAAM,qBAAqB,KAAK,iBAAiB;EACjD,MAAM,eAAe,MACnB,KAAK,IAAI;GAAE,SAAS,EAAE,KAAK,mBAAmB,KAAK,OAAO;GAAS,WAAW,EAAE,KAAK;GAAW,eAAe,EAAE,KAAK;EAAc,CAAC;EACvI,MAAM,eAAe,KAAK,IAAI,EAAE,OAAO,KAAK,SAAS,EAAE,CAAC;EACxD,MAAM,cAAc,MAClB,KAAK,IAAI,EAAE,UAAU;GAAE,MAAM,EAAE;GAAM,OAAO,EAAE;GAAY,GAAI,EAAE,YAAY,KAAA,IAAY,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;EAAG,EAAE,CAAC;EAC1H,OAAO,GAAG,cAAc,YAAY;EACpC,OAAO,GAAG,aAAa,WAAW;EAClC,OAAO,GAAG,mBAAmB,MAAM;EACnC,OAAO,GAAG,iBAAiB,UAAU;EACrC,KAAK,eAAe;GAClB,OAAO,IAAI,cAAc,YAAY;GACrC,OAAO,IAAI,aAAa,WAAW;GACnC,OAAO,IAAI,mBAAmB,MAAM;GACpC,OAAO,IAAI,iBAAiB,UAAU;EACxC;EAEA,MAAM,OAAO,OAAO,iBAAiB;EACrC,KAAK,IAAI;GACP,OAAO;GACP,SAAS,OAAO,eAAe,CAAC,CAAC;GACjC,eAAe,MAAM;GACrB,SAAS,KAAK,eAAe;GAC7B,OAAO,KAAK,SAAS;EACvB,CAAC;EACD,OAAO,eAAe;CACxB;CAEA,mBAAiC;EAC/B,KAAK,SAAS;EACd,KAAK,SAAS;EACd,KAAK,SAAS;EACd,KAAK,QAAQ,YAAY;CAC3B;CAEA,MAAM,aAA4B;EAChC,MAAM,IAAI,KAAK;EACf,IAAI,CAAC,GAAG;EACR,EAAE,cAAc;EAChB,IAAI;GACF,MAAM,EAAE,WAAW;EACrB,UAAU;GACR,KAAK,iBAAiB;EACxB;CACF;;;;;;CAOA,MAAc,gBAAgB,QAAoB,MAAqB,MAAc,QAA+B;EAClH,IAAI,SAAS,UAAU,OAAO,KAAK,gBAAgB;EACnD,KAAK,IAAI,EAAE,UAAU;GAAE,MAAM;GAAG,OAAO;EAAO,EAAE,CAAC;EACjD,OAAO,CAAE,MAAM,OAAO,SAAS,SAAS,GAAI,MAAM,MAAM,iBAAiB;EACzE,KAAK,IAAI,EAAE,UAAU;GAAE,MAAM;GAAQ,OAAO;EAAO,EAAE,CAAC;CACxD;CAEA,MAAM,MAAM,QAAwC,MAAmC;EACrF,MAAM,SAAS,KAAK;EACpB,MAAM,UAAU,KAAK,OAAO;EAC5B,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,yBAAyB;EACtD,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,0BAA0B;EACxD,IAAI,KAAK,OAAO,UAAU,YAAY,MAAM,IAAI,MAAM,kBAAkB;EAExE,MAAM,QAAoB,UAAU,SAAS,SAAS;GAAE,OAAO;GAAG,MAAM,YAAY;EAAO;EAC3F,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;EAClD,MAAM,QAAQ,SAAS,KAAK,IAAI,GAAG,MAAM,KAAK;EAC9C,MAAM,UAAU,KAAK,IAAI,QAAQ,YAAY,KAAK,IAAI,QAAQ,YAAY,KAAK,MAAM,KAAK,OAAO,CAAC,CAAC;EACnG,MAAM,OAAO,OAAO,SAAS,aAAa,QAAQ,WAAW;GAC3D,YAAY;GACZ;GACA,WAAW,KAAK,aAAa;GAC7B,WAAW;GACX,sBAAsB;GACtB,iBAAiB;EACnB,CAAC;EAGD,OAAO,cAAc;EACrB,KAAK,IAAI;GAAE,OAAO;GAAY,UAAU;IAAE,MAAM;IAAG;GAAM;EAAE,CAAC;EAC5D,IAAI;GACF,MAAM,SAAS,KAAK,UAAU;IAAE,GAAG;IAAG,GAAG;GAAE;GAC3C,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,OAAO,KAAK;IACpC,MAAM,SAAS,MAAM,MAAM,KAAK,CAAC;IAEjC,MAAM,OAAO,eADG,YAAY,QAAQ,OAAO,OAAO,GAAG,QAAQ,GAAG,GAAG,OAAO,OAAO,GAAG,QAAQ,GAAG,CACnE,GAAS,OAAO;IAC5C,MAAM,QAAQ,OAAO,aAAa,MAAM,OAAO,WAAW,IAAI,IAAI,MAAM,KAAK,OAAO,MAAM,QAAQ,cAAc;IAChH,IAAI,MAAM,GAAG;KACX,QAAQ,KAAK,kBAAkB;MAC7B,SAAS,QAAQ;MACjB,MAAM,QAAQ;MACd,WAAW,QAAQ;MACnB,WAAW,QAAQ;MACnB,MAAM,GAAG,OAAO,MAAM,GAAG,OAAO;MAChC,MAAM,GAAG,KAAK,MAAM,GAAG,KAAK;MAC5B;MACA;MACA,OAAO,MAAM;MACb,KAAK,UAAU,IAAI;KACrB,CAAC;KACD,MAAM,KAAK,UAAU;IACvB;IACA,MAAM,KAAK,UAAU,OAAO,MAAM;GACpC;GACA,MAAM,YAAY,KAAK,gBAAgB,QAAQ,MAAM,QAAQ,WAAW,KAAK,GAAG,mBAAmB,QAAQ,eAAe;EAC5H,SAAS,KAAK;GACZ,MAAM,IAAI,MAAM,cAAc,GAAG,CAAC;EACpC,UAAU;GACR,IAAI;IACF,MAAM,KAAK,SAAS;GACtB,QAAQ,CAER;GACA,IAAI,KAAK,QAAQ;IACf,OAAO,eAAe;IACtB,KAAK,IAAI;KAAE,OAAO;KAAS,UAAU,KAAA;IAAU,CAAC;IAChD,OAAY,cAAc,CAAC,CAAC,WAAW,KAAK,IAAI,EAAE,OAAO,KAAK,SAAS,EAAE,CAAC,SAAS,CAAC,CAAC;GACvF;EACF;CACF;AACF;;ACzRA,IAAa,0BAAmC,OAAO,cAAc,eAAe,eAAe;AACnG,IAAa,uBAAgC,OAAO,cAAc,eAAe,YAAY;AAI7F,SAAgB,eAAe,KAAK,OAAO,cAAc,cAAc,KAAK,UAAU,WAAW,cAAc,OAAO,cAAc,cAAc,IAAI,UAAU,gBAA0B;CAExL,IAAI,mBAAmB,KAAK,EAAE,KAAM,YAAY,KAAK,EAAE,KAAK,cAAc,GAAI,OAAO;CACrF,IAAI,UAAU,KAAK,EAAE,GAAG,OAAO;CAC/B,OAAO;AACT;;;;;;AAOA,SAAgB,kBAAkB,MAAsB,WAAqB,eAAe,GAAkB;CAC5G,IAAI,SAAS,QAAQ,OAAO;CAC5B,IAAI,SAAS,cAAc,kBAAkB,IAAI,eAAe,GAAG,OAAO;CAC1E,IAAI,aAAa,OAAO,OAAO,SAAS,cAAc,+CAA+C;CACrG,IAAI,aAAa,WAAW,OAAO;CACnC,OAAO;AACT;;;;;;ACzDA,IAAM,gBAAgB,OAAO;;;;;;AAO7B,eAAsB,aAAa,MAA2C;CAC5E,IAAI,SAAS,QAAQ,OAAO,IAAI,WAAW;CAC3C,IAAI,SAAS,aAAa,OAAO,IAAI,iBAAiB,OAAO;CAE7D,OAAO,KAAI,OADO,QAAQ,GAAA,CACX,oBAAoB;AACrC;;AAWA,SAAgB,cAAc,UAAgC,CAAC,GAAmB;CAChF,OAAO,IAAI,eAAe,QAAQ,gBAAgB,cAAc,OAAO,QAAQ,eAC5E,MAAM,QAAQ,EAAA,CAAG,aAAa,aAAa,QAAQ,GAAG,SAAS,CAClE;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libniimai",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Print to NIIMBOT, TSPL, ESC/POS and cat printers from the browser over Web Bluetooth and Web Serial.",
5
5
  "keywords": [
6
6
  "niimbot",