@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
@@ -62,7 +62,7 @@ function argbToPdfColor(argb) {
62
62
  }
63
63
  /**
64
64
  * Convert a color data object to PDF color.
65
- * Handles both ARGB and theme-based colors.
65
+ * Handles ARGB, theme-based, and indexed colors.
66
66
  */
67
67
  function excelColorToPdf(color) {
68
68
  if (!color) {
@@ -84,6 +84,10 @@ function excelColorToPdf(color) {
84
84
  }
85
85
  return base;
86
86
  }
87
+ // Indexed colors (legacy Excel color palette)
88
+ if (color.indexed !== undefined) {
89
+ return indexedColorToPdf(color.indexed);
90
+ }
87
91
  return null;
88
92
  }
89
93
  /**
@@ -109,6 +113,99 @@ function themeColorToPdf(themeIndex) {
109
113
  }
110
114
  return null;
111
115
  }
116
+ /**
117
+ * Standard Excel indexed color palette (56 colors + system colors).
118
+ * Index 0–7: legacy base colors
119
+ * Index 8–63: standard palette (indices 8–63)
120
+ * Index 64: system foreground (black)
121
+ * Index 65: system background (white)
122
+ *
123
+ * @see ECMA-376 §18.8.27 — indexedColors
124
+ */
125
+ const INDEXED_COLORS = [
126
+ // 0–7: legacy base colors (same as 8–15 but less commonly used directly)
127
+ "000000", // 0: Black
128
+ "FFFFFF", // 1: White
129
+ "FF0000", // 2: Red
130
+ "00FF00", // 3: Green
131
+ "0000FF", // 4: Blue
132
+ "FFFF00", // 5: Yellow
133
+ "FF00FF", // 6: Magenta
134
+ "00FFFF", // 7: Cyan
135
+ // 8–63: standard palette
136
+ "000000", // 8: Black
137
+ "FFFFFF", // 9: White
138
+ "FF0000", // 10: Red
139
+ "00FF00", // 11: Green
140
+ "0000FF", // 12: Blue
141
+ "FFFF00", // 13: Yellow
142
+ "FF00FF", // 14: Magenta
143
+ "00FFFF", // 15: Cyan
144
+ "800000", // 16: Dark Red
145
+ "008000", // 17: Dark Green
146
+ "000080", // 18: Dark Blue (Navy)
147
+ "808000", // 19: Dark Yellow (Olive)
148
+ "800080", // 20: Purple
149
+ "008080", // 21: Teal
150
+ "C0C0C0", // 22: Silver
151
+ "808080", // 23: Gray
152
+ "9999FF", // 24: Periwinkle
153
+ "993366", // 25: Plum
154
+ "FFFFCC", // 26: Ivory
155
+ "CCFFFF", // 27: Light Cyan
156
+ "660066", // 28: Dark Purple
157
+ "FF8080", // 29: Coral
158
+ "0066CC", // 30: Ocean Blue
159
+ "CCCCFF", // 31: Ice Blue
160
+ "000080", // 32: Dark Blue
161
+ "FF00FF", // 33: Pink
162
+ "FFFF00", // 34: Yellow
163
+ "00FFFF", // 35: Cyan
164
+ "800080", // 36: Purple
165
+ "800000", // 37: Dark Red
166
+ "008080", // 38: Teal
167
+ "0000FF", // 39: Blue
168
+ "00CCFF", // 40: Sky Blue
169
+ "CCFFFF", // 41: Light Turquoise
170
+ "CCFFCC", // 42: Light Green
171
+ "FFFF99", // 43: Light Yellow
172
+ "99CCFF", // 44: Pale Blue
173
+ "FF99CC", // 45: Rose
174
+ "CC99FF", // 46: Lavender
175
+ "FFCC99", // 47: Tan
176
+ "3366FF", // 48: Light Blue
177
+ "33CCCC", // 49: Aqua
178
+ "99CC00", // 50: Lime
179
+ "FFCC00", // 51: Gold
180
+ "FF9900", // 52: Light Orange
181
+ "FF6600", // 53: Orange
182
+ "666699", // 54: Blue Gray
183
+ "969696", // 55: Gray 40%
184
+ "003366", // 56: Dark Teal
185
+ "339966", // 57: Sea Green
186
+ "003300", // 58: Very Dark Green
187
+ "333300", // 59: Dark Olive
188
+ "993300", // 60: Brown
189
+ "993366", // 61: Plum
190
+ "333399", // 62: Indigo
191
+ "333333" // 63: Gray 80%
192
+ ];
193
+ /**
194
+ * Convert an indexed color to PDF color.
195
+ * Index 64 = system foreground (black), 65 = system background (white).
196
+ */
197
+ function indexedColorToPdf(index) {
198
+ if (index === 64) {
199
+ return { r: 0, g: 0, b: 0 }; // System foreground (black)
200
+ }
201
+ if (index === 65) {
202
+ return { r: 1, g: 1, b: 1 }; // System background (white)
203
+ }
204
+ if (index >= 0 && index < INDEXED_COLORS.length) {
205
+ return argbToPdfColor(INDEXED_COLORS[index]) ?? null;
206
+ }
207
+ return null;
208
+ }
112
209
  /**
113
210
  * Apply a tint value to a color.
114
211
  * Tint range: -1.0 (fully dark) to +1.0 (fully light).
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * DOCX Module - Theme Color Utilities
4
+ *
5
+ * Resolves OOXML theme colors with tint/shade transformations.
6
+ * Extracted to a standalone file so that html-renderer and document.ts
7
+ * can both import it without creating circular heavy dependencies.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.resolveThemeColor = resolveThemeColor;
11
+ /**
12
+ * Map OOXML theme color attribute names to theme color scheme keys.
13
+ * Word uses different names in run/paragraph properties vs the theme XML.
14
+ */
15
+ const THEME_COLOR_MAP = {
16
+ dark1: "dk1",
17
+ light1: "lt1",
18
+ dark2: "dk2",
19
+ light2: "lt2",
20
+ accent1: "accent1",
21
+ accent2: "accent2",
22
+ accent3: "accent3",
23
+ accent4: "accent4",
24
+ accent5: "accent5",
25
+ accent6: "accent6",
26
+ hyperlink: "hlink",
27
+ followedHyperlink: "folHlink",
28
+ // Direct names also work
29
+ dk1: "dk1",
30
+ lt1: "lt1",
31
+ dk2: "dk2",
32
+ lt2: "lt2",
33
+ hlink: "hlink",
34
+ folHlink: "folHlink"
35
+ };
36
+ /**
37
+ * Resolve a ColorSpec to an actual hex RGB color using the document theme.
38
+ *
39
+ * Applies theme color lookup + tint/shade transformations per OOXML spec.
40
+ *
41
+ * @param color - The color value (HexColor string or ColorSpec).
42
+ * @param theme - The document theme (from `doc.theme`).
43
+ * @returns Resolved hex color string (6 chars, no #), or undefined if unresolvable.
44
+ */
45
+ function resolveThemeColor(color, theme) {
46
+ if (color === undefined) {
47
+ return undefined;
48
+ }
49
+ if (typeof color === "string") {
50
+ return color;
51
+ }
52
+ // ColorSpec with val — use directly
53
+ if (color.val && color.val !== "auto") {
54
+ return color.val;
55
+ }
56
+ // Resolve via theme
57
+ if (!color.themeColor || !theme) {
58
+ return color.val;
59
+ }
60
+ const key = THEME_COLOR_MAP[color.themeColor] ?? color.themeColor;
61
+ const base = theme.colorScheme.colors[key];
62
+ if (!base) {
63
+ return color.val;
64
+ }
65
+ // Apply tint or shade
66
+ if (color.themeTint) {
67
+ return applyTint(base, parseInt(color.themeTint, 16) / 255);
68
+ }
69
+ if (color.themeShade) {
70
+ return applyShade(base, parseInt(color.themeShade, 16) / 255);
71
+ }
72
+ return base;
73
+ }
74
+ /** Apply tint to a hex color. tint=1 → white, tint=0 → original. */
75
+ function applyTint(hex, tint) {
76
+ const r = parseInt(hex.slice(0, 2), 16);
77
+ const g = parseInt(hex.slice(2, 4), 16);
78
+ const b = parseInt(hex.slice(4, 6), 16);
79
+ const nr = Math.round(r + (255 - r) * tint);
80
+ const ng = Math.round(g + (255 - g) * tint);
81
+ const nb = Math.round(b + (255 - b) * tint);
82
+ return toHex2(nr) + toHex2(ng) + toHex2(nb);
83
+ }
84
+ /** Apply shade to a hex color. shade=1 → original, shade=0 → black. */
85
+ function applyShade(hex, shade) {
86
+ const r = parseInt(hex.slice(0, 2), 16);
87
+ const g = parseInt(hex.slice(2, 4), 16);
88
+ const b = parseInt(hex.slice(4, 6), 16);
89
+ const nr = Math.round(r * shade);
90
+ const ng = Math.round(g * shade);
91
+ const nb = Math.round(b * shade);
92
+ return toHex2(nr) + toHex2(ng) + toHex2(nb);
93
+ }
94
+ function toHex2(n) {
95
+ const h = Math.max(0, Math.min(255, n)).toString(16);
96
+ return h.length < 2 ? "0" + h : h;
97
+ }
@@ -3,55 +3,54 @@
3
3
  * DOCX Module - Content Types Generator
