@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
|
@@ -21,25 +21,37 @@ const png_decoder_1 = require("./png-decoder");
|
|
|
21
21
|
const errors_1 = require("../errors");
|
|
22
22
|
const types_1 = require("../types");
|
|
23
23
|
const style_converter_1 = require("./style-converter");
|
|
24
|
+
const utils_base_1 = require("../../../utils/utils.base.js");
|
|
24
25
|
// =============================================================================
|
|
25
26
|
// Public API
|
|
26
27
|
// =============================================================================
|
|
27
28
|
/**
|
|
28
29
|
* Export a PdfWorkbook to PDF format.
|
|
30
|
+
* Yields to the event loop between each output page during layout and rendering.
|
|
29
31
|
*
|
|
30
32
|
* @param workbook - The workbook data to export
|
|
31
33
|
* @param options - Export options controlling layout, pagination, and appearance
|
|
32
|
-
* @returns PDF file as a Uint8Array
|
|
34
|
+
* @returns Promise of PDF file as a Uint8Array
|
|
33
35
|
* @throws {PdfError} If the workbook has no sheets or export fails
|
|
34
36
|
*/
|
|
35
|
-
function exportPdf(workbook, options) {
|
|
37
|
+
async function exportPdf(workbook, options) {
|
|
38
|
+
const ctx = prepareExport(workbook, options);
|
|
39
|
+
for (const sheet of ctx.sheets) {
|
|
40
|
+
await layoutSheetInto(ctx, sheet, options);
|
|
41
|
+
}
|
|
42
|
+
return finishExport(ctx, workbook, options);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Shared setup: validate sheets, create font manager and writer,
|
|
46
|
+
* register embedded font.
|
|
47
|
+
*/
|
|
48
|
+
function prepareExport(workbook, options) {
|
|
36
49
|
const sheets = selectSheets(workbook, options?.sheets);
|
|
37
50
|
if (sheets.length === 0) {
|
|
38
51
|
throw new errors_1.PdfError("No sheets to export. The workbook is empty or no sheets matched.");
|
|
39
52
|
}
|
|
40
53
|
const fontManager = new font_manager_1.FontManager();
|
|
41
54
|
const writer = new pdf_writer_1.PdfWriter();
|
|
42
|
-
// --- Step 0: Register embedded font if provided ---
|
|
43
55
|
if (options?.font) {
|
|
44
56
|
try {
|
|
45
57
|
const ttf = (0, ttf_parser_1.parseTtf)(options.font);
|
|
@@ -49,21 +61,37 @@ function exportPdf(workbook, options) {
|
|
|
49
61
|
throw new errors_1.PdfRenderError("Failed to parse TrueType font", { cause: err });
|
|
50
62
|
}
|
|
51
63
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
return { sheets, fontManager, writer, allPages: [] };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Layout a single sheet and append its pages to the context.
|
|
68
|
+
*/
|
|
69
|
+
async function layoutSheetInto(ctx, sheet, options) {
|
|
70
|
+
try {
|
|
71
|
+
const resolved = resolveOptions(options, sheet);
|
|
72
|
+
const pages = await (0, layout_engine_1.layoutSheet)(sheet, resolved, ctx.fontManager);
|
|
73
|
+
ctx.allPages.push(...pages);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
throw new errors_1.PdfRenderError(`Failed to layout sheet "${sheet.name}"`, { cause: err });
|
|
63
77
|
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* After layout: fix page numbers, track fonts, write resources,
|
|
81
|
+
* render pages, and build the final PDF binary.
|
|
82
|
+
*/
|
|
83
|
+
async function finishExport(ctx, workbook, options) {
|
|
84
|
+
const { allPages, fontManager, writer, sheets } = ctx;
|
|
64
85
|
const documentOptions = resolveOptions(options, sheets[0]);
|
|
86
|
+
ensureAtLeastOnePage(allPages, documentOptions, sheets);
|
|
87
|
+
fixPageNumbers(allPages);
|
|
88
|
+
trackFontsForHeaders(allPages, fontManager);
|
|
89
|
+
const fontObjectMap = fontManager.writeFontResources(writer);
|
|
90
|
+
const { pageObjNums, sheetFirstPage, pagesTreeObjNum } = await renderAllPages(allPages, fontManager, writer, fontObjectMap);
|
|
91
|
+
return buildFinalPdf(writer, pageObjNums, pagesTreeObjNum, sheetFirstPage, documentOptions, workbook, options);
|
|
92
|
+
}
|
|
93
|
+
function ensureAtLeastOnePage(allPages, documentOptions, sheets) {
|
|
65
94
|
if (allPages.length === 0) {
|
|
66
|
-
// Create at least one empty page
|
|
67
95
|
allPages.push({
|
|
68
96
|
pageNumber: 1,
|
|
69
97
|
options: documentOptions,
|
|
@@ -77,15 +105,17 @@ function exportPdf(workbook, options) {
|
|
|
77
105
|
sheetRows: [],
|
|
78
106
|
rowYPositions: [],
|
|
79
107
|
rowHeights: [],
|
|
80
|
-
images: []
|
|
108
|
+
images: [],
|
|
109
|
+
scaleFactor: 1
|
|
81
110
|
});
|
|
82
111
|
}
|
|
83
|
-
|
|
112
|
+
}
|
|
113
|
+
function fixPageNumbers(allPages) {
|
|
84
114
|
for (let i = 0; i < allPages.length; i++) {
|
|
85
115
|
allPages[i].pageNumber = i + 1;
|
|
86
116
|
}
|
|
87
|
-
|
|
88
|
-
|
|
117
|
+
}
|
|
118
|
+
function trackFontsForHeaders(allPages, fontManager) {
|
|
89
119
|
if (fontManager.hasEmbeddedFont()) {
|
|
90
120
|
for (const page of allPages) {
|
|
91
121
|
if (page.options.showSheetNames) {
|
|
@@ -105,99 +135,102 @@ function exportPdf(workbook, options) {
|
|
|
105
135
|
}
|
|
106
136
|
}
|
|
107
137
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// --- Step 3: Render pages and build PDF structure ---
|
|
138
|
+
}
|
|
139
|
+
async function renderAllPages(allPages, fontManager, writer, fontObjectMap) {
|
|
111
140
|
const pageObjNums = [];
|
|
112
|
-
const pagesTreeObjNum = writer.allocObject();
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
for (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// Add resources dictionary object
|
|
136
|
-
const resourcesObjNum = writer.allocObject();
|
|
137
|
-
const fontDictStr = fontManager.buildFontDictString(fontObjectMap);
|
|
138
|
-
const resourcesDict = new pdf_object_1.PdfDict().set("Font", fontDictStr);
|
|
139
|
-
// Add XObject resources for images
|
|
140
|
-
if (imageXObjects.size > 0) {
|
|
141
|
-
const xobjParts = ["<<"];
|
|
142
|
-
for (const [name, objNum] of imageXObjects) {
|
|
143
|
-
xobjParts.push(`/${name} ${(0, pdf_object_1.pdfRef)(objNum)}`);
|
|
144
|
-
}
|
|
145
|
-
xobjParts.push(">>");
|
|
146
|
-
resourcesDict.set("XObject", xobjParts.join("\n"));
|
|
141
|
+
const pagesTreeObjNum = writer.allocObject();
|
|
142
|
+
const sheetFirstPage = new Map();
|
|
143
|
+
const totalPages = allPages.length;
|
|
144
|
+
for (let i = 0; i < allPages.length; i++) {
|
|
145
|
+
renderSinglePage(allPages[i], fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage);
|
|
146
|
+
if (i < allPages.length - 1) {
|
|
147
|
+
await (0, utils_base_1.yieldToEventLoop)();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { pageObjNums, sheetFirstPage, pagesTreeObjNum };
|
|
151
|
+
}
|
|
152
|
+
function renderSinglePage(page, fontManager, writer, fontObjectMap, totalPages, pageObjNums, pagesTreeObjNum, sheetFirstPage) {
|
|
153
|
+
try {
|
|
154
|
+
const { stream: contentStream, alphaValues } = (0, page_renderer_1.renderPage)(page, page.options, fontManager, totalPages);
|
|
155
|
+
// Handle images: create XObject Image entries and draw them
|
|
156
|
+
const imageXObjects = new Map();
|
|
157
|
+
if (page.images.length > 0) {
|
|
158
|
+
for (let imgIdx = 0; imgIdx < page.images.length; imgIdx++) {
|
|
159
|
+
const img = page.images[imgIdx];
|
|
160
|
+
const imgName = `Im${imgIdx + 1}`;
|
|
161
|
+
const imgObjNum = writeImageXObject(writer, img.data, img.format);
|
|
162
|
+
imageXObjects.set(imgName, imgObjNum);
|
|
163
|
+
contentStream.drawImage(imgName, img.rect.x, img.rect.y, img.rect.width, img.rect.height);
|
|
147
164
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
resourcesDict.set("ExtGState", gsParts.join("\n"));
|
|
165
|
+
}
|
|
166
|
+
// Add content stream object
|
|
167
|
+
const contentObjNum = writer.allocObject();
|
|
168
|
+
const contentDict = new pdf_object_1.PdfDict();
|
|
169
|
+
writer.addStreamObject(contentObjNum, contentDict, contentStream);
|
|
170
|
+
// Add resources dictionary object
|
|
171
|
+
const resourcesObjNum = writer.allocObject();
|
|
172
|
+
const fontDictStr = fontManager.buildFontDictString(fontObjectMap);
|
|
173
|
+
const resourcesDict = new pdf_object_1.PdfDict().set("Font", fontDictStr);
|
|
174
|
+
if (imageXObjects.size > 0) {
|
|
175
|
+
const xobjParts = ["<<"];
|
|
176
|
+
for (const [name, objNum] of imageXObjects) {
|
|
177
|
+
xobjParts.push(`/${name} ${(0, pdf_object_1.pdfRef)(objNum)}`);
|
|
162
178
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
writer.addObject(annotObjNum, annotDict);
|
|
177
|
-
annotRefs.push(annotObjNum);
|
|
178
|
-
}
|
|
179
|
+
xobjParts.push(">>");
|
|
180
|
+
resourcesDict.set("XObject", xobjParts.join("\n"));
|
|
181
|
+
}
|
|
182
|
+
if (alphaValues.size > 0) {
|
|
183
|
+
const gsParts = ["<<"];
|
|
184
|
+
for (const alpha of alphaValues) {
|
|
185
|
+
const gsObjNum = writer.allocObject();
|
|
186
|
+
const gsDict = new pdf_object_1.PdfDict()
|
|
187
|
+
.set("Type", "/ExtGState")
|
|
188
|
+
.set("ca", (0, pdf_object_1.pdfNumber)(alpha))
|
|
189
|
+
.set("CA", (0, pdf_object_1.pdfNumber)(alpha));
|
|
190
|
+
writer.addObject(gsObjNum, gsDict);
|
|
191
|
+
gsParts.push(`/${(0, page_renderer_1.alphaGsName)(alpha)} ${(0, pdf_object_1.pdfRef)(gsObjNum)}`);
|
|
179
192
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
gsParts.push(">>");
|
|
194
|
+
resourcesDict.set("ExtGState", gsParts.join("\n"));
|
|
195
|
+
}
|
|
196
|
+
writer.addObject(resourcesObjNum, resourcesDict);
|
|
197
|
+
// Create link annotations for hyperlinks
|
|
198
|
+
const annotRefs = [];
|
|
199
|
+
for (const cell of page.cells) {
|
|
200
|
+
if (cell.hyperlink) {
|
|
201
|
+
const annotObjNum = writer.allocObject();
|
|
202
|
+
const rect = `[${(0, pdf_object_1.pdfNumber)(cell.rect.x)} ${(0, pdf_object_1.pdfNumber)(cell.rect.y)} ${(0, pdf_object_1.pdfNumber)(cell.rect.x + cell.rect.width)} ${(0, pdf_object_1.pdfNumber)(cell.rect.y + cell.rect.height)}]`;
|
|
203
|
+
const annotDict = new pdf_object_1.PdfDict()
|
|
204
|
+
.set("Type", "/Annot")
|
|
205
|
+
.set("Subtype", "/Link")
|
|
206
|
+
.set("Rect", rect)
|
|
207
|
+
.set("Border", "[0 0 0]")
|
|
208
|
+
.set("A", `<< /Type /Action /S /URI /URI (${cell.hyperlink.replace(/[()\\]/g, "\\$&")}) >>`);
|
|
209
|
+
writer.addObject(annotObjNum, annotDict);
|
|
210
|
+
annotRefs.push(annotObjNum);
|
|
193
211
|
}
|
|
194
212
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
213
|
+
// Add page object
|
|
214
|
+
const pageObjNum = writer.addPage({
|
|
215
|
+
parentRef: pagesTreeObjNum,
|
|
216
|
+
width: page.width,
|
|
217
|
+
height: page.height,
|
|
218
|
+
contentsRef: contentObjNum,
|
|
219
|
+
resourcesRef: resourcesObjNum,
|
|
220
|
+
annotRefs: annotRefs.length > 0 ? annotRefs : undefined
|
|
221
|
+
});
|
|
222
|
+
pageObjNums.push(pageObjNum);
|
|
223
|
+
if (!sheetFirstPage.has(page.sheetName)) {
|
|
224
|
+
sheetFirstPage.set(page.sheetName, pageObjNums.length - 1);
|
|
199
225
|
}
|
|
200
226
|
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
throw new errors_1.PdfRenderError(`Failed to render page ${page.pageNumber} of "${page.sheetName}"`, {
|
|
229
|
+
cause: err
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function buildFinalPdf(writer, pageObjNums, pagesTreeObjNum, sheetFirstPage, documentOptions, workbook, options) {
|
|
201
234
|
// --- Step 4: Build page tree ---
|
|
202
235
|
const pagesKids = "[" + pageObjNums.map(n => (0, pdf_object_1.pdfRef)(n)).join(" ") + "]";
|
|
203
236
|
const pagesDict = new pdf_object_1.PdfDict()
|
|
@@ -289,7 +322,14 @@ function resolveOptions(options, sheet) {
|
|
|
289
322
|
orientation,
|
|
290
323
|
margins,
|
|
291
324
|
fitToPage: options?.fitToPage !== undefined ? options.fitToPage : true,
|
|
292
|
-
scale: Math.max(0.1, Math.min(3.0, options?.scale ??
|
|
325
|
+
scale: Math.max(0.1, Math.min(3.0, options?.scale ??
|
|
326
|
+
// When fitToPage is active (default), ignore sheet's pageSetup.scale
|
|
327
|
+
// to avoid double-scaling. Only apply sheet scale when fitToPage is off.
|
|
328
|
+
((options?.fitToPage !== undefined ? options.fitToPage : true)
|
|
329
|
+
? 1.0
|
|
330
|
+
: ps?.scale
|
|
331
|
+
? ps.scale / 100
|
|
332
|
+
: 1.0))),
|
|
293
333
|
showGridLines: options?.showGridLines ?? ps?.showGridLines ?? false,
|
|
294
334
|
gridLineColor,
|
|
295
335
|
repeatRows,
|
|
@@ -90,18 +90,18 @@ function excelColorToPdf(color) {
|
|
|
90
90
|
* These are the default Office theme colors.
|
|
91
91
|
*/
|
|
92
92
|
function themeColorToPdf(themeIndex) {
|
|
93
|
-
// Default Office theme color palette
|
|
93
|
+
// Default Office 2019+ theme color palette (hex values verified)
|
|
94
94
|
const themeColors = [
|
|
95
|
-
{ r: 1, g: 1, b: 1 }, // 0: lt1 (white / window background)
|
|
96
|
-
{ r: 0, g: 0, b: 0 }, // 1: dk1 (black / window text)
|
|
97
|
-
{ r: 0.
|
|
98
|
-
{ r: 0.267, g: 0.
|
|
99
|
-
{ r: 0.
|
|
100
|
-
{ r: 0.
|
|
101
|
-
{ r: 0.
|
|
102
|
-
{ r: 1, g: 0.753, b: 0 }, // 7: accent4 (gold)
|
|
103
|
-
{ r: 0.
|
|
104
|
-
{ r: 0.439, g: 0.678, b: 0.278 } // 9: accent6 (green)
|
|
95
|
+
{ r: 1, g: 1, b: 1 }, // 0: lt1 — #FFFFFF (white / window background)
|
|
96
|
+
{ r: 0, g: 0, b: 0 }, // 1: dk1 — #000000 (black / window text)
|
|
97
|
+
{ r: 0.906, g: 0.902, b: 0.902 }, // 2: lt2 — #E7E6E6
|
|
98
|
+
{ r: 0.267, g: 0.329, b: 0.416 }, // 3: dk2 — #44546A
|
|
99
|
+
{ r: 0.267, g: 0.447, b: 0.769 }, // 4: accent1 — #4472C4 (blue)
|
|
100
|
+
{ r: 0.929, g: 0.49, b: 0.192 }, // 5: accent2 — #ED7D31 (orange)
|
|
101
|
+
{ r: 0.647, g: 0.647, b: 0.647 }, // 6: accent3 — #A5A5A5 (gray)
|
|
102
|
+
{ r: 1, g: 0.753, b: 0 }, // 7: accent4 — #FFC000 (gold)
|
|
103
|
+
{ r: 0.357, g: 0.608, b: 0.835 }, // 8: accent5 — #5B9BD5 (light blue)
|
|
104
|
+
{ r: 0.439, g: 0.678, b: 0.278 } // 9: accent6 — #70AD47 (green)
|
|
105
105
|
];
|
|
106
106
|
if (themeIndex >= 0 && themeIndex < themeColors.length) {
|
|
107
107
|
return themeColors[themeIndex];
|
|
@@ -193,33 +193,33 @@ function excelFillToPdfColor(fill) {
|
|
|
193
193
|
function borderStyleToLineWidth(style) {
|
|
194
194
|
switch (style) {
|
|
195
195
|
case "thin":
|
|
196
|
-
return 0.
|
|
196
|
+
return 0.25;
|
|
197
197
|
case "medium":
|
|
198
|
-
return
|
|
198
|
+
return 0.5;
|
|
199
199
|
case "thick":
|
|
200
|
-
return 1
|
|
200
|
+
return 1;
|
|
201
201
|
case "double":
|
|
202
|
-
return 0.5;
|
|
203
|
-
case "hair":
|
|
204
202
|
return 0.25;
|
|
203
|
+
case "hair":
|
|
204
|
+
return 0.1;
|
|
205
205
|
case "dotted":
|
|
206
|
-
return 0.
|
|
206
|
+
return 0.25;
|
|
207
207
|
case "dashed":
|
|
208
|
-
return 0.
|
|
208
|
+
return 0.25;
|
|
209
209
|
case "dashDot":
|
|
210
|
-
return 0.
|
|
210
|
+
return 0.25;
|
|
211
211
|
case "dashDotDot":
|
|
212
|
-
return 0.
|
|
212
|
+
return 0.25;
|
|
213
213
|
case "slantDashDot":
|
|
214
|
-
return 0.
|
|
214
|
+
return 0.25;
|
|
215
215
|
case "mediumDashed":
|
|
216
|
-
return
|
|
216
|
+
return 0.5;
|
|
217
217
|
case "mediumDashDot":
|
|
218
|
-
return
|
|
218
|
+
return 0.5;
|
|
219
219
|
case "mediumDashDotDot":
|
|
220
|
-
return 1;
|
|
221
|
-
default:
|
|
222
220
|
return 0.5;
|
|
221
|
+
default:
|
|
222
|
+
return 0.25;
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
/**
|
|
@@ -257,7 +257,8 @@ function convertBorder(border) {
|
|
|
257
257
|
return {
|
|
258
258
|
width: borderStyleToLineWidth(border.style),
|
|
259
259
|
color: excelColorToPdf(border.color) ?? exports.DEFAULT_COLORS.black,
|
|
260
|
-
dashPattern: borderStyleToDashPattern(border.style)
|
|
260
|
+
dashPattern: borderStyleToDashPattern(border.style),
|
|
261
|
+
isDouble: border.style === "double"
|
|
261
262
|
};
|
|
262
263
|
}
|
|
263
264
|
/**
|
|
@@ -23,6 +23,7 @@ exports.toSortedArray = toSortedArray;
|
|
|
23
23
|
exports.bufferToString = bufferToString;
|
|
24
24
|
exports.uint8ArrayToBase64 = uint8ArrayToBase64;
|
|
25
25
|
exports.stringToUtf16Le = stringToUtf16Le;
|
|
26
|
+
exports.yieldToEventLoop = yieldToEventLoop;
|
|
26
27
|
const env_1 = require("./env.js");
|
|
27
28
|
// =============================================================================
|
|
28
29
|
// Base64 utilities (with native Buffer optimization for Node.js)
|
|
@@ -357,3 +358,13 @@ function stringToUtf16Le(str) {
|
|
|
357
358
|
}
|
|
358
359
|
return bytes;
|
|
359
360
|
}
|
|
361
|
+
// =============================================================================
|
|
362
|
+
// Async utilities
|
|
363
|
+
// =============================================================================
|
|
364
|
+
/**
|
|
365
|
+
* Yield to the event loop via a macrotask.
|
|
366
|
+
* Uses `setTimeout(0)` which works in both Node.js and browsers.
|
|
367
|
+
*/
|
|
368
|
+
function yieldToEventLoop() {
|
|
369
|
+
return new Promise(resolve => setTimeout(resolve, 0));
|
|
370
|
+
}
|
|
@@ -3,6 +3,7 @@ import { Enums } from "./enums.js";
|
|
|
3
3
|
import { Note } from "./note.js";
|
|
4
4
|
import { escapeHtml } from "./utils/under-dash.js";
|
|
5
5
|
import { slideFormula } from "./utils/shared-formula.js";
|
|
6
|
+
import { copyStyle } from "./utils/copy-style.js";
|
|
6
7
|
import { ExcelError, InvalidValueTypeError } from "./errors.js";
|
|
7
8
|
// Returns true if the value is a non-empty object (has at least one own key),
|
|
8
9
|
// or any truthy non-object value. Returns false for undefined, null, false, 0,
|
|
@@ -87,27 +88,27 @@ class Cell {
|
|
|
87
88
|
const font = (rowStyle && hasOwnKeys(rowStyle.font) && rowStyle.font) ||
|
|
88
89
|
(colStyle && hasOwnKeys(colStyle.font) && colStyle.font);
|
|
89
90
|
if (font) {
|
|
90
|
-
style.font = font;
|
|
91
|
+
style.font = structuredClone(font);
|
|
91
92
|
}
|
|
92
93
|
const alignment = (rowStyle && hasOwnKeys(rowStyle.alignment) && rowStyle.alignment) ||
|
|
93
94
|
(colStyle && hasOwnKeys(colStyle.alignment) && colStyle.alignment);
|
|
94
95
|
if (alignment) {
|
|
95
|
-
style.alignment = alignment;
|
|
96
|
+
style.alignment = structuredClone(alignment);
|
|
96
97
|
}
|
|
97
98
|
const border = (rowStyle && hasOwnKeys(rowStyle.border) && rowStyle.border) ||
|
|
98
99
|
(colStyle && hasOwnKeys(colStyle.border) && colStyle.border);
|
|
99
100
|
if (border) {
|
|
100
|
-
style.border = border;
|
|
101
|
+
style.border = structuredClone(border);
|
|
101
102
|
}
|
|
102
103
|
const fill = (rowStyle && hasOwnKeys(rowStyle.fill) && rowStyle.fill) ||
|
|
103
104
|
(colStyle && hasOwnKeys(colStyle.fill) && colStyle.fill);
|
|
104
105
|
if (fill) {
|
|
105
|
-
style.fill = fill;
|
|
106
|
+
style.fill = structuredClone(fill);
|
|
106
107
|
}
|
|
107
108
|
const protection = (rowStyle && hasOwnKeys(rowStyle.protection) && rowStyle.protection) ||
|
|
108
109
|
(colStyle && hasOwnKeys(colStyle.protection) && colStyle.protection);
|
|
109
110
|
if (protection) {
|
|
110
|
-
style.protection = protection;
|
|
111
|
+
style.protection = structuredClone(protection);
|
|
111
112
|
}
|
|
112
113
|
return style;
|
|
113
114
|
}
|
|
@@ -151,7 +152,10 @@ class Cell {
|
|
|
151
152
|
this._value.release();
|
|
152
153
|
this._value = Value.create(Cell.Types.Merge, this, master);
|
|
153
154
|
if (!ignoreStyle) {
|
|
154
|
-
|
|
155
|
+
// Deep-copy so each cell has an independent style object.
|
|
156
|
+
// Without this, all cells in the merge share the same reference,
|
|
157
|
+
// and setting a property (e.g. border) on any cell mutates all of them.
|
|
158
|
+
this.style = copyStyle(master.style) ?? {};
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
unmerge() {
|
|
@@ -318,7 +322,7 @@ class Cell {
|
|
|
318
322
|
}
|
|
319
323
|
}
|
|
320
324
|
if (value.style) {
|
|
321
|
-
this.style = value.style;
|
|
325
|
+
this.style = copyStyle(value.style) ?? {};
|
|
322
326
|
}
|
|
323
327
|
else {
|
|
324
328
|
this.style = {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { colCache } from "./utils/col-cache.js";
|
|
2
2
|
import { isEqual } from "./utils/under-dash.js";
|
|
3
|
+
import { copyStyle } from "./utils/copy-style.js";
|
|
3
4
|
import { Enums } from "./enums.js";
|
|
4
5
|
const DEFAULT_COLUMN_WIDTH = 9;
|
|
5
6
|
/**
|
|
@@ -48,7 +49,7 @@ class Column {
|
|
|
48
49
|
this.width = value.width !== undefined ? value.width : DEFAULT_COLUMN_WIDTH;
|
|
49
50
|
this.outlineLevel = value.outlineLevel;
|
|
50
51
|
if (value.style) {
|
|
51
|
-
this.style = value.style;
|
|
52
|
+
this.style = copyStyle(value.style) ?? {};
|
|
52
53
|
}
|
|
53
54
|
else {
|
|
54
55
|
this.style = {};
|
|
@@ -259,7 +260,7 @@ class Column {
|
|
|
259
260
|
set font(value) {
|
|
260
261
|
this.style.font = value;
|
|
261
262
|
this.eachCell(cell => {
|
|
262
|
-
cell.font = value;
|
|
263
|
+
cell.font = value ? structuredClone(value) : value;
|
|
263
264
|
});
|
|
264
265
|
}
|
|
265
266
|
get alignment() {
|
|
@@ -268,7 +269,7 @@ class Column {
|
|
|
268
269
|
set alignment(value) {
|
|
269
270
|
this.style.alignment = value;
|
|
270
271
|
this.eachCell(cell => {
|
|
271
|
-
cell.alignment = value;
|
|
272
|
+
cell.alignment = value ? structuredClone(value) : value;
|
|
272
273
|
});
|
|
273
274
|
}
|
|
274
275
|
get protection() {
|
|
@@ -277,7 +278,7 @@ class Column {
|
|
|
277
278
|
set protection(value) {
|
|
278
279
|
this.style.protection = value;
|
|
279
280
|
this.eachCell(cell => {
|
|
280
|
-
cell.protection = value;
|
|
281
|
+
cell.protection = value ? structuredClone(value) : value;
|
|
281
282
|
});
|
|
282
283
|
}
|
|
283
284
|
get border() {
|
|
@@ -286,7 +287,7 @@ class Column {
|
|
|
286
287
|
set border(value) {
|
|
287
288
|
this.style.border = value;
|
|
288
289
|
this.eachCell(cell => {
|
|
289
|
-
cell.border = value;
|
|
290
|
+
cell.border = value ? structuredClone(value) : value;
|
|
290
291
|
});
|
|
291
292
|
}
|
|
292
293
|
get fill() {
|
|
@@ -295,7 +296,7 @@ class Column {
|
|
|
295
296
|
set fill(value) {
|
|
296
297
|
this.style.fill = value;
|
|
297
298
|
this.eachCell(cell => {
|
|
298
|
-
cell.fill = value;
|
|
299
|
+
cell.fill = value ? structuredClone(value) : value;
|
|
299
300
|
});
|
|
300
301
|
}
|
|
301
302
|
// =============================================================================
|
|
@@ -297,7 +297,11 @@ class Row {
|
|
|
297
297
|
this.style[name] = value;
|
|
298
298
|
this._cells.forEach(cell => {
|
|
299
299
|
if (cell) {
|
|
300
|
-
cell
|
|
300
|
+
// Clone object values so each cell gets an independent copy.
|
|
301
|
+
// Without this, mutating a sub-property (e.g. cell.border.top = ...)
|
|
302
|
+
// would leak to every other cell that received the same reference.
|
|
303
|
+
cell.style[name] =
|
|
304
|
+
typeof value === "object" && value !== null ? structuredClone(value) : value;
|
|
301
305
|
}
|
|
302
306
|
});
|
|
303
307
|
}
|
|
@@ -7,6 +7,7 @@ import { EventEmitter } from "../../../utils/event-emitter.js";
|
|
|
7
7
|
import { SaxParser } from "../../xml/sax.js";
|
|
8
8
|
import { ExcelStreamStateError } from "../errors.js";
|
|
9
9
|
import { isDateFmt, excelToDate, decodeOoxmlEscape } from "../../../utils/utils.js";
|
|
10
|
+
import { copyStyle } from "../utils/copy-style.js";
|
|
10
11
|
import { colCache } from "../utils/col-cache.js";
|
|
11
12
|
import { Dimensions } from "../range.js";
|
|
12
13
|
import { Row } from "../row.js";
|
|
@@ -178,7 +179,7 @@ class WorksheetReader extends EventEmitter {
|
|
|
178
179
|
const styleId = parseInt(node.attributes.s, 10);
|
|
179
180
|
const style = styles.getStyleModel(styleId);
|
|
180
181
|
if (style) {
|
|
181
|
-
row.style = style;
|
|
182
|
+
row.style = copyStyle(style) ?? {};
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
}
|
|
@@ -279,7 +280,7 @@ class WorksheetReader extends EventEmitter {
|
|
|
279
280
|
if (c.s !== undefined) {
|
|
280
281
|
const style = styles.getStyleModel(c.s);
|
|
281
282
|
if (style) {
|
|
282
|
-
cell.style = style;
|
|
283
|
+
cell.style = copyStyle(style) ?? {};
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
if (c.f) {
|