label-printer 0.4.10 → 0.5.2

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
@@ -39,6 +39,42 @@ This layer contains code to interact with printers
39
39
 
40
40
  - [TSPL](documentations/TSPL.pdf)
41
41
 
42
+ ## Label fields
43
+
44
+ ### Table
45
+
46
+ The `Table` field draws a grid and places text into each cell. It uses the existing `Text` field for cell contents and the existing `Line` field for the grid lines.
47
+
48
+ ```ts
49
+ import { Label } from "label-printer/dist/labels"
50
+ import { Table } from "label-printer/dist/labels/fields"
51
+
52
+ const label = new Label(50, 25)
53
+
54
+ const table = new Table(10, 10, [
55
+ ["A1", "A2"],
56
+ ["B1", "B2"],
57
+ ], {
58
+ size: { width: 200, height: 100 },
59
+ columnWidths: [80, 120],
60
+ rowHeights: [40, 60],
61
+ lineThickness: 2,
62
+ cellPadding: 4,
63
+ formatted: false,
64
+ font: { name: "default", size: 10 },
65
+ })
66
+
67
+ label.add(table)
68
+ ```
69
+
70
+ Sizing rules:
71
+
72
+ - **If `size.width`/`size.height` are set**
73
+ - Any unspecified row/column sizes share the remaining space equally.
74
+ - Cell text is constrained to the cell content box and will wrap to multiple lines. If a row height is fixed, the text may be clipped once it reaches the height limit.
75
+ - **If table size is not set**
76
+ - Unspecified row/column sizes are measured from their content.
77
+
42
78
  # Usefull units:
43
79
 
44
80
  - 1 pt = 1/72 inch
@@ -52,6 +88,9 @@ This layer contains code to interact with printers
52
88
 
53
89
  # Update package
54
90
 
55
- - Run `pnpm changeset` to create change. This change has to be merged into main first
56
- - Run `pnpm changeset version` to create an update with all the versions.
57
- - PR is automatically created on push, merge it to trigger publish
91
+ - Make changes on feature branch.
92
+ - Run `pnpm changeset` on feature branch to create change.
93
+ - Merge feature branch into main.
94
+ - Run `pnpm changeset version` on main to create an update with all the versions.
95
+ - PR is automatically created on push, merge it to trigger publish
96
+ - Note: If there are unpublished changes on main and the release fails, it will be run again on the next push to main, no need to start again from the first step
package/dist/index.d.mts CHANGED
@@ -525,6 +525,7 @@ type FontOption = Expand<Partial<Pick<Font, "style" | "weight">> & Pick<Font, "n
525
525
 
526
526
  type PrintConfig = {
527
527
  dpi: number;
528
+ /** Width of the text in dots */
528
529
  textWidth: (text: string, font: FontOption) => number;
529
530
  getFontName: (font: FontOption) => string;
530
531
  };
