@kreiseck/kasseneck-api 0.13.2 → 0.14.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.
Files changed (146) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +31 -5
  3. package/dist/cjs/client/receipts.d.ts +7 -0
  4. package/dist/cjs/client/receipts.js +1 -0
  5. package/dist/cjs/kasse/drucker.d.ts +5 -0
  6. package/dist/cjs/kasse/drucker.js +9 -0
  7. package/dist/cjs/kasse/settings.js +5 -2
  8. package/dist/cjs/printing/escpos.d.ts +38 -4
  9. package/dist/cjs/printing/escpos.js +87 -15
  10. package/dist/cjs/printing/index.d.ts +2 -2
  11. package/dist/cjs/printing/index.js +5 -1
  12. package/dist/cjs/printing/qr-groesse.d.ts +26 -21
  13. package/dist/cjs/printing/qr-groesse.js +22 -8
  14. package/dist/cjs/react/index.d.ts +53 -0
  15. package/dist/cjs/react/index.js +81 -0
  16. package/dist/cjs/receipt/bild.d.ts +14 -0
  17. package/dist/cjs/receipt/bild.js +65 -0
  18. package/dist/cjs/receipt/blatt.d.ts +107 -0
  19. package/dist/cjs/receipt/blatt.js +137 -0
  20. package/dist/cjs/receipt/epos.d.ts +19 -8
  21. package/dist/cjs/receipt/epos.js +39 -20
  22. package/dist/cjs/receipt/grid.d.ts +1 -1
  23. package/dist/cjs/receipt/grid.js +9 -1
  24. package/dist/cjs/receipt/index.d.ts +4 -2
  25. package/dist/cjs/receipt/index.js +17 -1
  26. package/dist/cjs/receipt/layout-escpos.d.ts +31 -3
  27. package/dist/cjs/receipt/layout-escpos.js +58 -18
  28. package/dist/esm/client/receipts.d.ts +7 -0
  29. package/dist/esm/client/receipts.js +1 -0
  30. package/dist/esm/kasse/drucker.d.ts +5 -0
  31. package/dist/esm/kasse/drucker.js +9 -0
  32. package/dist/esm/kasse/settings.js +5 -2
  33. package/dist/esm/printing/escpos.d.ts +38 -4
  34. package/dist/esm/printing/escpos.js +84 -15
  35. package/dist/esm/printing/index.d.ts +2 -2
  36. package/dist/esm/printing/index.js +2 -2
  37. package/dist/esm/printing/qr-groesse.d.ts +26 -21
  38. package/dist/esm/printing/qr-groesse.js +21 -8
  39. package/dist/esm/react/index.d.ts +53 -0
  40. package/dist/esm/react/index.js +80 -1
  41. package/dist/esm/receipt/bild.d.ts +14 -0
  42. package/dist/esm/receipt/bild.js +62 -0
  43. package/dist/esm/receipt/blatt.d.ts +107 -0
  44. package/dist/esm/receipt/blatt.js +128 -0
  45. package/dist/esm/receipt/epos.d.ts +19 -8
  46. package/dist/esm/receipt/epos.js +40 -22
  47. package/dist/esm/receipt/grid.d.ts +1 -1
  48. package/dist/esm/receipt/grid.js +9 -1
  49. package/dist/esm/receipt/index.d.ts +4 -2
  50. package/dist/esm/receipt/index.js +4 -2
  51. package/dist/esm/receipt/layout-escpos.d.ts +31 -3
  52. package/dist/esm/receipt/layout-escpos.js +58 -20
  53. package/fixtures/erwartet/karte-eigener.blatt32.json +159 -0
  54. package/fixtures/erwartet/karte-eigener.blatt48.json +159 -0
  55. package/fixtures/erwartet/karte-gptom-ios.blatt32.json +231 -0
  56. package/fixtures/erwartet/karte-gptom-ios.blatt48.json +231 -0
  57. package/fixtures/erwartet/karte-gptom.blatt32.json +231 -0
  58. package/fixtures/erwartet/karte-gptom.blatt48.json +231 -0
  59. package/fixtures/erwartet/karte-hobex-cloud.blatt32.json +201 -0
  60. package/fixtures/erwartet/karte-hobex-cloud.blatt48.json +201 -0
  61. package/fixtures/erwartet/karte-hobex-hps.blatt32.json +225 -0
  62. package/fixtures/erwartet/karte-hobex-hps.blatt48.json +225 -0
  63. package/fixtures/erwartet/karte-mypos.blatt32.json +255 -0
  64. package/fixtures/erwartet/karte-mypos.blatt48.json +243 -0
  65. package/fixtures/erwartet/karte-stripe-eps.blatt32.json +195 -0
  66. package/fixtures/erwartet/karte-stripe-eps.blatt48.json +195 -0
  67. package/fixtures/erwartet/karte-stripe.blatt32.json +213 -0
  68. package/fixtures/erwartet/karte-stripe.blatt48.json +213 -0
  69. package/fixtures/erwartet/karte-sumup.blatt32.json +219 -0
  70. package/fixtures/erwartet/karte-sumup.blatt48.json +207 -0
  71. package/fixtures/erwartet/langer-artikelname.blatt32.json +177 -0
  72. package/fixtures/erwartet/langer-artikelname.blatt48.json +171 -0
  73. package/fixtures/erwartet/logo-probe-hoch.raster32.txt +120 -0
  74. package/fixtures/erwartet/logo-probe.raster32.txt +54 -0
  75. package/fixtures/erwartet/null-ausfall.blatt32.json +207 -0
  76. package/fixtures/erwartet/null-ausfall.blatt48.json +189 -0
  77. package/fixtures/erwartet/null-ausfall.grid32.txt +2 -0
  78. package/fixtures/erwartet/null-ausfall.grid48.txt +2 -0
  79. package/fixtures/erwartet/null-jahr.blatt32.json +213 -0
  80. package/fixtures/erwartet/null-jahr.blatt48.json +189 -0
  81. package/fixtures/erwartet/null-jahr.grid32.txt +2 -0
  82. package/fixtures/erwartet/null-jahr.grid48.txt +2 -0
  83. package/fixtures/erwartet/null-monat.blatt32.json +207 -0
  84. package/fixtures/erwartet/null-monat.blatt48.json +189 -0
  85. package/fixtures/erwartet/null-monat.grid32.txt +2 -0
  86. package/fixtures/erwartet/null-monat.grid48.txt +2 -0
  87. package/fixtures/erwartet/null-pruef.blatt32.json +207 -0
  88. package/fixtures/erwartet/null-pruef.blatt48.json +189 -0
  89. package/fixtures/erwartet/null-pruef.grid32.txt +2 -0
  90. package/fixtures/erwartet/null-pruef.grid48.txt +2 -0
  91. package/fixtures/erwartet/null-schluss.blatt32.json +207 -0
  92. package/fixtures/erwartet/null-schluss.blatt48.json +189 -0
  93. package/fixtures/erwartet/null-schluss.grid32.txt +2 -0
  94. package/fixtures/erwartet/null-schluss.grid48.txt +2 -0
  95. package/fixtures/erwartet/null-start.blatt32.json +207 -0
  96. package/fixtures/erwartet/null-start.blatt48.json +189 -0
  97. package/fixtures/erwartet/null-start.grid32.txt +2 -0
  98. package/fixtures/erwartet/null-start.grid48.txt +2 -0
  99. package/fixtures/erwartet/rabatt-chef-trinkgeld.blatt32.json +213 -0
  100. package/fixtures/erwartet/rabatt-chef-trinkgeld.blatt48.json +207 -0
  101. package/fixtures/erwartet/rabatt-einfach.blatt32.json +183 -0
  102. package/fixtures/erwartet/rabatt-einfach.blatt48.json +177 -0
  103. package/fixtures/erwartet/rabatt-trinkgeld.blatt32.json +213 -0
  104. package/fixtures/erwartet/rabatt-trinkgeld.blatt48.json +207 -0
  105. package/fixtures/erwartet/rabatt-wertgutschein.blatt32.json +219 -0
  106. package/fixtures/erwartet/rabatt-wertgutschein.blatt48.json +213 -0
  107. package/fixtures/erwartet/rabattzeilen.blatt32.json +201 -0
  108. package/fixtures/erwartet/rabattzeilen.blatt48.json +195 -0
  109. package/fixtures/erwartet/signaturausfall-verkauf.blatt32.json +195 -0
  110. package/fixtures/erwartet/signaturausfall-verkauf.blatt48.json +183 -0
  111. package/fixtures/erwartet/storno-rabatt.blatt32.json +249 -0
  112. package/fixtures/erwartet/storno-rabatt.blatt48.json +237 -0
  113. package/fixtures/erwartet/storno-rabatt.grid32.txt +2 -0
  114. package/fixtures/erwartet/storno-rabatt.grid48.txt +2 -0
  115. package/fixtures/erwartet/storno-teil.blatt32.json +207 -0
  116. package/fixtures/erwartet/storno-teil.blatt48.json +201 -0
  117. package/fixtures/erwartet/storno-teil.grid32.txt +2 -0
  118. package/fixtures/erwartet/storno-teil.grid48.txt +2 -0
  119. package/fixtures/erwartet/storno-voll.blatt32.json +219 -0
  120. package/fixtures/erwartet/storno-voll.blatt48.json +213 -0
  121. package/fixtures/erwartet/storno-voll.grid32.txt +2 -0
  122. package/fixtures/erwartet/storno-voll.grid48.txt +2 -0
  123. package/fixtures/erwartet/testkasse-verkauf.blatt32.json +225 -0
  124. package/fixtures/erwartet/testkasse-verkauf.blatt48.json +219 -0
  125. package/fixtures/erwartet/testkasse-verkauf.grid32.txt +5 -1
  126. package/fixtures/erwartet/testkasse-verkauf.grid48.txt +5 -1
  127. package/fixtures/erwartet/testsignatur-verkauf.blatt32.json +237 -0
  128. package/fixtures/erwartet/testsignatur-verkauf.blatt48.json +219 -0
  129. package/fixtures/erwartet/testsignatur-verkauf.grid32.txt +5 -1
  130. package/fixtures/erwartet/testsignatur-verkauf.grid48.txt +5 -1
  131. package/fixtures/erwartet/training.blatt32.json +237 -0
  132. package/fixtures/erwartet/training.blatt48.json +219 -0
  133. package/fixtures/erwartet/training.grid32.txt +2 -0
  134. package/fixtures/erwartet/training.grid48.txt +2 -0
  135. package/fixtures/erwartet/verkauf-bar.blatt32.json +177 -0
  136. package/fixtures/erwartet/verkauf-bar.blatt48.json +171 -0
  137. package/fixtures/erwartet/verkauf-karte.blatt32.json +177 -0
  138. package/fixtures/erwartet/verkauf-karte.blatt48.json +171 -0
  139. package/fixtures/erwartet/verkauf-kleinunternehmer.blatt32.json +183 -0
  140. package/fixtures/erwartet/verkauf-kleinunternehmer.blatt48.json +177 -0
  141. package/fixtures/hobex-hps-codes.json +1 -1
  142. package/fixtures/kasse-settings-standard.json +1 -1
  143. package/fixtures/kasse-texte.json +1 -1
  144. package/fixtures/manifest.json +109 -43
  145. package/fixtures/oberflaeche.json +1 -1
  146. package/package.json +1 -1
