@cj-tech-master/excelts 9.6.1 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +18 -3
  2. package/README_zh.md +18 -3
  3. package/dist/browser/modules/excel/cell.d.ts +4 -0
  4. package/dist/browser/modules/excel/note.js +5 -1
  5. package/dist/browser/modules/excel/row.js +35 -2
  6. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  7. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +22 -2
  8. package/dist/browser/modules/excel/types.d.ts +81 -0
  9. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +8 -0
  10. package/dist/browser/modules/excel/utils/drawing-utils.js +19 -2
  11. package/dist/browser/modules/excel/workbook.browser.d.ts +16 -0
  12. package/dist/browser/modules/excel/workbook.browser.js +32 -2
  13. package/dist/browser/modules/excel/worksheet.d.ts +31 -1
  14. package/dist/browser/modules/excel/worksheet.js +83 -0
  15. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  16. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  17. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  18. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  19. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  20. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  21. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  22. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  23. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  24. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  25. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  26. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  27. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  28. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  29. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  30. package/dist/browser/modules/pdf/builder/document-builder.js +22 -49
  31. package/dist/browser/modules/pdf/builder/pdf-editor.js +1 -1
  32. package/dist/browser/modules/pdf/core/pdf-stream.d.ts +28 -1
  33. package/dist/browser/modules/pdf/core/pdf-stream.js +38 -2
  34. package/dist/browser/modules/pdf/font/font-manager.d.ts +26 -0
  35. package/dist/browser/modules/pdf/font/font-manager.js +35 -18
  36. package/dist/browser/modules/pdf/render/page-renderer.d.ts +51 -3
  37. package/dist/browser/modules/pdf/render/page-renderer.js +111 -18
  38. package/dist/browser/modules/word/advanced/field-engine.js +45 -20
  39. package/dist/browser/modules/word/advanced/glossary.d.ts +10 -36
  40. package/dist/browser/modules/word/advanced/glossary.js +8 -9
  41. package/dist/browser/modules/word/advanced/math-convert.js +94 -12
  42. package/dist/browser/modules/word/advanced/ole-objects.d.ts +28 -0
  43. package/dist/browser/modules/word/advanced/ole-objects.js +122 -19
  44. package/dist/browser/modules/word/advanced/style-map.js +31 -10
  45. package/dist/browser/modules/word/builder/run-builders.d.ts +7 -1
  46. package/dist/browser/modules/word/builder/run-builders.js +7 -1
  47. package/dist/browser/modules/word/constants.d.ts +4 -0
  48. package/dist/browser/modules/word/constants.js +5 -1
  49. package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +2 -1
  50. package/dist/browser/modules/word/convert/docx-to-semantic.js +135 -1
  51. package/dist/browser/modules/word/convert/html/html-import.d.ts +32 -1
  52. package/dist/browser/modules/word/convert/html/html-import.js +167 -14
  53. package/dist/browser/modules/word/convert/html/html.d.ts +2 -2
  54. package/dist/browser/modules/word/convert/html/html.js +1 -1
  55. package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  56. package/dist/browser/modules/word/convert/markdown/markdown-import.js +279 -69
  57. package/dist/browser/modules/word/convert/markdown/markdown.d.ts +1 -1
  58. package/dist/browser/modules/word/convert/odt/odt.js +407 -56
  59. package/dist/browser/modules/word/html.d.ts +2 -2
  60. package/dist/browser/modules/word/html.js +1 -1
  61. package/dist/browser/modules/word/index.base.d.ts +3 -3
  62. package/dist/browser/modules/word/index.base.js +1 -1
  63. package/dist/browser/modules/word/layout/layout-full.js +326 -19
  64. package/dist/browser/modules/word/layout/render-page.js +35 -8
  65. package/dist/browser/modules/word/markdown.d.ts +1 -1
  66. package/dist/browser/modules/word/query/compat.d.ts +10 -2
  67. package/dist/browser/modules/word/query/compat.js +29 -21
  68. package/dist/browser/modules/word/reader/docx-reader.js +105 -2
  69. package/dist/browser/modules/word/reader/math-parser.js +8 -2
  70. package/dist/browser/modules/word/security/cfb-reader.js +5 -5
  71. package/dist/browser/modules/word/types.d.ts +96 -1
  72. package/dist/browser/modules/word/writer/docx-packager.js +108 -2
  73. package/dist/browser/modules/word/writer/glossary-writer.d.ts +28 -0
  74. package/dist/browser/modules/word/writer/glossary-writer.js +121 -0
  75. package/dist/browser/modules/word/writer/header-footer-writer.js +105 -20
  76. package/dist/browser/modules/word/writer/math-writer.js +7 -2
  77. package/dist/browser/utils/font-metrics.d.ts +8 -0
  78. package/dist/browser/utils/font-metrics.js +43 -0
  79. package/dist/browser/utils/theme-colors.js +4 -1
  80. package/dist/cjs/modules/excel/note.js +5 -1
  81. package/dist/cjs/modules/excel/row.js +35 -2
  82. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +22 -2
  83. package/dist/cjs/modules/excel/utils/drawing-utils.js +19 -2
  84. package/dist/cjs/modules/excel/workbook.browser.js +31 -1
  85. package/dist/cjs/modules/excel/worksheet.js +83 -0
  86. package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  87. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  88. package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  89. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  90. package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  91. package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  92. package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  93. package/dist/cjs/modules/excel/xlsx/xform/drawing/shape-xform.js +112 -0
  94. package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  95. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  96. package/dist/cjs/modules/pdf/builder/document-builder.js +21 -48
  97. package/dist/cjs/modules/pdf/builder/pdf-editor.js +1 -1
  98. package/dist/cjs/modules/pdf/core/pdf-stream.js +38 -2
  99. package/dist/cjs/modules/pdf/font/font-manager.js +35 -18
  100. package/dist/cjs/modules/pdf/render/page-renderer.js +112 -18
  101. package/dist/cjs/modules/word/advanced/field-engine.js +45 -20
  102. package/dist/cjs/modules/word/advanced/glossary.js +8 -9
  103. package/dist/cjs/modules/word/advanced/math-convert.js +94 -12
  104. package/dist/cjs/modules/word/advanced/ole-objects.js +123 -19
  105. package/dist/cjs/modules/word/advanced/style-map.js +31 -10
  106. package/dist/cjs/modules/word/builder/run-builders.js +7 -1
  107. package/dist/cjs/modules/word/constants.js +5 -1
  108. package/dist/cjs/modules/word/convert/docx-to-semantic.js +135 -1
  109. package/dist/cjs/modules/word/convert/html/html-import.js +168 -14
  110. package/dist/cjs/modules/word/convert/html/html.js +2 -1
  111. package/dist/cjs/modules/word/convert/markdown/markdown-import.js +279 -69
  112. package/dist/cjs/modules/word/convert/odt/odt.js +407 -56
  113. package/dist/cjs/modules/word/html.js +2 -1
  114. package/dist/cjs/modules/word/index.base.js +4 -3
  115. package/dist/cjs/modules/word/layout/layout-full.js +325 -18
  116. package/dist/cjs/modules/word/layout/render-page.js +35 -8
  117. package/dist/cjs/modules/word/query/compat.js +29 -21
  118. package/dist/cjs/modules/word/reader/docx-reader.js +104 -1
  119. package/dist/cjs/modules/word/reader/math-parser.js +8 -2
  120. package/dist/cjs/modules/word/security/cfb-reader.js +5 -5
  121. package/dist/cjs/modules/word/writer/docx-packager.js +108 -2
  122. package/dist/cjs/modules/word/writer/glossary-writer.js +124 -0
  123. package/dist/cjs/modules/word/writer/header-footer-writer.js +105 -20
  124. package/dist/cjs/modules/word/writer/math-writer.js +7 -2
  125. package/dist/cjs/utils/font-metrics.js +44 -0
  126. package/dist/cjs/utils/theme-colors.js +4 -1
  127. package/dist/esm/modules/excel/note.js +5 -1
  128. package/dist/esm/modules/excel/row.js +35 -2
  129. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +22 -2
  130. package/dist/esm/modules/excel/utils/drawing-utils.js +19 -2
  131. package/dist/esm/modules/excel/workbook.browser.js +32 -2
  132. package/dist/esm/modules/excel/worksheet.js +83 -0
  133. package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  134. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  135. package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  136. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  137. package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  138. package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  139. package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  140. package/dist/esm/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  141. package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  142. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  143. package/dist/esm/modules/pdf/builder/document-builder.js +22 -49
  144. package/dist/esm/modules/pdf/builder/pdf-editor.js +1 -1
  145. package/dist/esm/modules/pdf/core/pdf-stream.js +38 -2
  146. package/dist/esm/modules/pdf/font/font-manager.js +35 -18
  147. package/dist/esm/modules/pdf/render/page-renderer.js +111 -18
  148. package/dist/esm/modules/word/advanced/field-engine.js +45 -20
  149. package/dist/esm/modules/word/advanced/glossary.js +8 -9
  150. package/dist/esm/modules/word/advanced/math-convert.js +94 -12
  151. package/dist/esm/modules/word/advanced/ole-objects.js +122 -19
  152. package/dist/esm/modules/word/advanced/style-map.js +31 -10
  153. package/dist/esm/modules/word/builder/run-builders.js +7 -1
  154. package/dist/esm/modules/word/constants.js +5 -1
  155. package/dist/esm/modules/word/convert/docx-to-semantic.js +135 -1
  156. package/dist/esm/modules/word/convert/html/html-import.js +167 -14
  157. package/dist/esm/modules/word/convert/html/html.js +1 -1
  158. package/dist/esm/modules/word/convert/markdown/markdown-import.js +279 -69
  159. package/dist/esm/modules/word/convert/odt/odt.js +407 -56
  160. package/dist/esm/modules/word/html.js +1 -1
  161. package/dist/esm/modules/word/index.base.js +1 -1
  162. package/dist/esm/modules/word/layout/layout-full.js +326 -19
  163. package/dist/esm/modules/word/layout/render-page.js +35 -8
  164. package/dist/esm/modules/word/query/compat.js +29 -21
  165. package/dist/esm/modules/word/reader/docx-reader.js +105 -2
  166. package/dist/esm/modules/word/reader/math-parser.js +8 -2
  167. package/dist/esm/modules/word/security/cfb-reader.js +5 -5
  168. package/dist/esm/modules/word/writer/docx-packager.js +108 -2
  169. package/dist/esm/modules/word/writer/glossary-writer.js +121 -0
  170. package/dist/esm/modules/word/writer/header-footer-writer.js +105 -20
  171. package/dist/esm/modules/word/writer/math-writer.js +7 -2
  172. package/dist/esm/utils/font-metrics.js +43 -0
  173. package/dist/esm/utils/theme-colors.js +4 -1
  174. package/dist/iife/excelts.iife.js +496 -59
  175. package/dist/iife/excelts.iife.js.map +1 -1
  176. package/dist/iife/excelts.iife.min.js +39 -39
  177. package/dist/types/modules/excel/cell.d.ts +4 -0
  178. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  179. package/dist/types/modules/excel/types.d.ts +81 -0
  180. package/dist/types/modules/excel/utils/drawing-utils.d.ts +8 -0
  181. package/dist/types/modules/excel/workbook.browser.d.ts +16 -0
  182. package/dist/types/modules/excel/worksheet.d.ts +31 -1
  183. package/dist/types/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  184. package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  185. package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  186. package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  187. package/dist/types/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  188. package/dist/types/modules/pdf/core/pdf-stream.d.ts +28 -1
  189. package/dist/types/modules/pdf/font/font-manager.d.ts +26 -0
  190. package/dist/types/modules/pdf/render/page-renderer.d.ts +51 -3
  191. package/dist/types/modules/word/advanced/glossary.d.ts +10 -36
  192. package/dist/types/modules/word/advanced/ole-objects.d.ts +28 -0
  193. package/dist/types/modules/word/builder/run-builders.d.ts +7 -1
  194. package/dist/types/modules/word/constants.d.ts +4 -0
  195. package/dist/types/modules/word/convert/docx-to-semantic.d.ts +2 -1
  196. package/dist/types/modules/word/convert/html/html-import.d.ts +32 -1
  197. package/dist/types/modules/word/convert/html/html.d.ts +2 -2
  198. package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  199. package/dist/types/modules/word/convert/markdown/markdown.d.ts +1 -1
  200. package/dist/types/modules/word/html.d.ts +2 -2
  201. package/dist/types/modules/word/index.base.d.ts +3 -3
  202. package/dist/types/modules/word/markdown.d.ts +1 -1
  203. package/dist/types/modules/word/query/compat.d.ts +10 -2
  204. package/dist/types/modules/word/types.d.ts +96 -1
  205. package/dist/types/modules/word/writer/glossary-writer.d.ts +28 -0
  206. package/dist/types/utils/font-metrics.d.ts +8 -0
  207. package/package.json +3 -1
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { unzip } from "../../archive/read-archive.js";
8
8
  import { parseXml, findChild, textContent } from "../../xml/dom.js";
