@cj-tech-master/excelts 8.1.2 → 9.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 +2 -2
- package/README_zh.md +2 -2
- package/dist/browser/modules/excel/cell.js +11 -7
- package/dist/browser/modules/excel/column.js +7 -6
- package/dist/browser/modules/excel/row.js +5 -1
- package/dist/browser/modules/excel/stream/worksheet-reader.js +3 -2
- package/dist/browser/modules/excel/utils/cell-format.js +64 -2
- package/dist/browser/modules/pdf/excel-bridge.d.ts +4 -3
- package/dist/browser/modules/pdf/excel-bridge.js +18 -5
- package/dist/browser/modules/pdf/index.d.ts +3 -3
- package/dist/browser/modules/pdf/index.js +3 -3
- package/dist/browser/modules/pdf/pdf.d.ts +7 -6
- package/dist/browser/modules/pdf/pdf.js +7 -6
- package/dist/browser/modules/pdf/reader/pdf-reader.d.ts +8 -7
- package/dist/browser/modules/pdf/reader/pdf-reader.js +81 -74
- package/dist/browser/modules/pdf/render/constants.d.ts +30 -0
- package/dist/browser/modules/pdf/render/constants.js +30 -0
- package/dist/browser/modules/pdf/render/layout-engine.d.ts +2 -1
- package/dist/browser/modules/pdf/render/layout-engine.js +359 -156
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +2 -2
- package/dist/browser/modules/pdf/render/page-renderer.js +245 -107
- package/dist/browser/modules/pdf/render/pdf-exporter.d.ts +3 -2
- package/dist/browser/modules/pdf/render/pdf-exporter.js +145 -105
- package/dist/browser/modules/pdf/render/style-converter.js +27 -26
- package/dist/browser/modules/pdf/types.d.ts +8 -0
- package/dist/browser/utils/utils.base.d.ts +5 -0
- package/dist/browser/utils/utils.base.js +10 -0
- package/dist/cjs/modules/excel/cell.js +11 -7
- package/dist/cjs/modules/excel/column.js +7 -6
- package/dist/cjs/modules/excel/row.js +5 -1
- package/dist/cjs/modules/excel/stream/worksheet-reader.js +3 -2
- package/dist/cjs/modules/excel/utils/cell-format.js +64 -2
- package/dist/cjs/modules/pdf/excel-bridge.js +18 -5
- package/dist/cjs/modules/pdf/index.js +3 -3
- package/dist/cjs/modules/pdf/pdf.js +7 -6
- package/dist/cjs/modules/pdf/reader/pdf-reader.js +81 -74
- package/dist/cjs/modules/pdf/render/constants.js +33 -0
- package/dist/cjs/modules/pdf/render/layout-engine.js +359 -156
- package/dist/cjs/modules/pdf/render/page-renderer.js +245 -107
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +145 -105
- package/dist/cjs/modules/pdf/render/style-converter.js +27 -26
- package/dist/cjs/utils/utils.base.js +11 -0
- package/dist/esm/modules/excel/cell.js +11 -7
- package/dist/esm/modules/excel/column.js +7 -6
- package/dist/esm/modules/excel/row.js +5 -1
- package/dist/esm/modules/excel/stream/worksheet-reader.js +3 -2
- package/dist/esm/modules/excel/utils/cell-format.js +64 -2
- package/dist/esm/modules/pdf/excel-bridge.js +18 -5
- package/dist/esm/modules/pdf/index.js +3 -3
- package/dist/esm/modules/pdf/pdf.js +7 -6
- package/dist/esm/modules/pdf/reader/pdf-reader.js +81 -74
- package/dist/esm/modules/pdf/render/constants.js +30 -0
- package/dist/esm/modules/pdf/render/layout-engine.js +359 -156
- package/dist/esm/modules/pdf/render/page-renderer.js +245 -107
- package/dist/esm/modules/pdf/render/pdf-exporter.js +145 -105
- package/dist/esm/modules/pdf/render/style-converter.js +27 -26
- package/dist/esm/utils/utils.base.js +10 -0
- package/dist/iife/excelts.iife.js +1022 -677
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +48 -48
- package/dist/types/modules/pdf/excel-bridge.d.ts +4 -3
- package/dist/types/modules/pdf/index.d.ts +3 -3
- package/dist/types/modules/pdf/pdf.d.ts +7 -6
- package/dist/types/modules/pdf/reader/pdf-reader.d.ts +8 -7
- package/dist/types/modules/pdf/render/constants.d.ts +30 -0
- package/dist/types/modules/pdf/render/layout-engine.d.ts +2 -1
- package/dist/types/modules/pdf/render/page-renderer.d.ts +2 -2
- package/dist/types/modules/pdf/render/pdf-exporter.d.ts +3 -2
- package/dist/types/modules/pdf/types.d.ts +8 -0
- package/dist/types/utils/utils.base.d.ts +5 -0
- package/package.json +1 -1
|
@@ -23,20 +23,86 @@ exports.paginateRows = paginateRows;
|
|
|
23
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
|
+
const page_renderer_1 = require("./page-renderer");
|
|
27
|
+
const constants_1 = require("./constants");
|
|
28
|
+
const utils_base_1 = require("../../../utils/utils.base.js");
|
|
26
29
|
// =============================================================================
|
|
27
30
|
// Constants
|
|
28
31
|
// =============================================================================
|
|
29
|
-
const EXCEL_CHAR_WIDTH_TO_POINTS = 7;
|
|
30
32
|
const DEFAULT_COLUMN_WIDTH = 8.43;
|
|
31
33
|
const DEFAULT_ROW_HEIGHT = 15;
|
|
32
|
-
const MIN_COLUMN_WIDTH =
|
|
34
|
+
const MIN_COLUMN_WIDTH = 3;
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Type-based Default Alignment
|
|
37
|
+
// =============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Resolve horizontal alignment, using Excel's type-based defaults when
|
|
40
|
+
* no explicit alignment is set (or when alignment is "general"):
|
|
41
|
+
* - Numbers/Dates → right
|
|
42
|
+
* - Booleans/Errors → center
|
|
43
|
+
* - Text/RichText/Hyperlink → left
|
|
44
|
+
* - Formulas → based on result type
|
|
45
|
+
*/
|
|
46
|
+
function resolveHorizontalAlign(alignment, cellType, formulaResult) {
|
|
47
|
+
// If explicitly set (and not "general"), use the explicit alignment
|
|
48
|
+
if (alignment?.horizontal && alignment.horizontal !== "general") {
|
|
49
|
+
return (0, style_converter_1.excelHAlignToPdf)(alignment);
|
|
50
|
+
}
|
|
51
|
+
// Use type-based default
|
|
52
|
+
if (cellType !== undefined) {
|
|
53
|
+
switch (cellType) {
|
|
54
|
+
case types_1.PdfCellType.Number:
|
|
55
|
+
case types_1.PdfCellType.Date:
|
|
56
|
+
return "right";
|
|
57
|
+
case types_1.PdfCellType.Boolean:
|
|
58
|
+
case types_1.PdfCellType.Error:
|
|
59
|
+
return "center";
|
|
60
|
+
case types_1.PdfCellType.Formula:
|
|
61
|
+
if (typeof formulaResult === "number" || formulaResult instanceof Date) {
|
|
62
|
+
return "right";
|
|
63
|
+
}
|
|
64
|
+
if (typeof formulaResult === "boolean") {
|
|
65
|
+
return "center";
|
|
66
|
+
}
|
|
67
|
+
return "left";
|
|
68
|
+
default:
|
|
69
|
+
return "left";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return "left";
|
|
73
|
+
}
|
|
33
74
|
// =============================================================================
|
|
34
75
|
// Layout Engine
|
|
35
76
|
// =============================================================================
|
|
36
77
|
/**
|
|
37
78
|
* Compute the layout for a sheet across one or more PDF pages.
|
|
79
|
+
* Yields to the event loop between each output page.
|
|
38
80
|
*/
|
|
39
|
-
function layoutSheet(sheet, options, fontManager) {
|
|
81
|
+
async function layoutSheet(sheet, options, fontManager) {
|
|
82
|
+
const ctx = prepareLayout(sheet, options, fontManager);
|
|
83
|
+
if (!ctx) {
|
|
84
|
+
return [createEmptyPage(sheet, options)];
|
|
85
|
+
}
|
|
86
|
+
const layoutPages = [];
|
|
87
|
+
const totalOutputPages = ctx.rowPages.length * ctx.colGroups.length;
|
|
88
|
+
for (const rowPage of ctx.rowPages) {
|
|
89
|
+
for (const colGroup of ctx.colGroups) {
|
|
90
|
+
layoutPages.push(buildPageLayout(ctx, rowPage, colGroup, layoutPages.length, sheet, options, fontManager));
|
|
91
|
+
if (layoutPages.length < totalOutputPages) {
|
|
92
|
+
await (0, utils_base_1.yieldToEventLoop)();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (layoutPages.length > 0 && sheet.images) {
|
|
97
|
+
assignImagesToPages(sheet.images, layoutPages, ctx.scaleFactor);
|
|
98
|
+
}
|
|
99
|
+
return layoutPages;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Steps 1–5: compute columns, scale, rows, merges, pagination.
|
|
103
|
+
* Returns null if the sheet has no visible columns (→ caller should emit an empty page).
|
|
104
|
+
*/
|
|
105
|
+
function prepareLayout(sheet, options, fontManager) {
|
|
40
106
|
const { margins } = options;
|
|
41
107
|
let pageWidth = options.pageSize.width;
|
|
42
108
|
let pageHeight = options.pageSize.height;
|
|
@@ -48,13 +114,11 @@ function layoutSheet(sheet, options, fontManager) {
|
|
|
48
114
|
const headerHeight = options.showSheetNames ? 20 : 0;
|
|
49
115
|
const footerHeight = options.showPageNumbers ? 20 : 0;
|
|
50
116
|
const availableHeight = contentHeight - headerHeight - footerHeight;
|
|
51
|
-
// Determine print area bounds (if set)
|
|
52
117
|
const printRange = getPrintRange(sheet);
|
|
53
118
|
// --- Step 1: Visible columns and widths ---
|
|
54
119
|
const { columnWidths, visibleCols } = computeColumnWidths(sheet, printRange);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return [emptyPage(pageWidth, pageHeight, sheet.name, options)];
|
|
120
|
+
if (visibleCols.length === 0) {
|
|
121
|
+
return null;
|
|
58
122
|
}
|
|
59
123
|
// --- Step 2: Scale ---
|
|
60
124
|
const totalTableWidth = columnWidths.reduce((sum, w) => sum + w, 0);
|
|
@@ -67,136 +131,159 @@ function layoutSheet(sheet, options, fontManager) {
|
|
|
67
131
|
}
|
|
68
132
|
const scaledColumnWidths = columnWidths.map(w => w * scaleFactor);
|
|
69
133
|
// --- Step 3: Visible rows and heights ---
|
|
70
|
-
const { rowHeights, visibleRows } = computeRowHeights(sheet, scaleFactor, printRange);
|
|
134
|
+
const { rowHeights, visibleRows } = computeRowHeights(sheet, scaleFactor, printRange, fontManager, options);
|
|
71
135
|
// --- Step 4: Merge map ---
|
|
72
136
|
const mergeMap = buildMergeMap(sheet);
|
|
73
|
-
// --- Step 5: Paginate
|
|
137
|
+
// --- Step 5: Paginate ---
|
|
74
138
|
const repeatRowCount = typeof options.repeatRows === "number" ? options.repeatRows : 0;
|
|
75
139
|
const rowBreakSet = buildRowBreakSet(sheet, visibleRows);
|
|
76
140
|
const rowPages = paginateRows(rowHeights, availableHeight, repeatRowCount, rowBreakSet);
|
|
77
141
|
const colGroups = paginateColumns(scaledColumnWidths, contentWidth, sheet, visibleCols);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
142
|
+
return {
|
|
143
|
+
pageWidth,
|
|
144
|
+
pageHeight,
|
|
145
|
+
contentWidth,
|
|
146
|
+
headerHeight,
|
|
147
|
+
scaleFactor,
|
|
148
|
+
scaledColumnWidths,
|
|
149
|
+
rowHeights,
|
|
150
|
+
visibleRows,
|
|
151
|
+
visibleCols,
|
|
152
|
+
mergeMap,
|
|
153
|
+
rowPages,
|
|
154
|
+
colGroups,
|
|
155
|
+
margins
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build the LayoutPage for a single rowPage × colGroup combination.
|
|
160
|
+
*/
|
|
161
|
+
function buildPageLayout(ctx, rowPage, colGroup, currentPageCount, sheet, options, fontManager) {
|
|
162
|
+
const { scaledColumnWidths, rowHeights, visibleRows, visibleCols, mergeMap, pageWidth, pageHeight, contentWidth, headerHeight, scaleFactor, margins } = ctx;
|
|
163
|
+
const cells = [];
|
|
164
|
+
// Compute column offsets for this column group
|
|
165
|
+
const groupColWidths = colGroup.map(ci => scaledColumnWidths[ci]);
|
|
166
|
+
const groupTotalWidth = groupColWidths.reduce((s, w) => s + w, 0);
|
|
167
|
+
const groupColOffsets = [];
|
|
168
|
+
let gx = margins.left;
|
|
169
|
+
if (groupTotalWidth < contentWidth) {
|
|
170
|
+
gx = margins.left + (contentWidth - groupTotalWidth) / 2;
|
|
171
|
+
}
|
|
172
|
+
for (const w of groupColWidths) {
|
|
173
|
+
groupColOffsets.push(gx);
|
|
174
|
+
gx += w;
|
|
175
|
+
}
|
|
176
|
+
// Row Y positions
|
|
177
|
+
const rowYPositions = [];
|
|
178
|
+
const pageRowHeights = [];
|
|
179
|
+
let currentY = pageHeight - margins.top - headerHeight;
|
|
180
|
+
for (const rowIdx of rowPage) {
|
|
181
|
+
const rowH = rowHeights[rowIdx] ?? DEFAULT_ROW_HEIGHT * scaleFactor;
|
|
182
|
+
rowYPositions.push(currentY);
|
|
183
|
+
pageRowHeights.push(rowH);
|
|
184
|
+
currentY -= rowH;
|
|
185
|
+
}
|
|
186
|
+
// Build cells for this row page × column group
|
|
187
|
+
const cellGrid = new Map();
|
|
188
|
+
for (let ri = 0; ri < rowPage.length; ri++) {
|
|
189
|
+
const visibleRowIdx = rowPage[ri];
|
|
190
|
+
const wsRowNumber = visibleRows[visibleRowIdx];
|
|
191
|
+
for (let gci = 0; gci < colGroup.length; gci++) {
|
|
192
|
+
const ci = colGroup[gci];
|
|
193
|
+
const wsColNumber = visibleCols[ci];
|
|
194
|
+
const mergeKey = `${wsRowNumber}:${wsColNumber}`;
|
|
195
|
+
const mergeInfo = mergeMap.get(mergeKey);
|
|
196
|
+
if (mergeInfo && !mergeInfo.isMaster) {
|
|
197
|
+
continue;
|
|
104
198
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
continue;
|
|
199
|
+
const row = sheet.rows.get(wsRowNumber);
|
|
200
|
+
const cell = row?.cells.get(wsColNumber);
|
|
201
|
+
let colSpan = 1;
|
|
202
|
+
let rowSpan = 1;
|
|
203
|
+
if (mergeInfo && mergeInfo.isMaster) {
|
|
204
|
+
const mergeEndCol = wsColNumber + mergeInfo.colSpan - 1;
|
|
205
|
+
colSpan = 0;
|
|
206
|
+
for (let s = gci; s < colGroup.length; s++) {
|
|
207
|
+
if (visibleCols[colGroup[s]] <= mergeEndCol) {
|
|
208
|
+
colSpan++;
|
|
116
209
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let colSpan = 1;
|
|
120
|
-
let rowSpan = 1;
|
|
121
|
-
if (mergeInfo && mergeInfo.isMaster) {
|
|
122
|
-
const mergeEndCol = wsColNumber + mergeInfo.colSpan - 1;
|
|
123
|
-
colSpan = 0;
|
|
124
|
-
for (let s = gci; s < colGroup.length; s++) {
|
|
125
|
-
if (visibleCols[colGroup[s]] <= mergeEndCol) {
|
|
126
|
-
colSpan++;
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const mergeEndRow = wsRowNumber + mergeInfo.rowSpan - 1;
|
|
133
|
-
rowSpan = 0;
|
|
134
|
-
for (let s = visibleRowIdx; s < visibleRows.length; s++) {
|
|
135
|
-
if (visibleRows[s] <= mergeEndRow) {
|
|
136
|
-
rowSpan++;
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
colSpan = Math.max(colSpan, 1);
|
|
143
|
-
rowSpan = Math.max(rowSpan, 1);
|
|
210
|
+
else {
|
|
211
|
+
break;
|
|
144
212
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
213
|
+
}
|
|
214
|
+
const mergeEndRow = wsRowNumber + mergeInfo.rowSpan - 1;
|
|
215
|
+
rowSpan = 0;
|
|
216
|
+
for (let s = visibleRowIdx; s < visibleRows.length; s++) {
|
|
217
|
+
if (visibleRows[s] <= mergeEndRow) {
|
|
218
|
+
rowSpan++;
|
|
150
219
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
cellHeight += pageRowHeights[ri + s];
|
|
220
|
+
else {
|
|
221
|
+
break;
|
|
154
222
|
}
|
|
155
|
-
const rectY = cellY - cellHeight;
|
|
156
|
-
cells.push(buildLayoutCell(cell, cellX, rectY, cellWidth, cellHeight, colSpan, rowSpan, options, fontManager, scaleFactor));
|
|
157
223
|
}
|
|
224
|
+
colSpan = Math.max(colSpan, 1);
|
|
225
|
+
rowSpan = Math.max(rowSpan, 1);
|
|
226
|
+
}
|
|
227
|
+
const cellX = groupColOffsets[gci];
|
|
228
|
+
const cellY = rowYPositions[ri];
|
|
229
|
+
let cellWidth = 0;
|
|
230
|
+
for (let s = 0; s < colSpan && gci + s < groupColWidths.length; s++) {
|
|
231
|
+
cellWidth += groupColWidths[gci + s];
|
|
232
|
+
}
|
|
233
|
+
let cellHeight = 0;
|
|
234
|
+
for (let s = 0; s < rowSpan && ri + s < pageRowHeights.length; s++) {
|
|
235
|
+
cellHeight += pageRowHeights[ri + s];
|
|
236
|
+
}
|
|
237
|
+
const rectY = cellY - cellHeight;
|
|
238
|
+
cells.push(buildLayoutCell(cell, cellX, rectY, cellWidth, cellHeight, colSpan, rowSpan, options, fontManager, scaleFactor));
|
|
239
|
+
const layoutCell = cells[cells.length - 1];
|
|
240
|
+
// Propagate merged cell borders from boundary cells
|
|
241
|
+
if (mergeInfo?.isMaster) {
|
|
242
|
+
propagateMergeBorders(layoutCell, mergeInfo, wsRowNumber, wsColNumber, sheet);
|
|
158
243
|
}
|
|
159
|
-
|
|
160
|
-
pageNumber: layoutPages.length + 1,
|
|
161
|
-
options,
|
|
162
|
-
cells,
|
|
163
|
-
width: pageWidth,
|
|
164
|
-
height: pageHeight,
|
|
165
|
-
sheetName: sheet.name,
|
|
166
|
-
sheetCols: colGroup.map(ci => visibleCols[ci]),
|
|
167
|
-
columnOffsets: groupColOffsets,
|
|
168
|
-
columnWidths: groupColWidths,
|
|
169
|
-
sheetRows: rowPage.map(ri => visibleRows[ri]),
|
|
170
|
-
rowYPositions,
|
|
171
|
-
rowHeights: pageRowHeights,
|
|
172
|
-
images: []
|
|
173
|
-
});
|
|
244
|
+
cellGrid.set(`${ri}:${gci}`, layoutCell);
|
|
174
245
|
}
|
|
175
246
|
}
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
247
|
+
// Compute text overflow widths for non-wrapped cells
|
|
248
|
+
computeTextOverflows(cellGrid, rowPage, colGroup, visibleRows, visibleCols, groupColWidths, mergeMap, fontManager);
|
|
249
|
+
return {
|
|
250
|
+
pageNumber: currentPageCount + 1,
|
|
251
|
+
options,
|
|
252
|
+
cells,
|
|
253
|
+
width: pageWidth,
|
|
254
|
+
height: pageHeight,
|
|
255
|
+
sheetName: sheet.name,
|
|
256
|
+
sheetCols: colGroup.map(ci => visibleCols[ci]),
|
|
257
|
+
columnOffsets: groupColOffsets,
|
|
258
|
+
columnWidths: groupColWidths,
|
|
259
|
+
sheetRows: rowPage.map(ri => visibleRows[ri]),
|
|
260
|
+
rowYPositions,
|
|
261
|
+
rowHeights: pageRowHeights,
|
|
262
|
+
images: [],
|
|
263
|
+
scaleFactor
|
|
264
|
+
};
|
|
181
265
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
266
|
+
function createEmptyPage(sheet, options) {
|
|
267
|
+
let pageWidth = options.pageSize.width;
|
|
268
|
+
let pageHeight = options.pageSize.height;
|
|
269
|
+
if (options.orientation === "landscape") {
|
|
270
|
+
[pageWidth, pageHeight] = [pageHeight, pageWidth];
|
|
271
|
+
}
|
|
186
272
|
return {
|
|
187
273
|
pageNumber: 1,
|
|
188
274
|
options,
|
|
189
275
|
cells: [],
|
|
190
|
-
width,
|
|
191
|
-
height,
|
|
192
|
-
sheetName,
|
|
276
|
+
width: pageWidth,
|
|
277
|
+
height: pageHeight,
|
|
278
|
+
sheetName: sheet.name,
|
|
193
279
|
sheetCols: [],
|
|
194
280
|
columnOffsets: [],
|
|
195
281
|
columnWidths: [],
|
|
196
282
|
sheetRows: [],
|
|
197
283
|
rowYPositions: [],
|
|
198
284
|
rowHeights: [],
|
|
199
|
-
images: []
|
|
285
|
+
images: [],
|
|
286
|
+
scaleFactor: 1
|
|
200
287
|
};
|
|
201
288
|
}
|
|
202
289
|
/**
|
|
@@ -274,7 +361,8 @@ function computeColumnWidths(sheet, printRange) {
|
|
|
274
361
|
continue;
|
|
275
362
|
}
|
|
276
363
|
const excelWidth = col?.width ?? DEFAULT_COLUMN_WIDTH;
|
|
277
|
-
const
|
|
364
|
+
const pixelWidth = excelWidth * constants_1.MAX_DIGIT_WIDTH_PX + constants_1.EXCEL_COLUMN_PADDING_PX;
|
|
365
|
+
const pointWidth = Math.max(pixelWidth * constants_1.PX_TO_PT, MIN_COLUMN_WIDTH);
|
|
278
366
|
columnWidths.push(pointWidth);
|
|
279
367
|
visibleCols.push(c);
|
|
280
368
|
}
|
|
@@ -283,7 +371,7 @@ function computeColumnWidths(sheet, printRange) {
|
|
|
283
371
|
// =============================================================================
|
|
284
372
|
// Row Height Computation
|
|
285
373
|
// =============================================================================
|
|
286
|
-
function computeRowHeights(sheet, scaleFactor, printRange) {
|
|
374
|
+
function computeRowHeights(sheet, scaleFactor, printRange, fontManager, options) {
|
|
287
375
|
const bounds = sheet.bounds;
|
|
288
376
|
if (bounds.top <= 0) {
|
|
289
377
|
return { rowHeights: [], visibleRows: [] };
|
|
@@ -294,45 +382,27 @@ function computeRowHeights(sheet, scaleFactor, printRange) {
|
|
|
294
382
|
const visibleRows = [];
|
|
295
383
|
for (let r = startRow; r <= endRow; r++) {
|
|
296
384
|
const row = sheet.rows.get(r);
|
|
297
|
-
if (row
|
|
385
|
+
if (row?.hidden) {
|
|
298
386
|
continue;
|
|
299
387
|
}
|
|
300
388
|
let height;
|
|
301
|
-
if (row?.height) {
|
|
302
|
-
//
|
|
389
|
+
if (row?.height && row.customHeight) {
|
|
390
|
+
// Custom height explicitly set by user — use as-is
|
|
391
|
+
height = row.height;
|
|
392
|
+
}
|
|
393
|
+
else if (row?.height) {
|
|
394
|
+
// Excel auto-calculated height — trust it as-is
|
|
303
395
|
height = row.height;
|
|
304
396
|
}
|
|
305
397
|
else {
|
|
306
|
-
//
|
|
398
|
+
// No height info: auto-size based on cell content
|
|
307
399
|
height = DEFAULT_ROW_HEIGHT;
|
|
308
400
|
if (row) {
|
|
309
401
|
for (const cell of row.cells.values()) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
for (const run of rtValue.richText) {
|
|
315
|
-
const runSize = run.font?.size ?? fontSize;
|
|
316
|
-
if (runSize > fontSize) {
|
|
317
|
-
fontSize = runSize;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
const lineHeight = fontSize * 1.5;
|
|
322
|
-
// Count lines: explicit newlines in the text
|
|
323
|
-
const text = cell.text ?? "";
|
|
324
|
-
const lineCount = Math.max(1, (text.match(/\n/g) ?? []).length + 1);
|
|
325
|
-
// For wrapText cells, estimate how many lines word-wrapping produces
|
|
326
|
-
let wrapLineCount = lineCount;
|
|
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;
|
|
330
|
-
const colPts = colWidth * EXCEL_CHAR_WIDTH_TO_POINTS * scaleFactor;
|
|
331
|
-
const avgCharWidth = fontSize * 0.55; // rough average char width
|
|
332
|
-
const charsPerLine = Math.max(1, Math.floor(colPts / avgCharWidth));
|
|
333
|
-
wrapLineCount = Math.ceil(text.length / charsPerLine);
|
|
334
|
-
}
|
|
335
|
-
const neededHeight = lineHeight * wrapLineCount;
|
|
402
|
+
const fontSize = getCellFontSize(cell);
|
|
403
|
+
const wrapLineCount = countWrapLines(cell, fontSize, scaleFactor, sheet, fontManager, options);
|
|
404
|
+
const lineHeight = fontSize * constants_1.LINE_HEIGHT_FACTOR;
|
|
405
|
+
const neededHeight = fontSize + (wrapLineCount - 1) * lineHeight + constants_1.CELL_PADDING_V * 2;
|
|
336
406
|
if (neededHeight > height) {
|
|
337
407
|
height = neededHeight;
|
|
338
408
|
}
|
|
@@ -344,6 +414,51 @@ function computeRowHeights(sheet, scaleFactor, printRange) {
|
|
|
344
414
|
}
|
|
345
415
|
return { rowHeights, visibleRows };
|
|
346
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Get the largest font size for a cell, checking rich text runs.
|
|
419
|
+
*/
|
|
420
|
+
function getCellFontSize(cell) {
|
|
421
|
+
let fontSize = cell.style?.font?.size ?? 11;
|
|
422
|
+
if (cell.type === types_1.PdfCellType.RichText) {
|
|
423
|
+
const value = cell.value;
|
|
424
|
+
if (value && typeof value === "object" && "richText" in value) {
|
|
425
|
+
const runs = value.richText;
|
|
426
|
+
for (const run of runs) {
|
|
427
|
+
const runSize = run.font?.size ?? fontSize;
|
|
428
|
+
if (runSize > fontSize) {
|
|
429
|
+
fontSize = runSize;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return fontSize;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Count the wrap-line count for a cell, using actual font measurements
|
|
438
|
+
* so row heights match the page renderer exactly.
|
|
439
|
+
*/
|
|
440
|
+
function countWrapLines(cell, fontSize, scaleFactor, sheet, fontManager, options) {
|
|
441
|
+
const text = typeof cell.text === "string" ? cell.text : String(cell.text ?? "");
|
|
442
|
+
const lineCount = Math.max(1, (text.match(/\n/g) ?? []).length + 1);
|
|
443
|
+
if (!cell.style?.alignment?.wrapText || text.length === 0) {
|
|
444
|
+
return lineCount;
|
|
445
|
+
}
|
|
446
|
+
const col = sheet.columns.get(cell.col);
|
|
447
|
+
const colWidth = col?.width ?? DEFAULT_COLUMN_WIDTH;
|
|
448
|
+
const scaledColPts = (colWidth * constants_1.MAX_DIGIT_WIDTH_PX + constants_1.EXCEL_COLUMN_PADDING_PX) * constants_1.PX_TO_PT * scaleFactor;
|
|
449
|
+
const indent = cell.style.alignment.indent ?? 0;
|
|
450
|
+
const padding = constants_1.CELL_PADDING_H * 2 + indent * constants_1.INDENT_WIDTH;
|
|
451
|
+
const effectiveWidth = Math.max(scaledColPts - padding, 1);
|
|
452
|
+
const scaledFontSize = fontSize * scaleFactor;
|
|
453
|
+
const fontProps = (0, style_converter_1.extractFontProperties)(cell.style.font, options.defaultFontFamily, options.defaultFontSize);
|
|
454
|
+
const pdfFontName = (0, font_manager_1.resolvePdfFontName)(fontProps.fontFamily, fontProps.bold, fontProps.italic);
|
|
455
|
+
const resourceName = fontManager.hasEmbeddedFont()
|
|
456
|
+
? fontManager.getEmbeddedResourceName()
|
|
457
|
+
: fontManager.ensureFont(pdfFontName);
|
|
458
|
+
const measure = (s) => fontManager.measureText(s, resourceName, scaledFontSize);
|
|
459
|
+
const wrappedLines = (0, page_renderer_1.wrapTextLines)(text, measure, effectiveWidth);
|
|
460
|
+
return Math.max(lineCount, wrappedLines.length);
|
|
461
|
+
}
|
|
347
462
|
// =============================================================================
|
|
348
463
|
// Row Breaks
|
|
349
464
|
// =============================================================================
|
|
@@ -537,7 +652,7 @@ function buildLayoutCell(cell, x, y, width, height, colSpan, rowSpan, options, f
|
|
|
537
652
|
underline: fontProps.underline,
|
|
538
653
|
textColor: fontProps.textColor,
|
|
539
654
|
fillColor: (0, style_converter_1.excelFillToPdfColor)(style.fill),
|
|
540
|
-
horizontalAlign: (
|
|
655
|
+
horizontalAlign: resolveHorizontalAlign(style.alignment, cell?.type, cell?.result),
|
|
541
656
|
verticalAlign: (0, style_converter_1.excelVAlignToPdf)(style.alignment),
|
|
542
657
|
wrapText: style.alignment?.wrapText ?? false,
|
|
543
658
|
borders: (0, style_converter_1.excelBordersToPdf)(style.border),
|
|
@@ -546,7 +661,8 @@ function buildLayoutCell(cell, x, y, width, height, colSpan, rowSpan, options, f
|
|
|
546
661
|
hyperlink: cell?.hyperlink ?? null,
|
|
547
662
|
richText,
|
|
548
663
|
indent: style.alignment?.indent ?? 0,
|
|
549
|
-
textRotation: style.alignment?.textRotation ?? 0
|
|
664
|
+
textRotation: style.alignment?.textRotation === 255 ? "vertical" : (style.alignment?.textRotation ?? 0),
|
|
665
|
+
textOverflowWidth: 0
|
|
550
666
|
};
|
|
551
667
|
}
|
|
552
668
|
// =============================================================================
|
|
@@ -555,7 +671,7 @@ function buildLayoutCell(cell, x, y, width, height, colSpan, rowSpan, options, f
|
|
|
555
671
|
/**
|
|
556
672
|
* Assign pre-collected images to the pages that contain their top-left anchor.
|
|
557
673
|
*/
|
|
558
|
-
function assignImagesToPages(images, layoutPages) {
|
|
674
|
+
function assignImagesToPages(images, layoutPages, scaleFactor) {
|
|
559
675
|
for (const img of images) {
|
|
560
676
|
const tl = img.range.tl;
|
|
561
677
|
const tlCol = (tl.nativeCol ?? tl.col ?? 0) + 1; // convert 0-indexed to 1-indexed
|
|
@@ -571,17 +687,17 @@ function assignImagesToPages(images, layoutPages) {
|
|
|
571
687
|
targetPage.height -
|
|
572
688
|
targetPage.options.margins.top -
|
|
573
689
|
(targetPage.options.showSheetNames ? 20 : 0);
|
|
574
|
-
// Apply sub-cell offsets (EMU: 1pt = 12700 EMU)
|
|
575
|
-
const tlColOff = (tl.nativeColOff ?? 0) / 12700 || 0;
|
|
576
|
-
const tlRowOff = (tl.nativeRowOff ?? 0) / 12700 || 0;
|
|
690
|
+
// Apply sub-cell offsets (EMU: 1pt = 12700 EMU), scaled to match page layout
|
|
691
|
+
const tlColOff = ((tl.nativeColOff ?? 0) / 12700 || 0) * scaleFactor;
|
|
692
|
+
const tlRowOff = ((tl.nativeRowOff ?? 0) / 12700 || 0) * scaleFactor;
|
|
577
693
|
const imgX = baseX + tlColOff;
|
|
578
694
|
const imgY = baseY - tlRowOff;
|
|
579
695
|
// Determine image size
|
|
580
696
|
let imgWidth = 100;
|
|
581
697
|
let imgHeight = 100;
|
|
582
698
|
if (img.range.ext) {
|
|
583
|
-
imgWidth = (img.range.ext.width ?? 100) * 0.75;
|
|
584
|
-
imgHeight = (img.range.ext.height ?? 100) * 0.75;
|
|
699
|
+
imgWidth = (img.range.ext.width ?? 100) * 0.75 * scaleFactor;
|
|
700
|
+
imgHeight = (img.range.ext.height ?? 100) * 0.75 * scaleFactor;
|
|
585
701
|
}
|
|
586
702
|
else if (img.range.br) {
|
|
587
703
|
const br = img.range.br;
|
|
@@ -595,8 +711,8 @@ function assignImagesToPages(images, layoutPages) {
|
|
|
595
711
|
const brBaseY = brPageRowIndex >= 0
|
|
596
712
|
? targetPage.rowYPositions[brPageRowIndex]
|
|
597
713
|
: imgY - (targetPage.rowHeights[pageRowIndex] ?? 100);
|
|
598
|
-
const brColOff = (br.nativeColOff ?? 0) / 12700 || 0;
|
|
599
|
-
const brRowOff = (br.nativeRowOff ?? 0) / 12700 || 0;
|
|
714
|
+
const brColOff = ((br.nativeColOff ?? 0) / 12700 || 0) * scaleFactor;
|
|
715
|
+
const brRowOff = ((br.nativeRowOff ?? 0) / 12700 || 0) * scaleFactor;
|
|
600
716
|
const brX = brBaseX + brColOff;
|
|
601
717
|
const brY = brBaseY - brRowOff;
|
|
602
718
|
imgWidth = brX - imgX;
|
|
@@ -615,6 +731,89 @@ function assignImagesToPages(images, layoutPages) {
|
|
|
615
731
|
}
|
|
616
732
|
}
|
|
617
733
|
// =============================================================================
|
|
734
|
+
// Merge Border Propagation
|
|
735
|
+
// =============================================================================
|
|
736
|
+
/**
|
|
737
|
+
* Excel stores merged-cell borders on the boundary cells, not on the master.
|
|
738
|
+
* Copy the right border from the rightmost column cell and the bottom border
|
|
739
|
+
* from the bottom row cell so the layout cell renders them correctly.
|
|
740
|
+
*/
|
|
741
|
+
function propagateMergeBorders(layoutCell, mergeInfo, wsRowNumber, wsColNumber, sheet) {
|
|
742
|
+
if (mergeInfo.colSpan > 1) {
|
|
743
|
+
const rightCol = wsColNumber + mergeInfo.colSpan - 1;
|
|
744
|
+
const rightCellData = sheet.rows.get(wsRowNumber)?.cells.get(rightCol);
|
|
745
|
+
if (rightCellData?.style?.border?.right) {
|
|
746
|
+
const converted = (0, style_converter_1.excelBordersToPdf)({ right: rightCellData.style.border.right });
|
|
747
|
+
if (converted.right) {
|
|
748
|
+
layoutCell.borders.right = converted.right;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (mergeInfo.rowSpan > 1) {
|
|
753
|
+
const bottomRowNum = wsRowNumber + mergeInfo.rowSpan - 1;
|
|
754
|
+
const bottomCellData = sheet.rows.get(bottomRowNum)?.cells.get(wsColNumber);
|
|
755
|
+
if (bottomCellData?.style?.border?.bottom) {
|
|
756
|
+
const converted = (0, style_converter_1.excelBordersToPdf)({ bottom: bottomCellData.style.border.bottom });
|
|
757
|
+
if (converted.bottom) {
|
|
758
|
+
layoutCell.borders.bottom = converted.bottom;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// =============================================================================
|
|
764
|
+
// Text Overflow Calculation
|
|
765
|
+
// =============================================================================
|
|
766
|
+
/**
|
|
767
|
+
* In Excel, non-wrapped text overflows into adjacent empty cells.
|
|
768
|
+
* Fill color alone does NOT block overflow — only text content does.
|
|
769
|
+
* Computes `textOverflowWidth` for cells whose text exceeds the cell width.
|
|
770
|
+
*/
|
|
771
|
+
function computeTextOverflows(cellGrid, rowPage, colGroup, visibleRows, visibleCols, groupColWidths, mergeMap, fontManager) {
|
|
772
|
+
for (let ri = 0; ri < rowPage.length; ri++) {
|
|
773
|
+
for (let gci = 0; gci < colGroup.length; gci++) {
|
|
774
|
+
const cell = cellGrid.get(`${ri}:${gci}`);
|
|
775
|
+
if (!cell ||
|
|
776
|
+
cell.wrapText ||
|
|
777
|
+
cell.colSpan > 1 ||
|
|
778
|
+
!cell.text ||
|
|
779
|
+
cell.richText ||
|
|
780
|
+
(typeof cell.textRotation === "number" && cell.textRotation !== 0) ||
|
|
781
|
+
cell.textRotation === "vertical") {
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
const resourceName = fontManager.hasEmbeddedFont()
|
|
785
|
+
? fontManager.getEmbeddedResourceName()
|
|
786
|
+
: fontManager.ensureFont((0, font_manager_1.resolvePdfFontName)(cell.fontFamily, cell.bold, cell.italic));
|
|
787
|
+
const textWidth = fontManager.measureText(cell.text, resourceName, cell.fontSize);
|
|
788
|
+
const cellContentWidth = cell.rect.width - constants_1.CELL_PADDING_H * 2;
|
|
789
|
+
if (textWidth <= cellContentWidth) {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const overflowNeeded = textWidth - cellContentWidth;
|
|
793
|
+
let overflowAvailable = 0;
|
|
794
|
+
for (let j = gci + 1; j < colGroup.length; j++) {
|
|
795
|
+
const visibleRowIdx = rowPage[ri];
|
|
796
|
+
const wsRow = visibleRows[visibleRowIdx];
|
|
797
|
+
const wsCol = visibleCols[colGroup[j]];
|
|
798
|
+
if (mergeMap.has(`${wsRow}:${wsCol}`)) {
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
const neighborCell = cellGrid.get(`${ri}:${j}`);
|
|
802
|
+
if (neighborCell?.text) {
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
overflowAvailable += groupColWidths[j];
|
|
806
|
+
if (overflowAvailable >= overflowNeeded) {
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (overflowAvailable > 0) {
|
|
811
|
+
cell.textOverflowWidth = Math.min(overflowNeeded, overflowAvailable);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// =============================================================================
|
|
618
817
|
// Rich Text
|
|
619
818
|
// =============================================================================
|
|
620
819
|
/**
|
|
@@ -625,11 +824,15 @@ function buildRichTextRuns(cell, options, fontManager, scaleFactor) {
|
|
|
625
824
|
if (!cell || cell.type !== types_1.PdfCellType.RichText) {
|
|
626
825
|
return null;
|
|
627
826
|
}
|
|
628
|
-
const
|
|
629
|
-
if (!
|
|
827
|
+
const value = cell.value;
|
|
828
|
+
if (!value || typeof value !== "object" || !("richText" in value)) {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
const runs = value.richText;
|
|
832
|
+
if (runs.length === 0) {
|
|
630
833
|
return null;
|
|
631
834
|
}
|
|
632
|
-
return
|
|
835
|
+
return runs.map(run => {
|
|
633
836
|
const fontProps = (0, style_converter_1.extractFontProperties)(run.font, options.defaultFontFamily, options.defaultFontSize);
|
|
634
837
|
// Register font for this run
|
|
635
838
|
if (fontManager.hasEmbeddedFont()) {
|