@cj-tech-master/excelts 9.5.0 → 9.5.1

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 (68) hide show
  1. package/dist/browser/modules/pdf/excel-bridge.js +27 -1
  2. package/dist/browser/modules/pdf/render/layout-engine.js +74 -9
  3. package/dist/browser/modules/pdf/render/style-converter.d.ts +1 -1
  4. package/dist/browser/modules/pdf/render/style-converter.js +98 -1
  5. package/dist/browser/modules/pdf/types.d.ts +1 -0
  6. package/dist/browser/modules/word/color-utils.d.ts +18 -0
  7. package/dist/browser/modules/word/color-utils.js +94 -0
  8. package/dist/browser/modules/word/content-types.d.ts +15 -15
  9. package/dist/browser/modules/word/content-types.js +39 -43
  10. package/dist/browser/modules/word/crypto.d.ts +17 -0
  11. package/dist/browser/modules/word/crypto.js +18 -0
  12. package/dist/browser/modules/word/document-io.d.ts +58 -0
  13. package/dist/browser/modules/word/document-io.js +239 -0
  14. package/dist/browser/modules/word/document.d.ts +64 -135
  15. package/dist/browser/modules/word/document.js +207 -469
  16. package/dist/browser/modules/word/docx-packager.js +90 -90
  17. package/dist/browser/modules/word/html-renderer.js +1 -1
  18. package/dist/browser/modules/word/html.d.ts +13 -0
  19. package/dist/browser/modules/word/html.js +12 -0
  20. package/dist/browser/modules/word/index.base.d.ts +6 -9
  21. package/dist/browser/modules/word/index.base.js +7 -10
  22. package/dist/browser/modules/word/namespaces.d.ts +159 -0
  23. package/dist/browser/modules/word/namespaces.js +189 -0
  24. package/dist/browser/modules/word/relationships.d.ts +15 -16
  25. package/dist/browser/modules/word/relationships.js +37 -45
  26. package/dist/cjs/modules/pdf/excel-bridge.js +27 -1
  27. package/dist/cjs/modules/pdf/render/layout-engine.js +74 -9
  28. package/dist/cjs/modules/pdf/render/style-converter.js +98 -1
  29. package/dist/cjs/modules/word/color-utils.js +97 -0
  30. package/dist/cjs/modules/word/content-types.js +44 -45
  31. package/dist/cjs/modules/word/crypto.js +34 -0
  32. package/dist/cjs/modules/word/document-io.js +244 -0
  33. package/dist/cjs/modules/word/document.js +209 -473
  34. package/dist/cjs/modules/word/docx-packager.js +88 -88
  35. package/dist/cjs/modules/word/html-renderer.js +2 -2
  36. package/dist/cjs/modules/word/html.js +16 -0
  37. package/dist/cjs/modules/word/index.base.js +17 -27
  38. package/dist/cjs/modules/word/namespaces.js +192 -0
  39. package/dist/cjs/modules/word/relationships.js +42 -47
  40. package/dist/esm/modules/pdf/excel-bridge.js +27 -1
  41. package/dist/esm/modules/pdf/render/layout-engine.js +74 -9
  42. package/dist/esm/modules/pdf/render/style-converter.js +98 -1
  43. package/dist/esm/modules/word/color-utils.js +94 -0
  44. package/dist/esm/modules/word/content-types.js +39 -43
  45. package/dist/esm/modules/word/crypto.js +18 -0
  46. package/dist/esm/modules/word/document-io.js +239 -0
  47. package/dist/esm/modules/word/document.js +207 -469
  48. package/dist/esm/modules/word/docx-packager.js +90 -90
  49. package/dist/esm/modules/word/html-renderer.js +1 -1
  50. package/dist/esm/modules/word/html.js +12 -0
  51. package/dist/esm/modules/word/index.base.js +7 -10
  52. package/dist/esm/modules/word/namespaces.js +189 -0
  53. package/dist/esm/modules/word/relationships.js +37 -45
  54. package/dist/iife/excelts.iife.js +153 -11
  55. package/dist/iife/excelts.iife.js.map +1 -1
  56. package/dist/iife/excelts.iife.min.js +4 -4
  57. package/dist/types/modules/pdf/render/style-converter.d.ts +1 -1
  58. package/dist/types/modules/pdf/types.d.ts +1 -0
  59. package/dist/types/modules/word/color-utils.d.ts +18 -0
  60. package/dist/types/modules/word/content-types.d.ts +15 -15
  61. package/dist/types/modules/word/crypto.d.ts +17 -0
  62. package/dist/types/modules/word/document-io.d.ts +58 -0
  63. package/dist/types/modules/word/document.d.ts +64 -135
  64. package/dist/types/modules/word/html.d.ts +13 -0
  65. package/dist/types/modules/word/index.base.d.ts +6 -9
  66. package/dist/types/modules/word/namespaces.d.ts +159 -0
  67. package/dist/types/modules/word/relationships.d.ts +15 -16
  68. package/package.json +1 -1
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ /**
3
+ * DOCX Module — Sub-namespace objects
4
+ *
5
+ * Groups flat builder helpers into logical namespaces for better
6
+ * IDE discoverability. These are re-exports aggregated into objects;
7
+ * since they reference the same underlying functions, tree-shaking
8
+ * still applies at the individual function level for consumers who
9
+ * import the flat named exports instead.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { Run, Paragraph, Table, Math, Field } from "excelts/word";
14
+ *
15
+ * const doc = Document.create();
16
+ * Document.addParagraphElement(doc, Paragraph.create([
17
+ * Run.bold("Hello"),
18
+ * Run.text(" world")
19
+ * ]));
20
+ * ```
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.Query = exports.Sdt = exports.Drawing = exports.Table = exports.Math = exports.TrackChanges = exports.Comment = exports.Paragraph = exports.Field = exports.Run = void 0;
24
+ const document_1 = require("./document");
25
+ // =============================================================================
26
+ // Run namespace — text run constructors
27
+ // =============================================================================
28
+ /** Namespace for creating text runs (inline content). */
29
+ exports.Run = {
30
+ text: document_1.text,
31
+ bold: document_1.bold,
32
+ italic: document_1.italic,
33
+ pageBreak: document_1.pageBreak,
34
+ lineBreak: document_1.lineBreak,
35
+ columnBreak: document_1.columnBreak,
36
+ tab: document_1.tab,
37
+ positionalTab: document_1.positionalTab,
38
+ ruby: document_1.ruby,
39
+ carriageReturn: document_1.carriageReturn,
40
+ noBreakHyphen: document_1.noBreakHyphen,
41
+ softHyphen: document_1.softHyphen,
42
+ symbol: document_1.symbol
43
+ };
44
+ // =============================================================================
45
+ // Field namespace — field code constructors
46
+ // =============================================================================
47
+ /** Namespace for creating field codes. */
48
+ exports.Field = {
49
+ create: document_1.field,
50
+ pageNumber: document_1.pageNumberField,
51
+ totalPages: document_1.totalPagesField,
52
+ sectionPages: document_1.sectionPagesField,
53
+ section: document_1.sectionField,
54
+ date: document_1.dateField,
55
+ sequence: document_1.sequenceField,
56
+ time: document_1.timeField,
57
+ author: document_1.authorField,
58
+ title: document_1.titleField,
59
+ subject: document_1.subjectField,
60
+ keywords: document_1.keywordsField,
61
+ fileName: document_1.fileNameField,
62
+ fileSize: document_1.fileSizeField,
63
+ styleRef: document_1.styleRefField,
64
+ ref: document_1.refField,
65
+ pageRef: document_1.pageRefField,
66
+ noteRef: document_1.noteRefField,
67
+ hyperlink: document_1.hyperlinkField,
68
+ quote: document_1.quoteField,
69
+ toc: document_1.tocField,
70
+ tc: document_1.tcField,
71
+ indexEntry: document_1.indexEntryField,
72
+ index: document_1.indexField,
73
+ condition: document_1.ifField,
74
+ includeText: document_1.includeTextField,
75
+ includePicture: document_1.includePictureField,
76
+ formText: document_1.formTextField,
77
+ formCheckbox: document_1.formCheckboxField,
78
+ formDropdown: document_1.formDropdownField
79
+ };
80
+ // =============================================================================
81
+ // Paragraph namespace — paragraph constructors
82
+ // =============================================================================
83
+ /** Namespace for creating paragraphs. */
84
+ exports.Paragraph = {
85
+ create: document_1.paragraph,
86
+ text: document_1.textParagraph,
87
+ heading: document_1.heading,
88
+ hyperlink: document_1.hyperlink,
89
+ bookmarkStart: document_1.bookmarkStart,
90
+ bookmarkEnd: document_1.bookmarkEnd
91
+ };
92
+ // =============================================================================
93
+ // Comment namespace — comment markers
94
+ // =============================================================================
95
+ /** Namespace for comment-related markers. */
96
+ exports.Comment = {
97
+ rangeStart: document_1.commentRangeStart,
98
+ rangeEnd: document_1.commentRangeEnd,
99
+ reference: document_1.commentReference
100
+ };
101
+ // =============================================================================
102
+ // TrackChanges namespace — revision markers
103
+ // =============================================================================
104
+ /** Namespace for track-changes (revision) markers. */
105
+ exports.TrackChanges = {
106
+ insertedRun: document_1.insertedRun,
107
+ deletedRun: document_1.deletedRun,
108
+ movedFromRun: document_1.movedFromRun,
109
+ movedToRun: document_1.movedToRun,
110
+ moveFromRangeStart: document_1.moveFromRangeStart,
111
+ moveFromRangeEnd: document_1.moveFromRangeEnd,
112
+ moveToRangeStart: document_1.moveToRangeStart,
113
+ moveToRangeEnd: document_1.moveToRangeEnd
114
+ };
115
+ // =============================================================================
116
+ // Math namespace — Office Math constructors
117
+ // =============================================================================
118
+ /** Namespace for OMML (Office Math) content. */
119
+ exports.Math = {
120
+ block: document_1.mathBlock,
121
+ run: document_1.mathRun,
122
+ fraction: document_1.mathFraction,
123
+ sqrt: document_1.mathSqrt,
124
+ root: document_1.mathRoot,
125
+ sum: document_1.mathSum,
126
+ integral: document_1.mathIntegral,
127
+ product: document_1.mathProduct,
128
+ superScript: document_1.mathSuperScript,
129
+ subScript: document_1.mathSubScript,
130
+ subSuperScript: document_1.mathSubSuperScript,
131
+ preSubSuperScript: document_1.mathPreSubSuperScript,
132
+ phantom: document_1.mathPhantom,
133
+ groupChar: document_1.mathGroupChar,
134
+ borderBox: document_1.mathBorderBox,
135
+ delimiter: document_1.mathDelimiter,
136
+ nary: document_1.mathNary,
137
+ func: document_1.mathFunction,
138
+ limit: document_1.mathLimit,
139
+ matrix: document_1.mathMatrix,
140
+ accent: document_1.mathAccent,
141
+ bar: document_1.mathBar,
142
+ box: document_1.mathBox,
143
+ equationArray: document_1.mathEquationArray
144
+ };
145
+ // =============================================================================
146
+ // Table namespace — table constructors
147
+ // =============================================================================
148
+ /** Namespace for creating tables. */
149
+ exports.Table = {
150
+ create: document_1.table,
151
+ simple: document_1.simpleTable,
152
+ row: document_1.row,
153
+ cell: document_1.cell,
154
+ border: document_1.border,
155
+ gridBorders: document_1.gridBorders
156
+ };
157
+ // =============================================================================
158
+ // Drawing namespace — images, shapes, charts
159
+ // =============================================================================
160
+ /** Namespace for drawings (images, shapes, charts). */
161
+ exports.Drawing = {
162
+ floatingImage: document_1.floatingImage,
163
+ shape: document_1.drawingShape,
164
+ chart: document_1.chart
165
+ };
166
+ // =============================================================================
167
+ // Sdt namespace — structured document tags
168
+ // =============================================================================
169
+ /** Namespace for structured document tags (content controls). */
170
+ exports.Sdt = {
171
+ create: document_1.structuredDocumentTag,
172
+ checkBox: document_1.checkBox
173
+ };
174
+ // =============================================================================
175
+ // Query namespace — document query/search functions
176
+ // =============================================================================
177
+ /** Namespace for querying/searching document content. */
178
+ exports.Query = {
179
+ search: document_1.searchText,
180
+ replace: document_1.replaceText,
181
+ mailMerge: document_1.mailMerge,
182
+ paragraphCount: document_1.paragraphCount,
183
+ countWords: document_1.countWords,
184
+ getHeadings: document_1.getHeadings,
185
+ findBookmark: document_1.findBookmark,
186
+ findComment: document_1.findComment,
187
+ listImages: document_1.listImages,
188
+ listTables: document_1.listTables,
189
+ listHyperlinks: document_1.listHyperlinks,
190
+ tableCount: document_1.tableCount,
191
+ extractText: document_1.extractText
192
+ };
@@ -4,57 +4,52 @@
4
4
  *
