@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
|
@@ -18,25 +18,37 @@ import { decodePng } from "./png-decoder.js";
|
|
|
18
18
|
import { PdfError, PdfRenderError } from "../errors.js";
|
|
19
19
|
import { PageSizes } from "../types.js";
|
|
20
20
|
import { argbToPdfColor } from "./style-converter.js";
|
|
21
|
+
import { yieldToEventLoop } from "../../../utils/utils.base.js";
|
|
21
22
|
// =============================================================================
|
|
22
23
|
// Public API
|
|
23
24
|
// =============================================================================
|
|
24
25
|
/**
|
|
25
26
|
* Export a PdfWorkbook to PDF format.
|
|
27
|
+
* Yields to the event loop between each output page during layout and rendering.
|
|
26
28
|
*
|
|
27
29
|
* @param workbook - The workbook data to export
|
|
28
30
|
* @param options - Export options controlling layout, pagination, and appearance
|
|
29
|
-
* @returns PDF file as a Uint8Array
|
|
31
|
+
* @returns Promise of PDF file as a Uint8Array
|
|
30
32
|
* @throws {PdfError} If the workbook has no sheets or export fails
|
|
31
33
|
*/
|
|
32
|
-
export function exportPdf(workbook, options) {
|
|
34
|
+
export async function exportPdf(workbook, options) {
|
|
35
|
+
const ctx = prepareExport(workbook, options);
|
|
36
|
+
for (const sheet of ctx.sheets) {
|
|
37
|
+
await layoutSheetInto(ctx, sheet, options);
|
|
38
|
+
}
|
|
39
|
+
return finishExport(ctx, workbook, options);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Shared setup: validate sheets, create font manager and writer,
|
|
43
|
+
* register embedded font.
|
|
44
|
+
*/
|
|
45
|
+
function prepareExport(workbook, options) {
|
|
33
46
|
const sheets = selectSheets(workbook, options?.sheets);
|
|
34
47
|
if (sheets.length === 0) {
|
|
35
48
|
throw new PdfError("No sheets to export. The workbook is empty or no sheets matched.");
|
|
36
49
|
}
|
|
37
50
|
const fontManager = new FontManager();
|
|
38
51
|
const writer = new PdfWriter();
|
|
39
|
-
// --- Step 0: Register embedded font if provided ---
|
|
40
52
|
if (options?.font) {
|
|
41
53
|
try {
|
|
42
54
|
const ttf = parseTtf(options.font);
|
|
@@ -46,21 +58,37 @@ export function exportPdf(workbook, options) {
|
|
|
46
58
|
throw new PdfRenderError("Failed to parse TrueType font", { cause: err });
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
return { sheets, fontManager, writer, allPages: [] };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Layout a single sheet and append its pages to the context.
|
|
65
|
+
*/
|
|
66
|
+
async function layoutSheetInto(ctx, sheet, options) {
|
|
67
|
+
try {
|
|
68
|
+
const resolved = resolveOptions(options, sheet);
|
|
69
|
+
const pages = await layoutSheet(sheet, resolved, ctx.fontManager);
|
|
70
|
+
ctx.allPages.push(...pages);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
throw new PdfRenderError(`Failed to layout sheet "${sheet.name}"`, { cause: err });
|
|
60
74
|
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* After layout: fix page numbers, track fonts, write resources,
|
|
78
|
+
* render pages, and build the final PDF binary.
|
|
79
|
+
*/
|
|
80
|
+
async function finishExport(ctx, workbook, options) {
|
|
81
|
+
const { allPages, fontManager, writer, sheets } = ctx;
|
|
61
82
|
const documentOptions = resolveOptions(options, sheets[0]);
|
|
83
|
+
ensureAtLeastOnePage(allPages, documentOptions, sheets);
|
|
84
|
+
fixPageNumbers(allPages);
|
|
85
|
+
trackFontsForHeaders(allPages, fontManager);
|
|
86
|
+
const fontObjectMap = fontManager.writeFontResources(writer);
|
|
87
|
+
const { pageObjNums, sheetFirstPage, pagesTreeObjNum } = await renderAllPages(allPages, fontManager, writer, fontObjectMap);
|
|
88
|
+
return buildFinalPdf(writer, pageObjNums, pagesTreeObjNum, sheetFirstPage, documentOptions, workbook, options);
|
|
89
|
+
}
|
|
90
|
+
function ensureAtLeastOnePage(allPages, documentOptions, sheets) {
|
|
62
91
|
if (allPages.length === 0) {
|
|
63
|
-
// Create at least one empty page
|
|
64
92
|
allPages.push({
|
|
65
93
|
pageNumber: 1,
|
|
66
94
|
options: documentOptions,
|
|
@@ -74,15 +102,17 @@ export function exportPdf(workbook, options) {
|
|
|
74
102
|
sheetRows: [],
|
|
75
103
|
rowYPositions: [],
|
|
76
104
|
rowHeights: [],
|
|
77
|
-
images: []
|
|
105
|
+
images: [],
|
|
106
|
+
scaleFactor: 1
|
|
78
107
|
});
|
|
79
108
|
}
|
|
80
|
-
|
|
109
|
+
}
|
|
110
|
+
function fixPageNumbers(allPages) {
|
|
81
111
|
for (let i = 0; i < allPages.length; i++) {
|
|
82
112
|
allPages[i].pageNumber = i + 1;
|
|
83
113
|
}
|
|
84
|
-
|
|
85
|
-
|
|
114
|
+
}
|
|
115
|
+
function trackFontsForHeaders(allPages, fontManager) {
|
|
86
116
|
if (fontManager.hasEmbeddedFont()) {
|
|
87
117
|
for (const page of allPages) {
|
|
88
118
|
if (page.options.showSheetNames) {
|
|
@@ -102,99 +132,102 @@ export function exportPdf(workbook, options) {
|
|
|
102
132
|
}
|
|
103
133
|
}
|
|
104
134
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// --- Step 3: Render pages and build PDF structure ---
|
|
135
|
+
}
|
|
136
|
+
async function renderAllPages(allPages, fontManager, writer, fontObjectMap) {
|
|
108
137
|
const pageObjNums = [];
|
|
109
|
-
const pagesTreeObjNum = writer.allocObject();
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
for (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// Add resources dictionary object
|
|
133
|
-
const resourcesObjNum = writer.allocObject();
|
|
134
|
-
const fontDictStr = fontManager.buildFontDictString(fontObjectMap);
|
|
135
|
-
const resourcesDict = new PdfDict().set("Font", fontDictStr);
|
|
136
|
-
// Add XObject resources for images
|
|
137
|
-
if (imageXObjects.size > 0) {
|
|
138
|
-
const xobjParts = ["<<"];
|
|
139
|
-
for (const [name, objNum] of imageXObjects) {
|
|
140
|
-
xobjParts.push(`/${name} ${pdfRef(objNum)}`);
|
|
141
|
-
}
|
|
142
|
-
xobjParts.push(">>");
|
|
143
|
-
resourcesDict.set("XObject", xobjParts.join("\n"));
|
|
138
|
+
const pagesTreeObjNum = writer.allocObject();
|
|
139
|
+
const sheetFirstPage = new Map();
|
|
140
|
+
const totalPages = allPages.length;
|
|
141
|
+
for (let i = 0; i < allPages.length; i++) {
|
|
142
|
+
renderSinglePage(allPages[i], fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage);
|
|
143
|
+
if (i < allPages.length - 1) {
|
|
144
|
+
await yieldToEventLoop();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { pageObjNums, sheetFirstPage, pagesTreeObjNum };
|
|
148
|
+
}
|
|
149
|
+
function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage) {
|
|
150
|
+
try {
|
|
151
|
+
const { stream: contentStream, alphaValues } = renderPage(page, page.options, fontManager, totalPages);
|
|
152
|
+
// Handle images: create XObject Image entries and draw them
|
|
153
|
+
const imageXObjects = new Map();
|
|
154
|
+
if (page.images.length > 0) {
|
|
155
|
+
for (let imgIdx = 0; imgIdx < page.images.length; imgIdx++) {
|
|
156
|
+
const img = page.images[imgIdx];
|
|
157
|
+
const imgName = `Im${imgIdx + 1}`;
|
|
158
|
+
const imgObjNum = writeImageXObject(writer, img.data, img.format);
|
|
159
|
+
imageXObjects.set(imgName, imgObjNum);
|
|
160
|
+
contentStream.drawImage(imgName, img.rect.x, img.rect.y, img.rect.width, img.rect.height);
|
|
144
161
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
resourcesDict.set("ExtGState", gsParts.join("\n"));
|
|
162
|
+
}
|
|
163
|
+
// Add content stream object
|
|
164
|
+
const contentObjNum = writer.allocObject();
|
|
165
|
+
const contentDict = new PdfDict();
|
|
166
|
+
writer.addStreamObject(contentObjNum, contentDict, contentStream);
|
|
167
|
+
// Add resources dictionary object
|
|
168
|
+
const resourcesObjNum = writer.allocObject();
|
|
169
|
+
const fontDictStr = fontManager.buildFontDictString(fontObjectMap);
|
|
170
|
+
const resourcesDict = new PdfDict().set("Font", fontDictStr);
|
|
171
|
+
if (imageXObjects.size > 0) {
|
|
172
|
+
const xobjParts = ["<<"];
|
|
173
|
+
for (const [name, objNum] of imageXObjects) {
|
|
174
|
+
xobjParts.push(`/${name} ${pdfRef(objNum)}`);
|
|
159
175
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
writer.addObject(annotObjNum, annotDict);
|
|
174
|
-
annotRefs.push(annotObjNum);
|
|
175
|
-
}
|
|
176
|
+
xobjParts.push(">>");
|
|
177
|
+
resourcesDict.set("XObject", xobjParts.join("\n"));
|
|
178
|
+
}
|
|
179
|
+
if (alphaValues.size > 0) {
|
|
180
|
+
const gsParts = ["<<"];
|
|
181
|
+
for (const alpha of alphaValues) {
|
|
182
|
+
const gsObjNum = writer.allocObject();
|
|
183
|
+
const gsDict = new PdfDict()
|
|
184
|
+
.set("Type", "/ExtGState")
|
|
185
|
+
.set("ca", pdfNumber(alpha))
|
|
186
|
+
.set("CA", pdfNumber(alpha));
|
|
187
|
+
writer.addObject(gsObjNum, gsDict);
|
|
188
|
+
gsParts.push(`/${alphaGsName(alpha)} ${pdfRef(gsObjNum)}`);
|
|
176
189
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
gsParts.push(">>");
|
|
191
|
+
resourcesDict.set("ExtGState", gsParts.join("\n"));
|
|
192
|
+
}
|
|
193
|
+
writer.addObject(resourcesObjNum, resourcesDict);
|
|
194
|
+
// Create link annotations for hyperlinks
|
|
195
|
+
const annotRefs = [];
|
|
196
|
+
for (const cell of page.cells) {
|
|
197
|
+
if (cell.hyperlink) {
|
|
198
|
+
const annotObjNum = writer.allocObject();
|
|
199
|
+
const rect = `[${pdfNumber(cell.rect.x)} ${pdfNumber(cell.rect.y)} ${pdfNumber(cell.rect.x + cell.rect.width)} ${pdfNumber(cell.rect.y + cell.rect.height)}]`;
|
|
200
|
+
const annotDict = new PdfDict()
|
|
201
|
+
.set("Type", "/Annot")
|
|
202
|
+
.set("Subtype", "/Link")
|
|
203
|
+
.set("Rect", rect)
|
|
204
|
+
.set("Border", "[0 0 0]")
|
|
205
|
+
.set("A", `<< /Type /Action /S /URI /URI (${cell.hyperlink.replace(/[()\\]/g, "\\$&")}) >>`);
|
|
206
|
+
writer.addObject(annotObjNum, annotDict);
|
|
207
|
+
annotRefs.push(annotObjNum);
|
|
190
208
|
}
|
|
191
209
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
210
|
+
// Add page object
|
|
211
|
+
const pageObjNum = writer.addPage({
|
|
212
|
+
parentRef: pagesTreeObjNum,
|
|
213
|
+
width: page.width,
|
|
214
|
+
height: page.height,
|
|
215
|
+
contentsRef: contentObjNum,
|
|
216
|
+
resourcesRef: resourcesObjNum,
|
|
217
|
+
annotRefs: annotRefs.length > 0 ? annotRefs : undefined
|
|
218
|
+
});
|
|
219
|
+
pageObjNums.push(pageObjNum);
|
|
220
|
+
if (!sheetFirstPage.has(page.sheetName)) {
|
|
221
|
+
sheetFirstPage.set(page.sheetName, pageObjNums.length - 1);
|
|
196
222
|
}
|
|
197
223
|
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
throw new PdfRenderError(`Failed to render page ${page.pageNumber} of "${page.sheetName}"`, {
|
|
226
|
+
cause: err
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function buildFinalPdf(writer, pageObjNums, pagesTreeObjNum, sheetFirstPage, documentOptions, workbook, options) {
|
|
198
231
|
// --- Step 4: Build page tree ---
|
|
199
232
|
const pagesKids = "[" + pageObjNums.map(n => pdfRef(n)).join(" ") + "]";
|
|
200
233
|
const pagesDict = new PdfDict()
|
|
@@ -286,7 +319,14 @@ function resolveOptions(options, sheet) {
|
|
|
286
319
|
orientation,
|
|
287
320
|
margins,
|
|
288
321
|
fitToPage: options?.fitToPage !== undefined ? options.fitToPage : true,
|
|
289
|
-
scale: Math.max(0.1, Math.min(3.0, options?.scale ??
|
|
322
|
+
scale: Math.max(0.1, Math.min(3.0, options?.scale ??
|
|
323
|
+
// When fitToPage is active (default), ignore sheet's pageSetup.scale
|
|
324
|
+
// to avoid double-scaling. Only apply sheet scale when fitToPage is off.
|
|
325
|
+
((options?.fitToPage !== undefined ? options.fitToPage : true)
|
|
326
|
+
? 1.0
|
|
327
|
+
: ps?.scale
|
|
328
|
+
? ps.scale / 100
|
|
329
|
+
: 1.0))),
|
|
290
330
|
showGridLines: options?.showGridLines ?? ps?.showGridLines ?? false,
|
|
291
331
|
gridLineColor,
|
|
292
332
|
repeatRows,
|
|
@@ -79,18 +79,18 @@ export function excelColorToPdf(color) {
|
|
|
79
79
|
* These are the default Office theme colors.
|
|
80
80
|
*/
|
|
81
81
|
function themeColorToPdf(themeIndex) {
|
|
82
|
-
// Default Office theme color palette
|
|
82
|
+
// Default Office 2019+ theme color palette (hex values verified)
|
|
83
83
|
const themeColors = [
|
|
84
|
-
{ r: 1, g: 1, b: 1 }, // 0: lt1 (white / window background)
|
|
85
|
-
{ r: 0, g: 0, b: 0 }, // 1: dk1 (black / window text)
|
|
86
|
-
{ r: 0.
|
|
87
|
-
{ r: 0.267, g: 0.
|
|
88
|
-
{ r: 0.
|
|
89
|
-
{ r: 0.
|
|
90
|
-
{ r: 0.
|
|
91
|
-
{ r: 1, g: 0.753, b: 0 }, // 7: accent4 (gold)
|
|
92
|
-
{ r: 0.
|
|
93
|
-
{ r: 0.439, g: 0.678, b: 0.278 } // 9: accent6 (green)
|
|
84
|
+
{ r: 1, g: 1, b: 1 }, // 0: lt1 — #FFFFFF (white / window background)
|
|
85
|
+
{ r: 0, g: 0, b: 0 }, // 1: dk1 — #000000 (black / window text)
|
|
86
|
+
{ r: 0.906, g: 0.902, b: 0.902 }, // 2: lt2 — #E7E6E6
|
|
87
|
+
{ r: 0.267, g: 0.329, b: 0.416 }, // 3: dk2 — #44546A
|
|
88
|
+
{ r: 0.267, g: 0.447, b: 0.769 }, // 4: accent1 — #4472C4 (blue)
|
|
89
|
+
{ r: 0.929, g: 0.49, b: 0.192 }, // 5: accent2 — #ED7D31 (orange)
|
|
90
|
+
{ r: 0.647, g: 0.647, b: 0.647 }, // 6: accent3 — #A5A5A5 (gray)
|
|
91
|
+
{ r: 1, g: 0.753, b: 0 }, // 7: accent4 — #FFC000 (gold)
|
|
92
|
+
{ r: 0.357, g: 0.608, b: 0.835 }, // 8: accent5 — #5B9BD5 (light blue)
|
|
93
|
+
{ r: 0.439, g: 0.678, b: 0.278 } // 9: accent6 — #70AD47 (green)
|
|
94
94
|
];
|
|
95
95
|
if (themeIndex >= 0 && themeIndex < themeColors.length) {
|
|
96
96
|
return themeColors[themeIndex];
|
|
@@ -182,33 +182,33 @@ export function excelFillToPdfColor(fill) {
|
|
|
182
182
|
function borderStyleToLineWidth(style) {
|
|
183
183
|
switch (style) {
|
|
184
184
|
case "thin":
|
|
185
|
-
return 0.
|
|
185
|
+
return 0.25;
|
|
186
186
|
case "medium":
|
|
187
|
-
return
|
|
187
|
+
return 0.5;
|
|
188
188
|
case "thick":
|
|
189
|
-
return 1
|
|
189
|
+
return 1;
|
|
190
190
|
case "double":
|
|
191
|
-
return 0.5;
|
|
192
|
-
case "hair":
|
|
193
191
|
return 0.25;
|
|
192
|
+
case "hair":
|
|
193
|
+
return 0.1;
|
|
194
194
|
case "dotted":
|
|
195
|
-
return 0.
|
|
195
|
+
return 0.25;
|
|
196
196
|
case "dashed":
|
|
197
|
-
return 0.
|
|
197
|
+
return 0.25;
|
|
198
198
|
case "dashDot":
|
|
199
|
-
return 0.
|
|
199
|
+
return 0.25;
|
|
200
200
|
case "dashDotDot":
|
|
201
|
-
return 0.
|
|
201
|
+
return 0.25;
|
|
202
202
|
case "slantDashDot":
|
|
203
|
-
return 0.
|
|
203
|
+
return 0.25;
|
|
204
204
|
case "mediumDashed":
|
|
205
|
-
return
|
|
205
|
+
return 0.5;
|
|
206
206
|
case "mediumDashDot":
|
|
207
|
-
return
|
|
207
|
+
return 0.5;
|
|
208
208
|
case "mediumDashDotDot":
|
|
209
|
-
return 1;
|
|
210
|
-
default:
|
|
211
209
|
return 0.5;
|
|
210
|
+
default:
|
|
211
|
+
return 0.25;
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
/**
|
|
@@ -246,7 +246,8 @@ function convertBorder(border) {
|
|
|
246
246
|
return {
|
|
247
247
|
width: borderStyleToLineWidth(border.style),
|
|
248
248
|
color: excelColorToPdf(border.color) ?? DEFAULT_COLORS.black,
|
|
249
|
-
dashPattern: borderStyleToDashPattern(border.style)
|
|
249
|
+
dashPattern: borderStyleToDashPattern(border.style),
|
|
250
|
+
isDouble: border.style === "double"
|
|
250
251
|
};
|
|
251
252
|
}
|
|
252
253
|
/**
|
|
@@ -99,6 +99,8 @@ export interface PdfCellData {
|
|
|
99
99
|
export interface PdfRowData {
|
|
100
100
|
hidden?: boolean;
|
|
101
101
|
height?: number;
|
|
102
|
+
/** Whether the height was explicitly set by the user (vs auto-calculated) */
|
|
103
|
+
customHeight?: boolean;
|
|
102
104
|
/** Cells keyed by 1-based column number */
|
|
103
105
|
cells: Map<number, PdfCellData>;
|
|
104
106
|
}
|
|
@@ -432,6 +434,8 @@ export interface LayoutCell {
|
|
|
432
434
|
indent: number;
|
|
433
435
|
/** Text rotation in degrees (0-90 ccw, 91-180 cw) or "vertical" for stacked */
|
|
434
436
|
textRotation: number | "vertical";
|
|
437
|
+
/** Extra width (in points) that text can overflow into adjacent empty cells */
|
|
438
|
+
textOverflowWidth: number;
|
|
435
439
|
}
|
|
436
440
|
/**
|
|
437
441
|
* A single run within a rich text cell.
|
|
@@ -465,6 +469,8 @@ export interface LayoutBorder {
|
|
|
465
469
|
color: PdfColor;
|
|
466
470
|
/** Dash pattern (empty array = solid) */
|
|
467
471
|
dashPattern: number[];
|
|
472
|
+
/** Whether this is a double-line border */
|
|
473
|
+
isDouble?: boolean;
|
|
468
474
|
}
|
|
469
475
|
/**
|
|
470
476
|
* A single page of laid-out content.
|
|
@@ -496,6 +502,8 @@ export interface LayoutPage {
|
|
|
496
502
|
rowHeights: number[];
|
|
497
503
|
/** Images to render on this page */
|
|
498
504
|
images: LayoutImage[];
|
|
505
|
+
/** Scale factor applied to this page (for fitToPage) */
|
|
506
|
+
scaleFactor: number;
|
|
499
507
|
}
|
|
500
508
|
/**
|
|
501
509
|
* A positioned image on a PDF page.
|
|
@@ -78,3 +78,8 @@ export declare function uint8ArrayToBase64(bytes: Uint8Array): string;
|
|
|
78
78
|
* Convert string to UTF-16LE Uint8Array (used for Excel password hashing)
|
|
79
79
|
*/
|
|
80
80
|
export declare function stringToUtf16Le(str: string): Uint8Array;
|
|
81
|
+
/**
|
|
82
|
+
* Yield to the event loop via a macrotask.
|
|
83
|
+
* Uses `setTimeout(0)` which works in both Node.js and browsers.
|
|
84
|
+
*/
|
|
85
|
+
export declare function yieldToEventLoop(): Promise<void>;
|
|
@@ -335,3 +335,13 @@ export function stringToUtf16Le(str) {
|
|
|
335
335
|
}
|
|
336
336
|
return bytes;
|
|
337
337
|
}
|
|
338
|
+
// =============================================================================
|
|
339
|
+
// Async utilities
|
|
340
|
+
// =============================================================================
|
|
341
|
+
/**
|
|
342
|
+
* Yield to the event loop via a macrotask.
|
|
343
|
+
* Uses `setTimeout(0)` which works in both Node.js and browsers.
|
|
344
|
+
*/
|
|
345
|
+
export function yieldToEventLoop() {
|
|
346
|
+
return new Promise(resolve => setTimeout(resolve, 0));
|
|
347
|
+
}
|
|
@@ -6,6 +6,7 @@ const enums_1 = require("./enums.js");
|
|
|
6
6
|
const note_1 = require("./note.js");
|
|
7
7
|
const under_dash_1 = require("./utils/under-dash.js");
|
|
8
8
|
const shared_formula_1 = require("./utils/shared-formula.js");
|
|
9
|
+
const copy_style_1 = require("./utils/copy-style.js");
|
|
9
10
|
const errors_1 = require("./errors.js");
|
|
10
11
|
// Returns true if the value is a non-empty object (has at least one own key),
|
|
11
12
|
// or any truthy non-object value. Returns false for undefined, null, false, 0,
|
|
@@ -90,27 +91,27 @@ class Cell {
|
|
|
90
91
|
const font = (rowStyle && hasOwnKeys(rowStyle.font) && rowStyle.font) ||
|
|
91
92
|
(colStyle && hasOwnKeys(colStyle.font) && colStyle.font);
|
|
92
93
|
if (font) {
|
|
93
|
-
style.font = font;
|
|
94
|
+
style.font = structuredClone(font);
|
|
94
95
|
}
|
|
95
96
|
const alignment = (rowStyle && hasOwnKeys(rowStyle.alignment) && rowStyle.alignment) ||
|
|
96
97
|
(colStyle && hasOwnKeys(colStyle.alignment) && colStyle.alignment);
|
|
97
98
|
if (alignment) {
|
|
98
|
-
style.alignment = alignment;
|
|
99
|
+
style.alignment = structuredClone(alignment);
|
|
99
100
|
}
|
|
100
101
|
const border = (rowStyle && hasOwnKeys(rowStyle.border) && rowStyle.border) ||
|
|
101
102
|
(colStyle && hasOwnKeys(colStyle.border) && colStyle.border);
|
|
102
103
|
if (border) {
|
|
103
|
-
style.border = border;
|
|
104
|
+
style.border = structuredClone(border);
|
|
104
105
|
}
|
|
105
106
|
const fill = (rowStyle && hasOwnKeys(rowStyle.fill) && rowStyle.fill) ||
|
|
106
107
|
(colStyle && hasOwnKeys(colStyle.fill) && colStyle.fill);
|
|
107
108
|
if (fill) {
|
|
108
|
-
style.fill = fill;
|
|
109
|
+
style.fill = structuredClone(fill);
|
|
109
110
|
}
|
|
110
111
|
const protection = (rowStyle && hasOwnKeys(rowStyle.protection) && rowStyle.protection) ||
|
|
111
112
|
(colStyle && hasOwnKeys(colStyle.protection) && colStyle.protection);
|
|
112
113
|
if (protection) {
|
|
113
|
-
style.protection = protection;
|
|
114
|
+
style.protection = structuredClone(protection);
|
|
114
115
|
}
|
|
115
116
|
return style;
|
|
116
117
|
}
|
|
@@ -154,7 +155,10 @@ class Cell {
|
|
|
154
155
|
this._value.release();
|
|
155
156
|
this._value = Value.create(Cell.Types.Merge, this, master);
|
|
156
157
|
if (!ignoreStyle) {
|
|
157
|
-
|
|
158
|
+
// Deep-copy so each cell has an independent style object.
|
|
159
|
+
// Without this, all cells in the merge share the same reference,
|
|
160
|
+
// and setting a property (e.g. border) on any cell mutates all of them.
|
|
161
|
+
this.style = (0, copy_style_1.copyStyle)(master.style) ?? {};
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
164
|
unmerge() {
|
|
@@ -321,7 +325,7 @@ class Cell {
|
|
|
321
325
|
}
|
|
322
326
|
}
|
|
323
327
|
if (value.style) {
|
|
324
|
-
this.style = value.style;
|
|
328
|
+
this.style = (0, copy_style_1.copyStyle)(value.style) ?? {};
|
|
325
329
|
}
|
|
326
330
|
else {
|
|
327
331
|
this.style = {};
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Column = void 0;
|
|
4
4
|
const col_cache_1 = require("./utils/col-cache.js");
|
|
5
5
|
const under_dash_1 = require("./utils/under-dash.js");
|
|
6
|
+
const copy_style_1 = require("./utils/copy-style.js");
|
|
6
7
|
const enums_1 = require("./enums.js");
|
|
7
8
|
const DEFAULT_COLUMN_WIDTH = 9;
|
|
8
9
|
/**
|
|
@@ -51,7 +52,7 @@ class Column {
|
|
|
51
52
|
this.width = value.width !== undefined ? value.width : DEFAULT_COLUMN_WIDTH;
|
|
52
53
|
this.outlineLevel = value.outlineLevel;
|
|
53
54
|
if (value.style) {
|
|
54
|
-
this.style = value.style;
|
|
55
|
+
this.style = (0, copy_style_1.copyStyle)(value.style) ?? {};
|
|
55
56
|
}
|
|
56
57
|
else {
|
|
57
58
|
this.style = {};
|
|
@@ -262,7 +263,7 @@ class Column {
|
|
|
262
263
|
set font(value) {
|
|
263
264
|
this.style.font = value;
|
|
264
265
|
this.eachCell(cell => {
|
|
265
|
-
cell.font = value;
|
|
266
|
+
cell.font = value ? structuredClone(value) : value;
|
|
266
267
|
});
|
|
267
268
|
}
|
|
268
269
|
get alignment() {
|
|
@@ -271,7 +272,7 @@ class Column {
|
|
|
271
272
|
set alignment(value) {
|
|
272
273
|
this.style.alignment = value;
|
|
273
274
|
this.eachCell(cell => {
|
|
274
|
-
cell.alignment = value;
|
|
275
|
+
cell.alignment = value ? structuredClone(value) : value;
|
|
275
276
|
});
|
|
276
277
|
}
|
|
277
278
|
get protection() {
|
|
@@ -280,7 +281,7 @@ class Column {
|
|
|
280
281
|
set protection(value) {
|
|
281
282
|
this.style.protection = value;
|
|
282
283
|
this.eachCell(cell => {
|
|
283
|
-
cell.protection = value;
|
|
284
|
+
cell.protection = value ? structuredClone(value) : value;
|
|
284
285
|
});
|
|
285
286
|
}
|
|
286
287
|
get border() {
|
|
@@ -289,7 +290,7 @@ class Column {
|
|
|
289
290
|
set border(value) {
|
|
290
291
|
this.style.border = value;
|
|
291
292
|
this.eachCell(cell => {
|
|
292
|
-
cell.border = value;
|
|
293
|
+
cell.border = value ? structuredClone(value) : value;
|
|
293
294
|
});
|
|
294
295
|
}
|
|
295
296
|
get fill() {
|
|
@@ -298,7 +299,7 @@ class Column {
|
|
|
298
299
|
set fill(value) {
|
|
299
300
|
this.style.fill = value;
|
|
300
301
|
this.eachCell(cell => {
|
|
301
|
-
cell.fill = value;
|
|
302
|
+
cell.fill = value ? structuredClone(value) : value;
|
|
302
303
|
});
|
|
303
304
|
}
|
|
304
305
|
// =============================================================================
|
|
@@ -300,7 +300,11 @@ class Row {
|
|
|
300
300
|
this.style[name] = value;
|
|
301
301
|
this._cells.forEach(cell => {
|
|
302
302
|
if (cell) {
|
|
303
|
-
cell
|
|
303
|
+
// Clone object values so each cell gets an independent copy.
|
|
304
|
+
// Without this, mutating a sub-property (e.g. cell.border.top = ...)
|
|
305
|
+
// would leak to every other cell that received the same reference.
|
|
306
|
+
cell.style[name] =
|
|
307
|
+
typeof value === "object" && value !== null ? structuredClone(value) : value;
|
|
304
308
|
}
|
|
305
309
|
});
|
|
306
310
|
}
|
|
@@ -10,6 +10,7 @@ const event_emitter_1 = require("../../../utils/event-emitter.js");
|
|
|
10
10
|
const sax_1 = require("../../xml/sax.js");
|
|
11
11
|
const errors_1 = require("../errors.js");
|
|
12
12
|
const utils_1 = require("../../../utils/utils.js");
|
|
13
|
+
const copy_style_1 = require("../utils/copy-style.js");
|
|
13
14
|
const col_cache_1 = require("../utils/col-cache.js");
|
|
14
15
|
const range_1 = require("../range.js");
|
|
15
16
|
const row_1 = require("../row.js");
|
|
@@ -181,7 +182,7 @@ class WorksheetReader extends event_emitter_1.EventEmitter {
|
|
|
181
182
|
const styleId = parseInt(node.attributes.s, 10);
|
|
182
183
|
const style = styles.getStyleModel(styleId);
|
|
183
184
|
if (style) {
|
|
184
|
-
row.style = style;
|
|
185
|
+
row.style = (0, copy_style_1.copyStyle)(style) ?? {};
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
}
|
|
@@ -282,7 +283,7 @@ class WorksheetReader extends event_emitter_1.EventEmitter {
|
|
|
282
283
|
if (c.s !== undefined) {
|
|
283
284
|
const style = styles.getStyleModel(c.s);
|
|
284
285
|
if (style) {
|
|
285
|
-
cell.style = style;
|
|
286
|
+
cell.style = (0, copy_style_1.copyStyle)(style) ?? {};
|
|
286
287
|
}
|
|
287
288
|
}
|
|
288
289
|
if (c.f) {
|