@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.
Files changed (71) hide show
  1. package/README.md +2 -2
  2. package/README_zh.md +2 -2
  3. package/dist/browser/modules/excel/cell.js +11 -7
  4. package/dist/browser/modules/excel/column.js +7 -6
  5. package/dist/browser/modules/excel/row.js +5 -1
  6. package/dist/browser/modules/excel/stream/worksheet-reader.js +3 -2
  7. package/dist/browser/modules/excel/utils/cell-format.js +64 -2
  8. package/dist/browser/modules/pdf/excel-bridge.d.ts +4 -3
  9. package/dist/browser/modules/pdf/excel-bridge.js +18 -5
  10. package/dist/browser/modules/pdf/index.d.ts +3 -3
  11. package/dist/browser/modules/pdf/index.js +3 -3
  12. package/dist/browser/modules/pdf/pdf.d.ts +7 -6
  13. package/dist/browser/modules/pdf/pdf.js +7 -6
  14. package/dist/browser/modules/pdf/reader/pdf-reader.d.ts +8 -7
  15. package/dist/browser/modules/pdf/reader/pdf-reader.js +81 -74
  16. package/dist/browser/modules/pdf/render/constants.d.ts +30 -0
  17. package/dist/browser/modules/pdf/render/constants.js +30 -0
  18. package/dist/browser/modules/pdf/render/layout-engine.d.ts +2 -1
  19. package/dist/browser/modules/pdf/render/layout-engine.js +359 -156
  20. package/dist/browser/modules/pdf/render/page-renderer.d.ts +2 -2
  21. package/dist/browser/modules/pdf/render/page-renderer.js +245 -107
  22. package/dist/browser/modules/pdf/render/pdf-exporter.d.ts +3 -2
  23. package/dist/browser/modules/pdf/render/pdf-exporter.js +145 -105
  24. package/dist/browser/modules/pdf/render/style-converter.js +27 -26
  25. package/dist/browser/modules/pdf/types.d.ts +8 -0
  26. package/dist/browser/utils/utils.base.d.ts +5 -0
  27. package/dist/browser/utils/utils.base.js +10 -0
  28. package/dist/cjs/modules/excel/cell.js +11 -7
  29. package/dist/cjs/modules/excel/column.js +7 -6
  30. package/dist/cjs/modules/excel/row.js +5 -1
  31. package/dist/cjs/modules/excel/stream/worksheet-reader.js +3 -2
  32. package/dist/cjs/modules/excel/utils/cell-format.js +64 -2
  33. package/dist/cjs/modules/pdf/excel-bridge.js +18 -5
  34. package/dist/cjs/modules/pdf/index.js +3 -3
  35. package/dist/cjs/modules/pdf/pdf.js +7 -6
  36. package/dist/cjs/modules/pdf/reader/pdf-reader.js +81 -74
  37. package/dist/cjs/modules/pdf/render/constants.js +33 -0
  38. package/dist/cjs/modules/pdf/render/layout-engine.js +359 -156
  39. package/dist/cjs/modules/pdf/render/page-renderer.js +245 -107
  40. package/dist/cjs/modules/pdf/render/pdf-exporter.js +145 -105
  41. package/dist/cjs/modules/pdf/render/style-converter.js +27 -26
  42. package/dist/cjs/utils/utils.base.js +11 -0
  43. package/dist/esm/modules/excel/cell.js +11 -7
  44. package/dist/esm/modules/excel/column.js +7 -6
  45. package/dist/esm/modules/excel/row.js +5 -1
  46. package/dist/esm/modules/excel/stream/worksheet-reader.js +3 -2
  47. package/dist/esm/modules/excel/utils/cell-format.js +64 -2
  48. package/dist/esm/modules/pdf/excel-bridge.js +18 -5
  49. package/dist/esm/modules/pdf/index.js +3 -3
  50. package/dist/esm/modules/pdf/pdf.js +7 -6
  51. package/dist/esm/modules/pdf/reader/pdf-reader.js +81 -74
  52. package/dist/esm/modules/pdf/render/constants.js +30 -0
  53. package/dist/esm/modules/pdf/render/layout-engine.js +359 -156
  54. package/dist/esm/modules/pdf/render/page-renderer.js +245 -107
  55. package/dist/esm/modules/pdf/render/pdf-exporter.js +145 -105
  56. package/dist/esm/modules/pdf/render/style-converter.js +27 -26
  57. package/dist/esm/utils/utils.base.js +10 -0
  58. package/dist/iife/excelts.iife.js +1022 -677
  59. package/dist/iife/excelts.iife.js.map +1 -1
  60. package/dist/iife/excelts.iife.min.js +48 -48
  61. package/dist/types/modules/pdf/excel-bridge.d.ts +4 -3
  62. package/dist/types/modules/pdf/index.d.ts +3 -3
  63. package/dist/types/modules/pdf/pdf.d.ts +7 -6
  64. package/dist/types/modules/pdf/reader/pdf-reader.d.ts +8 -7
  65. package/dist/types/modules/pdf/render/constants.d.ts +30 -0
  66. package/dist/types/modules/pdf/render/layout-engine.d.ts +2 -1
  67. package/dist/types/modules/pdf/render/page-renderer.d.ts +2 -2
  68. package/dist/types/modules/pdf/render/pdf-exporter.d.ts +3 -2
  69. package/dist/types/modules/pdf/types.d.ts +8 -0
  70. package/dist/types/utils/utils.base.d.ts +5 -0
  71. 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