5
5
  * Manages OPC relationships for the DOCX package.
6
6
  * Generates .rels files for package-level and part-level relationships.
7
+ * Uses a plain data record + free functions for tree-shakeability.
7
8
  */
8
9
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.RelationshipManager = void 0;
10
+ exports.createRelationships = createRelationships;
11
+ exports.addRelationship = addRelationship;
12
+ exports.addRelationshipWithId = addRelationshipWithId;
13
+ exports.getRelationshipCount = getRelationshipCount;
14
+ exports.renderRelationships = renderRelationships;
10
15
  const constants_1 = require("./constants");
11
- /**
12
- * Manages relationships and generates .rels XML.
13
- */
14
- class RelationshipManager {
15
- constructor() {
16
- this._rels = [];
17
- this._nextId = 1;
18
- }
19
- /** Add a relationship and return its assigned rId. */
20
- add(type, target, targetMode) {
21
- const id = `rId${this._nextId++}`;
22
- this._rels.push({ id, type, target, targetMode });
23
- return id;
24
- }
25
- /** Add a relationship with a specific ID. */
26
- addWithId(id, type, target, targetMode) {
27
- this._rels.push({ id, type, target, targetMode });
28
- // Keep nextId above any manually-assigned IDs
29
- const num = parseInt(id.replace("rId", ""), 10);
30
- if (!isNaN(num) && num >= this._nextId) {
31
- this._nextId = num + 1;
32
- }
33
- }
34
- /** Get all relationships. */
35
- get relationships() {
36
- return this._rels;
37
- }
38
- /** Get the number of relationships. */
39
- get count() {
40
- return this._rels.length;
16
+ /** Create a new empty RelationshipsState. */
17
+ function createRelationships() {
18
+ return { rels: [], nextId: 1 };
19
+ }
20
+ /** Add a relationship and return its assigned rId. */
21
+ function addRelationship(state, type, target, targetMode) {
22
+ const id = `rId${state.nextId++}`;
23
+ state.rels.push({ id, type, target, targetMode });
24
+ return id;
25
+ }
26
+ /** Add a relationship with a specific ID. */
27
+ function addRelationshipWithId(state, id, type, target, targetMode) {
28
+ state.rels.push({ id, type, target, targetMode });
29
+ // Keep nextId above any manually-assigned IDs
30
+ const num = parseInt(id.replace("rId", ""), 10);
31
+ if (!isNaN(num) && num >= state.nextId) {
32
+ state.nextId = num + 1;
41
33
  }
42
- /** Render the relationships XML to a sink. */
43
- render(xml) {
44
- xml.openXml(constants_1.STD_DOC_ATTRIBUTES);
45
- xml.openNode("Relationships", { xmlns: constants_1.NS_PKG_RELS });
46
- for (const rel of this._rels) {
47
- const attrs = {
48
- Id: rel.id,
49
- Type: rel.type,
50
- Target: rel.target
51
- };
52
- if (rel.targetMode) {
53
- attrs.TargetMode = rel.targetMode;
54
- }
55
- xml.leafNode("Relationship", attrs);
34
+ }
35
+ /** Get the number of relationships. */
36
+ function getRelationshipCount(state) {
37
+ return state.rels.length;
38
+ }
39
+ /** Render the relationships XML to a sink. */
40
+ function renderRelationships(state, xml) {
41
+ xml.openXml(constants_1.STD_DOC_ATTRIBUTES);
42
+ xml.openNode("Relationships", { xmlns: constants_1.NS_PKG_RELS });
43
+ for (const rel of state.rels) {
44
+ const attrs = {
45
+ Id: rel.id,
46
+ Type: rel.type,
47
+ Target: rel.target
48
+ };
49
+ if (rel.targetMode) {
50
+ attrs.TargetMode = rel.targetMode;
56
51
  }
57
- xml.closeNode();
52
+ xml.leafNode("Relationship", attrs);
58
53
  }
54
+ xml.closeNode();
59
55
  }
60
- exports.RelationshipManager = RelationshipManager;
@@ -187,6 +187,31 @@ async function convertSheet(ws, workbook) {
187
187
  right: dimensions.model.right
188
188
  }
189
189
  : { top: 0, left: 0, bottom: 0, right: 0 };
190
+ // Expand bounds to include cells that only have styles (borders, fills, fonts)
191
+ // but no values — these are not tracked by dimensions.
192
+ if (hasData) {
193
+ for (let r = bounds.top; r <= bounds.bottom; r++) {
194
+ const row = ws.findRow(r);
195
+ if (!row) {
196
+ continue;
197
+ }
198
+ row.eachCell({ includeEmpty: true }, cell => {
199
+ if (cell.col > bounds.right) {
200
+ const hasStyle = cell.style &&
201
+ ((cell.style.border &&
202
+ (cell.style.border.top ||
203
+ cell.style.border.right ||
204
+ cell.style.border.bottom ||
205
+ cell.style.border.left)) ||
206
+ cell.style.fill ||
207
+ cell.style.font);
208
+ if (hasStyle || (cell.type !== ValueType.Null && cell.type !== ValueType.Merge)) {
209
+ bounds.right = cell.col;
210
+ }
211
+ }
212
+ });
213
+ }
214
+ }
190
215
  // Convert columns
191
216
  const columns = new Map();
192
217
  if (hasData) {
@@ -480,7 +505,8 @@ function convertColor(color) {
480
505
  return {
481
506
  argb: color.argb,
482
507
  theme: color.theme,
483
- tint: color.tint
508
+ tint: color.tint,
509
+ indexed: color.indexed
484
510
  };
485
511
  }
486
512
  function convertFill(fill) {
@@ -551,7 +551,7 @@ function countWrapLines(cell, fontSize, scaleFactor, sheet, fontManager, options
551
551
  if (value && typeof value === "object" && "richText" in value) {
552
552
  const runs = value.richText;
553
553
  if (runs.length > 0) {
554
- const wrappedCount = countRichTextWrapLines(text, runs, scaleFactor, effectiveWidth, fontManager, options);
554
+ const wrappedCount = countRichTextWrapLines(text, runs, scaleFactor, effectiveWidth, fontManager, options, cell.style?.font);
555
555
  return Math.max(lineCount, wrappedCount);
556
556
  }
557
557
  }
@@ -571,7 +571,10 @@ function countWrapLines(cell, fontSize, scaleFactor, sheet, fontManager, options
571
571
  * This mirrors the logic in wrapRichTextLines (page-renderer) so that
572
572
  * the row height calculation matches the actual rendering.
573
573
  */
574
- function countRichTextWrapLines(text, runs, scaleFactor, effectiveWidth, fontManager, options) {
574
+ function countRichTextWrapLines(text, runs, scaleFactor, effectiveWidth, fontManager, options, cellFont) {
575
+ // Use cell-level font as fallback for runs without their own font
576
+ const defaultFamily = cellFont?.name ?? options.defaultFontFamily;
577
+ const defaultSize = cellFont?.size ?? options.defaultFontSize;
575
578
  // Build character-to-run mapping
576
579
  const runForChar = [];
577
580
  for (let ri = 0; ri < runs.length; ri++) {
@@ -579,9 +582,20 @@ function countRichTextWrapLines(text, runs, scaleFactor, effectiveWidth, fontMan
579
582
  runForChar.push(ri);
580
583
  }
581
584
  }
582
- // Resolve font resources for each run
585
+ // Resolve font resources for each run (with cell font inheritance)
583
586
  const runResources = runs.map(run => {
584
- const fontProps = extractFontProperties(run.font, options.defaultFontFamily, options.defaultFontSize);
587
+ const effectiveRunFont = run.font
588
+ ? {
589
+ name: run.font.name ?? cellFont?.name,
590
+ size: run.font.size ?? cellFont?.size,
591
+ bold: run.font.bold ?? cellFont?.bold,
592
+ italic: run.font.italic ?? cellFont?.italic,
593
+ strike: run.font.strike ?? cellFont?.strike,
594
+ underline: run.font.underline ?? cellFont?.underline,
595
+ color: run.font.color ?? cellFont?.color
596
+ }
597
+ : cellFont;
598
+ const fontProps = extractFontProperties(effectiveRunFont, defaultFamily, defaultSize);
585
599
  const pdfFontName = resolvePdfFontName(fontProps.fontFamily, fontProps.bold, fontProps.italic);
586
600
  return fontManager.hasEmbeddedFont()
587
601
  ? fontManager.getEmbeddedResourceName()
@@ -589,7 +603,15 @@ function countRichTextWrapLines(text, runs, scaleFactor, effectiveWidth, fontMan
589
603
  });
590
604
  // Resolve scaled font sizes for each run
591
605
  const runFontSizes = runs.map(run => {
592
- const fontProps = extractFontProperties(run.font, options.defaultFontFamily, options.defaultFontSize);
606
+ const effectiveRunFont = run.font
607
+ ? {
608
+ name: run.font.name ?? cellFont?.name,
609
+ size: run.font.size ?? cellFont?.size,
610
+ bold: run.font.bold ?? cellFont?.bold,
611
+ italic: run.font.italic ?? cellFont?.italic
612
+ }
613
+ : cellFont;
614
+ const fontProps = extractFontProperties(effectiveRunFont, defaultFamily, defaultSize);
593
615
  return fontProps.fontSize * scaleFactor;
594
616
  });
595
617
  // Measure a range of fullText using per-character run font sizes
@@ -861,8 +883,10 @@ function buildLayoutCell(cell, x, y, width, height, colSpan, rowSpan, options, f
861
883
  // Track non-WinAnsi code points for Type3 fallback font generation
862
884
  fontManager.trackText(text);
863
885
  }
864
- // Rich text runs
865
- const richText = buildRichTextRuns(cell, options, fontManager, scaleFactor);
886
+ // Rich text runs — pass cell-level font as the fallback for runs without
887
+ // their own font definition (e.g. the first run often has no font object
888
+ // and should inherit the cell's style font including bold/italic).
889
+ const richText = buildRichTextRuns(cell, options, fontManager, scaleFactor, style.font);
866
890
  const borders = excelBordersToPdf(style.border);
867
891
  return {
868
892
  text,
@@ -1305,6 +1329,30 @@ function computeTextOverflows(cellGrid, rowPage, colGroup, visibleRows, visibleC
1305
1329
  }
1306
1330
  if (overflowAvailable > 0) {
1307
1331
  cell.textOverflowWidth = Math.min(overflowNeeded, overflowAvailable);
1332
+ // Hide internal vertical borders in the overflow region.
1333
+ // In Excel, when text overflows into adjacent empty cells, the shared
1334
+ // vertical borders between them are not drawn (the text appears to
1335
+ // span across seamlessly). We suppress:
1336
+ // - The overflowing cell's right border
1337
+ // - Each covered neighbor's left border (and right border if fully covered)
1338
+ let accumulated = 0;
1339
+ const actualOverflow = cell.textOverflowWidth;
1340
+ // Remove the source cell's right border if text overflows
1341
+ cell.borders.right = null;
1342
+ for (let j = gci + 1; j < colGroup.length; j++) {
1343
+ const neighborCell = cellGrid.get(`${ri}:${j}`);
1344
+ if (!neighborCell) {
1345
+ break;
1346
+ }
1347
+ // Remove the neighbor's left border (shared edge with previous cell)
1348
+ neighborCell.borders.left = null;
1349
+ accumulated += groupColWidths[j];
1350
+ if (accumulated >= actualOverflow) {
1351
+ break;
1352
+ }
1353
+ // If fully covered, also remove the neighbor's right border
1354
+ neighborCell.borders.right = null;
1355
+ }
1308
1356
  }
1309
1357
  }
1310
1358
  }
@@ -1316,7 +1364,7 @@ function computeTextOverflows(cellGrid, rowPage, colGroup, visibleRows, visibleC
1316
1364
  * Build rich text runs from a RichText cell.
1317
1365
  * Returns null for non-RichText cells.
1318
1366
  */
1319
- function buildRichTextRuns(cell, options, fontManager, scaleFactor) {
1367
+ function buildRichTextRuns(cell, options, fontManager, scaleFactor, cellFont) {
1320
1368
  if (!cell || cell.type !== PdfCellType.RichText) {
1321
1369
  return null;
1322
1370
  }
@@ -1328,8 +1376,25 @@ function buildRichTextRuns(cell, options, fontManager, scaleFactor) {
1328
1376
  if (runs.length === 0) {
1329
1377
  return null;
1330
1378
  }
1379
+ // Use cell-level font as fallback for runs without their own font,
1380
+ // falling back to global defaults only if cell font is not available.
1381
+ const defaultFamily = cellFont?.name ?? options.defaultFontFamily;
1382
+ const defaultSize = cellFont?.size ?? options.defaultFontSize;
1331
1383
  return runs.map(run => {
1332
- const fontProps = extractFontProperties(run.font, options.defaultFontFamily, options.defaultFontSize);
1384
+ // When a run has no font at all, use cell font entirely.
1385
+ // When a run has a partial font, merge with cell font for missing properties.
1386
+ const effectiveFont = run.font
1387
+ ? {
1388
+ name: run.font.name ?? cellFont?.name,
1389
+ size: run.font.size ?? cellFont?.size,
1390
+ bold: run.font.bold ?? cellFont?.bold,
1391
+ italic: run.font.italic ?? cellFont?.italic,
1392
+ strike: run.font.strike ?? cellFont?.strike,
1393
+ underline: run.font.underline ?? cellFont?.underline,
1394
+ color: run.font.color ?? cellFont?.color
1395
+ }
1396
+ : cellFont;
1397
+ const fontProps = extractFontProperties(effectiveFont, defaultFamily, defaultSize);
1333
1398
  // Register font for this run
1334
1399
  if (fontManager.hasEmbeddedFont()) {
1335
1400
  fontManager.trackText(run.text);
@@ -50,7 +50,7 @@ export function argbToPdfColor(argb) {
50
50
  }
51
51
  /**
52
52
  * Convert a color data object to PDF color.
53
- * Handles both ARGB and theme-based colors.
53
+ * Handles ARGB, theme-based, and indexed colors.
54
54
  */
55
55
  export function excelColorToPdf(color) {
56
56
  if (!color) {
@@ -72,6 +72,10 @@ export function excelColorToPdf(color) {
72
72
  }
73
73
  return base;
74
74
  }
75
+ // Indexed colors (legacy Excel color palette)
76
+ if (color.indexed !== undefined) {
77
+ return indexedColorToPdf(color.indexed);
78
+ }
75
79
  return null;
76
80
  }
77
81
  /**
@@ -97,6 +101,99 @@ function themeColorToPdf(themeIndex) {
97
101
  }
98
102
  return null;
99
103
  }
104
+ /**
105
+ * Standard Excel indexed color palette (56 colors + system colors).
106
+ * Index 0–7: legacy base colors
107
+ * Index 8–63: standard palette (indices 8–63)
108
+ * Index 64: system foreground (black)
109
+ * Index 65: system background (white)
110
+ *
111
+ * @see ECMA-376 §18.8.27 — indexedColors
112
+ */
113
+ const INDEXED_COLORS = [
114
+ // 0–7: legacy base colors (same as 8–15 but less commonly used directly)
115
+ "000000", // 0: Black
116
+ "FFFFFF", // 1: White
117
+ "FF0000", // 2: Red
118
+ "00FF00", // 3: Green
119
+ "0000FF", // 4: Blue
120
+ "FFFF00", // 5: Yellow
121
+ "FF00FF", // 6: Magenta
122
+ "00FFFF", // 7: Cyan
123
+ // 8–63: standard palette
124
+ "000000", // 8: Black
125
+ "FFFFFF", // 9: White
126
+ "FF0000", // 10: Red
127
+ "00FF00", // 11: Green
128
+ "0000FF", // 12: Blue
129
+ "FFFF00", // 13: Yellow
130
+ "FF00FF", // 14: Magenta
131
+ "00FFFF", // 15: Cyan
132
+ "800000", // 16: Dark Red
133
+ "008000", // 17: Dark Green
134
+ "000080", // 18: Dark Blue (Navy)
135
+ "808000", // 19: Dark Yellow (Olive)
136
+ "800080", // 20: Purple
137
+ "008080", // 21: Teal
138
+ "C0C0C0", // 22: Silver
139
+ "808080", // 23: Gray
140
+ "9999FF", // 24: Periwinkle
141
+ "993366", // 25: Plum
142
+ "FFFFCC", // 26: Ivory
143
+ "CCFFFF", // 27: Light Cyan
144
+ "660066", // 28: Dark Purple
145
+ "FF8080", // 29: Coral
146
+ "0066CC", // 30: Ocean Blue
147
+ "CCCCFF", // 31: Ice Blue
148
+ "000080", // 32: Dark Blue
149
+ "FF00FF", // 33: Pink
150
+ "FFFF00", // 34: Yellow
151
+ "00FFFF", // 35: Cyan
152
+ "800080", // 36: Purple
153
+ "800000", // 37: Dark Red
154
+ "008080", // 38: Teal
155
+ "0000FF", // 39: Blue
156
+ "00CCFF", // 40: Sky Blue
157
+ "CCFFFF", // 41: Light Turquoise
158
+ "CCFFCC", // 42: Light Green
159
+ "FFFF99", // 43: Light Yellow
160
+ "99CCFF", // 44: Pale Blue
161
+ "FF99CC", // 45: Rose
162
+ "CC99FF", // 46: Lavender
163
+ "FFCC99", // 47: Tan
164
+ "3366FF", // 48: Light Blue
165
+ "33CCCC", // 49: Aqua
166
+ "99CC00", // 50: Lime
167
+ "FFCC00", // 51: Gold
168
+ "FF9900", // 52: Light Orange
169
+ "FF6600", // 53: Orange
170
+ "666699", // 54: Blue Gray
171
+ "969696", // 55: Gray 40%
172
+ "003366", // 56: Dark Teal
173
+ "339966", // 57: Sea Green
174
+ "003300", // 58: Very Dark Green
175
+ "333300", // 59: Dark Olive
176
+ "993300", // 60: Brown
177
+ "993366", // 61: Plum
178
+ "333399", // 62: Indigo
179
+ "333333" // 63: Gray 80%
180
+ ];
181
+ /**
182
+ * Convert an indexed color to PDF color.
183
+ * Index 64 = system foreground (black), 65 = system background (white).
184
+ */
185
+ function indexedColorToPdf(index) {
186
+ if (index === 64) {
187
+ return { r: 0, g: 0, b: 0 }; // System foreground (black)
188
+ }
189
+ if (index === 65) {
190
+ return { r: 1, g: 1, b: 1 }; // System background (white)
191
+ }
192
+ if (index >= 0 && index < INDEXED_COLORS.length) {
193
+ return argbToPdfColor(INDEXED_COLORS[index]) ?? null;
194
+ }
195
+ return null;
196
+ }
100
197
  /**
101
198
  * Apply a tint value to a color.
102
199
  * Tint range: -1.0 (fully dark) to +1.0 (fully light).