@thermal-print/escpos 0.3.1-beta.6 → 0.3.1-beta.7
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/dist/command-adapters/escbematech-adapter.d.ts +6 -0
- package/dist/command-adapters/escbematech-adapter.d.ts.map +1 -1
- package/dist/command-adapters/escbematech-adapter.js +10 -1
- package/dist/command-adapters/types.d.ts +12 -0
- package/dist/command-adapters/types.d.ts.map +1 -1
- package/dist/commands/escbematech.d.ts +2 -0
- package/dist/commands/escbematech.d.ts.map +1 -1
- package/dist/commands/escbematech.js +4 -3
- package/dist/converter.d.ts +17 -0
- package/dist/converter.d.ts.map +1 -1
- package/dist/converter.js +3 -2
- package/dist/generator.d.ts +89 -15
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +213 -31
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/styles.d.ts +76 -10
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +180 -35
- package/dist/traverser.d.ts +19 -2
- package/dist/traverser.d.ts.map +1 -1
- package/dist/traverser.js +184 -95
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -9
- package/LICENSE +0 -21
package/dist/styles.js
CHANGED
|
@@ -32,6 +32,7 @@ export function extractViewStyle(style) {
|
|
|
32
32
|
borderBottom: style?.borderBottom,
|
|
33
33
|
borderTop: style?.borderTop,
|
|
34
34
|
width: style?.width,
|
|
35
|
+
height: style?.height,
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
@@ -43,32 +44,67 @@ export function isBold(style) {
|
|
|
43
44
|
style.fontWeight === "Helvetica-Bold" ||
|
|
44
45
|
style.fontFamily === "Helvetica-Bold");
|
|
45
46
|
}
|
|
47
|
+
export const FONT_LEVEL_SIZES = {
|
|
48
|
+
0: { font: 1, width: 1, height: 1 },
|
|
49
|
+
1: { font: 0, width: 1, height: 1 },
|
|
50
|
+
2: { font: 0, width: 2, height: 2 },
|
|
51
|
+
};
|
|
46
52
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
* How many Font A 1x1 columns one character of each level occupies.
|
|
54
|
+
* Font B fits 4/3 of the characters Font A does (56 vs 42 on the MP-4200 TH),
|
|
55
|
+
* so one Font B character is 3/4 of a column.
|
|
56
|
+
*/
|
|
57
|
+
export const FONT_LEVEL_COLUMN_UNITS = {
|
|
58
|
+
0: 0.75,
|
|
59
|
+
1: 1,
|
|
60
|
+
2: 2,
|
|
61
|
+
};
|
|
62
|
+
/** The level a document sits at before any per-element fontSize is applied. */
|
|
63
|
+
export function baseFontLevel(fontMode) {
|
|
64
|
+
return fontMode === "small" ? 0 : 1;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* How many levels a fontSize moves relative to the document's base level.
|
|
53
68
|
*
|
|
54
|
-
*
|
|
69
|
+
* fontSize is RELATIVE, not absolute: the same `fontSize: 22` means "one step
|
|
70
|
+
* bigger than this document's body text", so a receipt printed in `fontMode:
|
|
71
|
+
* "small"` does not suddenly jump to double size.
|
|
55
72
|
*/
|
|
56
|
-
export function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return { font: 0, width: 1, height: 1 };
|
|
60
|
-
// Parse fontSize if it's a string (e.g., "8.28px")
|
|
73
|
+
export function fontSizeLevelDelta(fontSize) {
|
|
74
|
+
if (fontSize === undefined || fontSize === null || fontSize === "")
|
|
75
|
+
return 0;
|
|
61
76
|
const size = typeof fontSize === "string" ? parseFloat(fontSize) : fontSize;
|
|
62
|
-
if (isNaN(size))
|
|
63
|
-
return
|
|
64
|
-
// Condensada: Font B (64 cols) — fontSize <= 10
|
|
77
|
+
if (typeof size !== "number" || Number.isNaN(size))
|
|
78
|
+
return 0;
|
|
65
79
|
if (size <= 10)
|
|
66
|
-
return
|
|
67
|
-
// Normal: Font A (48 cols) — fontSize 11-19
|
|
80
|
+
return -1;
|
|
68
81
|
if (size <= 19)
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
return 0;
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
/** Clamps base level + fontSize delta into the three levels ESC ! can express. */
|
|
86
|
+
export function resolveFontLevel(baseLevel, fontSize) {
|
|
87
|
+
const level = baseLevel + fontSizeLevelDelta(fontSize);
|
|
88
|
+
return Math.max(0, Math.min(2, level));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Maps fontSize to the ESC/POS font + character size multipliers of its level.
|
|
92
|
+
*
|
|
93
|
+
* @param fontSize - fontSize from the component style (relative to baseLevel)
|
|
94
|
+
* @param baseLevel - the document's base level (see baseFontLevel)
|
|
95
|
+
*/
|
|
96
|
+
export function mapFontSizeToESCPOS(fontSize, baseLevel = 1) {
|
|
97
|
+
return FONT_LEVEL_SIZES[resolveFontLevel(baseLevel, fontSize)];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Characters that fit on one line at `level`, on a paper that fits
|
|
101
|
+
* `baseColumns` characters at `baseLevel`.
|
|
102
|
+
*
|
|
103
|
+
* @param reservedColumns - columns taken by horizontal padding, in Font A units
|
|
104
|
+
*/
|
|
105
|
+
export function columnsForLevel(baseColumns, baseLevel, level, reservedColumns = 0) {
|
|
106
|
+
const fontAColumns = baseColumns * FONT_LEVEL_COLUMN_UNITS[baseLevel] - reservedColumns;
|
|
107
|
+
return Math.max(1, Math.floor(fontAColumns / FONT_LEVEL_COLUMN_UNITS[level]));
|
|
72
108
|
}
|
|
73
109
|
/**
|
|
74
110
|
* Maps textAlign to ESC/POS alignment
|
|
@@ -81,14 +117,51 @@ export function mapTextAlign(textAlign) {
|
|
|
81
117
|
return "left";
|
|
82
118
|
}
|
|
83
119
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
120
|
+
* Points per printed line, and per character column.
|
|
121
|
+
*
|
|
122
|
+
* margin/padding/height arrive in POINTS, the same unit @react-pdf/renderer
|
|
123
|
+
* and @thermal-print/pdf use — a 10pt body line is 12pt tall with the default
|
|
124
|
+
* 1.2 line height, and an 80mm page is 226pt wide for 42 Font A columns.
|
|
125
|
+
* Converting with those two constants is what makes a `marginTop: 12` produce
|
|
126
|
+
* the same visual gap in the PDF preview and on the thermal printer.
|
|
127
|
+
*/
|
|
128
|
+
export const POINTS_PER_LINE = 12;
|
|
129
|
+
export const POINTS_PER_COLUMN = 226 / 42;
|
|
130
|
+
/** Parses a size that may arrive as a number or as "12px" / "12pt". */
|
|
131
|
+
export function parseSize(value) {
|
|
132
|
+
if (typeof value === "number")
|
|
133
|
+
return Number.isFinite(value) ? value : 0;
|
|
134
|
+
if (typeof value === "string") {
|
|
135
|
+
const parsed = parseFloat(value);
|
|
136
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
137
|
+
}
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Converts a vertical spacing (margin/padding/height) in points to line feeds.
|
|
142
|
+
* Capped at 6 lines so a stray `marginTop: 400` cannot eject a page of paper.
|
|
86
143
|
*/
|
|
87
144
|
export function calculateSpacing(value) {
|
|
88
|
-
|
|
145
|
+
const points = parseSize(value);
|
|
146
|
+
if (points <= 0)
|
|
89
147
|
return 0;
|
|
90
|
-
|
|
91
|
-
|
|
148
|
+
return Math.min(6, Math.round(points / POINTS_PER_LINE));
|
|
149
|
+
}
|
|
150
|
+
/** Converts a horizontal spacing (padding left/right) in points to columns. */
|
|
151
|
+
export function calculateHorizontalSpacing(value) {
|
|
152
|
+
const points = parseSize(value);
|
|
153
|
+
if (points <= 0)
|
|
154
|
+
return 0;
|
|
155
|
+
return Math.round(points / POINTS_PER_COLUMN);
|
|
156
|
+
}
|
|
157
|
+
/** Parses a percentage width ("30%") into a fraction (0.3). Returns undefined otherwise. */
|
|
158
|
+
export function parsePercentageWidth(width) {
|
|
159
|
+
if (typeof width === "string" && width.includes("%")) {
|
|
160
|
+
const percentage = parseFloat(width);
|
|
161
|
+
if (!Number.isNaN(percentage))
|
|
162
|
+
return percentage / 100;
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
92
165
|
}
|
|
93
166
|
/**
|
|
94
167
|
* Determines if a border is dashed
|
|
@@ -126,6 +199,69 @@ export function parseWidth(width, totalWidth) {
|
|
|
126
199
|
}
|
|
127
200
|
return totalWidth;
|
|
128
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Splits the row width across its columns.
|
|
204
|
+
*
|
|
205
|
+
* Columns with an explicit width (fixed or percentage) take it; whatever is
|
|
206
|
+
* left over is shared evenly by the columns that declared none. Before
|
|
207
|
+
* DEV-2390 a column without a width got `parseWidth(undefined) === totalWidth`,
|
|
208
|
+
* so a three-column row asked for three full paper widths and printed each
|
|
209
|
+
* cell padded to the whole line.
|
|
210
|
+
*/
|
|
211
|
+
export function distributeColumnWidths(widths, totalWidth) {
|
|
212
|
+
if (widths.length === 0)
|
|
213
|
+
return [];
|
|
214
|
+
const resolved = widths.map((width) => width === undefined ? undefined : Math.max(0, parseWidth(width, totalWidth)));
|
|
215
|
+
const explicitTotal = resolved.reduce((sum, width) => sum + (width ?? 0), 0);
|
|
216
|
+
const autoColumns = resolved.filter((width) => width === undefined).length;
|
|
217
|
+
// A row whose declared widths do not fit the paper — three cells of "50%", or
|
|
218
|
+
// a `width: "100%"` cell with a second cell beside it — cannot be honoured.
|
|
219
|
+
// Splitting it evenly is the only bounded answer: keeping the declared widths
|
|
220
|
+
// leaves the leftover columns at zero, their capacity floors at one character,
|
|
221
|
+
// and a product name then prints one letter per line, metres of paper for one
|
|
222
|
+
// row. Every column also needs at least one character, hence the +autoColumns.
|
|
223
|
+
if (explicitTotal + autoColumns > totalWidth) {
|
|
224
|
+
return evenColumnWidths(resolved.length, totalWidth);
|
|
225
|
+
}
|
|
226
|
+
if (autoColumns === 0)
|
|
227
|
+
return resolved;
|
|
228
|
+
const remaining = Math.max(autoColumns, totalWidth - explicitTotal);
|
|
229
|
+
const share = Math.floor(remaining / autoColumns);
|
|
230
|
+
let leftover = remaining - share * autoColumns;
|
|
231
|
+
return resolved.map((width) => {
|
|
232
|
+
if (width !== undefined)
|
|
233
|
+
return width;
|
|
234
|
+
const extra = leftover > 0 ? 1 : 0;
|
|
235
|
+
leftover -= extra;
|
|
236
|
+
return share + extra;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/** Splits a row width evenly, giving the remainder to the leftmost columns. */
|
|
240
|
+
function evenColumnWidths(columns, totalWidth) {
|
|
241
|
+
const share = Math.max(1, Math.floor(totalWidth / columns));
|
|
242
|
+
let leftover = Math.max(0, totalWidth - share * columns);
|
|
243
|
+
return Array.from({ length: columns }, () => {
|
|
244
|
+
const extra = leftover > 0 ? 1 : 0;
|
|
245
|
+
leftover -= extra;
|
|
246
|
+
return share + extra;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Splits the free space of a space-between row across its gaps.
|
|
251
|
+
* Every gap gets at least one space; the remainder goes to the leftmost gaps.
|
|
252
|
+
*/
|
|
253
|
+
export function distributeGaps(contentWidth, totalWidth, gapCount) {
|
|
254
|
+
if (gapCount <= 0)
|
|
255
|
+
return [];
|
|
256
|
+
const free = Math.max(gapCount, totalWidth - contentWidth);
|
|
257
|
+
const share = Math.floor(free / gapCount);
|
|
258
|
+
let leftover = free - share * gapCount;
|
|
259
|
+
return Array.from({ length: gapCount }, () => {
|
|
260
|
+
const extra = leftover > 0 ? 1 : 0;
|
|
261
|
+
leftover -= extra;
|
|
262
|
+
return share + extra;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
129
265
|
/**
|
|
130
266
|
* Aligns text within a column width (using CP860 byte length for accurate padding)
|
|
131
267
|
*/
|
|
@@ -162,25 +298,34 @@ export function alignTextInColumn(text, width, align) {
|
|
|
162
298
|
* Splits text into multiple lines if it exceeds width
|
|
163
299
|
*/
|
|
164
300
|
export function wrapText(text, width) {
|
|
301
|
+
if (width <= 0)
|
|
302
|
+
return [text];
|
|
165
303
|
if (text.length <= width) {
|
|
166
304
|
return [text];
|
|
167
305
|
}
|
|
168
306
|
const lines = [];
|
|
169
307
|
let currentLine = "";
|
|
170
|
-
const
|
|
171
|
-
|
|
308
|
+
const pushCurrent = () => {
|
|
309
|
+
if (currentLine) {
|
|
310
|
+
lines.push(currentLine);
|
|
311
|
+
currentLine = "";
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
for (const word of text.split(" ")) {
|
|
172
315
|
if ((currentLine + " " + word).trim().length <= width) {
|
|
173
316
|
currentLine = (currentLine + " " + word).trim();
|
|
317
|
+
continue;
|
|
174
318
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
319
|
+
pushCurrent();
|
|
320
|
+
// A single word wider than the column has to be broken, otherwise the
|
|
321
|
+
// printer wraps it wherever it likes and the column grid falls apart.
|
|
322
|
+
let rest = word;
|
|
323
|
+
while (rest.length > width) {
|
|
324
|
+
lines.push(rest.slice(0, width));
|
|
325
|
+
rest = rest.slice(width);
|
|
180
326
|
}
|
|
327
|
+
currentLine = rest;
|
|
181
328
|
}
|
|
182
|
-
|
|
183
|
-
lines.push(currentLine);
|
|
184
|
-
}
|
|
329
|
+
pushCurrent();
|
|
185
330
|
return lines.length > 0 ? lines : [text.substring(0, width)];
|
|
186
331
|
}
|
package/dist/traverser.d.ts
CHANGED
|
@@ -6,6 +6,14 @@ import { ESCPOSGenerator } from "./generator";
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class TreeTraverser {
|
|
8
8
|
private generator;
|
|
9
|
+
/**
|
|
10
|
+
* Alignment inherited from an ancestor View's `alignItems`, mirroring
|
|
11
|
+
* PDFTraverser.alignmentContext. A Text or Image without its own textAlign
|
|
12
|
+
* follows it.
|
|
13
|
+
*/
|
|
14
|
+
private alignmentContext;
|
|
15
|
+
/** Fraction of the line an ancestor View's percentage width allows (0.3 for "30%"). */
|
|
16
|
+
private widthFraction;
|
|
9
17
|
constructor(generator: ESCPOSGenerator);
|
|
10
18
|
/**
|
|
11
19
|
* Traverse the entire tree starting from root
|
|
@@ -16,7 +24,11 @@ export declare class TreeTraverser {
|
|
|
16
24
|
*/
|
|
17
25
|
private handleDocument;
|
|
18
26
|
/**
|
|
19
|
-
* Handle Page element
|
|
27
|
+
* Handle Page element.
|
|
28
|
+
*
|
|
29
|
+
* In "rico" mode the Page padding becomes the receipt margin, the same way
|
|
30
|
+
* PDFTraverser.handlePage turns it into page margins. Margins arrive in
|
|
31
|
+
* points; the generator clamps them so they can never eat the whole line.
|
|
20
32
|
*/
|
|
21
33
|
private handlePage;
|
|
22
34
|
/**
|
|
@@ -25,13 +37,18 @@ export declare class TreeTraverser {
|
|
|
25
37
|
private handleView;
|
|
26
38
|
/**
|
|
27
39
|
* Handle column layout (stacked vertically)
|
|
28
|
-
* Adds newlines between sibling elements for proper spacing
|
|
29
40
|
*/
|
|
30
41
|
private handleColumnLayout;
|
|
31
42
|
/**
|
|
32
43
|
* Handle row layout (side-by-side columns)
|
|
33
44
|
*/
|
|
34
45
|
private handleRowLayout;
|
|
46
|
+
/**
|
|
47
|
+
* Emits one line of a column-grid row, padding every cell to its capacity.
|
|
48
|
+
* With per-cell styling the cells are printed one by one so each can carry
|
|
49
|
+
* its own print mode; otherwise the whole line goes out as a single string.
|
|
50
|
+
*/
|
|
51
|
+
private emitGridLine;
|
|
35
52
|
/**
|
|
36
53
|
* Find the first Text node in a tree
|
|
37
54
|
*/
|
package/dist/traverser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traverser.d.ts","sourceRoot":"","sources":["../src/traverser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"traverser.d.ts","sourceRoot":"","sources":["../src/traverser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA6B9C;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAkB;IAEnC;;;;OAIG;IACH,OAAO,CAAC,gBAAgB,CAAiB;IAEzC,uFAAuF;IACvF,OAAO,CAAC,aAAa,CAAqB;gBAE9B,SAAS,EAAE,eAAe;IAItC;;OAEG;IACG,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BvD;;OAEG;YACW,cAAc;IAO5B;;;;;;OAMG;YACW,UAAU;IAyBxB;;OAEG;YACW,UAAU;IAyDxB;;OAEG;YACW,kBAAkB;IAMhC;;OAEG;YACW,eAAe;IA+H7B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAkCpB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAczB;;;OAGG;YACW,kBAAkB;IAyChC;;;OAGG;YACW,UAAU;IA+BxB;;OAEG;YACW,cAAc;IAM5B;;OAEG;YACW,WAAW;IAuCzB;;OAEG;YACW,gBAAgB;CAK/B"}
|