4
4
  *
5
5
  * Generates [Content_Types].xml for the DOCX package.
6
+ * Uses a plain data record + free functions for tree-shakeability.
6
7
  */
7
8
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.ContentTypesManager = void 0;
9
+ exports.createContentTypes = createContentTypes;
10
+ exports.addContentTypeDefault = addContentTypeDefault;
11
+ exports.addContentTypeOverride = addContentTypeOverride;
12
+ exports.addImageContentTypeDefaults = addImageContentTypeDefaults;
13
+ exports.renderContentTypes = renderContentTypes;
9
14
  const constants_1 = require("./constants");
10
- /**
11
- * Generates the [Content_Types].xml part.
12
- */
13
- class ContentTypesManager {
14
- constructor() {
15
- this._defaults = new Map();
16
- this._overrides = [];
17
- // Always include rels and xml defaults
18
- this._defaults.set("rels", constants_1.ContentType.Relationships);
19
- this._defaults.set("xml", constants_1.ContentType.Xml);
20
- }
21
- /** Add a default content type for a file extension. */
22
- addDefault(extension, contentType) {
23
- this._defaults.set(extension, contentType);
24
- }
25
- /** Add an override content type for a specific part. */
26
- addOverride(partName, contentType) {
27
- this._overrides.push({
28
- partName: partName.startsWith("/") ? partName : `/${partName}`,
29
- contentType
30
- });
31
- }
32
- /** Add image extension defaults from a set of used extensions. */
33
- addImageDefaults(extensions) {
34
- for (const ext of extensions) {
35
- const ct = constants_1.IMAGE_CONTENT_TYPES[ext.toLowerCase()];
36
- if (ct) {
37
- this._defaults.set(ext.toLowerCase(), ct);
38
- }
15
+ /** Create a new ContentTypesState with standard defaults (rels, xml). */
16
+ function createContentTypes() {
17
+ const defaults = new Map();
18
+ defaults.set("rels", constants_1.ContentType.Relationships);
19
+ defaults.set("xml", constants_1.ContentType.Xml);
20
+ return { defaults, overrides: [] };
21
+ }
22
+ /** Add a default content type for a file extension. */
23
+ function addContentTypeDefault(state, extension, contentType) {
24
+ state.defaults.set(extension, contentType);
25
+ }
26
+ /** Add an override content type for a specific part. */
27
+ function addContentTypeOverride(state, partName, contentType) {
28
+ state.overrides.push({
29
+ partName: partName.startsWith("/") ? partName : `/${partName}`,
30
+ contentType
31
+ });
32
+ }
33
+ /** Add image extension defaults from a set of used extensions. */
34
+ function addImageContentTypeDefaults(state, extensions) {
35
+ for (const ext of extensions) {
36
+ const ct = constants_1.IMAGE_CONTENT_TYPES[ext.toLowerCase()];
37
+ if (ct) {
38
+ state.defaults.set(ext.toLowerCase(), ct);
39
39
  }
40
40
  }
41
- /** Render the [Content_Types].xml to a sink. */
42
- render(xml) {
43
- xml.openXml(constants_1.STD_DOC_ATTRIBUTES);
44
- xml.openNode("Types", { xmlns: constants_1.NS_CONTENT_TYPES });
45
- // Defaults sorted by extension
46
- const sortedDefaults = [...this._defaults.entries()].sort((a, b) => a[0].localeCompare(b[0]));
47
- for (const [ext, ct] of sortedDefaults) {
48
- xml.leafNode("Default", { Extension: ext, ContentType: ct });
49
- }
50
- // Overrides in order
51
- for (const override of this._overrides) {
52
- xml.leafNode("Override", { PartName: override.partName, ContentType: override.contentType });
53
- }
54
- xml.closeNode();
41
+ }
42
+ /** Render the [Content_Types].xml to a sink. */
43
+ function renderContentTypes(state, xml) {
44
+ xml.openXml(constants_1.STD_DOC_ATTRIBUTES);
45
+ xml.openNode("Types", { xmlns: constants_1.NS_CONTENT_TYPES });
46
+ // Defaults sorted by extension
47
+ const sortedDefaults = [...state.defaults.entries()].sort((a, b) => a[0].localeCompare(b[0]));
48
+ for (const [ext, ct] of sortedDefaults) {
49
+ xml.leafNode("Default", { Extension: ext, ContentType: ct });
50
+ }
51
+ // Overrides in order
52
+ for (const override of state.overrides) {
53
+ xml.leafNode("Override", { PartName: override.partName, ContentType: override.contentType });
55
54
  }
55
+ xml.closeNode();
56
56
  }
57
- exports.ContentTypesManager = ContentTypesManager;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * DOCX Module - Encryption & Digital Signatures (Subpath Export)
4
+ *
5
+ * Import separately to avoid pulling crypto code into the bundle
6
+ * when only core document building is needed.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { isEncryptedDocx, decryptPackage } from "excelts/word/crypto";
11
+ * import { extractSignatures } from "excelts/word/crypto";
12
+ * ```
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.generateFontKey = exports.obfuscateFont = exports.deobfuscateFont = exports.isWellFormedSignature = exports.extractSignatures = exports.parseSignatureXml = exports.hasDigitalSignatures = exports.AGILE_BLOCK_KEYS = exports.deriveEncryptionKey = exports.parseEncryptionInfoXml = exports.decryptPackage = exports.verifyPassword = exports.isEncryptedDocx = void 0;
16
+ // Encryption utilities
17
+ var encryption_1 = require("./encryption");
18
+ Object.defineProperty(exports, "isEncryptedDocx", { enumerable: true, get: function () { return encryption_1.isEncryptedDocx; } });
19
+ Object.defineProperty(exports, "verifyPassword", { enumerable: true, get: function () { return encryption_1.verifyPassword; } });
20
+ Object.defineProperty(exports, "decryptPackage", { enumerable: true, get: function () { return encryption_1.decryptPackage; } });
21
+ Object.defineProperty(exports, "parseEncryptionInfoXml", { enumerable: true, get: function () { return encryption_1.parseEncryptionInfoXml; } });
22
+ Object.defineProperty(exports, "deriveEncryptionKey", { enumerable: true, get: function () { return encryption_1.deriveEncryptionKey; } });
23
+ Object.defineProperty(exports, "AGILE_BLOCK_KEYS", { enumerable: true, get: function () { return encryption_1.AGILE_BLOCK_KEYS; } });
24
+ // Digital signature utilities
25
+ var digital_signatures_1 = require("./digital-signatures");
26
+ Object.defineProperty(exports, "hasDigitalSignatures", { enumerable: true, get: function () { return digital_signatures_1.hasDigitalSignatures; } });
27
+ Object.defineProperty(exports, "parseSignatureXml", { enumerable: true, get: function () { return digital_signatures_1.parseSignatureXml; } });
28
+ Object.defineProperty(exports, "extractSignatures", { enumerable: true, get: function () { return digital_signatures_1.extractSignatures; } });
29
+ Object.defineProperty(exports, "isWellFormedSignature", { enumerable: true, get: function () { return digital_signatures_1.isWellFormedSignature; } });
30
+ // Font obfuscation utilities
31
+ var font_obfuscation_1 = require("./font-obfuscation");
32
+ Object.defineProperty(exports, "deobfuscateFont", { enumerable: true, get: function () { return font_obfuscation_1.deobfuscateFont; } });
33
+ Object.defineProperty(exports, "obfuscateFont", { enumerable: true, get: function () { return font_obfuscation_1.obfuscateFont; } });
34
+ Object.defineProperty(exports, "generateFontKey", { enumerable: true, get: function () { return font_obfuscation_1.generateFontKey; } });
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ /**
3
+ * DOCX Module - Document IO
4
+ *
5
+ * IO operations that depend on docx-packager and docx-reader.
6
+ * Separated from document.ts so that builder helpers can be imported
7
+ * without pulling in archive/xml/writer code.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.toBuffer = toBuffer;
11
+ exports.toBase64 = toBase64;
12
+ exports.patchDocument = patchDocument;
13
+ const docx_packager_1 = require("./docx-packager");
14
+ const docx_reader_1 = require("./docx-reader");
15
+ const internal_utils_1 = require("./internal-utils");
16
+ // =============================================================================
17
+ // Document IO (toBuffer / toBase64)
18
+ // =============================================================================
19
+ /** Package a DocxDocument model to DOCX bytes. */
20
+ async function toBuffer(doc, compressionLevel) {
21
+ return (0, docx_packager_1.packageDocx)(doc, compressionLevel);
22
+ }
23
+ /** Package a DocxDocument model to base64 string. */
24
+ async function toBase64(doc, compressionLevel) {
25
+ const bytes = await toBuffer(doc, compressionLevel);
26
+ return (0, internal_utils_1.bytesToBase64)(bytes);
27
+ }
28
+ /**
29
+ * Read an existing DOCX file, replace placeholders with content, and produce a new DOCX.
30
+ *
31
+ * Placeholders are strings like `{{name}}` embedded in the document text.
32
+ * They may span across multiple runs — the patcher handles cross-run matching.
33
+ *
34
+ * Supported patch content types:
35
+ * - `text` — simple text replacement (preserves formatting of the first run)
36
+ * - `paragraph` — replaces the entire paragraph containing the placeholder
37
+ * - `table` — replaces the entire paragraph with a table
38
+ * - `image` — replaces the placeholder with an inline image
39
+ *
40
+ * @param buffer - The source DOCX file as a Uint8Array.
41
+ * @param patches - Array of patch operations to apply.
42
+ * @param options - Optional compression settings.
43
+ * @returns New DOCX file as a Uint8Array.
44
+ */
45
+ async function patchDocument(buffer, patches, options) {
46
+ const doc = await (0, docx_reader_1.readDocx)(buffer);
47
+ // Build lookup map for quick placeholder matching
48
+ const patchMap = new Map();
49
+ for (const patch of patches) {
50
+ patchMap.set(patch.placeholder, patch);
51
+ }
52
+ // Process body content
53
+ const newBody = [];
54
+ for (const block of doc.body) {
55
+ if (block.type === "paragraph") {
56
+ const result = patchParagraph(block, patchMap);
57
+ if (result) {
58
+ if (Array.isArray(result)) {
59
+ newBody.push(...result);
60
+ }
61
+ else {
62
+ newBody.push(result);
63
+ }
64
+ }
65
+ }
66
+ else if (block.type === "table") {
67
+ patchTable(block, patchMap);
68
+ newBody.push(block);
69
+ }
70
+ else {
71
+ newBody.push(block);
72
+ }
73
+ }
74
+ // Patch headers
75
+ if (doc.headers) {
76
+ for (const [, headerDef] of doc.headers) {
77
+ patchHeaderFooterContent(headerDef.content, patchMap);
78
+ }
79
+ }
80
+ // Patch footers
81
+ if (doc.footers) {
82
+ for (const [, footerDef] of doc.footers) {
83
+ patchHeaderFooterContent(footerDef.content, patchMap);
84
+ }
85
+ }
86
+ // Add any new images from patches
87
+ const images = doc.images ? [...doc.images] : [];
88
+ for (const patch of patches) {
89
+ if (patch.content.type === "image") {
90
+ const imgContent = patch.content;
91
+ const existing = images.find(i => i.fileName === imgContent.image.fileName);
92
+ if (!existing) {
93
+ images.push(imgContent.image);
94
+ }
95
+ }
96
+ }
97
+ const patched = {
98
+ ...doc,
99
+ body: newBody,
100
+ images: images.length > 0 ? images : undefined
101
+ };
102
+ return (0, docx_packager_1.packageDocx)(patched, options?.compressionLevel);
103
+ }
104
+ // =============================================================================
105
+ // Internal helpers
106
+ // =============================================================================
107
+ /** Extract concatenated plain text from a paragraph's runs. */
108
+ function paragraphText(para) {
109
+ let t = "";
110
+ for (const child of para.children) {
111
+ if ("content" in child && Array.isArray(child.content)) {
112
+ for (const c of child.content) {
113
+ if ("type" in c && c.type === "text" && "text" in c) {
114
+ t += c.text;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ return t;
120
+ }
121
+ /** Replace text within a single paragraph. */
122
+ function replaceInParagraph(para, search, replacement) {
123
+ for (const child of para.children) {
124
+ if (!("content" in child) || !Array.isArray(child.content)) {
125
+ continue;
126
+ }
127
+ for (const c of child.content) {
128
+ if (!("type" in c) || c.type !== "text" || !("text" in c)) {
129
+ continue;
130
+ }
131
+ const before = c.text;
132
+ if (before.includes(search)) {
133
+ c.text = before.replaceAll(search, replacement);
134
+ }
135
+ }
136
+ }
137
+ // Cross-run replacement fallback
138
+ const fullText = paragraphText(para);
139
+ if (fullText.includes(search)) {
140
+ const newText = fullText.replaceAll(search, replacement);
141
+ let placed = false;
142
+ for (const child of para.children) {
143
+ if (!("content" in child) || !Array.isArray(child.content)) {
144
+ continue;
145
+ }
146
+ for (const c of child.content) {
147
+ if (!("type" in c) || c.type !== "text" || !("text" in c)) {
148
+ continue;
149
+ }
150
+ if (!placed) {
151
+ c.text = newText;
152
+ placed = true;
153
+ }
154
+ else {
155
+ c.text = "";
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ /** Patch a paragraph — returns replacement content or null to remove. */
162
+ function patchParagraph(para, patchMap) {
163
+ const text = paragraphText(para);
164
+ for (const [placeholder, patch] of patchMap) {
165
+ if (!text.includes(placeholder)) {
166
+ continue;
167
+ }
168
+ switch (patch.content.type) {
169
+ case "text": {
170
+ replaceInParagraph(para, placeholder, patch.content.text);
171
+ return para;
172
+ }
173
+ case "paragraph": {
174
+ return patch.content.children;
175
+ }
176
+ case "table": {
177
+ return patch.content.table;
178
+ }
179
+ case "image": {
180
+ const img = patch.content.image;
181
+ const rId = img.rId ?? `rId_img_${img.fileName}`;
182
+ const imgContent = {
183
+ type: "image",
184
+ rId,
185
+ width: patch.content.width,
186
+ height: patch.content.height,
187
+ altText: img.fileName,
188
+ name: img.fileName
189
+ };
190
+ const newPara = {
191
+ type: "paragraph",
192
+ properties: para.properties,
193
+ children: [{ content: [imgContent] }]
194
+ };
195
+ return newPara;
196
+ }
197
+ }
198
+ }
199
+ return para;
200
+ }
201
+ /** Patch text inside table cells recursively. */
202
+ function patchTable(table, patchMap) {
203
+ for (const row of table.rows) {
204
+ for (const cell of row.cells) {
205
+ const newContent = [];
206
+ for (const block of cell.content) {
207
+ if (block.type === "paragraph") {
208
+ const result = patchParagraph(block, patchMap);
209
+ if (result) {
210
+ if (Array.isArray(result)) {
211
+ newContent.push(...result);
212
+ }
213
+ else {
214
+ newContent.push(result);
215
+ }
216
+ }
217
+ }
218
+ else if (block.type === "table") {
219
+ patchTable(block, patchMap);
220
+ newContent.push(block);
221
+ }
222
+ else {
223
+ newContent.push(block);
224
+ }
225
+ }
226
+ cell.content = newContent;
227
+ }
228
+ }
229
+ }
230
+ /** Patch text in header/footer content. */
231
+ function patchHeaderFooterContent(content, patchMap) {
232
+ for (const child of content.children) {
233
+ if (child.type === "paragraph") {
234
+ for (const [placeholder, patch] of patchMap) {
235
+ if (patch.content.type === "text") {
236
+ const text = paragraphText(child);
237
+ if (text.includes(placeholder)) {
238
+ replaceInParagraph(child, placeholder, patch.content.text);
239
+ }
240
+ }
241
+ }
242
+ }
243
+ }
244
+ }