@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.
- package/README.md +45 -17
- package/README_zh.md +43 -15
- package/dist/browser/index.browser.d.ts +1 -1
- package/dist/browser/index.browser.js +1 -1
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.js +1 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +0 -2
- package/dist/browser/modules/excel/stream/workbook-writer.d.ts +2 -2
- package/dist/browser/modules/excel/types.d.ts +0 -2
- package/dist/browser/modules/pdf/excel-bridge.d.ts +29 -0
- package/dist/browser/modules/pdf/excel-bridge.js +423 -0
- package/dist/browser/modules/pdf/index.d.ts +22 -24
- package/dist/browser/modules/pdf/index.js +22 -25
- package/dist/browser/modules/pdf/pdf.d.ts +121 -0
- package/dist/browser/modules/pdf/pdf.js +255 -0
- package/dist/browser/modules/pdf/render/layout-engine.d.ts +10 -8
- package/dist/browser/modules/pdf/render/layout-engine.js +115 -209
- package/dist/browser/modules/pdf/render/pdf-exporter.d.ts +9 -62
- package/dist/browser/modules/pdf/render/pdf-exporter.js +38 -78
- package/dist/browser/modules/pdf/render/style-converter.d.ts +20 -18
- package/dist/browser/modules/pdf/render/style-converter.js +24 -23
- package/dist/browser/modules/pdf/types.d.ts +193 -11
- package/dist/browser/modules/pdf/types.js +22 -1
- package/dist/cjs/index.js +3 -3
- package/dist/cjs/modules/pdf/excel-bridge.js +426 -0
- package/dist/cjs/modules/pdf/index.js +25 -28
- package/dist/cjs/modules/pdf/pdf.js +258 -0
- package/dist/cjs/modules/pdf/render/layout-engine.js +116 -210
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +37 -79
- package/dist/cjs/modules/pdf/render/style-converter.js +24 -23
- package/dist/cjs/modules/pdf/types.js +23 -2
- package/dist/esm/index.browser.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/modules/pdf/excel-bridge.js +423 -0
- package/dist/esm/modules/pdf/index.js +22 -25
- package/dist/esm/modules/pdf/pdf.js +255 -0
- package/dist/esm/modules/pdf/render/layout-engine.js +115 -209
- package/dist/esm/modules/pdf/render/pdf-exporter.js +38 -78
- package/dist/esm/modules/pdf/render/style-converter.js +24 -23
- package/dist/esm/modules/pdf/types.js +22 -1
- package/dist/iife/excelts.iife.js +728 -251
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +34 -34
- package/dist/types/index.browser.d.ts +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +0 -2
- package/dist/types/modules/excel/stream/workbook-writer.d.ts +2 -2
- package/dist/types/modules/excel/types.d.ts +0 -2
- package/dist/types/modules/pdf/excel-bridge.d.ts +29 -0
- package/dist/types/modules/pdf/index.d.ts +22 -24
- package/dist/types/modules/pdf/pdf.d.ts +121 -0
- package/dist/types/modules/pdf/render/layout-engine.d.ts +10 -8
- package/dist/types/modules/pdf/render/pdf-exporter.d.ts +9 -62
- package/dist/types/modules/pdf/render/style-converter.d.ts +20 -18
- package/dist/types/modules/pdf/types.d.ts +193 -11
- package/package.json +1 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Layout engine for
|
|
3
|
+
* Layout engine for PDF generation.
|
|
4
4
|
*
|
|
5
|
-
* Takes a
|
|
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
|
|
10
|
-
* - Convert
|
|
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.
|
|
21
|
+
exports.layoutSheet = layoutSheet;
|
|
19
22
|
exports.paginateRows = paginateRows;
|
|
20
|
-
const
|
|
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
|
|
37
|
+
* Compute the layout for a sheet across one or more PDF pages.
|
|
38
38
|
*/
|
|
39
|
-
function
|
|
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(
|
|
52
|
+
const printRange = getPrintRange(sheet);
|
|
53
53
|
// --- Step 1: Visible columns and widths ---
|
|
54
|
-
const { columnWidths, visibleCols } = computeColumnWidths(
|
|
54
|
+
const { columnWidths, visibleCols } = computeColumnWidths(sheet, printRange);
|
|
55
55
|
const columnCount = visibleCols.length;
|
|
56
56
|
if (columnCount === 0) {
|
|
57
|
-
return [emptyPage(pageWidth, pageHeight,
|
|
57
|
+
return [emptyPage(pageWidth, pageHeight, sheet.name, options)];
|
|
58
58
|
}
|
|
59
59
|
// --- Step 2: Scale ---
|
|
60
|
-
|
|
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(
|
|
70
|
+
const { rowHeights, visibleRows } = computeRowHeights(sheet, scaleFactor, printRange);
|
|
82
71
|
// --- Step 4: Merge map ---
|
|
83
|
-
const mergeMap = buildMergeMap(
|
|
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(
|
|
75
|
+
const rowBreakSet = buildRowBreakSet(sheet, visibleRows);
|
|
87
76
|
const rowPages = paginateRows(rowHeights, availableHeight, repeatRowCount, rowBreakSet);
|
|
88
|
-
const colGroups = paginateColumns(scaledColumnWidths, contentWidth,
|
|
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 =
|
|
129
|
-
const cell = row
|
|
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,
|
|
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:
|
|
177
|
-
|
|
165
|
+
sheetName: sheet.name,
|
|
166
|
+
sheetCols: colGroup.map(ci => visibleCols[ci]),
|
|
178
167
|
columnOffsets: groupColOffsets,
|
|
179
168
|
columnWidths: groupColWidths,
|
|
180
|
-
|
|
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:
|
|
188
|
-
if (layoutPages.length > 0) {
|
|
189
|
-
assignImagesToPages(
|
|
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
|
-
|
|
193
|
+
sheetCols: [],
|
|
205
194
|
columnOffsets: [],
|
|
206
195
|
columnWidths: [],
|
|
207
|
-
|
|
196
|
+
sheetRows: [],
|
|
208
197
|
rowYPositions: [],
|
|
209
198
|
rowHeights: [],
|
|
210
199
|
images: []
|
|
211
200
|
};
|
|
212
201
|
}
|
|
213
202
|
/**
|
|
214
|
-
*
|
|
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(
|
|
218
|
-
const 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 = (
|
|
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(
|
|
245
|
-
const
|
|
246
|
-
const hasData =
|
|
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 ??
|
|
251
|
-
const endCol = printRange?.endCol ??
|
|
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 =
|
|
256
|
-
if (col
|
|
272
|
+
const col = sheet.columns.get(c);
|
|
273
|
+
if (col?.hidden) {
|
|
257
274
|
continue;
|
|
258
275
|
}
|
|
259
|
-
const excelWidth = col
|
|
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(
|
|
270
|
-
const
|
|
271
|
-
if (
|
|
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 ??
|
|
275
|
-
const endRow = printRange?.endRow ??
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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(
|
|
353
|
+
function buildRowBreakSet(sheet, visibleRows) {
|
|
337
354
|
const breaks = new Set();
|
|
338
|
-
const rowBreaks =
|
|
355
|
+
const rowBreaks = sheet.rowBreaks ?? [];
|
|
339
356
|
if (rowBreaks.length === 0) {
|
|
340
357
|
return breaks;
|
|
341
358
|
}
|
|
342
|
-
// Map
|
|
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
|
|
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
|
|
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(
|
|
377
|
+
function buildMergeMap(sheet) {
|
|
362
378
|
const map = new Map();
|
|
363
|
-
|
|
379
|
+
const merges = sheet.merges;
|
|
380
|
+
if (!merges || merges.length === 0) {
|
|
364
381
|
return map;
|
|
365
382
|
}
|
|
366
|
-
const
|
|
367
|
-
|
|
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,
|
|
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 =
|
|
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
|
|
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,
|
|
510
|
-
const text =
|
|
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
|
|
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
|
|
553
|
+
// Image Placement
|
|
550
554
|
// =============================================================================
|
|
551
555
|
/**
|
|
552
|
-
*
|
|
553
|
-
* their top-left anchor.
|
|
556
|
+
* Assign pre-collected images to the pages that contain their top-left anchor.
|
|
554
557
|
*/
|
|
555
|
-
function assignImagesToPages(
|
|
556
|
-
|
|
557
|
-
|
|
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.
|
|
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.
|
|
599
|
-
const pageRowIndex = targetPage.
|
|
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;
|
|
578
|
+
const imgY = baseY - tlRowOff;
|
|
610
579
|
// Determine image size
|
|
611
580
|
let imgWidth = 100;
|
|
612
581
|
let imgHeight = 100;
|
|
613
|
-
if (
|
|
614
|
-
|
|
615
|
-
|
|
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 (
|
|
619
|
-
|
|
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.
|
|
624
|
-
const brPageRowIndex = targetPage.
|
|
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 !==
|
|
625
|
+
if (!cell || cell.type !== types_1.PdfCellType.RichText) {
|
|
720
626
|
return null;
|
|
721
627
|
}
|
|
722
628
|
const rtValue = cell.value;
|