@@ -10,26 +10,21 @@ import type { PosPaperSize, QrSize } from './escpos.js';
10
10
  * und ein 58-mm-Kopf hat 384. Zu breit heisst bei den meisten Geraeten nicht
11
11
  * "abgeschnitten", sondern **gar kein QR** — auf einem Pflichtbeleg der
12
12
  * schlechteste aller Ausgaenge. (Im Flutter-Zwilling war genau das der
13
- * belegte Fehler; dieses Paket druckt seit jeher mit vier Punkten und blieb
14
- * darum verschont. Die Regel ist trotzdem dieselbe: sie deckelt auch eine
15
- * ausdruecklich gewaehlte groessere Modulgroesse gegen den Papierrand ab.)
13
+ * belegte Fehler.) Die Regel deckelt darum auch eine ausdruecklich gewaehlte
14
+ * groessere Modulgroesse gegen den Papierrand ab.
16
15
  */
17
16
  /**
18
17
  * Wie gross die Module des Beleg-QR werden duerfen — ein **Deckel**, keine
19
18
  * Vorgabe: gedruckt wird immer die groesste Groesse, die noch aufs Papier
20
19
  * passt, hoechstens aber diese hier.
21
20
  *
22
- * `auto` und `klein` decken beide bei 4 — das ist kein Versehen. 4 ist der
23
- * Wert, den der native Weg dieses Pakets seit jeher druckt
24
- * (`qrCodeBytes(..., size ?? 4)`); ohne diesen Deckel bekaeme jedes Geraet ab
25
- * sofort ungefragt einen groesseren QR als gestern. `auto` heisst also
26
- * "rechne, aber aendere den Bestand nicht".
27
- *
28
- * **Abweichung zum Flutter-Zwilling, bewusst:** dort deckelt `auto` bei 6,
29
- * weil dort 6 der Bestandswert ist. Die Regel ist in beiden Paketen
30
- * dieselbe — "so gross wie moeglich, hoechstens der Bestandswert" —, nur der
31
- * Bestandswert unterscheidet sich. `mittel` (6) und `gross` (8) sind in
32
- * beiden Paketen gleich; sie waehlt ein Mensch.
21
+ * `auto` heisst "gerechnet, hoechstens 6 Punkte je Modul" — in diesem Paket
22
+ * und im Flutter-Zwilling dasselbe. Bis 0.13 deckelte `auto` hier bei 4 (dem
23
+ * alten Wert des nativen ESC/POS-Wegs), in Dart bei 6; derselbe Beleg kam
24
+ * dadurch je nach Paket verschieden gross aus dem Drucker. Einheitlich gilt
25
+ * jetzt 6: der Wert von Dart und vom Epson-Weg, und ein groesseres Modul
26
+ * liest sich besser. `klein` (4) druckt den QR so gross wie frueher `auto`;
27
+ * `mittel` (6) und `gross` (8) waehlt ein Mensch.
33
28
  */
