@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
@@ -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
- // --- Step 1: Layout all sheets ---
50
- const allPages = [];
51
- for (const sheet of sheets) {
52
- try {
53
- const resolved = resolveOptions(options, sheet);
54
- const pages = layoutSheet(sheet, resolved, fontManager);
55
- allPages.push(...pages);
56
- }
57
- catch (err) {
58
- throw new PdfRenderError(`Failed to layout sheet "${sheet.name}"`, { cause: err });
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
- // Fix page numbers (they may be off after combining multiple sheets)
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
- const totalPages = allPages.length;
85
- // --- Step 1.5: Track page header/footer text for font subsetting ---
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
- // --- Step 2: Write font resources (builds subset from tracked code points) ---
106
- const fontObjectMap = fontManager.writeFontResources(writer);
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(); // Allocate early for forward ref
110
- // Track first page per sheet for outline/bookmarks
111
- const sheetFirstPage = new Map(); // sheetName → pageObjNum index
112
- for (const page of allPages) {
113
- try {
114
- // Render the page content (text, fills, borders)
115
- const { stream: contentStream, alphaValues } = renderPage(page, page.options, fontManager, totalPages);
116
- // Handle images: create XObject Image entries and draw them
117
- const imageXObjects = new Map(); // name → objNum
118
- if (page.images.length > 0) {
119
- for (let imgIdx = 0; imgIdx < page.images.length; imgIdx++) {
120
- const img = page.images[imgIdx];
121
- const imgName = `Im${imgIdx + 1}`;
122
- const imgObjNum = writeImageXObject(writer, img.data, img.format);
123
- imageXObjects.set(imgName, imgObjNum);
124
- // Draw the image in the content stream
125
- contentStream.drawImage(imgName, img.rect.x, img.rect.y, img.rect.width, img.rect.height);
126
- }
127
- }
128
- // Add content stream object
129
- const contentObjNum = writer.allocObject();
130
- const contentDict = new PdfDict();
131
- writer.addStreamObject(contentObjNum, contentDict, contentStream);
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
- // Add ExtGState resources for transparency
146
- if (alphaValues.size > 0) {
147
- const gsParts = ["<<"];
148
- for (const alpha of alphaValues) {
149
- const gsObjNum = writer.allocObject();
150
- const gsDict = new PdfDict()
151
- .set("Type", "/ExtGState")
152
- .set("ca", pdfNumber(alpha))
153
- .set("CA", pdfNumber(alpha));
154
- writer.addObject(gsObjNum, gsDict);
155
- gsParts.push(`/${alphaGsName(alpha)} ${pdfRef(gsObjNum)}`);
156
- }
157
- gsParts.push(">>");
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
- writer.addObject(resourcesObjNum, resourcesDict);
161
- // Create link annotations for hyperlinks
162
- const annotRefs = [];
163
- for (const cell of page.cells) {
164
- if (cell.hyperlink) {
165
- const annotObjNum = writer.allocObject();
166
- const rect = `[${pdfNumber(cell.rect.x)} ${pdfNumber(cell.rect.y)} ${pdfNumber(cell.rect.x + cell.rect.width)} ${pdfNumber(cell.rect.y + cell.rect.height)}]`;
167
- const annotDict = new PdfDict()
168
- .set("Type", "/Annot")
169
- .set("Subtype", "/Link")
170
- .set("Rect", rect)
171
- .set("Border", "[0 0 0]")
172
- .set("A", `<< /Type /Action /S /URI /URI (${cell.hyperlink.replace(/[()\\]/g, "\\$&")}) >>`);
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
- // Add page object
178
- const pageObjNum = writer.addPage({
179
- parentRef: pagesTreeObjNum,
180
- width: page.width,
181
- height: page.height,
182
- contentsRef: contentObjNum,
183
- resourcesRef: resourcesObjNum,
184
- annotRefs: annotRefs.length > 0 ? annotRefs : undefined
185
- });
186
- pageObjNums.push(pageObjNum);
187
- // Track first page of each sheet
188
- if (!sheetFirstPage.has(page.sheetName)) {
189
- sheetFirstPage.set(page.sheetName, pageObjNums.length - 1);
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
- catch (err) {
193
- throw new PdfRenderError(`Failed to render page ${page.pageNumber} of "${page.sheetName}"`, {
194
- cause: err
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 ?? (ps?.scale ? ps.scale / 100 : 1.0))),
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.918, g: 0.929, b: 0.941 }, // 2: lt2 (light gray)
87
- { r: 0.267, g: 0.278, b: 0.298 }, // 3: dk2 (dark gray)
88
- { r: 0.263, g: 0.522, b: 0.839 }, // 4: accent1 (blue)
89
- { r: 0.922, g: 0.494, b: 0.196 }, // 5: accent2 (orange)
90
- { r: 0.624, g: 0.624, b: 0.624 }, // 6: accent3 (gray)
91
- { r: 1, g: 0.753, b: 0 }, // 7: accent4 (gold)
92
- { r: 0.314, g: 0.686, b: 0.886 }, // 8: accent5 (light blue)
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.5;
185
+ return 0.25;
186
186
  case "medium":
187
- return 1;
187
+ return 0.5;
188
188
  case "thick":
189
- return 1.5;
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.5;
195
+ return 0.25;
196
196
  case "dashed":
197
- return 0.5;
197
+ return 0.25;
198
198
  case "dashDot":
199
- return 0.5;
199
+ return 0.25;
200
200
  case "dashDotDot":
201
- return 0.5;
201
+ return 0.25;
202
202
  case "slantDashDot":
203
- return 0.5;
203
+ return 0.25;
204
204
  case "mediumDashed":
205
- return 1;
205
+ return 0.5;
206
206
  case "mediumDashDot":
207
- return 1;
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
- this.style = master.style;
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.style[name] = value;
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) {