- // --- Step 1: Layout all sheets ---
53
- const allPages = [];
54
- for (const sheet of sheets) {
55
- try {
56
- const resolved = resolveOptions(options, sheet);
57
- const pages = (0, layout_engine_1.layoutSheet)(sheet, resolved, fontManager);
58
- allPages.push(...pages);
59
- }
60
- catch (err) {
61
- throw new errors_1.PdfRenderError(`Failed to layout sheet "${sheet.name}"`, { cause: err });
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
- // Fix page numbers (they may be off after combining multiple sheets)
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
- const totalPages = allPages.length;
88
- // --- Step 1.5: Track page header/footer text for font subsetting ---
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
- // --- Step 2: Write font resources (builds subset from tracked code points) ---
109
- const fontObjectMap = fontManager.writeFontResources(writer);
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(); // Allocate early for forward ref
113
- // Track first page per sheet for outline/bookmarks
114
- const sheetFirstPage = new Map(); // sheetName → pageObjNum index
115
- for (const page of allPages) {
116
- try {
117
- // Render the page content (text, fills, borders)
118
- const { stream: contentStream, alphaValues } = (0, page_renderer_1.renderPage)(page, page.options, fontManager, totalPages);
119
- // Handle images: create XObject Image entries and draw them
120
- const imageXObjects = new Map(); // name → objNum
121
- if (page.images.length > 0) {
122
- for (let imgIdx = 0; imgIdx < page.images.length; imgIdx++) {
123
- const img = page.images[imgIdx];
124
- const imgName = `Im${imgIdx + 1}`;
125
- const imgObjNum = writeImageXObject(writer, img.data, img.format);
126
- imageXObjects.set(imgName, imgObjNum);
127
- // Draw the image in the content stream
128
- contentStream.drawImage(imgName, img.rect.x, img.rect.y, img.rect.width, img.rect.height);
129
- }
130
- }
131
- // Add content stream object
132
- const contentObjNum = writer.allocObject();
133
- const contentDict = new pdf_object_1.PdfDict();
134
- writer.addStreamObject(contentObjNum, contentDict, contentStream);
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
- // Add ExtGState resources for transparency
149
- if (alphaValues.size > 0) {
150
- const gsParts = ["<<"];
151
- for (const alpha of alphaValues) {
152
- const gsObjNum = writer.allocObject();
153
- const gsDict = new pdf_object_1.PdfDict()
154
- .set("Type", "/ExtGState")
155
- .set("ca", (0, pdf_object_1.pdfNumber)(alpha))
156
- .set("CA", (0, pdf_object_1.pdfNumber)(alpha));
157
- writer.addObject(gsObjNum, gsDict);
158
- gsParts.push(`/${(0, page_renderer_1.alphaGsName)(alpha)} ${(0, pdf_object_1.pdfRef)(gsObjNum)}`);
159
- }
160
- gsParts.push(">>");
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
- writer.addObject(resourcesObjNum, resourcesDict);
164
- // Create link annotations for hyperlinks
165
- const annotRefs = [];
166
- for (const cell of page.cells) {
167
- if (cell.hyperlink) {
168
- const annotObjNum = writer.allocObject();
169
- 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)}]`;
170
- const annotDict = new pdf_object_1.PdfDict()
171
- .set("Type", "/Annot")
172
- .set("Subtype", "/Link")
173
- .set("Rect", rect)
174
- .set("Border", "[0 0 0]")
175
- .set("A", `<< /Type /Action /S /URI /URI (${cell.hyperlink.replace(/[()\\]/g, "\\$&")}) >>`);
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
- // Add page object
181
- const pageObjNum = writer.addPage({
182
- parentRef: pagesTreeObjNum,
183
- width: page.width,
184
- height: page.height,
185
- contentsRef: contentObjNum,
186
- resourcesRef: resourcesObjNum,
187
- annotRefs: annotRefs.length > 0 ? annotRefs : undefined
188
- });
189
- pageObjNums.push(pageObjNum);
190
- // Track first page of each sheet
191
- if (!sheetFirstPage.has(page.sheetName)) {
192
- sheetFirstPage.set(page.sheetName, pageObjNums.length - 1);
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
- catch (err) {
196
- throw new errors_1.PdfRenderError(`Failed to render page ${page.pageNumber} of "${page.sheetName}"`, {
197
- cause: err
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 ?? (ps?.scale ? ps.scale / 100 : 1.0))),
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.918, g: 0.929, b: 0.941 }, // 2: lt2 (light gray)
98
- { r: 0.267, g: 0.278, b: 0.298 }, // 3: dk2 (dark gray)
99
- { r: 0.263, g: 0.522, b: 0.839 }, // 4: accent1 (blue)
100
- { r: 0.922, g: 0.494, b: 0.196 }, // 5: accent2 (orange)
101
- { r: 0.624, g: 0.624, b: 0.624 }, // 6: accent3 (gray)
102
- { r: 1, g: 0.753, b: 0 }, // 7: accent4 (gold)
103
- { r: 0.314, g: 0.686, b: 0.886 }, // 8: accent5 (light blue)
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.5;
196
+ return 0.25;
197
197
  case "medium":
198
- return 1;
198
+ return 0.5;
199
199
  case "thick":
200
- return 1.5;
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.5;
206
+ return 0.25;
207
207
  case "dashed":
208
- return 0.5;
208
+ return 0.25;
209
209
  case "dashDot":
210
- return 0.5;
210
+ return 0.25;
211
211
  case "dashDotDot":
212
- return 0.5;
212
+ return 0.25;
213
213
  case "slantDashDot":
214
- return 0.5;
214
+ return 0.25;
215
215
  case "mediumDashed":
216
- return 1;
216
+ return 0.5;
217
217
  case "mediumDashDot":
218
- return 1;
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
- this.style = master.style;
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.style[name] = value;
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) {