34
29
  export type QrModulGroesse = 'auto' | 'klein' | 'mittel' | 'gross';
35
30
  /** Groesste Modulgroesse in Druckpunkten, die der jeweilige Deckel zulaesst. */
@@ -82,19 +77,29 @@ export declare const QR_DRUCK_PUNKTE: Readonly<Record<PosPaperSize, number>>;
82
77
  /**
83
78
  * Modulanzahl, die `nutzlast` bei Fehlerkorrektur **M** braucht.
84
79
  *
85
- * Warum M, obwohl der native Befehl mit L druckt: M braucht bei gleicher
86
- * Nutzlast gleich viele oder mehr Module als L. Wer mit M rechnet und mit L
87
- * druckt, druckt nie breiter als gerechnet — die Rechnung ist konservativ,
88
- * nie knapp. Umgekehrt waere sie eine Rechnung, die aufgeht, und ein Symbol,
89
- * das ueber den Papierrand laeuft. Dasselbe gilt fuer die Byte-Zaehlung: hier
90
- * zaehlt UTF-8 (wie im Flutter-Zwilling), waehrend `qrCodeBytes` die Nutzlast
91
- * als Latin-1 sendet — UTF-8 ist nie kuerzer, die Rechnung also nie zu klein.
80
+ * Alle Druckwege setzen den QR mit Fehlerkorrektur **M** (nativer
81
+ * ESC/POS-Befehl, ePOS `level_m`; das Raster fuer den Bildweg rechnet der
82
+ * Aufrufer ebenfalls mit M) — rechnet und druckt also mit M: gerechnete und
83
+ * gedruckte Modulanzahl stimmen ueberein, und der Anteil am Blatt
84
+ * (Bildschirm, PDF) ist genau der am Bon. Die Byte-Zaehlung ist
85
+ * konservativ: hier zaehlt UTF-8 (wie im Flutter-Zwilling), waehrend
86
+ * `qrCodeBytes` die Nutzlast als Latin-1 sendet — UTF-8 ist nie kuerzer, die
87
+ * Rechnung also nie zu klein.
92
88
  *
93
89
  * Wirft, wenn die Nutzlast in keine Version passt — wie `qrCodeBytes` bei zu
94
90
  * langem Inhalt. Ein still zurueckgegebenes "passt nicht" haette den
95
91
  * Datenfehler als Papierfehler getarnt.
96
92
  */
97
93
  export declare function qrModulAnzahl(nutzlast: string): number;
94
+ /**
95
+ * Ob `nutzlast` in irgendeine QR-Version 1..40 bei Fehlerkorrektur M passt --
96
+ * dieselbe Tabelle wie `qrModulAnzahl`, aber ohne zu werfen.
97
+ *
98
+ * Fuer Aufrufer, denen ein zu langer Inhalt kein Programmierfehler ist,
99
+ * sondern ein Datenfehler, den sie selbst behandeln (das Beleg-Blatt: ohne
100
+ * QR weiterbauen statt den ganzen Zeichner mitzureissen).
101
+ */
102
+ export declare function qrPasstInVersion(nutzlast: string): boolean;
98
103
  /**
99
104
  * Groesste Modulgroesse, mit der `moduleAnzahl` Module **samt Ruhezone** in
100
105
  * `papierbreitePunkte` passen, gedeckelt durch `groesse`.
@@ -1,6 +1,6 @@
1
1
  /** Groesste Modulgroesse in Druckpunkten, die der jeweilige Deckel zulaesst. */
