@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
@@ -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);
@@ -16,7 +16,7 @@ import type { PdfColor, LayoutBorders, PdfColorData, PdfFontStyle, PdfFillData,
16
16
  export declare function argbToPdfColor(argb: string | undefined): PdfColor | null;
17
17
  /**
18
18
  * Convert a color data object to PDF color.
19
- * Handles both ARGB and theme-based colors.
19
+ * Handles ARGB, theme-based, and indexed colors.
20
20
  */
21
21
  export declare function excelColorToPdf(color: Partial<PdfColorData> | undefined): PdfColor | null;
22
22
  /**
@@ -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).
@@ -26,6 +26,7 @@ export interface PdfColorData {
26
26
  argb?: string;
27
27
  theme?: number;
28
28
  tint?: number;
29
+ indexed?: number;
29
30
  }
30
31
  /** Font style in the PDF input model. */
31
32
  export interface PdfFontStyle {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * DOCX Module - Theme Color Utilities
3
+ *
4
+ * Resolves OOXML theme colors with tint/shade transformations.
5
+ * Extracted to a standalone file so that html-renderer and document.ts
6
+ * can both import it without creating circular heavy dependencies.
7
+ */
8
+ import type { DocumentTheme, ColorSpec, HexColor } from "./types.js";
9
+ /**
10
+ * Resolve a ColorSpec to an actual hex RGB color using the document theme.
11
+ *
12
+ * Applies theme color lookup + tint/shade transformations per OOXML spec.
13
+ *
14
+ * @param color - The color value (HexColor string or ColorSpec).
15
+ * @param theme - The document theme (from `doc.theme`).
16
+ * @returns Resolved hex color string (6 chars, no #), or undefined if unresolvable.
17
+ */
18
+ export declare function resolveThemeColor(color: HexColor | ColorSpec | undefined, theme?: DocumentTheme): HexColor | undefined;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * DOCX Module - Theme Color Utilities
3
+ *
4
+ * Resolves OOXML theme colors with tint/shade transformations.
5
+ * Extracted to a standalone file so that html-renderer and document.ts
6
+ * can both import it without creating circular heavy dependencies.
7
+ */
8
+ /**
9
+ * Map OOXML theme color attribute names to theme color scheme keys.
10
+ * Word uses different names in run/paragraph properties vs the theme XML.
11
+ */
12
+ const THEME_COLOR_MAP = {
13
+ dark1: "dk1",
14
+ light1: "lt1",
15
+ dark2: "dk2",
16
+ light2: "lt2",
17
+ accent1: "accent1",
18
+ accent2: "accent2",
19
+ accent3: "accent3",
20
+ accent4: "accent4",
21
+ accent5: "accent5",
22
+ accent6: "accent6",
23
+ hyperlink: "hlink",
24
+ followedHyperlink: "folHlink",
25
+ // Direct names also work
26
+ dk1: "dk1",
27
+ lt1: "lt1",
28
+ dk2: "dk2",
29
+ lt2: "lt2",
30
+ hlink: "hlink",
31
+ folHlink: "folHlink"
32
+ };
33
+ /**
34
+ * Resolve a ColorSpec to an actual hex RGB color using the document theme.
35
+ *
36
+ * Applies theme color lookup + tint/shade transformations per OOXML spec.
37
+ *
38
+ * @param color - The color value (HexColor string or ColorSpec).
39
+ * @param theme - The document theme (from `doc.theme`).
40
+ * @returns Resolved hex color string (6 chars, no #), or undefined if unresolvable.
41
+ */
42
+ export function resolveThemeColor(color, theme) {
43
+ if (color === undefined) {
44
+ return undefined;
45
+ }
46
+ if (typeof color === "string") {
47
+ return color;
48
+ }
49
+ // ColorSpec with val — use directly
50
+ if (color.val && color.val !== "auto") {
51
+ return color.val;
52
+ }
53
+ // Resolve via theme
54
+ if (!color.themeColor || !theme) {
55
+ return color.val;
56
+ }
57
+ const key = THEME_COLOR_MAP[color.themeColor] ?? color.themeColor;
58
+ const base = theme.colorScheme.colors[key];
59
+ if (!base) {
60
+ return color.val;
61
+ }
62
+ // Apply tint or shade
63
+ if (color.themeTint) {
64
+ return applyTint(base, parseInt(color.themeTint, 16) / 255);
65
+ }
66
+ if (color.themeShade) {
67
+ return applyShade(base, parseInt(color.themeShade, 16) / 255);
68
+ }
69
+ return base;
70
+ }
71
+ /** Apply tint to a hex color. tint=1 → white, tint=0 → original. */
72
+ function applyTint(hex, tint) {
73
+ const r = parseInt(hex.slice(0, 2), 16);
74
+ const g = parseInt(hex.slice(2, 4), 16);
75
+ const b = parseInt(hex.slice(4, 6), 16);
76
+ const nr = Math.round(r + (255 - r) * tint);
77
+ const ng = Math.round(g + (255 - g) * tint);
78
+ const nb = Math.round(b + (255 - b) * tint);
79
+ return toHex2(nr) + toHex2(ng) + toHex2(nb);
80
+ }
81
+ /** Apply shade to a hex color. shade=1 → original, shade=0 → black. */
82
+ function applyShade(hex, shade) {
83
+ const r = parseInt(hex.slice(0, 2), 16);
84
+ const g = parseInt(hex.slice(2, 4), 16);
85
+ const b = parseInt(hex.slice(4, 6), 16);
86
+ const nr = Math.round(r * shade);
87
+ const ng = Math.round(g * shade);
88
+ const nb = Math.round(b * shade);
89
+ return toHex2(nr) + toHex2(ng) + toHex2(nb);
90
+ }
91
+ function toHex2(n) {
92
+ const h = Math.max(0, Math.min(255, n)).toString(16);
93
+ return h.length < 2 ? "0" + h : h;
94
+ }
@@ -2,6 +2,7 @@
2
2
  * DOCX Module - Content Types Generator
3
3
  *
4
4
  * Generates [Content_Types].xml for the DOCX package.
5
+ * Uses a plain data record + free functions for tree-shakeability.
5
6
  */
6
7
  import type { XmlSink } from "../xml/types.js";
7
8
  /** Content type override entry. */
@@ -9,19 +10,18 @@ export interface ContentTypeOverride {
9
10
  readonly partName: string;
10
11
  readonly contentType: string;
11
12
  }
12
- /**
13
- * Generates the [Content_Types].xml part.
14
- */
15
- export declare class ContentTypesManager {
16
- private readonly _defaults;
17
- private readonly _overrides;
18
- constructor();
19
- /** Add a default content type for a file extension. */
20
- addDefault(extension: string, contentType: string): void;
21
- /** Add an override content type for a specific part. */
22
- addOverride(partName: string, contentType: string): void;
23
- /** Add image extension defaults from a set of used extensions. */
24
- addImageDefaults(extensions: Iterable<string>): void;
25
- /** Render the [Content_Types].xml to a sink. */
26
- render(xml: XmlSink): void;
13
+ /** Internal state for content types (plain record, not a class). */
14
+ export interface ContentTypesState {
15
+ readonly defaults: Map<string, string>;
16
+ readonly overrides: ContentTypeOverride[];
27
17
  }
18
+ /** Create a new ContentTypesState with standard defaults (rels, xml). */
19
+ export declare function createContentTypes(): ContentTypesState;
20
+ /** Add a default content type for a file extension. */
21
+ export declare function addContentTypeDefault(state: ContentTypesState, extension: string, contentType: string): void;
22
+ /** Add an override content type for a specific part. */
23
+ export declare function addContentTypeOverride(state: ContentTypesState, partName: string, contentType: string): void;
24
+ /** Add image extension defaults from a set of used extensions. */
25
+ export declare function addImageContentTypeDefaults(state: ContentTypesState, extensions: Iterable<string>): void;
26
+ /** Render the [Content_Types].xml to a sink. */
27
+ export declare function renderContentTypes(state: ContentTypesState, xml: XmlSink): void;
@@ -2,52 +2,48 @@
2
2
  * DOCX Module - Content Types Generator
3
3
  *
4
4
  * Generates [Content_Types].xml for the DOCX package.
5
+ * Uses a plain data record + free functions for tree-shakeability.
5
6
  */
6
7
  import { NS_CONTENT_TYPES, STD_DOC_ATTRIBUTES, ContentType, IMAGE_CONTENT_TYPES } from "./constants.js";
7
- /**
8
- * Generates the [Content_Types].xml part.
9
- */
10
- export class ContentTypesManager {
11
- constructor() {
12
- this._defaults = new Map();
13
- this._overrides = [];
14
- // Always include rels and xml defaults
15
- this._defaults.set("rels", ContentType.Relationships);
16
- this._defaults.set("xml", ContentType.Xml);
17
- }
18
- /** Add a default content type for a file extension. */
19
- addDefault(extension, contentType) {
20
- this._defaults.set(extension, contentType);
21
- }
22
- /** Add an override content type for a specific part. */
23
- addOverride(partName, contentType) {
24
- this._overrides.push({
25
- partName: partName.startsWith("/") ? partName : `/${partName}`,
26
- contentType
27
- });
28
- }
29
- /** Add image extension defaults from a set of used extensions. */
30
- addImageDefaults(extensions) {
31
- for (const ext of extensions) {
32
- const ct = IMAGE_CONTENT_TYPES[ext.toLowerCase()];
33
- if (ct) {
34
- this._defaults.set(ext.toLowerCase(), ct);
35
- }
8
+ /** Create a new ContentTypesState with standard defaults (rels, xml). */
9
+ export function createContentTypes() {
10
+ const defaults = new Map();
11
+ defaults.set("rels", ContentType.Relationships);
12
+ defaults.set("xml", ContentType.Xml);
13
+ return { defaults, overrides: [] };
14
+ }
15
+ /** Add a default content type for a file extension. */
16
+ export function addContentTypeDefault(state, extension, contentType) {
17
+ state.defaults.set(extension, contentType);
18
+ }
19
+ /** Add an override content type for a specific part. */
20
+ export function addContentTypeOverride(state, partName, contentType) {
21
+ state.overrides.push({
22
+ partName: partName.startsWith("/") ? partName : `/${partName}`,
23
+ contentType
24
+ });
25
+ }
26
+ /** Add image extension defaults from a set of used extensions. */
27
+ export function addImageContentTypeDefaults(state, extensions) {
28
+ for (const ext of extensions) {
29
+ const ct = IMAGE_CONTENT_TYPES[ext.toLowerCase()];
30
+ if (ct) {
31
+ state.defaults.set(ext.toLowerCase(), ct);
36
32
  }
37
33
  }
38
- /** Render the [Content_Types].xml to a sink. */
39
- render(xml) {
40
- xml.openXml(STD_DOC_ATTRIBUTES);
41
- xml.openNode("Types", { xmlns: NS_CONTENT_TYPES });
42
- // Defaults sorted by extension
43
- const sortedDefaults = [...this._defaults.entries()].sort((a, b) => a[0].localeCompare(b[0]));
44
- for (const [ext, ct] of sortedDefaults) {
45
- xml.leafNode("Default", { Extension: ext, ContentType: ct });
46
- }
47
- // Overrides in order
48
- for (const override of this._overrides) {
49
- xml.leafNode("Override", { PartName: override.partName, ContentType: override.contentType });
50
- }
51
- xml.closeNode();
34
+ }
35
+ /** Render the [Content_Types].xml to a sink. */
36
+ export function renderContentTypes(state, xml) {
37
+ xml.openXml(STD_DOC_ATTRIBUTES);
38
+ xml.openNode("Types", { xmlns: NS_CONTENT_TYPES });
39
+ // Defaults sorted by extension
40
+ const sortedDefaults = [...state.defaults.entries()].sort((a, b) => a[0].localeCompare(b[0]));
41
+ for (const [ext, ct] of sortedDefaults) {
42
+ xml.leafNode("Default", { Extension: ext, ContentType: ct });
43
+ }
44
+ // Overrides in order
45
+ for (const override of state.overrides) {
46
+ xml.leafNode("Override", { PartName: override.partName, ContentType: override.contentType });
52
47
  }
48
+ xml.closeNode();
53
49
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * DOCX Module - Encryption & Digital Signatures (Subpath Export)
3
+ *
4
+ * Import separately to avoid pulling crypto code into the bundle
5
+ * when only core document building is needed.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { isEncryptedDocx, decryptPackage } from "excelts/word/crypto";
10
+ * import { extractSignatures } from "excelts/word/crypto";
11
+ * ```
12
+ */
13
+ export { isEncryptedDocx, verifyPassword, decryptPackage, parseEncryptionInfoXml, deriveEncryptionKey, AGILE_BLOCK_KEYS } from "./encryption.js";
14
+ export type { AgileEncryptionInfo } from "./encryption.js";
15
+ export { hasDigitalSignatures, parseSignatureXml, extractSignatures, isWellFormedSignature } from "./digital-signatures.js";
16
+ export type { DigitalSignatureInfo } from "./digital-signatures.js";
17
+ export { deobfuscateFont, obfuscateFont, generateFontKey } from "./font-obfuscation.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * DOCX Module - Encryption & Digital Signatures (Subpath Export)
3
+ *
4
+ * Import separately to avoid pulling crypto code into the bundle
5
+ * when only core document building is needed.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { isEncryptedDocx, decryptPackage } from "excelts/word/crypto";
10
+ * import { extractSignatures } from "excelts/word/crypto";
11
+ * ```
12
+ */
13
+ // Encryption utilities
14
+ export { isEncryptedDocx, verifyPassword, decryptPackage, parseEncryptionInfoXml, deriveEncryptionKey, AGILE_BLOCK_KEYS } from "./encryption.js";
15
+ // Digital signature utilities
16
+ export { hasDigitalSignatures, parseSignatureXml, extractSignatures, isWellFormedSignature } from "./digital-signatures.js";
17
+ // Font obfuscation utilities
18
+ export { deobfuscateFont, obfuscateFont, generateFontKey } from "./font-obfuscation.js";
@@ -0,0 +1,58 @@
1
+ /**
2
+ * DOCX Module - Document IO
3
+ *
4
+ * IO operations that depend on docx-packager and docx-reader.
5
+ * Separated from document.ts so that builder helpers can be imported
6
+ * without pulling in archive/xml/writer code.
7
+ */
8
+ import type { DocxDocument, Paragraph, Table, ImageDef } from "./types.js";
9
+ /** Package a DocxDocument model to DOCX bytes. */
10
+ export declare function toBuffer(doc: DocxDocument, compressionLevel?: number): Promise<Uint8Array>;
11
+ /** Package a DocxDocument model to base64 string. */
12
+ export declare function toBase64(doc: DocxDocument, compressionLevel?: number): Promise<string>;
13
+ /** Type of content to patch into a placeholder. */
14
+ export type PatchContent = {
15
+ readonly type: "text";
16
+ readonly text: string;
17
+ } | {
18
+ readonly type: "paragraph";
19
+ readonly children: readonly Paragraph[];
20
+ } | {
21
+ readonly type: "table";
22
+ readonly table: Table;
23
+ } | {
24
+ readonly type: "image";
25
+ readonly image: ImageDef;
26
+ readonly width: number;
27
+ readonly height: number;
28
+ };
29
+ /** A single patch operation mapping a placeholder to replacement content. */
30
+ export interface PatchOperation {
31
+ /** Placeholder string to find (e.g. "{{name}}"). */
32
+ readonly placeholder: string;
33
+ /** Content to replace the placeholder with. */
34
+ readonly content: PatchContent;
35
+ }
36
+ /** Options for patchDocument. */
37
+ export interface PatchOptions {
38
+ /** Compression level (0-9). Default: 6. */
39
+ readonly compressionLevel?: number;
40
+ }
41
+ /**
42
+ * Read an existing DOCX file, replace placeholders with content, and produce a new DOCX.
43
+ *
44
+ * Placeholders are strings like `{{name}}` embedded in the document text.
45
+ * They may span across multiple runs — the patcher handles cross-run matching.
46
+ *
47
+ * Supported patch content types:
48
+ * - `text` — simple text replacement (preserves formatting of the first run)
49
+ * - `paragraph` — replaces the entire paragraph containing the placeholder
50
+ * - `table` — replaces the entire paragraph with a table
51
+ * - `image` — replaces the placeholder with an inline image
52
+ *
53
+ * @param buffer - The source DOCX file as a Uint8Array.
54
+ * @param patches - Array of patch operations to apply.
55
+ * @param options - Optional compression settings.
56
+ * @returns New DOCX file as a Uint8Array.
57
+ */
58
+ export declare function patchDocument(buffer: Uint8Array, patches: readonly PatchOperation[], options?: PatchOptions): Promise<Uint8Array>;