/** * ESC/POS-Bytefolgen fuer Bondrucker — Zwilling von * `kasseneck_api/lib/src/printing/escpos/` (Flutter). * * Dieses Modul erzeugt **nur Bytes**. Es kennt keinen Transport: kein * Bluetooth, kein WLAN, kein USB, keine Verbindung. Wer die Bytes wohin * schickt, entscheidet der Aufrufer. * * Die Steuerbefehle sind wortwoertlich aus `commands.dart`, `generator.dart` * und `qrcode.dart` uebernommen. Bei ESC/POS zaehlt jedes Byte: ein * "ungefaehr richtiger" Befehl druckt Unsinn oder gar nichts. Reihenfolge und * Bytefolge deshalb nicht anpassen, ohne die Erwartungswerte in * `test/escpos.test.ts` gegen das Flutter-Vorbild neu abzugleichen. * * **Zeichenkodierung.** Bondrucker sprechen kein UTF-8, sondern eine * Codepage. Dieses Modul kodiert Text wie das Vorbild nach Latin-1 (ein Byte * je Zeichen) und meldet dem Drucker dazu die passende Codepage ueber * `ESC t n`. Vorgabe ist CP1252: dort stehen alle deutschen Umlaute an * denselben Stellen wie in Latin-1, "ä" ist also genau 0xE4 — ein Byte, nicht * zwei. Waeren es zwei (UTF-8), verschoeben sich zusaetzlich alle * Spaltenbreiten, weil die Spaltenrechnung mit Byte-Laengen arbeitet. * CP437 ist ebenfalls waehlbar — manche (Bluetooth-)Drucker ignorieren * `ESC t 16` und bleiben in CP437; dann werden die Umlaute auf ihre * CP437-Plaetze umkodiert (ä = 0x84, ü = 0x81, ß = 0xE1, ...). Zeichen ohne * CP437-Platz werden zu `?` — weiterhin ein Byte je Zeichen. * Weitere Tabellen (PC858, PC850, ISO 8859-15, reine Ersatzbuchstaben) und * ihre `ESC t`-Nummern stehen im Katalog `code-tables.ts`. * * Nicht enthalten (bewusst): Bilddekodierung (PNG/JPEG liest die Huelle), * Capability-Profile einzelner Druckermodelle, Kassenlade, 1D-Barcodes. * Fertige einfarbige Rasterbilder (Logo) gehen ueber [escPosRasterImage]. */ import { type CodeTableId } from './code-tables.js'; import { type QrModuleSize } from './qr-groesse.js'; /** Papierbreite der Bonrolle. */ export type PosPaperSize = 'mm58' | 'mm80'; /** Ausrichtung einer Textzeile bzw. innerhalb einer Spalte. */ export type PosAlign = 'left' | 'center' | 'right'; /** Druckerschrift: A ist breiter, B schmaler (mehr Zeichen je Zeile). */ export type PosFont = 'fontA' | 'fontB'; /** Voller oder teilweiser Papierschnitt. */ export type PosCutMode = 'full' | 'partial'; /** * Vom Drucker zu verwendende Codepage: eine Tabelle des Katalogs * (`CODE_TABLES`) oder einer der alten Namen. `CP1252` entspricht `wpc1252`, * `CP437` entspricht `pc437`. */ export type PosCodeTable = 'CP437' | 'CP1252' | CodeTableId; /** Zeichenvergroesserung (1 = einfach, 2 = doppelt, ... bis 8). */ export type PosTextSize = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; /** Modulgroesse des QR-Codes (Punkte je Modul). */ export type QrSize = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; /** Fehlerkorrekturstufe des QR-Codes. */ export type QrCorrection = 'L' | 'M' | 'Q' | 'H'; /** * Fertig gerechnetes QR-Raster, quadratisch, `true` = schwarzes Modul — * **ohne** Ruhezone, die setzt der Drucker-Weg selbst. * * Dieses Paket rastert bewusst nicht selbst (keine Bildverarbeitung, siehe * Kopf dieser Datei); wer den Bildweg braucht, bringt sein eigenes Raster mit. */ export type QrMatrix = readonly (readonly boolean[])[]; /** Textstil. Fehlende Felder gelten als Vorgabewert (nicht als "unveraendert"). */ export interface PosStyles { bold?: boolean; reverse?: boolean; underline?: boolean; turn90?: boolean; align?: PosAlign; height?: PosTextSize; width?: PosTextSize; fontType?: PosFont; codeTable?: PosCodeTable; } /** * Eine Spalte einer Zeile. `width` ist ein Zwoelftel-Anteil (1..12), die * Breiten aller Spalten einer Zeile muessen zusammen 12 ergeben. * Entweder `text` **oder** `textEncoded` angeben, nicht beides. */ export interface PosColumn { text?: string; textEncoded?: Uint8Array; width: number; styles?: PosStyles; } export interface EscPosOptions { /** Papierbreite, Vorgabe `mm58`. */ paperSize?: PosPaperSize; /** Globale Codepage, Vorgabe `CP1252`; `null` laesst die des Druckers stehen. */ codeTable?: PosCodeTable | null; /** Abstand zwischen Spalten in Punkten, Vorgabe 5. */ spaceBetweenRows?: number; } export interface EscPosTextOptions { styles?: PosStyles; /** Zusaetzliche Leerzeilen nach dem Text (der eine Zeilenumbruch kommt immer). */ linesAfter?: number; } export interface EscPosHrOptions { /** Fuellzeichen, Vorgabe `-`. */ ch?: string; /** Laenge in Zeichen; ohne Angabe die volle Zeilenbreite. */ len?: number; linesAfter?: number; } export interface EscPosQrOptions { align?: PosAlign; /** * Feste Modulgroesse in Druckpunkten. Gesetzt schaltet sie die Rechnung ab — * fuer Aufrufer, die genau wissen, was ihr Geraet kann. Ohne sie entscheidet * die Regel in `qr-groesse.ts`. */ size?: QrSize; /** Fehlerkorrektur; Vorgabe `M` wie auf allen Druckwegen (siehe `qrCodeBytes`). */ correction?: QrCorrection; /** Deckel fuer die gerechnete Modulgroesse; wirkungslos neben `size`. */ moduleSize?: QrModuleSize; /** Modell 1 des Symbols ausdruecklich waehlen — siehe `qrCodeBytes`. */ model1?: boolean; } export interface EscPosQrRasterOptions { align?: PosAlign; /** Feste Punkte je Modul; ohne Angabe gerechnet (mindestens 1). */ dotsPerModule?: number; /** Deckel fuer die gerechnete Groesse. */ moduleSize?: QrModuleSize; /** Ruhezone je Seite in Modulen; Vorgabe `QR_QUIET_ZONE_MODULES` (4). */ quietZoneModules?: number; } /** Interner Stilzustand — immer vollstaendig, nie teilbefuellt. */ interface AktuelleStile { bold: boolean; reverse: boolean; underline: boolean; turn90: boolean; align: PosAlign; height: PosTextSize; width: PosTextSize; fontType: PosFont | null; codeTable: PosCodeTable | null; } /** * Aufbauender Puffer. Die Felder ausser `paperSize`/`spaceBetweenRows` sind * interner Zustand des Bytestroms — nicht von aussen aendern, sondern ueber * die `escPos*`-Funktionen. */ export interface EscPosDocument { readonly paperSize: PosPaperSize; readonly spaceBetweenRows: number; /** * Der Beleg ging ohne QR hinaus — `null`, solange alles in Ordnung ist. * Gesetzt, wenn das Symbol auch mit der Ausnahmegroesse nicht auf das Papier * passt und kein Bildweg mitgegeben wurde. Auf einer oesterreichischen Kassa * ist der QR die maschinenlesbare Signatur: das muss der Aufrufer erfahren, * ohne den Bytestrom zu durchsuchen. */ qrError: string | null; /** * Der QR steht auf dem Papier, aber nicht so wie eingestellt — `null`, * solange nichts abgewichen ist. Zwei Faelle, beide mit Grund im Text: das * Symbol war fuer den nativen Befehl zu breit und ging als Bild hinaus, oder * es passte nur unter der Mindest-Modulgroesse. * * Getrennt von `qrError`, weil die Handlung eine andere ist: dort heisst es * "Beleg ohne QR, nachdrucken oder elektronisch ausgeben", hier "gedruckt, * aber der eingestellte Weg taugt fuer dieses Geraet nicht". */ qrFallback: string | null; /** intern: bisher erzeugte Bytes */ readonly bytes: number[]; /** intern: global gesetzte Codepage (ueberlebt `escPosReset`) */ globalCodeTable: PosCodeTable | null; /** intern: global gesetzte Schrift (ueberlebt `escPosReset`) */ globalFont: PosFont | null; /** intern: Zeichen je Zeile aus der globalen Schrift */ maxCharsPerLine: number | null; /** intern: zuletzt an den Drucker gesendeter Stil */ styles: AktuelleStile; } /** * Kodiert Text so, wie ihn der Drucker erwartet: ein Byte je Zeichen in der * gewaehlten Tabelle (`encodeForCodeTable`). Ein Umlaut wird damit zu genau * einem Byte -- Voraussetzung dafuer, dass die Spaltenrechnung stimmt. * * Die alten Namen `CP1252` und `CP437` bleiben streng: ein Zeichen ausserhalb * Latin-1 wird gemeldet statt verstuemmelt (vorher `escPosPrintableText` * anwenden). `CP1252` gibt Latin-1 byte-gleich wie bisher aus (auch ein * C1-Steuerzeichen roh, das `wpc1252` zu `?` macht); `CP437` hat die Bytes * von `pc437`. Die Tabellen des Katalogs wandeln dagegen jedes Zeichen um * (Ersatzbuchstaben, sonst `?`). */ export declare function encodeEscPosText(text: string, codeTable?: PosCodeTable): Uint8Array; /** Zeichen je Zeile fuer Papier und Schrift (`fontA`, wenn nichts gesetzt ist). */ export declare function escPosMaxCharsPerLine(paperSize: PosPaperSize, font?: PosFont | null): number; /** Legt einen leeren Bytepuffer an. Der erste Befehl ist ueblicherweise `escPosReset`. */ export declare function createEscPosDocument(options?: EscPosOptions): EscPosDocument; /** Die bisher erzeugten Bytes als Kopie. */ export declare function escPosBytes(doc: EscPosDocument): Uint8Array; /** * `ESC @`: Drucker initialisieren und den gemerkten Stil verwerfen. Danach * werden globale Codepage und Schrift erneut gesendet — der Drucker vergisst * sie beim Initialisieren. */ export declare function escPosReset(doc: EscPosDocument): void; /** * `GS L` + `GS W`: linker Rand auf 0, Druckbereich auf die Breite des Blatts. * * **Warum das noetig ist.** `ESC a 1` mittelt nicht im Blatt, sondern im * Druckbereich des *Geraets*. Steht das Blatt auf 58 mm (32 Zeichen) und * haengt ein 80-mm-Drucker daran, setzt er den Text in die linken 384 Punkte, * mittelt QR, Logo und Marke aber in seinen 576 -- am Papier sitzt dann alles * Bildhafte gegenueber dem Text nach rechts gerueckt. Am Geraet nachgestellt * und bestaetigt: mit diesen acht Bytes im Vorspann steht beides buendig. * * Der Befehl beschreibt also nicht das Papier, sondern das Blatt: er sagt dem * Drucker, welche Flaeche gemeint ist, statt ihn raten zu lassen. Passt das * Geraet ohnehin zum Blatt (der Regelfall), ist es sein eigener Vorgabewert * und aendert am Druckbild nichts. * * Es ist dieselbe Fehlerklasse wie in 0.24.0 und 0.25.0: eine Ausrichtung, * die sich am falschen Bezug orientiert. */ export declare function escPosSetDruckbereich(doc: EscPosDocument): void; /** * `ESC t n`: Codepage waehlen. Sie gilt bis zum naechsten `escPosReset` und * wird von diesem erneut gesendet. `null` schaltet die Vorgabe ab; dann * bleibt die Codepage des Druckers stehen und Umlaute werden unlesbar. */ export declare function escPosSetGlobalCodeTable(doc: EscPosDocument, codeTable: PosCodeTable | null): void; /** * `ESC M n`: Schrift waehlen. Die Schrift bestimmt auch, wie viele Zeichen in * eine Zeile passen (32/42 bei 58 mm, 48/64 bei 80 mm). */ export declare function escPosSetGlobalFont(doc: EscPosDocument, font: PosFont | null, options?: { maxCharsPerLine?: number; }): void; /** * Sendet die Stilbefehle, die sich gegenueber dem zuletzt gesendeten Stand * geaendert haben. Fehlende Felder in [styles] gelten als Vorgabe: wer fett * weglaesst, schaltet fett also ab. * * `atLineStart` (Vorgabe `true`) sagt, ob dieser Aufruf am Anfang einer * Druckzeile steht. Nur dort nimmt der Drucker `ESC a` (Ausrichtung) * ueberhaupt an; mitten in der Zeile verwirft er den Befehl wortlos. Der * Bytestrom bekommt ihn trotzdem -- die Bytefolge soll sich dadurch nicht * aendern --, aber der intern gemerkte Zustand darf sich NICHT auf den neuen * Wert stellen: sonst haelt eine spaetere, echte Zeile die Ausrichtung * faelschlich schon fuer gesetzt und unterlaesst den Befehl. */ export declare function escPosSetStyles(doc: EscPosDocument, styles?: PosStyles, options?: { atLineStart?: boolean; }): void; /** * Wortweiser Umbruch von (kodierten) Text-Bytes auf hoechstens `max` Zeichen * je Zeile: geschnitten wird am letzten Leerzeichen innerhalb der Grenze * (das Leerzeichen selbst faellt weg); gibt es keines, hart bei `max` -- * sonst kaeme die Zeile nie voran. Ein Bondrucker bricht sonst hart mitten * im Wort ("Kleinunter|nehmer", "0,7|9"); das soll nie aufs Papier. */ export declare function escPosWrapLines(textBytes: Uint8Array, max: number): Uint8Array[]; /** * Dieselbe Regel fuer Zeichenketten -- fuer Bildschirm-Vorschauen, die das * Papier nachstellen, und fuer das Zeichenraster. Zusaetzlich: * - geschuetztes Leerzeichen (U+00A0) ist kein Umbruchpunkt ("je 0,79" bleibt * zusammen) und wird als normales Leerzeichen ausgegeben; * - ein ueberlanges Wort bricht nach einem Bindestrich, wenn es einen hat * ("Bio-" / "Dinkelvollkornsemmel"), sonst hart. */ export declare function wrapText(text: string, max: number): string[]; /** * Druckt Text und schliesst die Zeile ab. Nach dem Text folgt immer genau ein * Zeilenumbruch, `linesAfter` haengt weitere an. */ export declare function escPosText(doc: EscPosDocument, text: string, options?: EscPosTextOptions): void; /** [n] Zeilenumbrueche (0x0A) — nicht der Vorschubbefehl, sondern echte Umbrueche. */ export declare function escPosEmptyLines(doc: EscPosDocument, n: number): void; /** `ESC d n`: Papier um [n] Zeilen vorschieben. */ export declare function escPosFeed(doc: EscPosDocument, n: number): void; /** * Papierschnitt: fuenf Leerzeilen (damit der Text ueber der Schneide steht) * und danach `GS V 0` bzw. `GS V 1`. */ export declare function escPosCut(doc: EscPosDocument, mode?: PosCutMode): void; /** Trennlinie ueber die volle Zeilenbreite (oder [len] Zeichen). */ export declare function escPosHr(doc: EscPosDocument, options?: EscPosHrOptions): void; /** * Druckt eine Zeile aus bis zu zwoelf Spalten. Die Breiten (Zwoelftel) muessen * zusammen 12 ergeben. Passt der Inhalt einer Spalte nicht, laeuft der Rest in * eine Folgezeile. */ export declare function escPosRow(doc: EscPosDocument, columns: readonly PosColumn[]): void; /** * Bytes des nativen QR-Befehls (`GS ( k`, Funktionen 167/169/180/182/181) — * ohne Ausrichtung, ohne Zeilenumbruch. Der Drucker zeichnet den Code selbst; * es wird kein Bild uebertragen. * * Bei Kasseneck-Belegen steht hier der maschinenlesbare RKSV-Code. * * Fehlerkorrektur ohne Angabe: **M** (bis 0.13: L). Die Groessenrechnung * (`qrModuleCount`), das Blatt (Anteil am Bildschirm und im PDF), der * ePOS-Weg (`level_m`) und der Bildweg rechnen alle mit M — druckte der * native Befehl mit L, kaeme fuer manche Nutzlasten ein kleineres Symbol * heraus als der Bildschirm zeigt. */ export declare function qrCodeBytes(text: string, options?: { size?: QrSize; correction?: QrCorrection; model1?: boolean; }): Uint8Array; /** * Setzt die Ausrichtung und haengt den nativen QR-Befehl an — mit * **gerechneter** Modulgroesse, sofern `options.size` sie nicht festlegt. * * Ohne `size` entscheidet die Regel aus `qr-groesse.ts`: so gross wie * moeglich, gedeckelt durch `options.groesse` (Vorgabe `auto` = hoechstens 6 * Punkte je Modul, wie im Flutter-Zwilling). Passt das Symbol auch mit der * Ausnahmegroesse nicht aufs Papier, geht **kein** QR-Befehl hinaus und * `doc.qrFehler` sagt warum: der Drucker schneidet ein zu breites Symbol * nicht ab, er laesst es weg — ein Befehl, von dem man weiss, dass er nichts * druckt, taeuschte nur einen Ausdruck vor. Den Bildweg kennt diese Ebene * nicht; den faehrt `escPosQrRaster` bzw. der Belegweg in `layout-escpos.ts`. * * Eine leere Nutzlast geht unveraendert durch (fester Bestandsweg, keine * Rechnung): sie ist ein Datenfehler, kein Papierfehler, und der Aufrufer * behandelt sie ohnehin schon. */ export declare function escPosQrCode(doc: EscPosDocument, text: string, options?: EscPosQrOptions): void; /** * Der Bildweg: ein fertiges QR-Raster als Rasterbild (`GS v 0`) — der * Notausgang, wenn der native Befehl das Symbol nicht auf das Papier bringt. * * Ein Pflichtbeleg ohne QR ist der schlechteste aller Ausgaenge; als Bild * geht jedes Symbol hinaus, weil die Punkte je Modul hier bis auf 1 fallen * duerfen. Dafuer ist das Bild langsamer und auf manchen Geraeten blasser. * * Das Raster selbst kommt vom Aufrufer (siehe `QrMatrix`): dieses Paket * rechnet keine QR-Codes und verarbeitet keine Bilder. */ export declare function escPosQrRaster(doc: EscPosDocument, matrix: QrMatrix, options?: EscPosQrRasterOptions): void; /** * Punkte je Modul, mit denen `escPosQrRaster` ein Raster dieser Groesse auf * dieses Papier bringt — dieselbe Rechnung, nur ohne Untergrenze 3: ein Bild * darf bis auf einen Punkt je Modul schrumpfen und passt darum immer. */ export declare function qrRasterDots(paperSize: PosPaperSize, moduleCount: number, options?: { moduleSize?: QrModuleSize; quietZoneModules?: number; }): number; /** * Einfarbiges Rasterbild in Druckpunkten, zeilenweise: `punkte[y * breite + x]`, * 1 = schwarz. Entsteht fuer das Firmenlogo in `rasterizeLogo` (receipt/bild.ts). */ export interface RasterImage { readonly width: number; readonly height: number; readonly dots: Uint8Array; } /** Die Rasterzeilen als Bytes: MSB zuerst, jede Zeile auf volle Bytes aufgefuellt. */ export declare function rasterRowsBytes(image: RasterImage): Uint8Array; /** * Die Rasterzeilen als Base64 -- ohne Buffer, laeuft im Browser und in Node. * Eine Stelle fuer ePOS-`` und den Druckjob an den Server. */ export declare function rasterRowsBase64(image: RasterImage): string; /** * Ein Rasterbild (`GS v 0`) -- das Firmenlogo am Bon. * * Anders als `escPosQrRaster` folgt KEIN Zeilenvorschub: `GS v 0` schiebt das * Papier um die Bildhoehe, ein LF danach waere eine zusaetzliche Leerzeile, * die Bildschirm und PDF nicht haben. Die Luft um das Logo kommt aus dem Blatt. */ export declare function escPosRasterImage(doc: EscPosDocument, image: RasterImage, options?: { align?: PosAlign; }): void; export {};