9
- import { RelType } from "../constants.js";
9
+ import { RelType, ContentType } from "../constants.js";
10
10
  import { utf8Decoder } from "../core/internal-utils.js";
11
11
  import { isRun } from "../core/text-utils.js";
12
12
  import { DocxError, DocxParseError, DocxMissingPartError, DocxEncryptedError, DocxLimitExceededError } from "../errors.js";
@@ -2457,6 +2457,107 @@ async function _readDocxInner(buffer, policy) {
2457
2457
  break;
2458
2458
  }
2459
2459
  }
2460
+ // Extract OLE embedded objects wired on document.xml.rels. We surface
2461
+ // them as structured `oleObjects` so callers can query/round-trip them
2462
+ // (getOleObjectData/extractOleObjects) without depending on the part's
2463
+ // own .rels (OLE binaries carry no .rels — their relationship lives on
2464
+ // document.xml.rels). The body still references each object through an
2465
+ // opaqueDrawing carrying the same r:id. Honours `preserveOleObjects`.
2466
+ let oleObjects;
2467
+ if (policy.preserveOleObjects) {
2468
+ const collected = [];
2469
+ // progId may be recoverable from the body's <o:OLEObject ProgID="…">.
2470
+ // The <w:object> markup is preserved either as a body-level opaqueDrawing
2471
+ // or (more commonly) as a run-level opaqueRun inside a paragraph. Collect
2472
+ // raw XML from both so the ProgID round-trips.
2473
+ const progIdByRId = new Map();
2474
+ const scanOleMarkup = (rawXml) => {
2475
+ // Match ProgID + r:id from within the same <o:OLEObject> element so a
2476
+ // preview <v:imagedata r:id="…"> earlier in the markup is not mistaken
2477
+ // for the OLE binary's relationship id.
2478
+ const oleTagRe = /<o:OLEObject\b[^>]*>/gi;
2479
+ let m;
2480
+ while ((m = oleTagRe.exec(rawXml)) !== null) {
2481
+ const tag = m[0];
2482
+ const progMatch = tag.match(/ProgID="([^"]+)"/i);
2483
+ const ridMatch = tag.match(/r:id="([^"]+)"/i);
2484
+ if (progMatch && ridMatch) {
2485
+ progIdByRId.set(ridMatch[1], progMatch[1]);
2486
+ }
2487
+ }
2488
+ };
2489
+ for (const item of body) {
2490
+ if (item.type === "opaqueDrawing") {
2491
+ scanOleMarkup(item.rawXml);
2492
+ }
2493
+ else if (item.type === "paragraph") {
2494
+ for (const child of item.children) {
2495
+ if (isRun(child)) {
2496
+ for (const rc of child.content) {
2497
+ if (rc.type === "opaqueRun") {
2498
+ scanOleMarkup(rc.rawXml);
2499
+ }
2500
+ }
2501
+ }
2502
+ }
2503
+ }
2504
+ }
2505
+ for (const rel of docRels) {
2506
+ if (rel.type !== RelType.Package) {
2507
+ continue;
2508
+ }
2509
+ const olePath = resolvePartPath(documentPartPath, rel.target);
2510
+ if (!olePath.startsWith("word/embeddings/")) {
2511
+ continue;
2512
+ }
2513
+ const oleData = entries.get(olePath);
2514
+ if (!oleData) {
2515
+ continue;
2516
+ }
2517
+ consumedPaths.add(olePath);
2518
+ collected.push({
2519
+ path: olePath,
2520
+ data: oleData,
2521
+ rId: rel.id,
2522
+ progId: progIdByRId.get(rel.id),
2523
+ contentType: ContentType.OleObject
2524
+ });
2525
+ }
2526
+ if (collected.length > 0) {
2527
+ oleObjects = collected;
2528
+ }
2529
+ }
2530
+ // Glossary document (Building Blocks). The glossary is a self-contained
2531
+ // sub-document: word/glossary/document.xml plus its own companion parts
2532
+ // (styles/settings/webSettings/fontTable) referenced from
2533
+ // word/glossary/_rels/document.xml.rels. We carry document.xml verbatim as
2534
+ // `rawXml` and ALL sibling glossary parts (including the .rels) verbatim in
2535
+ // `rawParts`, so a read→write round-trip re-emits a complete, Word-valid
2536
+ // glossary rather than dropping it (opaqueParts would lose both the
2537
+ // glossaryDocument relationship and the companion .rels). The structured
2538
+ // `blocks` are not reverse-parsed.
2539
+ let glossary;
2540
+ for (const rel of docRels) {
2541
+ if (rel.type !== RelType.Glossary) {
2542
+ continue;
2543
+ }
2544
+ const glossaryPath = resolvePartPath(documentPartPath, rel.target);
2545
+ const glossaryData = entries.get(glossaryPath);
2546
+ if (glossaryData) {
2547
+ // Collect every word/glossary/** part (document.xml, its .rels and all
2548
+ // companions) verbatim, and mark them consumed so they are not also
2549
+ // emitted via opaqueParts.
2550
+ const rawParts = new Map();
2551
+ for (const [path, data] of entries) {
2552
+ if (path.startsWith("word/glossary/")) {
2553
+ rawParts.set(path, data);
2554
+ consumedPaths.add(path);
2555
+ }
2556
+ }
2557
+ glossary = { blocks: [], rawXml: decoder.decode(glossaryData), rawParts };
2558
+ }
2559
+ break;
2560
+ }
2460
2561
  // Resolve altChunk data: body elements of type "altChunk" reference a rId.
