@cj-tech-master/excelts 6.2.0 → 7.0.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 (56) hide show
  1. package/README.md +45 -17
  2. package/README_zh.md +43 -15
  3. package/dist/browser/index.browser.d.ts +1 -1
  4. package/dist/browser/index.browser.js +1 -1
  5. package/dist/browser/index.d.ts +2 -2
  6. package/dist/browser/index.js +1 -1
  7. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +0 -2
  8. package/dist/browser/modules/excel/stream/workbook-writer.d.ts +2 -2
  9. package/dist/browser/modules/excel/types.d.ts +0 -2
  10. package/dist/browser/modules/pdf/excel-bridge.d.ts +29 -0
  11. package/dist/browser/modules/pdf/excel-bridge.js +423 -0
  12. package/dist/browser/modules/pdf/index.d.ts +22 -24
  13. package/dist/browser/modules/pdf/index.js +22 -25
  14. package/dist/browser/modules/pdf/pdf.d.ts +121 -0
  15. package/dist/browser/modules/pdf/pdf.js +255 -0
  16. package/dist/browser/modules/pdf/render/layout-engine.d.ts +10 -8
  17. package/dist/browser/modules/pdf/render/layout-engine.js +115 -209
  18. package/dist/browser/modules/pdf/render/pdf-exporter.d.ts +9 -62
  19. package/dist/browser/modules/pdf/render/pdf-exporter.js +38 -78
  20. package/dist/browser/modules/pdf/render/style-converter.d.ts +20 -18
  21. package/dist/browser/modules/pdf/render/style-converter.js +24 -23
  22. package/dist/browser/modules/pdf/types.d.ts +193 -11
  23. package/dist/browser/modules/pdf/types.js +22 -1
  24. package/dist/cjs/index.js +3 -3
  25. package/dist/cjs/modules/pdf/excel-bridge.js +426 -0
  26. package/dist/cjs/modules/pdf/index.js +25 -28
  27. package/dist/cjs/modules/pdf/pdf.js +258 -0
  28. package/dist/cjs/modules/pdf/render/layout-engine.js +116 -210
  29. package/dist/cjs/modules/pdf/render/pdf-exporter.js +37 -79
  30. package/dist/cjs/modules/pdf/render/style-converter.js +24 -23
  31. package/dist/cjs/modules/pdf/types.js +23 -2
  32. package/dist/esm/index.browser.js +1 -1
  33. package/dist/esm/index.js +1 -1
  34. package/dist/esm/modules/pdf/excel-bridge.js +423 -0
  35. package/dist/esm/modules/pdf/index.js +22 -25
  36. package/dist/esm/modules/pdf/pdf.js +255 -0
  37. package/dist/esm/modules/pdf/render/layout-engine.js +115 -209
  38. package/dist/esm/modules/pdf/render/pdf-exporter.js +38 -78
  39. package/dist/esm/modules/pdf/render/style-converter.js +24 -23
  40. package/dist/esm/modules/pdf/types.js +22 -1
  41. package/dist/iife/excelts.iife.js +728 -251
  42. package/dist/iife/excelts.iife.js.map +1 -1
  43. package/dist/iife/excelts.iife.min.js +34 -34
  44. package/dist/types/index.browser.d.ts +1 -1
  45. package/dist/types/index.d.ts +2 -2
  46. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +0 -2
  47. package/dist/types/modules/excel/stream/workbook-writer.d.ts +2 -2
  48. package/dist/types/modules/excel/types.d.ts +0 -2
  49. package/dist/types/modules/pdf/excel-bridge.d.ts +29 -0
  50. package/dist/types/modules/pdf/index.d.ts +22 -24
  51. package/dist/types/modules/pdf/pdf.d.ts +121 -0
  52. package/dist/types/modules/pdf/render/layout-engine.d.ts +10 -8
  53. package/dist/types/modules/pdf/render/pdf-exporter.d.ts +9 -62
  54. package/dist/types/modules/pdf/render/style-converter.d.ts +20 -18
  55. package/dist/types/modules/pdf/types.d.ts +193 -11
  56. package/package.json +1 -1
@@ -1,13 +1,16 @@
1
1
  "use strict";