2
2
  export const QR_MODUL_DECKEL = {
3
- auto: 4,
3
+ auto: 6,
4
4
  klein: 4,
5
5
  mittel: 6,
6
6
  gross: 8,
@@ -43,13 +43,14 @@ const BYTE_KAPAZITAET_M = [
43
43
  /**
44
44
  * Modulanzahl, die `nutzlast` bei Fehlerkorrektur **M** braucht.
45
45
  *
46
- * Warum M, obwohl der native Befehl mit L druckt: M braucht bei gleicher
47
- * Nutzlast gleich viele oder mehr Module als L. Wer mit M rechnet und mit L
48
- * druckt, druckt nie breiter als gerechnet — die Rechnung ist konservativ,
49
- * nie knapp. Umgekehrt waere sie eine Rechnung, die aufgeht, und ein Symbol,
50
- * das ueber den Papierrand laeuft. Dasselbe gilt fuer die Byte-Zaehlung: hier
51
- * zaehlt UTF-8 (wie im Flutter-Zwilling), waehrend `qrCodeBytes` die Nutzlast
52
- * als Latin-1 sendet — UTF-8 ist nie kuerzer, die Rechnung also nie zu klein.
46
+ * Alle Druckwege setzen den QR mit Fehlerkorrektur **M** (nativer
47
+ * ESC/POS-Befehl, ePOS `level_m`; das Raster fuer den Bildweg rechnet der
48
+ * Aufrufer ebenfalls mit M) — rechnet und druckt also mit M: gerechnete und
49
+ * gedruckte Modulanzahl stimmen ueberein, und der Anteil am Blatt
50
+ * (Bildschirm, PDF) ist genau der am Bon. Die Byte-Zaehlung ist
51
+ * konservativ: hier zaehlt UTF-8 (wie im Flutter-Zwilling), waehrend
52
+ * `qrCodeBytes` die Nutzlast als Latin-1 sendet — UTF-8 ist nie kuerzer, die
53
+ * Rechnung also nie zu klein.
53
54
  *
54
55
  * Wirft, wenn die Nutzlast in keine Version passt — wie `qrCodeBytes` bei zu
55
56
  * langem Inhalt. Ein still zurueckgegebenes "passt nicht" haette den
@@ -63,6 +64,18 @@ export function qrModulAnzahl(nutzlast) {
63
64
  }
64
65
  throw new Error('QR-Inhalt ist zu lang');
65
66
  }
67
+ /**
68
+ * Ob `nutzlast` in irgendeine QR-Version 1..40 bei Fehlerkorrektur M passt --
69
+ * dieselbe Tabelle wie `qrModulAnzahl`, aber ohne zu werfen.
70
+ *
71
+ * Fuer Aufrufer, denen ein zu langer Inhalt kein Programmierfehler ist,
72
+ * sondern ein Datenfehler, den sie selbst behandeln (das Beleg-Blatt: ohne
73
+ * QR weiterbauen statt den ganzen Zeichner mitzureissen).
74
+ */
75
+ export function qrPasstInVersion(nutzlast) {
76
+ const laenge = new TextEncoder().encode(nutzlast).length;
77
+ return laenge <= BYTE_KAPAZITAET_M[BYTE_KAPAZITAET_M.length - 1];
78
+ }
66
79
  /**
67
80
  * Groesste Modulgroesse, mit der `moduleAnzahl` Module **samt Ruhezone** in
68
81
  * `papierbreitePunkte` passen, gedeckelt durch `groesse`.
@@ -1,5 +1,7 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { ReceiptLayout } from '../receipt/layout.js';
3
+ import { type BelegBlatt, type LogoStufe } from '../receipt/blatt.js';
4
+ import type { QrModulGroesse } from '../printing/qr-groesse.js';
3
5
  /**
4
6
  * React-Adapter: zeichnet ein Beleg-Layout (siehe ../receipt/layout.ts).
5
7
  *
@@ -33,6 +35,11 @@ export interface ReceiptLayoutViewProps {
33
35
  /** Text auf dem verdeckten QR (Vorgabe „Antippen zum Anzeigen“). */
34
36
  qrVerdecktText?: string;
35
37
  }
38
+ /**
39
+ * @deprecated Seit 0.14.0: [BelegBlattView] setzt den Beleg zeichengleich zu
40
+ * Bon und PDF (Raster, Logo, Marke). Diese Ansicht rechnet Spalten mit Flex
41
+ * und bricht darum anders um als das Papier.
42
+ */
36
43
  export declare function ReceiptLayoutView({ layout, className, renderQr, qrVerdeckt, qrVerdecktText }: ReceiptLayoutViewProps): ReactNode;
37
44
  /**
38
45
  * Verdeckter QR: weichgezeichnet und nicht scannbar, bis der Betrachter ihn
@@ -44,3 +51,49 @@ export declare function QrVerdeckt({ data, text, children }: {
44
51
  text?: string;
45
52
  children: ReactNode;
46
53
  }): ReactNode;
54
+ export interface BelegBlattZeilenProps {
55
+ blatt: BelegBlatt;
56
+ /** Adresse des Firmenlogos; ohne sie bleibt der Logo-Block leer (Platz bleibt). */
57
+ logoUrl?: string;
58
+ /**
59
+ * Zeichnet den QR in seinen Kasten (`qrBlattAnteil`). Der Kasten schliesst
60
+ * die Ruhezone von 4 Modulen je Seite ein und setzt Fehlerkorrektur M
61
+ * voraus: das Symbol also mit Korrektur M und 4 Modulen Ruhezone zeichnen
62
+ * und den Kasten ganz ausfuellen -- dann ist ein Modul so gross wie am Bon.
63
+ */
64
+ renderQr?: (data: string) => ReactNode;
65
+ qrVerdeckt?: boolean;
66
+ qrVerdecktText?: string;
67
+ className?: string;
68
+ }
69
+ /**
70
+ * Das Blatt am Bildschirm -- Zeile fuer Zeile dieselben Zeichen wie am Bon und
71
+ * im PDF. Masse in `ch`: eine Zeichenbreite ist `1ch`, eine Zeile `2ch`, das
72
+ * Blatt `zeichen`ch breit. Der Verbraucher bestimmt nur Schrift (monospace,
73
+ * Vorgabe ueber `--keck-blatt-schrift`) und Schriftgroesse; alles andere steht
74
+ * im Blatt.
75
+ */
76
+ export declare function BelegBlattZeilen({ blatt, logoUrl, renderQr, qrVerdeckt, qrVerdecktText, className }: BelegBlattZeilenProps): ReactNode;
77
+ export interface BelegBlattViewProps extends Omit<BelegBlattZeilenProps, 'blatt' | 'logoUrl'> {
78
+ layout: ReceiptLayout;
79
+ zeichen?: number;
80
+ /** Firmenlogo und Stufe (`logoSkala`); das Pixelmass liest die Ansicht selbst. */
81
+ logo?: {
82
+ url: string;
83
+ stufe: LogoStufe;
84
+ } | null;
85
+ marke?: boolean;
86
+ /** Geraete-Einstellung fuer die QR-Modulgroesse; Vorgabe `auto` (hoechstens 6 Punkte je Modul) — wie die Druckwege. */
87
+ qrGroesse?: QrModulGroesse;
88
+ }
89
+ /**
90
+ * Laedt das Logo (fuer sein Pixelmass), baut das Blatt und zeichnet es. Bis
91
+ * das Bild geladen ist, fehlt der Logo-Block -- die Kasse laedt das Logo ohnehin
92
+ * vorab in den Browser-Cache.
93
+ *
94
+ * Ein Logo ueber [logoPixelZulaessig] (4096x4096px, dieselbe Grenze wie das
95
+ * Druck-Kit beim Rastern fuer den Bon) bleibt hier ebenso ohne Logo-Block --
96
+ * sonst zeigte der Bildschirm ein Logo, das nie gedruckt wird: Bildschirm und
97
+ * Bon zeigen dasselbe Logo.
98
+ */
99
+ export declare function BelegBlattView({ layout, zeichen, logo, marke, qrGroesse, ...rest }: BelegBlattViewProps): ReactNode;
@@ -1,10 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useEffect, useState } from 'react';
3
+ import { belegBlatt, logoPixelZulaessig } from '../receipt/blatt.js';
3
4
  const AUSRICHTUNG = {
4
5
  left: 'left',
5
6
  center: 'center',
6
7
  right: 'right',
7
8
  };
9
+ /**
10
+ * @deprecated Seit 0.14.0: [BelegBlattView] setzt den Beleg zeichengleich zu
11
+ * Bon und PDF (Raster, Logo, Marke). Diese Ansicht rechnet Spalten mit Flex
12
+ * und bricht darum anders um als das Papier.
13
+ */
8
14
  export function ReceiptLayoutView({ layout, className, renderQr, qrVerdeckt = false, qrVerdecktText }) {
9
15
  const klasse = className === undefined ? 'keck-receipt' : `keck-receipt ${className}`;
10
16
  return (_jsx("div", { className: klasse, "data-paper-size": layout.paperSize, children: layout.lines.map((zeile, index) => (_jsx(Zeile, { zeile: zeile, renderQr: renderQr, qrVerdeckt: qrVerdeckt, qrVerdecktText: qrVerdecktText }, index))) }));
@@ -55,3 +61,76 @@ function Zeile({ zeile, renderQr, qrVerdeckt, qrVerdecktText }) {
55
61
  }
56
62
  }
