@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,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,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,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,239 @@
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 { packageDocx } from "./docx-packager.js";
9
+ import { readDocx } from "./docx-reader.js";
10
+ import { bytesToBase64 } from "./internal-utils.js";
11
+ // =============================================================================
12
+ // Document IO (toBuffer / toBase64)
13
+ // =============================================================================
14
+ /** Package a DocxDocument model to DOCX bytes. */
15
+ export async function toBuffer(doc, compressionLevel) {
16
+ return packageDocx(doc, compressionLevel);
17
+ }
18
+ /** Package a DocxDocument model to base64 string. */
19
+ export async function toBase64(doc, compressionLevel) {
20
+ const bytes = await toBuffer(doc, compressionLevel);
21
+ return bytesToBase64(bytes);
22
+ }
23
+ /**
24
+ * Read an existing DOCX file, replace placeholders with content, and produce a new DOCX.
25
+ *
26
+ * Placeholders are strings like `{{name}}` embedded in the document text.
27
+ * They may span across multiple runs — the patcher handles cross-run matching.
28
+ *
29
+ * Supported patch content types:
30
+ * - `text` — simple text replacement (preserves formatting of the first run)
31
+ * - `paragraph` — replaces the entire paragraph containing the placeholder
32
+ * - `table` — replaces the entire paragraph with a table
33
+ * - `image` — replaces the placeholder with an inline image
34
+ *
35
+ * @param buffer - The source DOCX file as a Uint8Array.
36
+ * @param patches - Array of patch operations to apply.
37
+ * @param options - Optional compression settings.
38
+ * @returns New DOCX file as a Uint8Array.
39
+ */
40
+ export async function patchDocument(buffer, patches, options) {
41
+ const doc = await readDocx(buffer);
42
+ // Build lookup map for quick placeholder matching
43
+ const patchMap = new Map();
44
+ for (const patch of patches) {
45
+ patchMap.set(patch.placeholder, patch);
46
+ }
47
+ // Process body content
48
+ const newBody = [];
49
+ for (const block of doc.body) {
50
+ if (block.type === "paragraph") {
51
+ const result = patchParagraph(block, patchMap);
52
+ if (result) {
53
+ if (Array.isArray(result)) {
54
+ newBody.push(...result);
55
+ }
56
+ else {
57
+ newBody.push(result);
58
+ }
59
+ }
60
+ }
61
+ else if (block.type === "table") {
62
+ patchTable(block, patchMap);
63
+ newBody.push(block);
64
+ }
65
+ else {
66
+ newBody.push(block);
67
+ }
68
+ }
69
+ // Patch headers
70
+ if (doc.headers) {
71
+ for (const [, headerDef] of doc.headers) {
72
+ patchHeaderFooterContent(headerDef.content, patchMap);
73
+ }
74
+ }
75
+ // Patch footers
76
+ if (doc.footers) {
77
+ for (const [, footerDef] of doc.footers) {
78
+ patchHeaderFooterContent(footerDef.content, patchMap);
79
+ }
80
+ }
81
+ // Add any new images from patches
82
+ const images = doc.images ? [...doc.images] : [];
83
+ for (const patch of patches) {
84
+ if (patch.content.type === "image") {
85
+ const imgContent = patch.content;
86
+ const existing = images.find(i => i.fileName === imgContent.image.fileName);
87
+ if (!existing) {
88
+ images.push(imgContent.image);
89
+ }
90
+ }
91
+ }
92
+ const patched = {
93
+ ...doc,
94
+ body: newBody,
95
+ images: images.length > 0 ? images : undefined
96
+ };
97
+ return packageDocx(patched, options?.compressionLevel);
98
+ }
99
+ // =============================================================================
100
+ // Internal helpers
101
+ // =============================================================================
102
+ /** Extract concatenated plain text from a paragraph's runs. */
103
+ function paragraphText(para) {
104
+ let t = "";
105
+ for (const child of para.children) {
106
+ if ("content" in child && Array.isArray(child.content)) {
107
+ for (const c of child.content) {
108
+ if ("type" in c && c.type === "text" && "text" in c) {
109
+ t += c.text;
110
+ }
111
+ }
112
+ }
113
+ }
114
+ return t;
115
+ }
116
+ /** Replace text within a single paragraph. */
117
+ function replaceInParagraph(para, search, replacement) {
118
+ for (const child of para.children) {
119
+ if (!("content" in child) || !Array.isArray(child.content)) {
120
+ continue;
121
+ }
122
+ for (const c of child.content) {
123
+ if (!("type" in c) || c.type !== "text" || !("text" in c)) {
124
+ continue;
125
+ }
126
+ const before = c.text;
127
+ if (before.includes(search)) {
128
+ c.text = before.replaceAll(search, replacement);
129
+ }
130
+ }
131
+ }
132
+ // Cross-run replacement fallback
133
+ const fullText = paragraphText(para);
134
+ if (fullText.includes(search)) {
135
+ const newText = fullText.replaceAll(search, replacement);
136
+ let placed = false;
137
+ for (const child of para.children) {
138
+ if (!("content" in child) || !Array.isArray(child.content)) {
139
+ continue;
140
+ }
141
+ for (const c of child.content) {
142
+ if (!("type" in c) || c.type !== "text" || !("text" in c)) {
143
+ continue;
144
+ }
145
+ if (!placed) {
146
+ c.text = newText;
147
+ placed = true;
148
+ }
149
+ else {
150
+ c.text = "";
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ /** Patch a paragraph — returns replacement content or null to remove. */
157
+ function patchParagraph(para, patchMap) {
158
+ const text = paragraphText(para);
159
+ for (const [placeholder, patch] of patchMap) {
160
+ if (!text.includes(placeholder)) {
161
+ continue;
162
+ }
163
+ switch (patch.content.type) {
164
+ case "text": {
165
+ replaceInParagraph(para, placeholder, patch.content.text);
166
+ return para;
167
+ }
168
+ case "paragraph": {
169
+ return patch.content.children;
170
+ }
171
+ case "table": {
172
+ return patch.content.table;
173
+ }
174
+ case "image": {
175
+ const img = patch.content.image;
176
+ const rId = img.rId ?? `rId_img_${img.fileName}`;
177
+ const imgContent = {
178
+ type: "image",
179
+ rId,
180
+ width: patch.content.width,
181
+ height: patch.content.height,
182
+ altText: img.fileName,
183
+ name: img.fileName
184
+ };
185
+ const newPara = {
186
+ type: "paragraph",
187
+ properties: para.properties,
188
+ children: [{ content: [imgContent] }]
189
+ };
190
+ return newPara;
191
+ }
192
+ }
193
+ }
194
+ return para;
195
+ }
196
+ /** Patch text inside table cells recursively. */
197
+ function patchTable(table, patchMap) {
198
+ for (const row of table.rows) {
199
+ for (const cell of row.cells) {
200
+ const newContent = [];
201
+ for (const block of cell.content) {
202
+ if (block.type === "paragraph") {
203
+ const result = patchParagraph(block, patchMap);
204
+ if (result) {
205
+ if (Array.isArray(result)) {
206
+ newContent.push(...result);
207
+ }
208
+ else {
209
+ newContent.push(result);
210
+ }
211
+ }
212
+ }
213
+ else if (block.type === "table") {
214
+ patchTable(block, patchMap);
215
+ newContent.push(block);
216
+ }
217
+ else {
218
+ newContent.push(block);
219
+ }
220
+ }
221
+ cell.content = newContent;
222
+ }
223
+ }
224
+ }
225
+ /** Patch text in header/footer content. */
226
+ function patchHeaderFooterContent(content, patchMap) {
227
+ for (const child of content.children) {
228
+ if (child.type === "paragraph") {
229
+ for (const [placeholder, patch] of patchMap) {
230
+ if (patch.content.type === "text") {
231
+ const text = paragraphText(child);
232
+ if (text.includes(placeholder)) {
233
+ replaceInParagraph(child, placeholder, patch.content.text);
234
+ }
235
+ }
236
+ }
237
+ }
238
+ }
239
+ }