@@ -717,7 +718,7 @@ declare class Text extends LabelField {
717
718
  private textCommand;
718
719
  private textLineCommand;
719
720
  private getFontName;
720
- private get textWithFunction();
721
+ private get textWidthFunction();
721
722
  /**
722
723
  * This function is used to calculate the font size if no
723
724
  * print config is provided. This will asume that the font has square characters
@@ -773,6 +774,50 @@ declare class QRCode extends LabelField {
773
774
  commandForLanguage(language: PrinterLanguage, config?: PrintConfig | undefined): Promise<Command>;
774
775
  }
775
776
 
777
+ type TableCellContent = string;
778
+ type TableSize = {
779
+ width?: number;
780
+ height?: number;
781
+ };
782
+ type SizeSpec = number | undefined;
783
+ type TableOptions = {
784
+ size?: TableSize;
785
+ columnWidths?: SizeSpec[];
786
+ rowHeights?: SizeSpec[];
787
+ lineThickness?: number;
788
+ cellPadding?: number;
789
+ formatted?: boolean;
790
+ font?: FontOption;
791
+ };
792
+ declare class Table extends LabelField {
793
+ private readonly x;
794
+ private readonly y;
795
+ private readonly rows;
796
+ private size;
797
+ private columnWidths;
798
+ private rowHeights;
799
+ private lineThickness;
800
+ private cellPadding;
801
+ private formatted;
802
+ private font;
803
+ constructor(x: number, y: number, rows: TableCellContent[][], options?: TableOptions);
804
+ setSize(size: TableSize): void;
805
+ setColumnWidths(widths: SizeSpec[]): void;
806
+ setRowHeights(heights: SizeSpec[]): void;
807
+ setLineThickness(thickness: number): void;
808
+ setCellPadding(padding: number): void;
809
+ setFormatted(formatted: boolean): void;
810
+ setFont(font: FontOption): void;
811
+ commandForLanguage(language: PrinterLanguage, config?: PrintConfig): Promise<Command>;
812
+ private accumulatePositions;
813
+ private resolveTrackSizes;
814
+ private measureAutoColumnWidth;
815
+ private measureAutoRowHeight;
816
+ private estimateLineCount;
817
+ private textWidth;
818
+ private toPlainText;
819
+ }
820
+
776
821
  type index$1_BarCode = BarCode;
777
822
  declare const index$1_BarCode: typeof BarCode;
778
823
  type index$1_Image = Image;
@@ -785,10 +830,12 @@ type index$1_Line = Line;
785
830
  declare const index$1_Line: typeof Line;
786
831
  type index$1_QRCode = QRCode;
787
832
  declare const index$1_QRCode: typeof QRCode;
833
+ type index$1_Table = Table;
834
+ declare const index$1_Table: typeof Table;
788
835
  type index$1_Text = Text;
789
836
  declare const index$1_Text: typeof Text;
790
837
  declare namespace index$1 {
791
- export { index$1_BarCode as BarCode, index$1_Image as Image, index$1_Label as Label, index$1_LabelField as LabelField, index$1_Line as Line, index$1_QRCode as QRCode, index$1_Text as Text };
838
+ export { index$1_BarCode as BarCode, index$1_Image as Image, index$1_Label as Label, index$1_LabelField as LabelField, index$1_Line as Line, index$1_QRCode as QRCode, index$1_Table as Table, index$1_Text as Text };
792
839
  }
793
840
 
794
841
  /**
package/dist/index.d.ts CHANGED
@@ -525,6 +525,7 @@ type FontOption = Expand<Partial<Pick<Font, "style" | "weight">> & Pick<Font, "n
525
525
 
526
526
  type PrintConfig = {
527
527
  dpi: number;
528
+ /** Width of the text in dots */
528
529
  textWidth: (text: string, font: FontOption) => number;
529
530
  getFontName: (font: FontOption) => string;
530
531
  };
