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