57
63
  }
64
+ /**
65
+ * Das Blatt am Bildschirm -- Zeile fuer Zeile dieselben Zeichen wie am Bon und
66
+ * im PDF. Masse in `ch`: eine Zeichenbreite ist `1ch`, eine Zeile `2ch`, das
67
+ * Blatt `zeichen`ch breit. Der Verbraucher bestimmt nur Schrift (monospace,
68
+ * Vorgabe ueber `--keck-blatt-schrift`) und Schriftgroesse; alles andere steht
69
+ * im Blatt.
70
+ */
71
+ export function BelegBlattZeilen({ blatt, logoUrl, renderQr, qrVerdeckt = false, qrVerdecktText, className }) {
72
+ const z = blatt.zeichen;
73
+ const klasse = className === undefined ? 'keck-blatt' : `keck-blatt ${className}`;
74
+ const blattStil = {
75
+ width: `${z}ch`,
76
+ fontFamily: 'var(--keck-blatt-schrift, "DM Mono", ui-monospace, Menlo, Consolas, monospace)',
77
+ fontVariantLigatures: 'none',
78
+ };
79
+ return (_jsx("div", { className: klasse, "data-zeichen": z, style: blattStil, children: blatt.bloecke.map((b, i) => {
80
+ switch (b.art) {
81
+ case 'zeile':
82
+ return (_jsx("div", { className: "keck-blatt-zeile", style: { whiteSpace: 'pre', height: '2ch', lineHeight: '2ch', overflow: 'hidden', fontWeight: b.fett ? 'bold' : 'normal' }, children: b.text }, i));
83
+ case 'logo':
84
+ return (_jsx("div", { className: "keck-blatt-logo", style: { height: `${b.hoeheZeilen * 2}ch`, display: 'flex', justifyContent: 'center' }, children: logoUrl === undefined ? null : (_jsx("img", { src: logoUrl, alt: "", "aria-hidden": "true", style: { width: `${b.breiteAnteil * z}ch`, height: `${b.hoeheZeilen * 2}ch`, objectFit: 'contain' } })) }, i));
85
+ case 'qr': {
86
+ // Auch beim Anteil 0 (leer oder in keine Version passend) bekommt
87
+ // `renderQr` die Nutzlast: die Oberflaeche muss sagen koennen, dass
88
+ // der QR fehlt und ein Papierbeleg noetig ist. Bon und PDF lassen
89
+ // den QR dann weg und melden es ueber `qrFehler`.
90
+ const bild = renderQr !== undefined ? renderQr(b.nutzlast) : _jsx("div", { "data-qr": b.nutzlast, "aria-label": "RKSV-QR-Code" });
91
+ return (_jsx("div", { className: "keck-blatt-qr", style: { display: 'flex', justifyContent: 'center' }, children: _jsx("div", { style: { width: `${b.breiteAnteil * z}ch`, aspectRatio: '1 / 1' }, children: qrVerdeckt ? _jsx(QrVerdeckt, { data: b.nutzlast, text: qrVerdecktText, children: bild }) : bild }) }, i));
92
+ }
93
+ }
94
+ }) }));
95
+ }
96
+ /**
97
+ * Laedt das Logo (fuer sein Pixelmass), baut das Blatt und zeichnet es. Bis
98
+ * das Bild geladen ist, fehlt der Logo-Block -- die Kasse laedt das Logo ohnehin
99
+ * vorab in den Browser-Cache.
100
+ *
101
+ * Ein Logo ueber [logoPixelZulaessig] (4096x4096px, dieselbe Grenze wie das
102
+ * Druck-Kit beim Rastern fuer den Bon) bleibt hier ebenso ohne Logo-Block --
103
+ * sonst zeigte der Bildschirm ein Logo, das nie gedruckt wird: Bildschirm und
104
+ * Bon zeigen dasselbe Logo.
105
+ */
106
+ export function BelegBlattView({ layout, zeichen, logo, marke = false, qrGroesse, ...rest }) {
107
+ const [mass, setMass] = useState(null);
108
+ const url = logo?.url;
109
+ useEffect(() => {
110
+ if (url === undefined)
111
+ return undefined;
112
+ let aktiv = true;
113
+ const Bild = globalThis.Image;
114
+ if (Bild === undefined)
115
+ return undefined;
116
+ const bild = new Bild();
117
+ bild.onload = () => {
118
+ if (aktiv && logoPixelZulaessig(bild.naturalWidth, bild.naturalHeight))
119
+ setMass({ url, breite: bild.naturalWidth, hoehe: bild.naturalHeight });
120
+ };
121
+ bild.src = url;
122
+ return () => {
123
+ aktiv = false;
124
+ };
125
+ }, [url]);
126
+ const optionen = { marke };
127
+ if (zeichen !== undefined)
128
+ optionen.zeichen = zeichen;
129
+ if (qrGroesse !== undefined)
130
+ optionen.qrGroesse = qrGroesse;
131
+ const geladen = logo != null && mass !== null && mass.url === logo.url;
132
+ if (geladen)
133
+ optionen.logo = { stufe: logo.stufe, pxBreite: mass.breite, pxHoehe: mass.hoehe };
134
+ const blatt = belegBlatt(layout, optionen);
135
+ return geladen ? _jsx(BelegBlattZeilen, { blatt: blatt, logoUrl: logo.url, ...rest }) : _jsx(BelegBlattZeilen, { blatt: blatt, ...rest });
136
+ }
@@ -0,0 +1,14 @@
1
+ import type { RasterBild } from '../printing/escpos.js';
2
+ import { type LogoMass } from './blatt.js';
3
+ /**
4
+ * RGBA-Pixel (wie `ImageData.data` im Browser oder ein dekodiertes PNG in
5
+ * Node/Dart) -> einfarbiges Rasterbild in genau der Groesse, die das Blatt
6
+ * dem Logo gibt.
7
+ *
8
+ * Die Rechnung ist Vertrag und hat einen Dart-Zwilling (Golden
9
+ * `erwartet/logo-probe.raster32.txt`): Flaechenmittel je Druckpunkt,
10
+ * Durchsichtiges auf Papierweiss, Helligkeit nach BT.601, Floyd-Steinberg mit
11
+ * Schwelle 128. Die Reihenfolge der Rechenschritte nicht aendern -- beide
12
+ * Sprachen rechnen mit IEEE-Doubles und kommen nur so auf dieselben Punkte.
13
+ */
14
+ export declare function logoRaster(rgba: ArrayLike<number>, pxBreite: number, pxHoehe: number, mass: LogoMass, zeichen: number): RasterBild;
@@ -0,0 +1,62 @@
1
+ import { logoRasterMass } from './blatt.js';
2
+ /**
3
+ * RGBA-Pixel (wie `ImageData.data` im Browser oder ein dekodiertes PNG in
4
+ * Node/Dart) -> einfarbiges Rasterbild in genau der Groesse, die das Blatt
5
+ * dem Logo gibt.
6
+ *
7
+ * Die Rechnung ist Vertrag und hat einen Dart-Zwilling (Golden
8
+ * `erwartet/logo-probe.raster32.txt`): Flaechenmittel je Druckpunkt,
9
+ * Durchsichtiges auf Papierweiss, Helligkeit nach BT.601, Floyd-Steinberg mit
10
+ * Schwelle 128. Die Reihenfolge der Rechenschritte nicht aendern -- beide
11
+ * Sprachen rechnen mit IEEE-Doubles und kommen nur so auf dieselben Punkte.
12
+ */
13
+ export function logoRaster(rgba, pxBreite, pxHoehe, mass, zeichen) {
14
+ if (!Number.isInteger(pxBreite) || !Number.isInteger(pxHoehe) || pxBreite < 1 || pxHoehe < 1 || rgba.length !== pxBreite * pxHoehe * 4) {
15
+ throw new Error('RGBA-Laenge passt nicht zum Pixelmass');
16
+ }
17
+ const { breite, hoehe } = logoRasterMass(mass, zeichen);
18
+ const grau = new Float64Array(breite * hoehe);
19
+ const sx = pxBreite / breite;
20
+ const sy = pxHoehe / hoehe;
21
+ for (let y = 0; y < hoehe; y++) {
22
+ const y0 = Math.floor(y * sy);
23
+ const y1 = Math.min(pxHoehe, Math.max(y0 + 1, Math.floor((y + 1) * sy)));
24
+ for (let x = 0; x < breite; x++) {
25
+ const x0 = Math.floor(x * sx);
26
+ const x1 = Math.min(pxBreite, Math.max(x0 + 1, Math.floor((x + 1) * sx)));
27
+ let summe = 0;
28
+ let anzahl = 0;
29
+ for (let py = y0; py < y1; py++) {
30
+ for (let px = x0; px < x1; px++) {
31
+ const i = (py * pxBreite + px) * 4;
32
+ const deckung = rgba[i + 3] / 255;
33
+ const hell = 0.299 * rgba[i] + 0.587 * rgba[i + 1] + 0.114 * rgba[i + 2];
34
+ summe += hell * deckung + 255 * (1 - deckung);
35
+ anzahl += 1;
36
+ }
37
+ }
38
+ grau[y * breite + x] = anzahl === 0 ? 255 : summe / anzahl;
39
+ }
40
+ }
41
+ const punkte = new Uint8Array(breite * hoehe);
42
+ for (let y = 0; y < hoehe; y++) {
43
+ for (let x = 0; x < breite; x++) {
44
+ const i = y * breite + x;
45
+ const alt = grau[i];
46
+ const neu = alt < 128 ? 0 : 255;
47
+ if (neu === 0)
48
+ punkte[i] = 1;
49
+ const fehler = alt - neu;
50
+ if (x + 1 < breite)
51
+ grau[i + 1] = grau[i + 1] + (fehler * 7) / 16;
52
+ if (y + 1 < hoehe) {
53
+ if (x > 0)
54
+ grau[i + breite - 1] = grau[i + breite - 1] + (fehler * 3) / 16;
55
+ grau[i + breite] = grau[i + breite] + (fehler * 5) / 16;
56
+ if (x + 1 < breite)
57
+ grau[i + breite + 1] = grau[i + breite + 1] + fehler / 16;
58
+ }
59
+ }
60
+ }
61
+ return { breite, hoehe, punkte };
62
+ }
@@ -0,0 +1,107 @@
1
+ import type { PosPaperSize } from '../printing/escpos.js';
2
+ import { type QrModulGroesse } from '../printing/index.js';
3
+ import type { ReceiptLayout } from './layout.js';
4
+ /**
5
+ * Das Beleg-Blatt: die vollstaendige Folge dessen, was auf dem Papier steht --
6
+ * Rasterzeilen, Firmenlogo, QR und Marke, mit Groessen als Anteil der
7
+ * Blattbreite und in Zeilen.
8
+ *
9
+ * Bis 0.13 setzte jeder Zeichner das Raster selbst zusammen und entschied
10
+ * Logo-Ort, Logo-Groesse, Rahmen und QR-Groesse fuer sich. Heraus kamen
11
+ * fuenf verschiedene Belege (Panel: Logo ueber dem Testkassen-Rahmen, PDF:
12
+ * Logo auf dem Rahmen, App: kein Logo). Hier steht das jetzt einmal.
13
+ *
14
+ * Masseinheit ist der Druckkopf (Epson Font A, 203 dpi): ein Zeichen ist 12
15
+ * Punkte breit, eine Zeile 24 Punkte hoch. Am Bildschirm ist eine Zeile `2ch`,
16
+ * im PDF zwei Zeichenbreiten.
17
+ */
18
+ export type LogoStufe = 'S' | 'M' | 'L' | 'XL';
19
+ /** Kasten je Stufe: Anteil der Blattbreite und Hoehe in Zeilen (Werte der Web-Kasse). */
20
+ export declare const LOGO_STUFEN: Readonly<Record<LogoStufe, {
21
+ readonly breiteAnteil: number;
22
+ readonly hoeheZeilen: number;
23
+ }>>;
24
+ export declare const PUNKTE_JE_ZEICHEN = 12;
25
+ export declare const PUNKTE_JE_ZEILE = 24;
26
+ /** Die Marke am Belegende (Konto-Flag `kreiseck_logo`) -- Text, damit jeder Weg sie gleich setzt. */
27
+ export declare const MARKE_TEXT = "erstellt mit Kasseneck";
28
+ export interface BlattLogo {
29
+ stufe: LogoStufe;
30
+ /** Pixelmass des Originalbilds -- ohne das laesst sich nicht einpassen. */
31
+ pxBreite: number;
32
+ pxHoehe: number;
33
+ }
34
+ export interface LogoMass {
35
+ /** Anteil der Blattbreite (0..1). */
36
+ breiteAnteil: number;
37
+ /** Hoehe in Zeilen (nicht ganzzahlig). */
38
+ hoeheZeilen: number;
39
+ }
40
+ export type BlattBlock = {
41
+ readonly art: 'zeile';
42
+ readonly text: string;
43
+ readonly fett: boolean;
44
+ readonly leer: boolean;
45
+ } | {
46
+ readonly art: 'logo';
47
+ readonly breiteAnteil: number;
48
+ readonly hoeheZeilen: number;
49
+ } | {
50
+ readonly art: 'qr';
51
+ readonly nutzlast: string;
52
+ readonly breiteAnteil: number;
53
+ };
54
+ export interface BelegBlatt {
55
+ readonly zeichen: number;
56
+ readonly bloecke: readonly BlattBlock[];
57
+ }
58
+ export interface BelegBlattOptionen {
59
+ /** Zeichen je Zeile; Vorgabe nach `layout.paperSize` (32/48). */
60
+ zeichen?: number;
61
+ /** Firmenlogo; ohne Angabe kein Logo-Block. */
62
+ logo?: BlattLogo | null;
63
+ /** "erstellt mit Kasseneck" am Ende. */
64
+ marke?: boolean;
65
+ /** Geraete-Einstellung fuer die QR-Modulgroesse; Vorgabe `auto` (hoechstens 6 Punkte je Modul) — wie die Druckwege. */
66
+ qrGroesse?: QrModulGroesse;
67
+ }
68
+ /**
69
+ * 32 Zeichen sind 58 mm, 48 sind 80 mm; andere Breiten behalten das Papier des
70
+ * Layouts. Der QR-Anteil (`qrBlattAnteil`) wird dann fuer die Kopfbreite
71
+ * DIESES Papiers gerechnet, nicht fuer die gewaehlte Zeichenzahl.
72
+ */
73
+ export declare function papierFuerZeichen(zeichen: number, vorgabe: PosPaperSize): PosPaperSize;
74
+ /**
75
+ * Obergrenze der Logo-Pixel, die der Bildschirm noch anzeigen darf -- exakt
76
+ * dieselbe Grenze wie im Druck-Kit (`LOGO_PIXEL_MAX` in `druck-logo.ts`):
77
+ * Bon und ePOS lehnen ein Logo ueber 4096x4096px schon beim Rastern ab (ein
78
+ * grosses Bild blockiert das Geraet). Ohne diese Grenze wuerde der
79
+ * Bildschirm ein Logo zeigen, das der Bon nie druckt -- Bildschirm und
80
+ * Papier waeren sich uneins, genau das soll das Beleg-Blatt verhindern.
81
+ */
82
+ export declare const LOGO_PIXEL_MAX = 4096;
83
+ /** Reine Pruefung, ob ein Logo dieser Pixelmasse noch angezeigt/gedruckt werden darf. */
84
+ export declare function logoPixelZulaessig(breite: number, hoehe: number): boolean;
85
+ /**
86
+ * Das Logo in den Kasten seiner Stufe einpassen -- **nie hochrechnen**: ein
87
+ * Bildpixel wird hoechstens ein Druckpunkt. Ein kleines Logo bleibt klein,
88
+ * statt am Bon verwaschen zu werden (so hielten es PDF und Web-Kasse schon).
89
+ */
90
+ export declare function logoMass(logo: BlattLogo, zeichen: number): LogoMass;
91
+ /** Das Mass in ganzen Druckpunkten -- so gross muss das Rasterbild fuer den Bon sein. */
92
+ export declare function logoRasterMass(mass: LogoMass, zeichen: number): {
93
+ breite: number;
94
+ hoehe: number;
95
+ };
96
+ /**
97
+ * Anteil der Blattbreite, den der QR am Drucker einnimmt -- dieselbe Rechnung
98
+ * wie der Druckweg (nativ, sonst der Bildweg). Bildschirm und PDF zeigen den
99
+ * QR in genau dieser Groesse. Leere Nutzlast: 0 (kein Symbol).
100
+ *
101
+ * Der Kasten **schliesst die Ruhezone von 4 Modulen je Seite ein** und setzt
102
+ * Fehlerkorrektur M voraus. Ein Zeichner (`renderQr`, PDF) muss das Symbol
103
+ * darum mit Fehlerkorrektur M und einer Ruhezone von 4 Modulen zeichnen, die
104
+ * den Kasten ganz ausfuellt -- sonst stimmt die Modulgroesse nicht mit dem Bon.
105
+ */
106
+ export declare function qrBlattAnteil(nutzlast: string, papier: PosPaperSize, groesse?: QrModulGroesse): number;
107
+ export declare function belegBlatt(layout: ReceiptLayout, optionen?: BelegBlattOptionen): BelegBlatt;
@@ -0,0 +1,128 @@
1
+ import { QR_DRUCK_PUNKTE, QR_RUHEZONE_MODULE, qrGroesseFuer, qrPasstInVersion, qrRasterPunkte, } from '../printing/index.js';
2
+ import { renderReceiptGrid, ZEICHEN_JE_PAPIER } from './grid.js';
3
+ /** Kasten je Stufe: Anteil der Blattbreite und Hoehe in Zeilen (Werte der Web-Kasse). */
4
+ export const LOGO_STUFEN = {
5
+ S: { breiteAnteil: 0.42, hoeheZeilen: 5 },
6
+ M: { breiteAnteil: 0.62, hoeheZeilen: 8 },
7
+ L: { breiteAnteil: 0.8, hoeheZeilen: 12 },
8
+ XL: { breiteAnteil: 0.94, hoeheZeilen: 16 },
9
+ };
10
+ export const PUNKTE_JE_ZEICHEN = 12;
11
+ export const PUNKTE_JE_ZEILE = 24;
12
+ /** Die Marke am Belegende (Konto-Flag `kreiseck_logo`) -- Text, damit jeder Weg sie gleich setzt. */
13
+ export const MARKE_TEXT = 'erstellt mit Kasseneck';
14
+ /**
15
+ * 32 Zeichen sind 58 mm, 48 sind 80 mm; andere Breiten behalten das Papier des
16
+ * Layouts. Der QR-Anteil (`qrBlattAnteil`) wird dann fuer die Kopfbreite
17
+ * DIESES Papiers gerechnet, nicht fuer die gewaehlte Zeichenzahl.
18
+ */
19
+ export function papierFuerZeichen(zeichen, vorgabe) {
20
+ if (zeichen === ZEICHEN_JE_PAPIER.mm58)
21
+ return 'mm58';
22
+ if (zeichen === ZEICHEN_JE_PAPIER.mm80)
23
+ return 'mm80';
24
+ return vorgabe;
25
+ }
26
+ /**
27
+ * Obergrenze der Logo-Pixel, die der Bildschirm noch anzeigen darf -- exakt
28
+ * dieselbe Grenze wie im Druck-Kit (`LOGO_PIXEL_MAX` in `druck-logo.ts`):
29
+ * Bon und ePOS lehnen ein Logo ueber 4096x4096px schon beim Rastern ab (ein
30
+ * grosses Bild blockiert das Geraet). Ohne diese Grenze wuerde der
31
+ * Bildschirm ein Logo zeigen, das der Bon nie druckt -- Bildschirm und
32
+ * Papier waeren sich uneins, genau das soll das Beleg-Blatt verhindern.
33
+ */
34
+ export const LOGO_PIXEL_MAX = 4096;
35
+ /** Reine Pruefung, ob ein Logo dieser Pixelmasse noch angezeigt/gedruckt werden darf. */
36
+ export function logoPixelZulaessig(breite, hoehe) {
37
+ return breite > 0 && hoehe > 0 && breite <= LOGO_PIXEL_MAX && hoehe <= LOGO_PIXEL_MAX;
38
+ }
39
+ /**
40
+ * Das Logo in den Kasten seiner Stufe einpassen -- **nie hochrechnen**: ein
41
+ * Bildpixel wird hoechstens ein Druckpunkt. Ein kleines Logo bleibt klein,
42
+ * statt am Bon verwaschen zu werden (so hielten es PDF und Web-Kasse schon).
43
+ */
44
+ export function logoMass(logo, zeichen) {
45
+ if (!(logo.pxBreite > 0) || !(logo.pxHoehe > 0))
46
+ throw new Error('Logo ohne Pixelmass');
47
+ const stufe = LOGO_STUFEN[logo.stufe];
48
+ if (stufe === undefined)
49
+ throw new Error(`Unbekannte Logo-Stufe: ${String(logo.stufe)}`);
50
+ const blattPunkte = zeichen * PUNKTE_JE_ZEICHEN;
51
+ const faktor = Math.min((stufe.breiteAnteil * blattPunkte) / logo.pxBreite, (stufe.hoeheZeilen * PUNKTE_JE_ZEILE) / logo.pxHoehe, 1);
52
+ return {
53
+ breiteAnteil: (logo.pxBreite * faktor) / blattPunkte,
54
+ hoeheZeilen: (logo.pxHoehe * faktor) / PUNKTE_JE_ZEILE,
55
+ };
56
+ }
57
+ /** Das Mass in ganzen Druckpunkten -- so gross muss das Rasterbild fuer den Bon sein. */
58
+ export function logoRasterMass(mass, zeichen) {
59
+ return {
60
+ breite: Math.max(1, Math.round(mass.breiteAnteil * zeichen * PUNKTE_JE_ZEICHEN)),
61
+ hoehe: Math.max(1, Math.round(mass.hoeheZeilen * PUNKTE_JE_ZEILE)),
62
+ };
63
+ }
64
+ /**
65
+ * Anteil der Blattbreite, den der QR am Drucker einnimmt -- dieselbe Rechnung
66
+ * wie der Druckweg (nativ, sonst der Bildweg). Bildschirm und PDF zeigen den
67
+ * QR in genau dieser Groesse. Leere Nutzlast: 0 (kein Symbol).
68
+ *
69
+ * Der Kasten **schliesst die Ruhezone von 4 Modulen je Seite ein** und setzt
70
+ * Fehlerkorrektur M voraus. Ein Zeichner (`renderQr`, PDF) muss das Symbol
71
+ * darum mit Fehlerkorrektur M und einer Ruhezone von 4 Modulen zeichnen, die
72
+ * den Kasten ganz ausfuellt -- sonst stimmt die Modulgroesse nicht mit dem Bon.
73
+ */
74
+ export function qrBlattAnteil(nutzlast, papier, groesse = 'auto') {
75
+ if (nutzlast === '')
76
+ return 0;
77
+ // Ein Inhalt, der in keine QR-Version passt (Fehlerkorrektur M, hoechstens
78
+ // 2331 Byte), wuerde hier `qrGroesseFuer` -> `qrModulAnzahl` zum Werfen
79
+ // bringen -- und riss damit jeden Zeichner mit, der das Blatt baut
80
+ // (Bildschirm, Bon, PDF). 0, wie bei leerer Nutzlast: der Beleg steht ohne
81
+ // QR, statt gar nicht zu stehen. `qrModulAnzahl` selbst wirft weiter -- wer
82
+ // es ausserhalb des Blatts aufruft, soll den Fehler sehen.
83
+ if (!qrPasstInVersion(nutzlast))
84
+ return 0;
85
+ const papierPunkte = QR_DRUCK_PUNKTE[papier];
86
+ const mass = qrGroesseFuer({ nutzlast, papierbreitePunkte: papierPunkte, groesse });
87
+ if (mass.passt)
88
+ return mass.breitePunkte / papierPunkte;
89
+ return ((mass.module + 2 * QR_RUHEZONE_MODULE) * qrRasterPunkte(papier, mass.module, { groesse })) / papierPunkte;
90
+ }
91
+ function zentriert(text, zeichen) {
92
+ const t = text.length > zeichen ? text.slice(0, zeichen) : text;
93
+ const links = Math.floor((zeichen - t.length) / 2);
94
+ return ' '.repeat(links) + t + ' '.repeat(zeichen - t.length - links);
95
+ }
96
+ export function belegBlatt(layout, optionen = {}) {
97
+ const grid = renderReceiptGrid(layout, optionen.zeichen === undefined ? {} : { zeichen: optionen.zeichen });
98
+ const zeichen = grid.zeichen;
99
+ const papier = papierFuerZeichen(zeichen, layout.paperSize);
100
+ const qrGroesse = optionen.qrGroesse ?? 'auto';
101
+ const leerzeile = { art: 'zeile', text: ' '.repeat(zeichen), fett: false, leer: true };
102
+ const block = (z) => z.kind === 'qr'
103
+ ? { art: 'qr', nutzlast: z.qr ?? '', breiteAnteil: qrBlattAnteil(z.qr ?? '', papier, qrGroesse) }
104
+ : { art: 'zeile', text: z.text, fett: z.bold, leer: z.kind === 'space' };
105
+ const bloecke = [];
106
+ let i = 0;
107
+ if (optionen.logo) {
108
+ // Fuehrende Aufdrucke (Testkasse, Testsignatur) bleiben ganz oben: wer den
109
+ // Beleg sieht, sieht zuerst, dass er nicht gilt.
110
+ while (i < grid.lines.length && grid.lines[i].kind === 'banner') {
111
+ bloecke.push(block(grid.lines[i]));
112
+ i += 1;
113
+ }
114
+ // Die Leerzeilen gehoeren zum Vertrag: kein Zeichner kann das Logo an einen
115
+ // Rahmen oder an den Firmennamen kleben.
116
+ if (i > 0)
117
+ bloecke.push(leerzeile);
118
+ bloecke.push({ art: 'logo', ...logoMass(optionen.logo, zeichen) });
119
+ bloecke.push(leerzeile);
120
+ }
121
+ for (; i < grid.lines.length; i += 1)
122
+ bloecke.push(block(grid.lines[i]));
123
+ if (optionen.marke === true) {
124
+ bloecke.push(leerzeile);
125
+ bloecke.push({ art: 'zeile', text: zentriert(MARKE_TEXT, zeichen), fett: false, leer: false });
126
+ }
127
+ return { zeichen, bloecke };
128
+ }