@@ -717,7 +718,7 @@ declare class Text extends LabelField {
717
718
  private textCommand;
718
719
  private textLineCommand;
719
720
  private getFontName;
720
- private get textWithFunction();
721
+ private get textWidthFunction();
721
722
  /**
722
723
  * This function is used to calculate the font size if no
723
724
  * print config is provided. This will asume that the font has square characters
@@ -773,6 +774,50 @@ declare class QRCode extends LabelField {
773
774
  commandForLanguage(language: PrinterLanguage, config?: PrintConfig | undefined): Promise<Command>;
774
775
  }
775
776
 
777
+ type TableCellContent = string;
778
+ type TableSize = {
779
+ width?: number;
780
+ height?: number;
781
+ };
782
+ type SizeSpec = number | undefined;
783
+ type TableOptions = {
784
+ size?: TableSize;
785
+ columnWidths?: SizeSpec[];
786
+ rowHeights?: SizeSpec[];
787
+ lineThickness?: number;
788
+ cellPadding?: number;
789
+ formatted?: boolean;
790
+ font?: FontOption;
791
+ };
792
+ declare class Table extends LabelField {
793
+ private readonly x;
794
+ private readonly y;
795
+ private readonly rows;
796
+ private size;
797
+ private columnWidths;
798
+ private rowHeights;
799
+ private lineThickness;
800
+ private cellPadding;
801
+ private formatted;
802
+ private font;
803
+ constructor(x: number, y: number, rows: TableCellContent[][], options?: TableOptions);
804
+ setSize(size: TableSize): void;
805
+ setColumnWidths(widths: SizeSpec[]): void;
806
+ setRowHeights(heights: SizeSpec[]): void;
807
+ setLineThickness(thickness: number): void;
808
+ setCellPadding(padding: number): void;
809
+ setFormatted(formatted: boolean): void;
810
+ setFont(font: FontOption): void;
811
+ commandForLanguage(language: PrinterLanguage, config?: PrintConfig): Promise<Command>;
812
+ private accumulatePositions;
813
+ private resolveTrackSizes;
814
+ private measureAutoColumnWidth;
815
+ private measureAutoRowHeight;
816
+ private estimateLineCount;
817
+ private textWidth;
818
+ private toPlainText;
819
+ }
820
+
776
821
  type index$1_BarCode = BarCode;
777
822
  declare const index$1_BarCode: typeof BarCode;
778
823
  type index$1_Image = Image;
@@ -785,10 +830,12 @@ type index$1_Line = Line;
785
830
  declare const index$1_Line: typeof Line;
786
831
  type index$1_QRCode = QRCode;
787
832
  declare const index$1_QRCode: typeof QRCode;
833
+ type index$1_Table = Table;
834
+ declare const index$1_Table: typeof Table;
788
835
  type index$1_Text = Text;
789
836
  declare const index$1_Text: typeof Text;
790
837
  declare namespace index$1 {
791
- export { index$1_BarCode as BarCode, index$1_Image as Image, index$1_Label as Label, index$1_LabelField as LabelField, index$1_Line as Line, index$1_QRCode as QRCode, index$1_Text as Text };
838
+ export { index$1_BarCode as BarCode, index$1_Image as Image, index$1_Label as Label, index$1_LabelField as LabelField, index$1_Line as Line, index$1_QRCode as QRCode, index$1_Table as Table, index$1_Text as Text };
792
839
  }
793
840
 
794
841
  /**
package/dist/index.js CHANGED
@@ -443,7 +443,6 @@ var ImageProcessor = class {
443
443
  */
444
444
  static getImageDataBrowser(image2) {
445
445
  return __async(this, null, function* () {
446
- console.log("Processing image in browser environment");
447
446
  return new Promise((resolve, reject) => {
448
447
  const img = new Image();
449
448
  img.crossOrigin = "anonymous";
@@ -1439,6 +1438,9 @@ var UsbDevice = class {
1439
1438
  writeData(data) {
1440
1439
  return __async(this, null, function* () {
1441
1440
  const endpointNumber = this.outEndpoint;
1441
+ if (endpointNumber == null) {
1442
+ throw new Error("usb-no-out-endpoint");
1443
+ }
1442
1444
  yield this.device.transferOut(endpointNumber, data);
1443
1445
  });
1444
1446
  }
@@ -1460,6 +1462,9 @@ var UsbDevice = class {
1460
1462
  readData(length) {
1461
1463
  return __async(this, null, function* () {
1462
1464
  const endpointNumber = this.inEndpoint;
1465
+ if (endpointNumber == null) {
1466
+ throw new Error("usb-no-in-endpoint");
1467
+ }
1463
1468
  const result = yield this.device.transferIn(endpointNumber, length);
1464
1469
  if (result.status == "ok" && result.data) {
1465
1470
  return result.data;
@@ -1515,8 +1520,12 @@ var PrinterService = class _PrinterService {
1515
1520
  return __async(this, null, function* () {
1516
1521
  const classes = [TSPLPrinter];
1517
1522
  for (const key in classes) {
1518
- if (yield classes[key].try(device)) {
1519
- return new classes[key](device);
1523
+ try {
1524
+ if (yield classes[key].try(device)) {
1525
+ return new classes[key](device);
1526
+ }
1527
+ } catch (_e) {
1528
+ return void 0;
1520
1529
  }
1521
1530
  }
1522
1531
  return void 0;
@@ -1558,6 +1567,7 @@ __export(labels_exports, {
1558
1567
  LabelField: () => LabelField,
1559
1568
  Line: () => Line,
1560
1569
  QRCode: () => QRCode,
1570
+ Table: () => Table,
1561
1571
  Text: () => Text
1562
1572
  });
1563
1573
 
@@ -1611,11 +1621,16 @@ var Label = class extends Printable {
1611
1621
  return {
1612
1622
  dpi: this.dpi,
1613
1623
  textWidth: (text, font) => {
1614
- const size = dotToPoint(font.size, this.dpi);
1615
- const fontObject = this.getIndexedFont(font).font;
1616
- const run = fontObject.layout(text);
1617
- const scaledWidth = size * run.advanceWidth / fontObject.unitsPerEm;
1618
- return pointsToDots(scaledWidth, this.dpi);
1624
+ const indexedFont = this.getIndexedFont(font);
1625
+ if (indexedFont == null) {
1626
+ return text.length * font.size;
1627
+ } else {
1628
+ const size = dotToPoint(font.size, this.dpi);
1629
+ const fontObject = indexedFont.font;
1630
+ const run = fontObject.layout(text);
1631
+ const scaledWidth = size * run.advanceWidth / fontObject.unitsPerEm;
1632
+ return pointsToDots(scaledWidth, this.dpi);
1633
+ }
1619
1634
  },
1620
1635
  getFontName: this.getFontName.bind(this)
1621
1636
  };
@@ -1689,8 +1704,7 @@ var Label = class extends Printable {
1689
1704
  const generator = this.commandGeneratorFor(language);
1690
1705
  const commands = yield this.fullCommand(language, 0, direction, mirror, 0, generator);
1691
1706
  commands.push(generator.display());
1692
- const group = generator.commandGroup(commands);
1693
- return group;
1707
+ return generator.commandGroup(commands);
1694
1708
  });
1695
1709
  }
1696
1710
  /**
@@ -1722,6 +1736,7 @@ var Label = class extends Printable {
1722
1736
  getIndexedFont(font) {
1723
1737
  var _a, _b;
1724
1738
  const family = this.fonts[font.name];
1739
+ if (!family) return null;
1725
1740
  const style = (_a = font.style) != null ? _a : DEFAULT_FONT_STYLE;
1726
1741
  const weigth = (_b = font.weight) != null ? _b : DEFAULT_FONT_WEIGHT;
1727
1742
  const fontKeys = Object.keys(family.fonts);
@@ -1750,6 +1765,9 @@ var Label = class extends Printable {
1750
1765
  }
1751
1766
  getFontName(font) {
1752
1767
  const indexedFont = this.getIndexedFont(font);
1768
+ if (indexedFont == null) {
1769
+ return font.name;
1770
+ }
1753
1771
  return indexedFont.alias;
1754
1772
  }
1755
1773
  /// This can be extended when we want support multiple fonts
@@ -1798,7 +1816,7 @@ var Text = class extends LabelField {
1798
1816
  this.type = "singleline";
1799
1817
  this.context = void 0;
1800
1818
  this.lineSpacing = 1;
1801
- this.content = content.replace("\n", "");
1819
+ this.content = content.replace("\n", "").replace('"', '\\"');
1802
1820
  this.x = x;
1803
1821
  this.y = y;
1804
1822
  this.formatted = formatted;
@@ -1909,7 +1927,7 @@ var Text = class extends LabelField {
1909
1927
  */
1910
1928
  generatePlainTextCore(content, initialX, initialY, font, features = []) {
1911
1929
  if (!this.context) throw "context-not-set";
1912
- const textWidhtFunction = this.textWithFunction;
1930
+ const textWidhtFunction = this.textWidthFunction;
1913
1931
  let fullWidth = textWidhtFunction(content, font);
1914
1932
  if (this.width) {
1915
1933
  const initialPadding = initialX - this.x;
@@ -2011,7 +2029,7 @@ var Text = class extends LabelField {
2011
2029
  return textCommand;
2012
2030
  } else {
2013
2031
  let lineHeight = font.size * 0.1;
2014
- let textWidth = this.textWithFunction(text, font);
2032
+ let textWidth = this.textWidthFunction(text, font);
2015
2033
  if (features.includes("strike")) {
2016
2034
  commands.push(this.textLineCommand(textWidth, x, y, lineHeight, 0.5, font.size));
2017
2035
  }
@@ -2040,7 +2058,7 @@ var Text = class extends LabelField {
2040
2058
  return (_a = this.context.config) == null ? void 0 : _a.getFontName(font);
2041
2059
  }
2042
2060
  }
2043
- get textWithFunction() {
2061
+ get textWidthFunction() {
2044
2062
  var _a, _b, _c;
2045
2063
  if (this.font.name == "default") {
2046
2064
  return this.defaultTextWidth;
@@ -2128,6 +2146,210 @@ var QRCode = class extends LabelField {
2128
2146
  });
2129
2147
  }
2130
2148
  };
2149
+
2150
+ // src/labels/fields/Table.ts
2151
+ var Table = class extends LabelField {
2152
+ constructor(x, y, rows, options = {}) {
2153
+ var _a, _b, _c, _d;
2154
+ super();
2155
+ this.x = x;
2156
+ this.y = y;
2157
+ this.rows = rows;
2158
+ this.size = options.size;
2159
+ this.columnWidths = options.columnWidths;
2160
+ this.rowHeights = options.rowHeights;
2161
+ this.lineThickness = (_a = options.lineThickness) != null ? _a : 2;
2162
+ this.cellPadding = (_b = options.cellPadding) != null ? _b : 4;
2163
+ this.formatted = (_c = options.formatted) != null ? _c : true;
2164
+ this.font = (_d = options.font) != null ? _d : { name: "default", size: 10 };
2165
+ }
2166
+ setSize(size) {
2167
+ this.size = size;
2168
+ }
2169
+ setColumnWidths(widths) {
2170
+ this.columnWidths = widths;
2171
+ }
2172
+ setRowHeights(heights) {
2173
+ this.rowHeights = heights;
2174
+ }
2175
+ setLineThickness(thickness) {
2176
+ this.lineThickness = thickness;
2177
+ }
2178
+ setCellPadding(padding) {
2179
+ this.cellPadding = padding;
2180
+ }
2181
+ setFormatted(formatted) {
2182
+ this.formatted = formatted;
2183
+ }
2184
+ setFont(font) {
2185
+ this.font = font;
2186
+ }
2187
+ commandForLanguage(language, config) {
2188
+ return __async(this, null, function* () {
2189
+ var _a, _b, _c, _d;
2190
+ const generator = this.commandGeneratorFor(language);
2191
+ const rowCount = this.rows.length;
2192
+ const colCount = Math.max(0, ...this.rows.map((r) => r.length));
2193
+ const resolvedColumnWidths = this.resolveTrackSizes({
2194
+ trackCount: colCount,
2195
+ explicitSizes: this.columnWidths,
2196
+ total: (_a = this.size) == null ? void 0 : _a.width,
2197
+ measure: (i) => this.measureAutoColumnWidth(i, colCount, config)
2198
+ });
2199
+ const resolvedRowHeights = this.resolveTrackSizes({
2200
+ trackCount: rowCount,
2201
+ explicitSizes: this.rowHeights,
2202
+ total: (_b = this.size) == null ? void 0 : _b.height,
2203
+ measure: (i) => this.measureAutoRowHeight(i, resolvedColumnWidths, config)
2204
+ });
2205
+ const xPositions = this.accumulatePositions(this.x, resolvedColumnWidths);
2206
+ const yPositions = this.accumulatePositions(this.y, resolvedRowHeights);
2207
+ const fields = [];
2208
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
2209
+ for (let colIndex = 0; colIndex < colCount; colIndex++) {
2210
+ const cellContent = (_d = (_c = this.rows[rowIndex]) == null ? void 0 : _c[colIndex]) != null ? _d : "";
2211
+ const cellX = xPositions[colIndex];
2212
+ const cellY = yPositions[rowIndex];
2213
+ const cellWidth = resolvedColumnWidths[colIndex];
2214
+ const cellHeight = resolvedRowHeights[rowIndex];
2215
+ const textX = cellX + this.cellPadding;
2216
+ const textY = cellY + this.cellPadding;
2217
+ const textWidth = Math.max(1, cellWidth - this.cellPadding * 2);
2218
+ const textHeight = Math.max(1, cellHeight - this.cellPadding * 2);
2219
+ const text = new Text(cellContent, textX, textY, this.formatted);
2220
+ text.setFont(this.font);
2221
+ text.setMultiLine(textWidth, textHeight);
2222
+ fields.push(text);
2223
+ }
2224
+ }
2225
+ const totalWidth = resolvedColumnWidths.reduce((a, b) => a + b, 0);
2226
+ const totalHeight = resolvedRowHeights.reduce((a, b) => a + b, 0);
2227
+ for (let colIndex = 0; colIndex <= colCount; colIndex++) {
2228
+ const sx = colIndex == colCount ? this.x + totalWidth : xPositions[colIndex];
2229
+ fields.push(new Line(
2230
+ { x: sx, y: this.y },
2231
+ { x: sx, y: this.y + totalHeight },
2232
+ this.lineThickness
2233
+ ));
2234
+ }
2235
+ for (let rowIndex = 0; rowIndex <= rowCount; rowIndex++) {
2236
+ const sy = rowIndex == rowCount ? this.y + totalHeight : yPositions[rowIndex];
2237
+ fields.push(new Line(
2238
+ { x: this.x, y: sy },
2239
+ { x: this.x + totalWidth, y: sy },
2240
+ this.lineThickness
2241
+ ));
2242
+ }
2243
+ const commandList = yield Promise.all(fields.map((field) => field.commandForLanguage(language, config)));
2244
+ return generator.commandGroup(commandList);
2245
+ });
2246
+ }
2247
+ accumulatePositions(start, sizes) {
2248
+ const positions = [];
2249
+ let current = start;
2250
+ for (const s of sizes) {
2251
+ positions.push(current);
2252
+ current += s;
2253
+ }
2254
+ return positions;
2255
+ }
2256
+ resolveTrackSizes(params) {
2257
+ if (params.trackCount == 0) return [];
2258
+ const sizes = new Array(params.trackCount).fill(void 0);
2259
+ if (params.explicitSizes) {
2260
+ for (let i = 0; i < Math.min(params.trackCount, params.explicitSizes.length); i++) {
2261
+ sizes[i] = params.explicitSizes[i];
2262
+ }
2263
+ }
2264
+ if (params.total != void 0) {
2265
+ const fixedTotal = sizes.reduce((sum, s) => sum + (s != null ? s : 0), 0);
2266
+ const flexibleCount = sizes.filter((s) => s == void 0).length;
2267
+ const remaining = Math.max(0, params.total - fixedTotal);
2268
+ const flexSize = flexibleCount == 0 ? 0 : remaining / flexibleCount;
2269
+ return sizes.map((s, i) => {
2270
+ var _a;
2271
+ return Math.max(1, (_a = s != null ? s : flexSize) != null ? _a : params.measure(i));
2272
+ });
2273
+ }
2274
+ return sizes.map((s, i) => Math.max(1, s != null ? s : params.measure(i)));
2275
+ }
2276
+ measureAutoColumnWidth(columnIndex, colCount, config) {
2277
+ var _a;
2278
+ const contentWidths = [];
2279
+ for (const row of this.rows) {
2280
+ const content = (_a = row == null ? void 0 : row[columnIndex]) != null ? _a : "";
2281
+ const plain = this.toPlainText(content);
2282
+ const w = this.textWidth(plain, config);
2283
+ contentWidths.push(w);
2284
+ }
2285
+ const maxContentWidth = contentWidths.length == 0 ? 0 : Math.max(...contentWidths);
2286
+ const minWidth = this.font.size;
2287
+ const measured = Math.max(minWidth, maxContentWidth) + this.cellPadding * 2;
2288
+ if (colCount == 0) return measured;
2289
+ return measured;
2290
+ }
2291
+ measureAutoRowHeight(rowIndex, columnWidths, config) {
2292
+ var _a, _b;
2293
+ const row = (_a = this.rows[rowIndex]) != null ? _a : [];
2294
+ const contentHeights = [];
2295
+ for (let colIndex = 0; colIndex < columnWidths.length; colIndex++) {
2296
+ const content = (_b = row[colIndex]) != null ? _b : "";
2297
+ const plain = this.toPlainText(content);
2298
+ const availableWidth = Math.max(1, columnWidths[colIndex] - this.cellPadding * 2);
2299
+ const lineCount = this.estimateLineCount(plain, availableWidth, config);
2300
+ const lineHeight = this.font.size + 1;
2301
+ contentHeights.push(lineCount * lineHeight);
2302
+ }
2303
+ const maxContentHeight = contentHeights.length == 0 ? this.font.size : Math.max(...contentHeights);
2304
+ return maxContentHeight + this.cellPadding * 2;
2305
+ }
2306
+ estimateLineCount(content, width, config) {
2307
+ const normalized = content.replace("\n", " ");
2308
+ if (normalized.trim() == "") return 1;
2309
+ const words = [];
2310
+ let buffer2 = "";
2311
+ for (let i = 0; i < normalized.length; i++) {
2312
+ const ch = normalized.charAt(i);
2313
+ if (isWhitespace(ch)) {
2314
+ if (buffer2 != "") {
2315
+ words.push(buffer2);
2316
+ buffer2 = "";
2317
+ }
2318
+ continue;
2319
+ }
2320
+ buffer2 += ch;
2321
+ }
2322
+ if (buffer2 != "") words.push(buffer2);
2323
+ let lines = 1;
2324
+ let currentLine = "";
2325
+ for (const word of words) {
2326
+ const candidate = currentLine == "" ? word : `${currentLine} ${word}`;
2327
+ if (this.textWidth(candidate, config) <= width) {
2328
+ currentLine = candidate;
2329
+ } else {
2330
+ if (currentLine == "") {
2331
+ const charsPerLine = Math.max(1, Math.floor(width / Math.max(1, this.font.size)));
2332
+ const neededLines = Math.ceil(word.length / charsPerLine);
2333
+ lines += neededLines - 1;
2334
+ currentLine = word.substring((neededLines - 1) * charsPerLine);
2335
+ } else {
2336
+ lines += 1;
2337
+ currentLine = word;
2338
+ }
2339
+ }
2340
+ }
2341
+ return Math.max(1, lines);
2342
+ }
2343
+ textWidth(text, config) {
2344
+ if (this.font.name == "default" || !config) {
2345
+ return text.length * this.font.size;
2346
+ }
2347
+ return config.textWidth(text, this.font);
2348
+ }
2349
+ toPlainText(content) {
2350
+ return content.replace(/<[^>]*>/g, "");
2351
+ }
2352
+ };
2131
2353
  // Annotate the CommonJS export names for ESM import in node:
2132
2354
  0 && (module.exports = {
2133
2355
  commands,