2
2
  /**
3
- * Layout engine for Excel-to-PDF conversion.
3
+ * Layout engine for PDF generation.
4
4
  *
5
- * Takes a worksheet and produces LayoutPage objects that describe exactly
5
+ * Takes a PdfSheetData and produces LayoutPage objects that describe exactly
6
6
  * where each cell, border, and piece of text should be drawn on each PDF page.
7
7
  *
8
+ * This module is fully independent of the Excel module — it works with
9
+ * the PDF module's own data model (PdfSheetData, PdfCellData, etc.).
10
+ *
8
11
  * Key responsibilities:
9
- * - Convert Excel column widths (character units) to PDF points
10
- * - Convert Excel row heights (points already, but may need scaling)
12
+ * - Convert column widths (character units) to PDF points
13
+ * - Convert row heights (points already, but may need scaling)
11
14
  * - Handle merged cells spanning multiple rows/columns
12
15
  * - Paginate content across multiple pages
13
16
  * - Handle fitToPage scaling
@@ -15,12 +18,9 @@
15
18
  * - Skip hidden rows and columns
16
19
  */
17
20
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.layoutWorksheet = layoutWorksheet;
21
+ exports.layoutSheet = layoutSheet;
19
22
  exports.paginateRows = paginateRows;
20
- const enums_1 = require("../../excel/enums.js");
21
- const address_1 = require("../../excel/utils/address.js");
22
- const cell_format_1 = require("../../excel/utils/cell-format.js");
23
- const utils_base_1 = require("../../../utils/utils.base.js");
23
+ const types_1 = require("../types");
24
24
  const font_manager_1 = require("../font/font-manager");
25
25
  const style_converter_1 = require("./style-converter");
26
26
  // =============================================================================
@@ -34,9 +34,9 @@ const MIN_COLUMN_WIDTH = 5;
34
34
  // Layout Engine
35
35
  // =============================================================================
36
36
  /**
37
- * Compute the layout for a worksheet across one or more PDF pages.
37
+ * Compute the layout for a sheet across one or more PDF pages.
38
38
  */