2461
2562
  // The target file is stored in docRels + entries. We populate the altChunk
2462
2563
  // body item with its data here AND mark the target path as consumed so the
@@ -2575,6 +2676,8 @@ async function _readDocxInner(buffer, policy) {
2575
2676
  theme,
2576
2677
  watermark,
2577
2678
  opaqueParts: opaqueParts.length > 0 ? opaqueParts : undefined,
2578
- vbaProject
2679
+ vbaProject,
2680
+ oleObjects,
2681
+ glossary
2579
2682
  };
2580
2683
  }
@@ -122,8 +122,14 @@ function parseMathContent(el) {
122
122
  const v = mathAttrVal(el, "val");
123
123
  return v !== "0" && v !== "false";
124
124
  };
125
- if (boolAttr("show")) {
126
- ph.show = true;
125
+ // `show` is tri-state: it may be explicitly off (`m:val="0"`), which
126
+ // is what makes a phantom invisible, explicitly on, or absent
127
+ // (defaults to on). Preserve an explicit value so a round-trip keeps
128
+ // the invisibility, but leave it unset when the element is absent.
129
+ const showEl = findMathChild(phantPrEl, "show");
130
+ if (showEl) {
131
+ const v = mathAttrVal(showEl, "val");
132
+ ph.show = v !== "0" && v !== "false";
127
133
  }
128
134
  if (boolAttr("zeroWid")) {
129
135
  ph.zeroWidth = true;
@@ -336,9 +336,8 @@ export function writeCfb(entries) {
336
336
  const miniNodes = streamNodes.filter(n => n.size > 0 && n.size < MINI_STREAM_CUTOFF);
337
337
  const regularNodes = streamNodes.filter(n => n.size >= MINI_STREAM_CUTOFF);
338
338
  // Assemble the mini-stream and the mini-FAT.
339
- let miniStream = new Uint8Array(0);
340
339
  const miniFat = [];
341
- {
340
+ const miniStream = (() => {
342
341
  const parts = [];
343
342
  let miniSectorIdx = 0;
344
343
  let totalLen = 0;
@@ -354,13 +353,14 @@ export function writeCfb(entries) {
354
353
  parts.push(padded);
355
354
  totalLen += padded.length;
356
355
  }
357
- miniStream = new Uint8Array(totalLen);
356
+ const stream = new Uint8Array(totalLen);
358
357
  let off = 0;
359
358
  for (const p of parts) {
360
- miniStream.set(p, off);
359
+ stream.set(p, off);
361
360
  off += p.length;
362
361
  }
363
- }
362
+ return stream;
363
+ })();
364
364
  root.size = miniStream.length;
365
365
  // ---------------------------------------------------------------------------
366
366
  // 4. Lay out regular sectors.
@@ -1283,7 +1283,13 @@ export interface MathPreSubSuperScript {
1283
1283
  export interface MathPhantom {
1284
1284
  readonly type: "mathPhantom";
1285
1285
  readonly content: readonly MathContent[];
1286
- /** Phantom shows up in the layout (default: false). */
1286
+ /**
1287
+ * Whether the phantom's base content is drawn. In OOXML `m:show` defaults to
1288
+ * ON, so the base stays visible unless this is explicitly set to `false`
1289
+ * (serialized as `<m:show m:val="0"/>`). Set `false` for the classic
1290
+ * "occupies space but invisible" phantom; leave undefined to keep the
1291
+ * default (visible) behaviour.
1292
+ */
1287
1293
  readonly show?: boolean;
1288
1294
  /** Zero width (default: false). */
1289
1295
  readonly zeroWidth?: boolean;
@@ -1714,6 +1720,10 @@ export interface ImageWatermark {
1714
1720
  readonly scale?: number;
1715
1721
  /** Washout effect. */
1716
1722
  readonly washout?: boolean;
1723
+ /** Display width in points. Defaults to 415.2pt (full body width). */
1724
+ readonly widthPt?: number;
1725
+ /** Display height in points. Defaults to 415.2pt scaled to image ratio, or 233.5pt. */
1726
+ readonly heightPt?: number;
1717
1727
  }
1718
1728
  /** Watermark (text or image). */
1719
1729
  export type Watermark = TextWatermark | ImageWatermark;
@@ -2403,6 +2413,39 @@ export interface PersonInfo {
2403
2413
  * - "macroEnabledTemplate" — .dotm (macro-enabled template)
2404
2414
  */
2405
2415
  export type DocxDocumentType = "document" | "template" | "macroEnabledDocument" | "macroEnabledTemplate";
2416
+ /** Building block gallery category. */
2417
+ export type BuildingBlockGallery = "autoText" | "quickParts" | "coverPages" | "tableOfContents" | "headers" | "footers" | "pageNumbers" | "tables" | "textBoxes" | "watermarks" | "equations" | "bibliographies" | "custom1" | "custom2" | "custom3" | "custom4" | "custom5";
2418
+ /** A single building block (AutoText/Quick Part) entry. */
2419
+ export interface BuildingBlock {
2420
+ /** Name of the building block (displayed in gallery). */
2421
+ readonly name: string;
2422
+ /** Gallery this block belongs to. */
2423
+ readonly gallery: BuildingBlockGallery;
2424
+ /** Category within the gallery. */
2425
+ readonly category?: string;
2426
+ /** Description/tooltip. */
2427
+ readonly description?: string;
2428
+ /** The content of the building block. */
2429
+ readonly content: readonly BodyContent[];
2430
+ /** Section properties specific to this building block. */
2431
+ readonly sectionProperties?: SectionProperties;
2432
+ /** Unique identifier (GUID). */
2433
+ readonly guid?: string;
2434
+ }
2435
+ /** The glossary document model. */
2436
+ export interface GlossaryDocument {
2437
+ /** Building block entries. */
2438
+ readonly blocks: readonly BuildingBlock[];
2439
+ /** Raw parts preserved for round-trip (style, settings, fontTable). */
2440
+ readonly rawParts?: ReadonlyMap<string, Uint8Array>;
2441
+ /**
2442
+ * Verbatim `word/glossary/document.xml` markup. Populated by the reader so a
2443
+ * read→write round-trip preserves the glossary byte-faithfully even though
2444
+ * the structured `blocks` are not reverse-parsed. When present the writer
2445
+ * emits this verbatim and ignores `blocks`.
2446
+ */
2447
+ readonly rawXml?: string;
2448
+ }
2406
2449
  export interface DocxDocument {
2407
2450
  /**
2408
2451
  * Document type. Determines the content type used in the package.
@@ -2468,6 +2511,24 @@ export interface DocxDocument {
2468
2511
  readonly opaqueParts?: readonly OpaquePart[];
2469
2512
  /** VBA project binary (word/vbaProject.bin) for .docm/.dotm round-trip. */
2470
2513
  readonly vbaProject?: Uint8Array;
2514
+ /**
2515
+ * OLE embedded objects wired into the document. Unlike a bare
2516
+ * {@link OpaquePart}, each entry here is registered by the packager as a
2517
+ * relationship on `word/_rels/document.xml.rels` (so the `r:id` used by the
2518
+ * body `<w:object>`/`<o:OLEObject>` markup actually resolves) and gets a
2519
+ * `[Content_Types].xml` override. The body still references the object via
2520
+ * an {@link OpaqueDrawing} carrying the matching `r:id`. Mirrors the
2521
+ * `vbaProject` model-field pattern.
2522
+ */
2523
+ readonly oleObjects?: readonly OleObjectPart[];
2524
+ /**
2525
+ * Glossary document (Building Blocks / AutoText / Quick Parts). When set,
2526
+ * the packager serialises it to `word/glossary/document.xml`, registers the
2527
+ * `glossaryDocument` relationship on `document.xml.rels`, and adds the
2528
+ * `[Content_Types].xml` override — the canonical OOXML location Word reads
2529
+ * Quick Parts from.
2530
+ */
2531
+ readonly glossary?: GlossaryDocument;
2471
2532
  }
2472
2533
  /** A relationship entry preserved for round-trip. */
2473
2534
  export interface OpaqueRelationship {
@@ -2508,6 +2569,40 @@ export interface OpaquePart {
2508
2569
  /** Relationships of this part (from its .rels file). */
2509
2570
  readonly relationships?: readonly OpaqueRelationship[];
2510
2571
  }
2572
+ /**
2573
+ * An OLE embedded object that the packager wires into the main document.
2574
+ *
2575
+ * The binary is written at {@link path}, a relationship with the exact
2576
+ * {@link rId} is registered on `word/_rels/document.xml.rels` (so body
2577
+ * `<w:object r:id="…">` markup resolves), and a `[Content_Types].xml`
2578
+ * override is added. An optional preview image is wired the same way.
2579
+ */
2580
+ export interface OleObjectPart {
2581
+ /** Part path of the OLE binary (e.g. "word/embeddings/oleObject1.bin"). */
2582
+ readonly path: string;
2583
+ /** Raw OLE binary data (ideally an OLE2 compound document). */
2584
+ readonly data: Uint8Array;
2585
+ /**
2586
+ * Relationship ID referenced from the body markup. Registered verbatim on
2587
+ * the document relationships so the reference always resolves.
2588
+ */
2589
+ readonly rId: string;
2590
+ /** OLE ProgId (e.g. "Excel.Sheet.12", "Package"). Carried for round-trip. */
2591
+ readonly progId?: string;
2592
+ /**
2593
+ * Content type for the binary. Defaults to
2594
+ * `application/vnd.openxmlformats-officedocument.oleObject`.
2595
+ */
2596
+ readonly contentType?: string;
2597
+ /** Optional preview image part path (e.g. "word/media/oleImage1.png"). */
2598
+ readonly previewPath?: string;
2599
+ /** Preview image bytes (when {@link previewPath} is set). */
2600
+ readonly previewData?: Uint8Array;
2601
+ /** Relationship ID for the preview image, registered on the document. */
2602
+ readonly previewRId?: string;
2603
+ /** Content type for the preview image (e.g. "image/png"). */
2604
+ readonly previewContentType?: string;
2605
+ }
2511
2606
  /** An opaque drawing element (e.g. chart) preserved in a paragraph. */
2512
2607
  export interface OpaqueDrawing {
2513
2608
  readonly type: "opaqueDrawing";
@@ -20,6 +20,7 @@ import { renderComments, renderCommentsExtended } from "./comment-writer.js";
20
20
  import { createContentTypes, addContentTypeDefault, addContentTypeOverride, addImageContentTypeDefaults, renderContentTypes } from "./content-types.js";
21
21
  import { renderDocument } from "./document-writer.js";
22
22
  import { renderFootnotes, renderEndnotes } from "./footnote-writer.js";
23
+ import { renderGlossaryDocument } from "./glossary-writer.js";
23
24
  import { renderHeader, renderFooter, renderWatermarkHeader } from "./header-footer-writer.js";
24
25
  import { renderNumbering } from "./numbering-writer.js";
25
26
  import { renderSettings, renderFontTable, renderCoreProperties, renderAppProperties, renderCustomProperties, renderWebSettings, renderPeople, renderTheme } from "./parts-writer.js";
@@ -1083,7 +1084,13 @@ async function _packageDocxInner(doc, options) {
1083
1084
  // word/styles.xml
1084
1085
  archive.add(PartPath.Styles, renderXml(xml => renderStyles(xml, doc.docDefaults, doc.styles)));
1085
1086
  // word/settings.xml
1086
- archive.add(PartPath.Settings, renderXml(xml => renderSettings(xml, doc.settings, rawXmlPolicy)));
1087
+ // When the document defines a page background, Word only paints it if
1088
+ // <w:displayBackgroundShape/> is present in settings. Inject it
1089
+ // automatically so setBackground() actually shows up on screen.
1090
+ const settingsForRender = doc.background && !doc.settings?.displayBackgroundShape
1091
+ ? { ...(doc.settings ?? {}), displayBackgroundShape: true }
1092
+ : doc.settings;
1093
+ archive.add(PartPath.Settings, renderXml(xml => renderSettings(xml, settingsForRender, rawXmlPolicy)));
1087
1094
  // word/fontTable.xml
1088
1095
  archive.add(PartPath.FontTable, renderXml(xml => renderFontTable(xml, doc.fonts)));
1089
1096
  // word/fonts/*.odttf (embedded fonts)
@@ -1345,6 +1352,81 @@ async function _packageDocxInner(doc, options) {
1345
1352
  addRelationship(documentRels, RelType.VbaProject, "vbaProject.bin");
1346
1353
  addContentTypeOverride(contentTypes, "/word/vbaProject.bin", ContentType.VbaProject);
1347
1354
  }
1355
+ // OLE embedded objects (word/embeddings/*.bin + optional preview media).
1356
+ // Each object is registered with the *exact* rId referenced from the body
1357
+ // `<w:object r:id="…">` markup, so the reference always resolves. The
1358
+ // `preserveOleObjects` security policy can still strip the binaries below.
1359
+ if (doc.oleObjects && securityPolicy.preserveOleObjects) {
1360
+ for (const ole of doc.oleObjects) {
1361
+ archive.add(ole.path, ole.data);
1362
+ // Target is relative to word/ (document.xml lives in word/).
1363
+ const oleTarget = ole.path.replace(/^word\//, "");
1364
+ addRelationshipWithId(documentRels, ole.rId, RelType.Package, oleTarget);
1365
+ addContentTypeOverride(contentTypes, `/${ole.path}`, ole.contentType ?? ContentType.OleObject);
1366
+ if (ole.previewPath && ole.previewData && ole.previewRId) {
1367
+ archive.add(ole.previewPath, ole.previewData);
1368
+ addRelationshipWithId(documentRels, ole.previewRId, RelType.Image, ole.previewPath.replace(/^word\//, ""));
1369
+ if (ole.previewContentType) {
1370
+ addContentTypeOverride(contentTypes, `/${ole.previewPath}`, ole.previewContentType);
1371
+ }
1372
+ else {
1373
+ const ext = getFileExt(ole.previewPath);
1374
+ const inferred = ext ? inferContentType(ext) : undefined;
1375
+ if (inferred) {
1376
+ addContentTypeOverride(contentTypes, `/${ole.previewPath}`, inferred);
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+ }
1382
+ // word/glossary/document.xml (Building Blocks / AutoText / Quick Parts).
1383
+ //
1384
+ // A glossary is a self-contained sub-document: Word expects it to carry its
1385
+ // own styles / settings / webSettings / fontTable parts (referenced from
1386
+ // word/glossary/_rels/document.xml.rels) rather than sharing the main
1387
+ // document's. Omitting them makes Word discard the whole glossary on open.
1388
+ //
1389
+ // - Round-tripped glossary (`rawParts` set): re-emit every captured
1390
+ // word/glossary/** part verbatim (document.xml, its .rels, companions).
1391
+ // - Freshly-built glossary: synthesise document.xml + the companion parts
1392
+ // + the glossary's own .rels, reusing the main document's styles/fonts so
1393
+ // block content (e.g. Heading1) resolves.
1394
+ if (doc.glossary && (doc.glossary.rawXml || doc.glossary.blocks.length > 0)) {
1395
+ addRelationship(documentRels, RelType.Glossary, "glossary/document.xml");
1396
+ if (doc.glossary.rawParts && doc.glossary.rawParts.size > 0) {
1397
+ for (const [path, data] of doc.glossary.rawParts) {
1398
+ archive.add(path, data);
1399
+ if (path.endsWith(".rels")) {
1400
+ continue; // .rels parts are typed by the Default rels content type
1401
+ }
1402
+ const ct = path === "word/glossary/document.xml"
1403
+ ? ContentType.Glossary
1404
+ : inferContentType(getFileExt(path));
1405
+ if (ct) {
1406
+ addContentTypeOverride(contentTypes, `/${path}`, ct);
1407
+ }
1408
+ }
1409
+ }
1410
+ else {
1411
+ archive.add("word/glossary/document.xml", renderGlossaryDocument(doc.glossary));
1412
+ addContentTypeOverride(contentTypes, "/word/glossary/document.xml", ContentType.Glossary);
1413
+ // Synthesise the companion sub-document parts + the glossary's own rels.
1414
+ const glossaryRels = createRelationships();
1415
+ addRelationshipWithId(glossaryRels, "rId1", RelType.Styles, "styles.xml");
1416
+ addRelationshipWithId(glossaryRels, "rId2", RelType.Settings, "settings.xml");
1417
+ addRelationshipWithId(glossaryRels, "rId3", RelType.WebSettings, "webSettings.xml");
1418
+ addRelationshipWithId(glossaryRels, "rId4", RelType.FontTable, "fontTable.xml");
1419
+ archive.add("word/glossary/_rels/document.xml.rels", renderXml(xml => renderRelationships(glossaryRels, xml)));
1420
+ archive.add("word/glossary/styles.xml", renderXml(xml => renderStyles(xml, doc.docDefaults, doc.styles)));
1421
+ archive.add("word/glossary/settings.xml", renderXml(xml => renderSettings(xml, undefined, rawXmlPolicy)));
1422
+ archive.add("word/glossary/webSettings.xml", renderXml(xml => renderWebSettings(xml, undefined, rawXmlPolicy)));
1423
+ archive.add("word/glossary/fontTable.xml", renderXml(xml => renderFontTable(xml, doc.fonts)));
1424
+ addContentTypeOverride(contentTypes, "/word/glossary/styles.xml", ContentType.Styles);
1425
+ addContentTypeOverride(contentTypes, "/word/glossary/settings.xml", ContentType.Settings);
1426
+ addContentTypeOverride(contentTypes, "/word/glossary/webSettings.xml", ContentType.WebSettings);
1427
+ addContentTypeOverride(contentTypes, "/word/glossary/fontTable.xml", ContentType.FontTable);
1428
+ }
1429
+ }
1348
1430
  // Write opaque (unrecognized) parts for round-trip preservation.
1349
1431
  //
1350
1432
  // Opaque parts are written as-is into the ZIP. Their paths must not
@@ -1385,8 +1467,23 @@ async function _packageDocxInner(doc, options) {
1385
1467
  PartPath.CustomProps,
1386
1468
  PartPath.Thumbnail,
1387
1469
  "word/vbaProject.bin",
1388
- "word/_rels/vbaProject.bin.rels"
1470
+ "word/_rels/vbaProject.bin.rels",
1471
+ "word/glossary/document.xml",
1472
+ "word/glossary/_rels/document.xml.rels"
1389
1473
  ]);
1474
+ // Glossary sub-document parts (emitted either verbatim from rawParts or
1475
+ // synthesised); reserve them so opaqueParts can't collide.
1476
+ if (doc.glossary && (doc.glossary.rawXml || doc.glossary.blocks.length > 0)) {
1477
+ reservedExact.add("word/glossary/styles.xml");
1478
+ reservedExact.add("word/glossary/settings.xml");
1479
+ reservedExact.add("word/glossary/webSettings.xml");
1480
+ reservedExact.add("word/glossary/fontTable.xml");
1481
+ if (doc.glossary.rawParts) {
1482
+ for (const path of doc.glossary.rawParts.keys()) {
1483
+ reservedExact.add(path);
1484
+ }
1485
+ }
1486
+ }
1390
1487
  // Headers/footers we are emitting in this run.
1391
1488
  if (doc.headers) {
1392
1489
  let i = 1;
@@ -1430,6 +1527,15 @@ async function _packageDocxInner(doc, options) {
1430
1527
  }
1431
1528
  }
1432
1529
  }
1530
+ // OLE embedded objects emitted by this run.
1531
+ if (doc.oleObjects) {
1532
+ for (const ole of doc.oleObjects) {
1533
+ reservedExact.add(ole.path);
1534
+ if (ole.previewPath) {
1535
+ reservedExact.add(ole.previewPath);
1536
+ }
1537
+ }
1538
+ }
1433
1539
  for (const part of doc.opaqueParts) {
1434
1540
  if (reservedExact.has(part.path)) {
1435
1541
  throw new DocxWriteError(`Opaque part path "${part.path}" conflicts with a part the packager ` +
@@ -0,0 +1,28 @@
1
+ /**
2
+ * DOCX Module — Glossary (Building Blocks) Part Writer
3
+ *
4
+ * Serialises a {@link GlossaryDocument} into the canonical
5
+ * `word/glossary/document.xml` OOXML form:
6
+ *
7
+ * ```xml
8
+ * <w:glossaryDocument>
9
+ * <w:docParts>
10
+ * <w:docPart>
11
+ * <w:docPartPr>
12
+ * <w:name w:val="…"/>
13
+ * <w:category><w:name w:val="…"/><w:gallery w:val="…"/></w:category>
14
+ * <w:behaviors><w:behavior w:val="content"/></w:behaviors>
15
+ * <w:guid w:val="{…}"/>
16
+ * </w:docPartPr>
17
+ * <w:docPartBody>… body content …</w:docPartBody>
18
+ * </w:docPart>
19
+ * </w:docParts>
20
+ * </w:glossaryDocument>
21
+ * ```
22
+ *
23
+ * Lives in the writer layer (not `advanced/`) so the packager can depend on
24
+ * it without creating a `advanced/ → writer/` import cycle.
25
+ */
26
+ import type { GlossaryDocument } from "../types.js";
27
+ /** Render a {@link GlossaryDocument} to a `word/glossary/document.xml` string. */
28
+ export declare function renderGlossaryDocument(glossary: GlossaryDocument): string;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * DOCX Module — Glossary (Building Blocks) Part Writer
3
+ *
4
+ * Serialises a {@link GlossaryDocument} into the canonical
5
+ * `word/glossary/document.xml` OOXML form:
6
+ *
7
+ * ```xml
8
+ * <w:glossaryDocument>
9
+ * <w:docParts>
10
+ * <w:docPart>
11
+ * <w:docPartPr>
12
+ * <w:name w:val="…"/>
13
+ * <w:category><w:name w:val="…"/><w:gallery w:val="…"/></w:category>
14
+ * <w:behaviors><w:behavior w:val="content"/></w:behaviors>
15
+ * <w:guid w:val="{…}"/>
16
+ * </w:docPartPr>
17
+ * <w:docPartBody>… body content …</w:docPartBody>
18
+ * </w:docPart>
19
+ * </w:docParts>
20
+ * </w:glossaryDocument>
21
+ * ```
22
+ *
23
+ * Lives in the writer layer (not `advanced/`) so the packager can depend on
24
+ * it without creating a `advanced/ → writer/` import cycle.
25
+ */
26
+ import { XmlWriter } from "../../xml/writer.js";
27
+ import { DOCUMENT_NAMESPACES, STD_DOC_ATTRIBUTES } from "../constants.js";
28
+ import { renderBodyContent } from "./document-writer.js";
29
+ /**
30
+ * Map the friendly {@link BuildingBlockGallery} token to the OOXML
31
+ * `ST_DocPartGallery` value used in `<w:gallery w:val="…">` (ECMA-376
32
+ * §17.18.23). Values MUST be exact enum members — Word rejects (and silently
33
+ * discards) the entire glossary if it sees an out-of-enum gallery value.
34
+ * There is no plain "quickParts" value; Quick Parts map to "custQuickParts".
35
+ */
36
+ const GALLERY_TO_OOXML = {
37
+ autoText: "autoTxt",
38
+ quickParts: "custQuickParts",
39
+ coverPages: "coverPg",
40
+ tableOfContents: "tblOfContents",
41
+ headers: "hdrs",
42
+ footers: "ftrs",
43
+ pageNumbers: "pgNum",
44
+ tables: "tbls",
45
+ textBoxes: "txtBox",
46
+ watermarks: "watermarks",
47
+ equations: "eq",
48
+ bibliographies: "bib",
49
+ custom1: "custom1",
50
+ custom2: "custom2",
51
+ custom3: "custom3",
52
+ custom4: "custom4",
53
+ custom5: "custom5"
54
+ };
55
+ /** Render a {@link GlossaryDocument} to a `word/glossary/document.xml` string. */
56
+ export function renderGlossaryDocument(glossary) {
57
+ // Byte-faithful round-trip: a glossary read from an existing document is
58
+ // carried as verbatim XML and re-emitted unchanged.
59
+ if (glossary.rawXml) {
60
+ return glossary.rawXml;
61
+ }
62
+ const writer = new XmlWriter();
63
+ renderGlossary(writer, glossary);
64
+ return writer.xml;
65
+ }
66
+ function renderGlossary(xml, glossary) {
67
+ xml.openXml(STD_DOC_ATTRIBUTES);
68
+ xml.openNode("w:glossaryDocument", DOCUMENT_NAMESPACES);
69
+ xml.openNode("w:docParts");
70
+ for (const block of glossary.blocks) {
71
+ renderDocPart(xml, block);
72
+ }
73
+ xml.closeNode(); // w:docParts
74
+ xml.closeNode(); // w:glossaryDocument
75
+ }
76
+ function renderDocPart(xml, block) {
77
+ xml.openNode("w:docPart");
78
+ // CT_DocPartPr — child order is fixed by the schema (ECMA-376 §17.12.1):
79
+ // name → style → category → types → behaviors → description → guid.
80
+ // Emitting these out of order makes Word reject the package on open.
81
+ xml.openNode("w:docPartPr");
82
+ xml.leafNode("w:name", { "w:val": block.name });
83
+ xml.openNode("w:category");
84
+ xml.leafNode("w:name", { "w:val": block.category ?? "General" });
85
+ xml.leafNode("w:gallery", { "w:val": GALLERY_TO_OOXML[block.gallery] ?? "placeholder" });
86
+ xml.closeNode(); // w:category
87
+ // `<w:types>` is optional; we omit it so we never emit an out-of-enum
88
+ // ST_DocPartType value. A docPart placed in the body inserts its content.
89
+ xml.openNode("w:behaviors");
90
+ xml.leafNode("w:behavior", { "w:val": "content" });
91
+ xml.closeNode(); // w:behaviors
92
+ if (block.description) {
93
+ xml.leafNode("w:description", { "w:val": block.description });
94
+ }
95
+ if (block.guid) {
96
+ xml.leafNode("w:guid", { "w:val": normaliseGuid(block.guid) });
97
+ }
98
+ xml.closeNode(); // w:docPartPr
99
+ // docPartBody — reuse the main body renderer. A fresh render context keeps
100
+ // id counters local; building-block content here is plain text/tables so it
101
+ // does not need image/chart rId remapping.
102
+ xml.openNode("w:docPartBody");
103
+ for (const item of block.content) {
104
+ renderBodyContent(xml, item);
105
+ }
106
+ // CT_Body must end with a paragraph; emit one if the block had no content.
107
+ if (block.content.length === 0) {
108
+ xml.openNode("w:p");
109
+ xml.closeNode();
110
+ }
111
+ xml.closeNode(); // w:docPartBody
112
+ xml.closeNode(); // w:docPart
113
+ }
114
+ /** Word expects the docPart guid wrapped in braces: `{XXXXXXXX-…}`. */
115
+ function normaliseGuid(guid) {
116
+ const trimmed = guid.trim();
117
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
118
+ return trimmed;
119
+ }
120
+ return `{${trimmed}}`;
121
+ }