39
- function layoutWorksheet(worksheet, options, fontManager) {
39
+ function layoutSheet(sheet, options, fontManager) {
40
40
  const { margins } = options;
41
41
  let pageWidth = options.pageSize.width;
42
42
  let pageHeight = options.pageSize.height;
@@ -49,15 +49,15 @@ function layoutWorksheet(worksheet, options, fontManager) {
49
49
  const footerHeight = options.showPageNumbers ? 20 : 0;
50
50
  const availableHeight = contentHeight - headerHeight - footerHeight;
51
51
  // Determine print area bounds (if set)
52
- const printRange = getPrintRange(worksheet);
52
+ const printRange = getPrintRange(sheet);
53
53
  // --- Step 1: Visible columns and widths ---
54
- const { columnWidths, visibleCols } = computeColumnWidths(worksheet, printRange);
54
+ const { columnWidths, visibleCols } = computeColumnWidths(sheet, printRange);
55
55
  const columnCount = visibleCols.length;
56
56
  if (columnCount === 0) {
57
- return [emptyPage(pageWidth, pageHeight, worksheet.name, options)];
57
+ return [emptyPage(pageWidth, pageHeight, sheet.name, options)];
58
58
  }
59
59
  // --- Step 2: Scale ---
60
- let totalTableWidth = columnWidths.reduce((sum, w) => sum + w, 0);
60
+ const totalTableWidth = columnWidths.reduce((sum, w) => sum + w, 0);
61
61
  let scaleFactor = options.scale;
62
62
  if (options.fitToPage && totalTableWidth > 0) {
63
63
  const fitScale = contentWidth / totalTableWidth;
@@ -66,26 +66,15 @@ function layoutWorksheet(worksheet, options, fontManager) {
66
66
  }
67
67
  }
68
68
  const scaledColumnWidths = columnWidths.map(w => w * scaleFactor);
69
- totalTableWidth = scaledColumnWidths.reduce((sum, w) => sum + w, 0);
70
- // Column x-offsets
71
- const columnOffsets = [];
72
- let xOffset = margins.left;
73
- if (totalTableWidth < contentWidth) {
74
- xOffset = margins.left + (contentWidth - totalTableWidth) / 2;
75
- }
76
- for (let i = 0; i < scaledColumnWidths.length; i++) {
77
- columnOffsets.push(xOffset);
78
- xOffset += scaledColumnWidths[i];
79
- }
80
69
  // --- Step 3: Visible rows and heights ---
81
- const { rowHeights, visibleRows } = computeRowHeights(worksheet, scaleFactor, printRange);
70
+ const { rowHeights, visibleRows } = computeRowHeights(sheet, scaleFactor, printRange);
82
71
  // --- Step 4: Merge map ---
83
- const mergeMap = buildMergeMap(worksheet);
72
+ const mergeMap = buildMergeMap(sheet);
84
73
  // --- Step 5: Paginate vertically (rows) and horizontally (columns) ---
85
74
  const repeatRowCount = typeof options.repeatRows === "number" ? options.repeatRows : 0;
86
- const rowBreakSet = buildRowBreakSet(worksheet, visibleRows);
75
+ const rowBreakSet = buildRowBreakSet(sheet, visibleRows);
87
76
  const rowPages = paginateRows(rowHeights, availableHeight, repeatRowCount, rowBreakSet);
88
- const colGroups = paginateColumns(scaledColumnWidths, contentWidth, worksheet, visibleCols);
77
+ const colGroups = paginateColumns(scaledColumnWidths, contentWidth, sheet, visibleCols);
89
78
  // --- Step 6: Layout cells per page (row page × column page) ---
90
79
  const layoutPages = [];
91
80
  for (const rowPage of rowPages) {
@@ -125,8 +114,8 @@ function layoutWorksheet(worksheet, options, fontManager) {
125
114
  if (mergeInfo && !mergeInfo.isMaster) {
126
115
  continue;
127
116
  }
128
- const row = worksheet.findRow(wsRowNumber);
129
- const cell = row ? row.findCell(wsColNumber) : undefined;
117
+ const row = sheet.rows.get(wsRowNumber);
118
+ const cell = row?.cells.get(wsColNumber);
130
119
  let colSpan = 1;
131
120
  let rowSpan = 1;
132
121
  if (mergeInfo && mergeInfo.isMaster) {
@@ -164,7 +153,7 @@ function layoutWorksheet(worksheet, options, fontManager) {
164
153
  cellHeight += pageRowHeights[ri + s];
165
154
  }
166
155
  const rectY = cellY - cellHeight;
167
- cells.push(buildLayoutCell(cell, ci, visibleRowIdx, cellX, rectY, cellWidth, cellHeight, colSpan, rowSpan, options, fontManager, scaleFactor));
156
+ cells.push(buildLayoutCell(cell, cellX, rectY, cellWidth, cellHeight, colSpan, rowSpan, options, fontManager, scaleFactor));
168
157
  }
169
158
  }
170
159
  layoutPages.push({
@@ -173,20 +162,20 @@ function layoutWorksheet(worksheet, options, fontManager) {
173
162
  cells,
174
163
  width: pageWidth,
175
164
  height: pageHeight,
176
- sheetName: worksheet.name,
177
- worksheetCols: colGroup.map(ci => visibleCols[ci]),
165
+ sheetName: sheet.name,
166
+ sheetCols: colGroup.map(ci => visibleCols[ci]),
178
167
  columnOffsets: groupColOffsets,
179
168
  columnWidths: groupColWidths,
180
- worksheetRows: rowPage.map(ri => visibleRows[ri]),
169
+ sheetRows: rowPage.map(ri => visibleRows[ri]),
181
170
  rowYPositions,
182
171
  rowHeights: pageRowHeights,
183
172
  images: []
184
173
  });
185
174
  }
186
175
  }
187
- // --- Step 7: Collect images and place them on the correct pages ---
188
- if (layoutPages.length > 0) {
189
- assignImagesToPages(worksheet, layoutPages);
176
+ // --- Step 7: Place images on the correct pages ---
177
+ if (layoutPages.length > 0 && sheet.images) {
178
+ assignImagesToPages(sheet.images, layoutPages);
190
179
  }
191
180
  return layoutPages;
192
181
  }
@@ -201,21 +190,49 @@ function emptyPage(width, height, sheetName, options) {
201
190
  width,
202
191
  height,
203
192
  sheetName,
204
- worksheetCols: [],
193
+ sheetCols: [],
205
194
  columnOffsets: [],
206
195
  columnWidths: [],
207
- worksheetRows: [],
196
+ sheetRows: [],
208
197
  rowYPositions: [],
209
198
  rowHeights: [],
210
199
  images: []
211
200
  };
212
201
  }
213
202
  /**
214
- * Get the print area range from the worksheet's pageSetup.
203
+ * Parse a cell reference like "A1" into 0-indexed { c, r }.
204
+ */
205
+ function parseCellRef(ref) {
206
+ const upper = ref.replace(/\$/g, "").toUpperCase();
207
+ let col = 0;
208
+ let i = 0;
209
+ while (i < upper.length && upper.charCodeAt(i) >= 65 && upper.charCodeAt(i) <= 90) {
210
+ col = col * 26 + (upper.charCodeAt(i) - 64);
211
+ i++;
212
+ }
213
+ const row = parseInt(upper.substring(i), 10);
214
+ return { c: col - 1, r: row - 1 };
215
+ }
216
+ /**
217
+ * Parse a range string like "A1:B2" into 0-indexed start/end.
218
+ */
219
+ function parseRangeRef(range) {
220
+ const idx = range.indexOf(":");
221
+ if (idx === -1) {
222
+ const cell = parseCellRef(range);
223
+ return { s: cell, e: { ...cell } };
224
+ }
225
+ return {
226
+ s: parseCellRef(range.slice(0, idx)),
227
+ e: parseCellRef(range.slice(idx + 1))
228
+ };
229
+ }
230
+ /**
231
+ * Get the print area range from the sheet's pageSetup.
215
232
  * Returns null if no print area is set.
216
233
  */
217
- function getPrintRange(worksheet) {
218
- const printArea = worksheet.pageSetup?.printArea;
234
+ function getPrintRange(sheet) {
235
+ const printArea = sheet.pageSetup?.printArea;
219
236
  if (!printArea || typeof printArea !== "string") {
220
237
  return null;
221
238
  }
@@ -226,7 +243,7 @@ function getPrintRange(worksheet) {
226
243
  return null;
227
244
  }
228
245
  try {
229
- const range = (0, address_1.decodeRange)(firstRange);
246
+ const range = parseRangeRef(firstRange);
230
247
  return {
231
248
  startRow: range.s.r + 1,
232
249
  endRow: range.e.r + 1,
@@ -241,22 +258,22 @@ function getPrintRange(worksheet) {
241
258
  // =============================================================================
242
259
  // Column Width Computation
243
260
  // =============================================================================
244
- function computeColumnWidths(worksheet, printRange) {
245
- const dimensions = worksheet.dimensions;
246
- const hasData = dimensions && dimensions.model.top > 0 && dimensions.model.left > 0;
261
+ function computeColumnWidths(sheet, printRange) {
262
+ const bounds = sheet.bounds;
263
+ const hasData = bounds.top > 0 && bounds.left > 0;
247
264
  if (!hasData) {
248
265
  return { columnWidths: [], visibleCols: [] };
249
266
  }
250
- const startCol = printRange?.startCol ?? dimensions.model.left;
251
- const endCol = printRange?.endCol ?? dimensions.model.right;
267
+ const startCol = printRange?.startCol ?? bounds.left;
268
+ const endCol = printRange?.endCol ?? bounds.right;
252
269
  const columnWidths = [];
253
270
  const visibleCols = [];
254
271
  for (let c = startCol; c <= endCol; c++) {
255
- const col = worksheet.getColumn(c);
256
- if (col.hidden) {
272
+ const col = sheet.columns.get(c);
273
+ if (col?.hidden) {
257
274
  continue;
258
275
  }
259
- const excelWidth = col.width ?? DEFAULT_COLUMN_WIDTH;
276
+ const excelWidth = col?.width ?? DEFAULT_COLUMN_WIDTH;
260
277
  const pointWidth = Math.max(excelWidth * EXCEL_CHAR_WIDTH_TO_POINTS, MIN_COLUMN_WIDTH);
261
278
  columnWidths.push(pointWidth);
262
279
  visibleCols.push(c);
@@ -266,17 +283,17 @@ function computeColumnWidths(worksheet, printRange) {
266
283
  // =============================================================================
267
284
  // Row Height Computation
268
285
  // =============================================================================
269
- function computeRowHeights(worksheet, scaleFactor, printRange) {
270
- const dimensions = worksheet.dimensions;
271
- if (!dimensions || dimensions.model.top <= 0) {
286
+ function computeRowHeights(sheet, scaleFactor, printRange) {
287
+ const bounds = sheet.bounds;
288
+ if (bounds.top <= 0) {
272
289
  return { rowHeights: [], visibleRows: [] };
273
290
  }
274
- const startRow = printRange?.startRow ?? dimensions.model.top;
275
- const endRow = printRange?.endRow ?? dimensions.model.bottom;
291
+ const startRow = printRange?.startRow ?? bounds.top;
292
+ const endRow = printRange?.endRow ?? bounds.bottom;
276
293
  const rowHeights = [];
277
294
  const visibleRows = [];
278
295
  for (let r = startRow; r <= endRow; r++) {
279
- const row = worksheet.findRow(r);
296
+ const row = sheet.rows.get(r);
280
297
  if (row && row.hidden) {
281
298
  continue;
282
299
  }
@@ -287,10 +304,9 @@ function computeRowHeights(worksheet, scaleFactor, printRange) {
287
304
  }
288
305
  else {
289
306
  // Auto-size: scan cells in this row to find the largest needed height.
290
- // Account for font size and multi-line content (explicit newlines or wrapText).
291
307
  height = DEFAULT_ROW_HEIGHT;
292
308
  if (row) {
293
- row.eachCell({ includeEmpty: false }, cell => {
309
+ for (const cell of row.cells.values()) {
294
310
  let fontSize = cell.style?.font?.size ?? 11;
295
311
  // For rich text cells, find the largest font size across all runs
296
312
  const rtValue = cell.value;
@@ -308,8 +324,9 @@ function computeRowHeights(worksheet, scaleFactor, printRange) {
308
324
  const lineCount = Math.max(1, (text.match(/\n/g) ?? []).length + 1);
309
325
  // For wrapText cells, estimate how many lines word-wrapping produces
310
326
  let wrapLineCount = lineCount;
311
- if (cell.alignment?.wrapText && lineCount === 1 && text.length > 0) {
312
- const colWidth = worksheet.getColumn(cell.col).width ?? DEFAULT_COLUMN_WIDTH;
327
+ if (cell.style?.alignment?.wrapText && lineCount === 1 && text.length > 0) {
328
+ const col = sheet.columns.get(cell.col);
329
+ const colWidth = col?.width ?? DEFAULT_COLUMN_WIDTH;
313
330
  const colPts = colWidth * EXCEL_CHAR_WIDTH_TO_POINTS * scaleFactor;
314
331
  const avgCharWidth = fontSize * 0.55; // rough average char width
315
332
  const charsPerLine = Math.max(1, Math.floor(colPts / avgCharWidth));
@@ -319,7 +336,7 @@ function computeRowHeights(worksheet, scaleFactor, printRange) {
319
336
  if (neededHeight > height) {
320
337
  height = neededHeight;
321
338
  }
322
- });
339
+ }
323
340
  }
324
341
  }
325
342
  rowHeights.push(height * scaleFactor);
@@ -333,19 +350,19 @@ function computeRowHeights(worksheet, scaleFactor, printRange) {
333
350
  /**
334
351
  * Build a set of visible-row indices where manual page breaks occur.
335
352
  */
336
- function buildRowBreakSet(worksheet, visibleRows) {
353
+ function buildRowBreakSet(sheet, visibleRows) {
337
354
  const breaks = new Set();
338
- const rowBreaks = worksheet.rowBreaks ?? [];
355
+ const rowBreaks = sheet.rowBreaks ?? [];
339
356
  if (rowBreaks.length === 0) {
340
357
  return breaks;
341
358
  }
342
- // Map worksheet row numbers to visible-row indices
359
+ // Map row numbers to visible-row indices
343
360
  const rowToIndex = new Map();
344
361
  for (let i = 0; i < visibleRows.length; i++) {
345
362
  rowToIndex.set(visibleRows[i], i);
346
363
  }
347
364
  for (const brk of rowBreaks) {
348
- const idx = rowToIndex.get(brk.id);
365
+ const idx = rowToIndex.get(brk);
349
366
  if (idx !== undefined) {
350
367
  // Break AFTER this row, so the next row starts a new page
351
368
  breaks.add(idx + 1);
@@ -354,21 +371,17 @@ function buildRowBreakSet(worksheet, visibleRows) {
354
371
  return breaks;
355
372
  }
356
373
  /**
357
- * Build a map of all merged cell regions in the worksheet.
358
- * Uses the worksheet model's public mergeCells property.
374
+ * Build a map of all merged cell regions.
359
375
  * Key: "row:col" (1-based), Value: merge info
360
376
  */
361
- function buildMergeMap(worksheet) {
377
+ function buildMergeMap(sheet) {
362
378
  const map = new Map();
363
- if (!worksheet.hasMerges) {
379
+ const merges = sheet.merges;
380
+ if (!merges || merges.length === 0) {
364
381
  return map;
365
382
  }
366
- const mergeCells = worksheet.model.mergeCells;
367
- if (!mergeCells) {
368
- return map;
369
- }
370
- for (const rangeStr of mergeCells) {
371
- const range = (0, address_1.decodeRange)(rangeStr);
383
+ for (const rangeStr of merges) {
384
+ const range = parseRangeRef(rangeStr);
372
385
  const top = range.s.r + 1;
373
386
  const left = range.s.c + 1;
374
387
  const bottom = range.e.r + 1;
@@ -423,9 +436,6 @@ function paginateRows(rowHeights, availableHeight, repeatRowCount, rowBreaks) {
423
436
  currentPage.length > 0 &&
424
437
  currentPage.length === repeatedPrefixCount;
425
438
  if (pageHasOnlyRepeatRows) {
426
- // If repeated header rows consume too much space to allow any body row,
427
- // fall back to placing body rows without repeated headers rather than
428
- // emitting a header-only page or overflowing the page.
429
439
  currentPage = [];
430
440
  currentPageHeight = 0;
431
441
  repeatedPrefixCount = 0;
@@ -458,25 +468,22 @@ function paginateRows(rowHeights, availableHeight, repeatRowCount, rowBreaks) {
458
468
  }
459
469
  /**
460
470
  * Split columns into groups for horizontal pagination.
461
- * Each group is an array of column indices (into the visibleCols/scaledColumnWidths arrays).
462
- * If the total width fits in contentWidth and there are no colBreaks, returns a single group.
463
471
  */
464
- function paginateColumns(columnWidths, contentWidth, worksheet, visibleCols) {
472
+ function paginateColumns(columnWidths, contentWidth, sheet, visibleCols) {
465
473
  if (columnWidths.length === 0) {
466
474
  return [[]];
467
475
  }
468
476
  // Build col break set (indices into visibleCols)
469
477
  const colBreaks = new Set();
470
- const wsColBreaks = worksheet.colBreaks ?? [];
478
+ const wsColBreaks = sheet.colBreaks ?? [];
471
479
  if (wsColBreaks.length > 0) {
472
480
  const colToIndex = new Map();
473
481
  for (let i = 0; i < visibleCols.length; i++) {
474
482
  colToIndex.set(visibleCols[i], i);
475
483
  }
476
484
  for (const brk of wsColBreaks) {
477
- const idx = colToIndex.get(brk.id);
485
+ const idx = colToIndex.get(brk);
478
486
  if (idx !== undefined) {
479
- // Break AFTER this column, so the next column starts a new group
480
487
  colBreaks.add(idx + 1);
481
488
  }
482
489
  }
@@ -486,9 +493,6 @@ function paginateColumns(columnWidths, contentWidth, worksheet, visibleCols) {
486
493
  let currentWidth = 0;
487
494
  for (let i = 0; i < columnWidths.length; i++) {
488
495
  const colWidth = columnWidths[i];
489
- // Force break at column break positions or when exceeding page width
490
- // Use a small epsilon (0.01pt) to avoid floating-point precision issues
491
- // when fitToPage scales columns to exactly match content width
492
496
  const forceBreak = colBreaks.has(i) && currentGroup.length > 0;
493
497
  if ((forceBreak || currentWidth + colWidth > contentWidth + 0.01) && currentGroup.length > 0) {
494
498
  groups.push(currentGroup);
@@ -506,8 +510,8 @@ function paginateColumns(columnWidths, contentWidth, worksheet, visibleCols) {
506
510
  // =============================================================================
507
511
  // Cell Layout
508
512
  // =============================================================================
509
- function buildLayoutCell(cell, _colIndex, _rowIndex, x, y, width, height, colSpan, rowSpan, options, fontManager, scaleFactor) {
510
- const text = getCellText(cell);
513
+ function buildLayoutCell(cell, x, y, width, height, colSpan, rowSpan, options, fontManager, scaleFactor) {
514
+ const text = cell?.text ?? "";
511
515
  const style = cell?.style ?? {};
512
516
  const fontProps = (0, style_converter_1.extractFontProperties)(style.font, options.defaultFontFamily, options.defaultFontSize);
513
517
  // Scale font size proportionally when fitToPage shrinks the layout
@@ -520,7 +524,7 @@ function buildLayoutCell(cell, _colIndex, _rowIndex, x, y, width, height, colSpa
520
524
  const pdfFontName = (0, font_manager_1.resolvePdfFontName)(fontProps.fontFamily, fontProps.bold, fontProps.italic);
521
525
  fontManager.ensureFont(pdfFontName);
522
526
  }
523
- // Rich text runs (buildRichTextRuns handles font tracking internally)
527
+ // Rich text runs
524
528
  const richText = buildRichTextRuns(cell, options, fontManager, scaleFactor);
525
529
  return {
526
530
  text,
@@ -546,57 +550,22 @@ function buildLayoutCell(cell, _colIndex, _rowIndex, x, y, width, height, colSpa
546
550
  };
547
551
  }
548
552
  // =============================================================================
549
- // Image Collection
553
+ // Image Placement
550
554
  // =============================================================================
551
555
  /**
552
- * Collect images from a worksheet and assign them to the page that contains
553
- * their top-left anchor.
556
+ * Assign pre-collected images to the pages that contain their top-left anchor.
554
557
  */
555
- function assignImagesToPages(worksheet, layoutPages) {
556
- // Access worksheet images via getImages()
557
- const wsImages = worksheet.getImages?.();
558
- if (!wsImages || !Array.isArray(wsImages)) {
559
- return;
560
- }
561
- // Access the workbook for image data
562
- const workbook = worksheet.workbook;
563
- if (!workbook) {
564
- return;
565
- }
566
- for (const wsImage of wsImages) {
567
- if (!wsImage.range?.tl) {
568
- continue;
569
- }
570
- const imageId = wsImage.imageId;
571
- const mediaItem = workbook.getImage?.(Number(imageId));
572
- if (!mediaItem) {
573
- continue;
574
- }
575
- // Get image data
576
- let data;
577
- if (mediaItem.buffer instanceof Uint8Array) {
578
- data = mediaItem.buffer;
579
- }
580
- else if (mediaItem.base64) {
581
- data = (0, utils_base_1.base64ToUint8Array)(mediaItem.base64);
582
- }
583
- if (!data || data.length === 0) {
584
- continue;
585
- }
586
- const format = mediaItem.extension;
587
- if (format !== "jpeg" && format !== "png") {
588
- continue; // Only JPEG and PNG are supported
589
- }
590
- // Calculate position from anchor
591
- const tl = wsImage.range.tl;
558
+ function assignImagesToPages(images, layoutPages) {
559
+ for (const img of images) {
560
+ const tl = img.range.tl;
592
561
  const tlCol = (tl.nativeCol ?? tl.col ?? 0) + 1; // convert 0-indexed to 1-indexed
593
562
  const tlRow = (tl.nativeRow ?? tl.row ?? 0) + 1;
594
- const targetPage = layoutPages.find(page => page.worksheetCols.includes(tlCol) && page.worksheetRows.includes(tlRow));
563
+ const targetPage = layoutPages.find(page => page.sheetCols.includes(tlCol) && page.sheetRows.includes(tlRow));
595
564
  if (!targetPage) {
596
565
  continue;
597
566
  }
598
- const pageColIndex = targetPage.worksheetCols.indexOf(tlCol);
599
- const pageRowIndex = targetPage.worksheetRows.indexOf(tlRow);
567
+ const pageColIndex = targetPage.sheetCols.indexOf(tlCol);
568
+ const pageRowIndex = targetPage.sheetRows.indexOf(tlRow);
600
569
  const baseX = targetPage.columnOffsets[pageColIndex] ?? targetPage.options.margins.left;
601
570
  const baseY = targetPage.rowYPositions[pageRowIndex] ??
602
571
  targetPage.height -
@@ -606,22 +575,20 @@ function assignImagesToPages(worksheet, layoutPages) {
606
575
  const tlColOff = (tl.nativeColOff ?? 0) / 12700 || 0;
607
576
  const tlRowOff = (tl.nativeRowOff ?? 0) / 12700 || 0;
608
577
  const imgX = baseX + tlColOff;
609
- const imgY = baseY - tlRowOff; // PDF y-axis is bottom-up, offset moves down
578
+ const imgY = baseY - tlRowOff;
610
579
  // Determine image size
611
580
  let imgWidth = 100;
612
581
  let imgHeight = 100;
613
- if (wsImage.range.ext) {
614
- // ext.width and ext.height are in pixels; convert to points (1px ≈ 0.75pt)
615
- imgWidth = (wsImage.range.ext.width ?? 100) * 0.75;
616
- imgHeight = (wsImage.range.ext.height ?? 100) * 0.75;
582
+ if (img.range.ext) {
583
+ imgWidth = (img.range.ext.width ?? 100) * 0.75;
584
+ imgHeight = (img.range.ext.height ?? 100) * 0.75;
617
585
  }
618
- else if (wsImage.range.br) {
619
- // Calculate from bottom-right anchor
620
- const br = wsImage.range.br;
586
+ else if (img.range.br) {
587
+ const br = img.range.br;
621
588
  const brCol = (br.nativeCol ?? br.col ?? 0) + 1;
622
589
  const brRow = (br.nativeRow ?? br.row ?? 0) + 1;
623
- const brPageColIndex = targetPage.worksheetCols.indexOf(brCol);
624
- const brPageRowIndex = targetPage.worksheetRows.indexOf(brRow);
590
+ const brPageColIndex = targetPage.sheetCols.indexOf(brCol);
591
+ const brPageRowIndex = targetPage.sheetRows.indexOf(brRow);
625
592
  const brBaseX = brPageColIndex >= 0
626
593
  ? targetPage.columnOffsets[brPageColIndex]
627
594
  : imgX + (targetPage.columnWidths[pageColIndex] ?? 100);
@@ -635,10 +602,9 @@ function assignImagesToPages(worksheet, layoutPages) {
635
602
  imgWidth = brX - imgX;
636
603
  imgHeight = imgY - brY;
637
604
  }
638
- // PDF coordinates: y is bottom of image
639
605
  targetPage.images.push({
640
- data,
641
- format,
606
+ data: img.data,
607
+ format: img.format,
642
608
  rect: {
643
609
  x: imgX,
644
610
  y: imgY - imgHeight,
@@ -648,66 +614,6 @@ function assignImagesToPages(worksheet, layoutPages) {
648
614
  });
649
615
  }
650
616
  }
651
- /**
652
- * Extract display text from a cell, applying numFmt formatting.
653
- */
654
- function getCellText(cell) {
655
- if (!cell) {
656
- return "";
657
- }
658
- switch (cell.type) {
659
- case enums_1.ValueType.Null:
660
- case enums_1.ValueType.Merge:
661
- return "";
662
- case enums_1.ValueType.RichText: {
663
- // RichText cell.value is a CellRichTextValue object; cell.text joins the run texts
664
- return cell.text;
665
- }
666
- case enums_1.ValueType.Hyperlink:
667
- return cell.text;
668
- case enums_1.ValueType.Error: {
669
- // Error cells have value = { error: "#N/A" } etc.
670
- const errValue = cell.value;
671
- return errValue?.error ?? cell.text;
672
- }
673
- case enums_1.ValueType.Formula: {
674
- const result = cell.result;
675
- if (result !== undefined && result !== null) {
676
- if (typeof result === "object" && "error" in result) {
677
- return result.error;
678
- }
679
- return formatCellValueSafe(result, cell.style?.numFmt);
680
- }
681
- return cell.text;
682
- }
683
- default: {
684
- const value = cell.value;
685
- if (value === null || value === undefined) {
686
- return "";
687
- }
688
- return formatCellValueSafe(value, cell.style?.numFmt);
689
- }
690
- }
691
- }
692
- /**
693
- * Safely format a cell value using its numFmt.
694
- * Falls back to toString if formatting fails or no format is specified.
695
- */
696
- function formatCellValueSafe(value, numFmt) {
697
- const fmt = typeof numFmt === "string" ? numFmt : numFmt?.formatCode;
698
- if (fmt && (typeof value === "number" || value instanceof Date || typeof value === "boolean")) {
699
- try {
700
- return (0, cell_format_1.formatCellValue)(value, fmt);
701
- }
702
- catch {
703
- // Fall through to default
704
- }
705
- }
706
- if (value instanceof Date) {
707
- return value.toLocaleDateString();
708
- }
709
- return String(value);
710
- }
711
617
  // =============================================================================
712
618
  // Rich Text
713
619
  // =============================================================================
@@ -716,7 +622,7 @@ function formatCellValueSafe(value, numFmt) {
716
622
  * Returns null for non-RichText cells.
717
623
  */
718
624
  function buildRichTextRuns(cell, options, fontManager, scaleFactor) {
719
- if (!cell || cell.type !== enums_1.ValueType.RichText) {
625
+ if (!cell || cell.type !== types_1.PdfCellType.RichText) {
720
626
  return null;
721
627
  }
722
628
  const rtValue = cell.value;