@stll/folio-core 0.32.2 → 0.33.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 (227) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
  2. package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
  3. package/dist/ai-edits/apply.d.ts +49 -3
  4. package/dist/ai-edits/apply.js +652 -86
  5. package/dist/ai-edits/blockRange.d.ts +7 -3
  6. package/dist/ai-edits/blockRange.js +14 -6
  7. package/dist/ai-edits/headless.d.ts +39 -5
  8. package/dist/ai-edits/headless.js +76 -17
  9. package/dist/ai-edits/index.d.ts +6 -6
  10. package/dist/ai-edits/index.js +3 -3
  11. package/dist/ai-edits/read.d.ts +4 -0
  12. package/dist/ai-edits/read.js +30 -6
  13. package/dist/ai-edits/scoped-reading.js +4 -3
  14. package/dist/ai-edits/snapshot.d.ts +42 -2
  15. package/dist/ai-edits/snapshot.js +127 -62
  16. package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
  17. package/dist/ai-edits/table-row-column-mutations.js +98 -6
  18. package/dist/ai-edits/table-targets.d.ts +12 -3
  19. package/dist/ai-edits/table-targets.js +15 -5
  20. package/dist/ai-edits/types.d.ts +219 -15
  21. package/dist/ai-edits/word-diff.d.ts +54 -11
  22. package/dist/ai-edits/word-diff.js +221 -61
  23. package/dist/compare/__fixtures__/body-sequence.d.ts +56 -0
  24. package/dist/compare/__fixtures__/body-sequence.js +136 -0
  25. package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
  26. package/dist/compare/__fixtures__/nested-table.js +50 -0
  27. package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
  28. package/dist/compare/__fixtures__/numbered-list.js +85 -0
  29. package/dist/compare/column-alignment.d.ts +27 -0
  30. package/dist/compare/column-alignment.js +162 -0
  31. package/dist/compare/compare.d.ts +107 -0
  32. package/dist/compare/compare.js +412 -0
  33. package/dist/compare/formatting.d.ts +29 -0
  34. package/dist/compare/formatting.js +84 -0
  35. package/dist/compare/plan.d.ts +22 -0
  36. package/dist/compare/plan.js +1141 -0
  37. package/dist/compare/reproducible-package.d.ts +21 -0
  38. package/dist/compare/reproducible-package.js +47 -0
  39. package/dist/compare/scenario.d.ts +77 -0
  40. package/dist/compare/scenario.js +263 -0
  41. package/dist/compare/types.d.ts +297 -0
  42. package/dist/compare/types.js +42 -0
  43. package/dist/compare/verification.d.ts +83 -0
  44. package/dist/compare/verification.js +189 -0
  45. package/dist/compat/eigenpal.d.ts +9 -6
  46. package/dist/compat/eigenpal.js +6 -3
  47. package/dist/controller/layoutPipeline.d.ts +13 -0
  48. package/dist/controller/layoutPipeline.js +15 -1
  49. package/dist/display-list/build/buildContext.d.ts +39 -0
  50. package/dist/display-list/build/buildContext.js +31 -0
  51. package/dist/display-list/build/buildDisplayList.d.ts +79 -0
  52. package/dist/display-list/build/buildDisplayList.js +283 -0
  53. package/dist/display-list/build/colors.d.ts +20 -0
  54. package/dist/display-list/build/colors.js +136 -0
  55. package/dist/display-list/build/floatingImages.d.ts +24 -0
  56. package/dist/display-list/build/floatingImages.js +41 -0
  57. package/dist/display-list/build/fontTable.d.ts +31 -0
  58. package/dist/display-list/build/fontTable.js +97 -0
  59. package/dist/display-list/build/furniture.d.ts +86 -0
  60. package/dist/display-list/build/furniture.js +98 -0
  61. package/dist/display-list/build/glyphs.d.ts +55 -0
  62. package/dist/display-list/build/glyphs.js +103 -0
  63. package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
  64. package/dist/display-list/build/headerFooterPrimitives.js +40 -0
  65. package/dist/display-list/build/imagePrimitives.d.ts +35 -0
  66. package/dist/display-list/build/imagePrimitives.js +264 -0
  67. package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
  68. package/dist/display-list/build/pageBorderPrimitives.js +111 -0
  69. package/dist/display-list/build/pageFurniture.d.ts +35 -0
  70. package/dist/display-list/build/pageFurniture.js +141 -0
  71. package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
  72. package/dist/display-list/build/paragraphPrimitives.js +819 -0
  73. package/dist/display-list/build/regions.d.ts +46 -0
  74. package/dist/display-list/build/regions.js +56 -0
  75. package/dist/display-list/build/storyPrimitives.d.ts +29 -0
  76. package/dist/display-list/build/storyPrimitives.js +279 -0
  77. package/dist/display-list/build/strokes.d.ts +19 -0
  78. package/dist/display-list/build/strokes.js +78 -0
  79. package/dist/display-list/build/tablePrimitives.d.ts +38 -0
  80. package/dist/display-list/build/tablePrimitives.js +409 -0
  81. package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
  82. package/dist/display-list/build/textBoxPrimitives.js +129 -0
  83. package/dist/display-list/build/textDecorations.d.ts +29 -0
  84. package/dist/display-list/build/textDecorations.js +29 -0
  85. package/dist/display-list/build/unsupported.d.ts +57 -0
  86. package/dist/display-list/build/unsupported.js +0 -0
  87. package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
  88. package/dist/display-list/build/watermarkPrimitives.js +128 -0
  89. package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
  90. package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
  91. package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
  92. package/dist/display-list/editor/displayListPagePainter.js +63 -0
  93. package/dist/display-list/editor/pageRenderer.d.ts +24 -0
  94. package/dist/display-list/editor/pageRenderer.js +36 -0
  95. package/dist/display-list/primitives.d.ts +95 -0
  96. package/dist/display-list/primitives.js +127 -0
  97. package/dist/display-list/types.d.ts +472 -0
  98. package/dist/display-list/types.js +0 -0
  99. package/dist/document-operations.d.ts +39 -8
  100. package/dist/document-operations.js +169 -16
  101. package/dist/document-stories.d.ts +16 -0
  102. package/dist/document-stories.js +48 -4
  103. package/dist/docx/hyperlinkParser.d.ts +9 -1
  104. package/dist/docx/hyperlinkParser.js +19 -13
  105. package/dist/docx/packageParts.d.ts +35 -0
  106. package/dist/docx/packageParts.js +120 -0
  107. package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
  108. package/dist/docx/paraIdRangeNormalization.js +64 -0
  109. package/dist/docx/paragraphParser.js +101 -26
  110. package/dist/docx/paragraphTraversal.js +1 -1
  111. package/dist/docx/renderedPageBreakNormalization.js +3 -1
  112. package/dist/docx/revisionIdNormalization.d.ts +27 -0
  113. package/dist/docx/revisionIdNormalization.js +121 -0
  114. package/dist/docx/rezip.d.ts +6 -0
  115. package/dist/docx/rezip.js +51 -19
  116. package/dist/docx/selectiveSave.js +2 -2
  117. package/dist/docx/serializer/commentSerializer.js +6 -1
  118. package/dist/docx/serializer/paragraphSerializer.js +73 -20
  119. package/dist/docx/serializer/runSerializer.js +11 -10
  120. package/dist/docx/serializer/tableSerializer.js +26 -10
  121. package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
  122. package/dist/docx/server/createBilingualDocx.js +3 -2
  123. package/dist/docx/streamingXmlParser.d.ts +13 -1
  124. package/dist/docx/streamingXmlParser.js +49 -11
  125. package/dist/docx/unzip.d.ts +1 -2
  126. package/dist/docx/unzip.js +25 -6
  127. package/dist/docx/xmlParser.d.ts +3 -1
  128. package/dist/docx/xmlParser.js +33 -27
  129. package/dist/export-pdf.d.ts +43 -0
  130. package/dist/export-pdf.js +98 -0
  131. package/dist/fonts/headlessMeasure.d.ts +76 -0
  132. package/dist/fonts/headlessMeasure.js +0 -0
  133. package/dist/fonts/sfnt/parse.d.ts +51 -0
  134. package/dist/fonts/sfnt/parse.js +525 -0
  135. package/dist/fonts/sfnt/subset.d.ts +20 -0
  136. package/dist/fonts/sfnt/subset.js +350 -0
  137. package/dist/fonts/sfnt/tables.d.ts +88 -0
  138. package/dist/fonts/sfnt/tables.js +131 -0
  139. package/dist/fonts/sfnt/woff.d.ts +14 -0
  140. package/dist/fonts/sfnt/woff.js +163 -0
  141. package/dist/generated/text_shaper.js +324 -0
  142. package/dist/generated/text_shaper_bg.wasm +0 -0
  143. package/dist/headless-layout.d.ts +75 -0
  144. package/dist/headless-layout.js +350 -0
  145. package/dist/index.d.ts +9 -6
  146. package/dist/index.js +6 -3
  147. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  148. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  149. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  150. package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
  151. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
  152. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
  153. package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
  154. package/dist/layout-engine/measure/advanceComposition.js +148 -0
  155. package/dist/layout-engine/measure/cache.d.ts +2 -0
  156. package/dist/layout-engine/measure/measureContainer.js +13 -0
  157. package/dist/layout-engine/measure/measureParagraph.js +1 -2
  158. package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
  159. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
  160. package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
  161. package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
  162. package/dist/layout-engine/tableIndentCompatibility.js +15 -0
  163. package/dist/layout-engine/types.d.ts +8 -0
  164. package/dist/layout-painter/renderPage.d.ts +29 -1
  165. package/dist/layout-painter/renderPage.js +47 -19
  166. package/dist/layout-painter/renderParagraph.d.ts +21 -2
  167. package/dist/layout-painter/renderParagraph.js +1 -1
  168. package/dist/layout-painter/renderTable.js +37 -4
  169. package/dist/managers/editorShortcuts.d.ts +33 -1
  170. package/dist/managers/editorShortcuts.js +20 -1
  171. package/dist/markdown/renderRuns.js +7 -2
  172. package/dist/markdown/renderTable.js +5 -1
  173. package/dist/model.d.ts +3 -3
  174. package/dist/model.js +2 -2
  175. package/dist/pdf/contentStream.d.ts +73 -0
  176. package/dist/pdf/contentStream.js +143 -0
  177. package/dist/pdf/fonts.d.ts +96 -0
  178. package/dist/pdf/fonts.js +593 -0
  179. package/dist/pdf/images.d.ts +28 -0
  180. package/dist/pdf/images.js +439 -0
  181. package/dist/pdf/objects.d.ts +93 -0
  182. package/dist/pdf/objects.js +237 -0
  183. package/dist/pdf/pageSpace.d.ts +43 -0
  184. package/dist/pdf/pageSpace.js +57 -0
  185. package/dist/pdf/paint.d.ts +32 -0
  186. package/dist/pdf/paint.js +442 -0
  187. package/dist/pdf/writePdf.d.ts +56 -0
  188. package/dist/pdf/writePdf.js +345 -0
  189. package/dist/prosemirror/commands/comments.js +96 -6
  190. package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
  191. package/dist/prosemirror/conversion/toProseDoc.js +41 -4
  192. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
  193. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
  194. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
  195. package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
  196. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
  197. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
  198. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
  199. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
  200. package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
  201. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
  202. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
  203. package/dist/prosemirror/plugins/suggestionMode.js +1 -0
  204. package/dist/prosemirror/revisionCarriers.js +19 -2
  205. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
  206. package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
  207. package/dist/prosemirror/validation.js +55 -27
  208. package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
  209. package/dist/prosemirror/zeroWidthAnchors.js +23 -0
  210. package/dist/redline.js +12 -79
  211. package/dist/server.d.ts +3 -3
  212. package/dist/server.js +2 -2
  213. package/dist/shaping/placeRun.d.ts +51 -0
  214. package/dist/shaping/placeRun.js +84 -0
  215. package/dist/shaping/shaper.d.ts +92 -0
  216. package/dist/shaping/shaper.js +102 -0
  217. package/dist/types/block-id.d.ts +18 -2
  218. package/dist/types/block-id.js +49 -5
  219. package/dist/types/content.d.ts +2 -2
  220. package/dist/utils/clipboard.js +4 -3
  221. package/dist/utils/fontResolver.js +183 -0
  222. package/dist/utils/htmlComments.d.ts +17 -0
  223. package/dist/utils/htmlComments.js +22 -0
  224. package/dist/utils/scriptSegments.d.ts +12 -1
  225. package/dist/utils/scriptSegments.js +17 -1
  226. package/dist/version-comparison.d.ts +1 -1
  227. package/package.json +7 -3
@@ -210,10 +210,8 @@ function serializeParagraphPropertyChange(change) {
210
210
  const normalizedPreviousPPr = previousPPrInner.length > 0 ? `<w:pPr>${previousPPrInner}</w:pPr>` : "<w:pPr/>";
211
211
  return `<w:pPrChange ${attrs.join(" ")}>${normalizedPreviousPPr}</w:pPrChange>`;
212
212
  }
213
- /**
214
- * Serialize a hyperlink (w:hyperlink)
215
- */
216
- function serializeHyperlink(hyperlink) {
213
+ /** The attribute list of a `w:hyperlink`, without its children. */
214
+ function hyperlinkAttributes(hyperlink) {
217
215
  const attrs = [];
218
216
  if (hyperlink.rId) attrs.push(`r:id="${escapeXml(hyperlink.rId)}"`);
219
217
  if (hyperlink.anchor) attrs.push(`w:anchor="${escapeXml(hyperlink.anchor)}"`);
@@ -222,12 +220,19 @@ function serializeHyperlink(hyperlink) {
222
220
  if (hyperlink.history === true) attrs.push("w:history=\"1\"");
223
221
  else if (hyperlink.history === false) attrs.push("w:history=\"0\"");
224
222
  if (hyperlink.docLocation) attrs.push(`w:docLocation="${escapeXml(hyperlink.docLocation)}"`);
225
- const childrenXml = hyperlink.children.map((child) => {
226
- if (child.type === "run") return serializeRun(child);
227
- if (child.type === "bookmarkStart") return serializeBookmarkStart(child);
228
- return serializeBookmarkEnd(child);
229
- }).join("");
230
- return `<w:hyperlink${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${childrenXml}</w:hyperlink>`;
223
+ return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
224
+ }
225
+ /** One `w:hyperlink` child, with the caller deciding how a run is written. */
226
+ function serializeHyperlinkChild(child, serializeChildRun) {
227
+ if (child.type === "run") return serializeChildRun(child);
228
+ return child.type === "bookmarkStart" ? serializeBookmarkStart(child) : serializeBookmarkEnd(child);
229
+ }
230
+ /**
231
+ * Serialize a hyperlink (w:hyperlink)
232
+ */
233
+ function serializeHyperlink(hyperlink) {
234
+ const childrenXml = hyperlink.children.map((child) => serializeHyperlinkChild(child, serializeRun)).join("");
235
+ return `<w:hyperlink${hyperlinkAttributes(hyperlink)}>${childrenXml}</w:hyperlink>`;
231
236
  }
232
237
  /**
233
238
  * Serialize bookmark start (w:bookmarkStart)
@@ -368,6 +373,14 @@ function serializeMoveRangeStart(tag, marker) {
368
373
  function rewriteRunTextAsDeleted(xml) {
369
374
  return xml.replace(/<w:t\b/gu, "<w:delText").replace(/<\/w:t>/gu, "</w:delText>").replace(/<w:instrText\b/gu, "<w:delInstrText").replace(/<\/w:instrText>/gu, "</w:delInstrText>");
370
375
  }
376
+ function trackedChangeTag(change) {
377
+ switch (change.type) {
378
+ case "insertion": return "ins";
379
+ case "deletion": return "del";
380
+ case "moveFrom": return "moveFrom";
381
+ case "moveTo": return "moveTo";
382
+ }
383
+ }
371
384
  function serializeTrackedChange(tag, change) {
372
385
  const info = change.info;
373
386
  const normalizedId = normalizeRevisionId(info.id);
@@ -389,14 +402,39 @@ function serializeTrackedChange(tag, change) {
389
402
  return rewriteRunTextAsDeleted(contentXml);
390
403
  }).join("");
391
404
  };
392
- const contentXml = change.content.map((item) => {
393
- if (item.type === "run") {
394
- if (tag === "del" || tag === "moveFrom") return serializeDeletedRun(item);
395
- return serializeRun(item);
405
+ const serializeContentRun = (run) => tag === "del" || tag === "moveFrom" ? serializeDeletedRun(run) : serializeRun(run);
406
+ const serializeWrappedItem = (item) => {
407
+ if (item.type === "run") return serializeContentRun(item);
408
+ if (item.type === "simpleField" || item.type === "complexField") {
409
+ const xml = item.type === "simpleField" ? serializeSimpleField(item) : serializeComplexField(item);
410
+ return tag === "del" || tag === "moveFrom" ? rewriteRunTextAsDeleted(xml) : xml;
396
411
  }
397
- return serializeHyperlink(item);
398
- }).join("");
399
- return `<w:${tag} ${attrs.join(" ")}>${contentXml}</w:${tag}>`;
412
+ if (item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo") return serializeTrackedChange(trackedChangeTag(item), item);
413
+ return item.type === "bookmarkStart" ? serializeBookmarkStart(item) : serializeBookmarkEnd(item);
414
+ };
415
+ const open = `<w:${tag} ${attrs.join(" ")}>`;
416
+ const close = `</w:${tag}>`;
417
+ const wrap = (inner) => inner.length === 0 ? "" : `${open}${inner}${close}`;
418
+ if (change.content.length === 0) return `${open}${close}`;
419
+ const segments = [];
420
+ const pending = [];
421
+ const flushPending = () => {
422
+ if (pending.length > 0) {
423
+ segments.push(wrap(pending.join("")));
424
+ pending.length = 0;
425
+ }
426
+ };
427
+ for (const item of change.content) {
428
+ if (item.type === "hyperlink") {
429
+ flushPending();
430
+ const childrenXml = item.children.map((child) => serializeHyperlinkChild(child, serializeContentRun)).join("");
431
+ segments.push(`<w:hyperlink${hyperlinkAttributes(item)}>${open}${childrenXml}${close}</w:hyperlink>`);
432
+ continue;
433
+ }
434
+ pending.push(serializeWrappedItem(item));
435
+ }
436
+ flushPending();
437
+ return segments.join("");
400
438
  }
401
439
  /** Emit the `<w:commentReference>` run Word places after a comment range end. */
402
440
  function serializeCommentReferenceRun(id) {
@@ -470,12 +508,27 @@ function serializeParagraph(paragraph) {
470
508
  return `<w:p${attrsStr}>${parts.join("")}</w:p>`;
471
509
  }
472
510
  function injectRenderedPageBreakIntoFirstRun(xml) {
473
- const runOpeningTag = /<w:r(?=[\s>/])[^>]*>/u;
474
- const openingTag = runOpeningTag.exec(xml);
511
+ const openingTag = /<w:r(?=[\s>/])[^>]*>/u.exec(xml);
475
512
  if (!openingTag) return null;
476
513
  const runEnd = xml.indexOf("</w:r>", openingTag.index + openingTag[0].length);
477
514
  if (runEnd !== -1 && xml.slice(openingTag.index + openingTag[0].length, runEnd).includes("<w:lastRenderedPageBreak/>")) return xml;
478
- return xml.replace(runOpeningTag, (match) => `${match}<w:lastRenderedPageBreak/>`);
515
+ const contentStart = openingTag.index + openingTag[0].length;
516
+ let insertionOffset = contentStart;
517
+ if (xml.startsWith("<w:rPr", contentStart)) {
518
+ const propertyTag = /<\/?w:rPr(?=[\s>/])[^>]*>/gu;
519
+ propertyTag.lastIndex = contentStart;
520
+ let depth = 0;
521
+ for (const match of xml.matchAll(propertyTag)) {
522
+ if (match.index !== contentStart && depth === 0) break;
523
+ if (match[0].startsWith("</")) depth--;
524
+ else if (!match[0].endsWith("/>")) depth++;
525
+ if (depth === 0) {
526
+ insertionOffset = match.index + match[0].length;
527
+ break;
528
+ }
529
+ }
530
+ }
531
+ return `${xml.slice(0, insertionOffset)}<w:lastRenderedPageBreak/>${xml.slice(insertionOffset)}`;
479
532
  }
480
533
  /**
481
534
  * Serialize multiple paragraphs to OOXML XML
@@ -92,13 +92,6 @@ function serializeTextFormatting(formatting) {
92
92
  if (formatting.fontFamily.csTheme) fontAttrs.push(`w:cstheme="${escapeXml(formatting.fontFamily.csTheme)}"`);
93
93
  if (fontAttrs.length > 0) parts.push(`<w:rFonts ${fontAttrs.join(" ")}/>`);
94
94
  }
95
- if (formatting.language) {
96
- const languageAttrs = [];
97
- if (formatting.language.val) languageAttrs.push(`w:val="${escapeXml(formatting.language.val)}"`);
98
- if (formatting.language.eastAsia) languageAttrs.push(`w:eastAsia="${escapeXml(formatting.language.eastAsia)}"`);
99
- if (formatting.language.bidi) languageAttrs.push(`w:bidi="${escapeXml(formatting.language.bidi)}"`);
100
- if (languageAttrs.length > 0) parts.push(`<w:lang ${languageAttrs.join(" ")}/>`);
101
- }
102
95
  if (formatting.bold === true) parts.push("<w:b/>");
103
96
  else if (formatting.bold === false) parts.push("<w:b w:val=\"0\"/>");
104
97
  if (formatting.boldCs === true) parts.push("<w:bCs/>");
@@ -134,11 +127,12 @@ function serializeTextFormatting(formatting) {
134
127
  if (formatting.position !== void 0) parts.push(`<w:position w:val="${intAttr(formatting.position)}"/>`);
135
128
  if (formatting.fontSize !== void 0) parts.push(`<w:sz w:val="${intAttr(formatting.fontSize)}"/>`);
136
129
  if (formatting.fontSizeCs !== void 0) parts.push(`<w:szCs w:val="${intAttr(formatting.fontSizeCs)}"/>`);
130
+ let customHighlightShadingXml = "";
137
131
  if (formatting.highlight) {
138
132
  if (VALID_HIGHLIGHT_COLORS.has(formatting.highlight)) parts.push(`<w:highlight w:val="${formatting.highlight}"/>`);
139
133
  else if (!formatting.shading) {
140
134
  const hex = formatting.highlight.replace(/^#/u, "");
141
- if (/^[0-9a-fA-F]{6}$/u.test(hex)) parts.push(`<w:shd w:val="clear" w:color="auto" w:fill="${hex}"/>`);
135
+ if (/^[0-9a-fA-F]{6}$/u.test(hex)) customHighlightShadingXml = `<w:shd w:val="clear" w:color="auto" w:fill="${hex}"/>`;
142
136
  }
143
137
  }
144
138
  if (formatting.underline) {
@@ -152,14 +146,21 @@ function serializeTextFormatting(formatting) {
152
146
  parts.push(`<w:u ${uAttrs.join(" ")}/>`);
153
147
  }
154
148
  if (formatting.effect && formatting.effect !== "none") parts.push(`<w:effect w:val="${formatting.effect}"/>`);
155
- if (formatting.emphasisMark) parts.push(`<w:em w:val="${formatting.emphasisMark}"/>`);
156
- const shadingXml = serializeShading(formatting.shading);
149
+ const shadingXml = serializeShading(formatting.shading) || customHighlightShadingXml;
157
150
  if (shadingXml) parts.push(shadingXml);
158
151
  if (formatting.vertAlign) parts.push(`<w:vertAlign w:val="${formatting.vertAlign}"/>`);
159
152
  if (formatting.rtl === true) parts.push("<w:rtl/>");
160
153
  else if (formatting.rtl === false) parts.push("<w:rtl w:val=\"0\"/>");
161
154
  if (formatting.cs === true) parts.push("<w:cs/>");
162
155
  else if (formatting.cs === false) parts.push("<w:cs w:val=\"0\"/>");
156
+ if (formatting.emphasisMark) parts.push(`<w:em w:val="${formatting.emphasisMark}"/>`);
157
+ if (formatting.language) {
158
+ const languageAttrs = [];
159
+ if (formatting.language.val) languageAttrs.push(`w:val="${escapeXml(formatting.language.val)}"`);
160
+ if (formatting.language.eastAsia) languageAttrs.push(`w:eastAsia="${escapeXml(formatting.language.eastAsia)}"`);
161
+ if (formatting.language.bidi) languageAttrs.push(`w:bidi="${escapeXml(formatting.language.bidi)}"`);
162
+ if (languageAttrs.length > 0) parts.push(`<w:lang ${languageAttrs.join(" ")}/>`);
163
+ }
163
164
  if (parts.length === 0) return "";
164
165
  return `<w:rPr>${parts.join("")}</w:rPr>`;
165
166
  }
@@ -250,7 +250,8 @@ function serializeTableCellFormatting(formatting, propertyChanges, structuralCha
250
250
  if (formatting.textDirection) parts.push(`<w:textDirection w:val="${formatting.textDirection}"/>`);
251
251
  if (formatting.fitText) parts.push("<w:tcFitText/>");
252
252
  if (formatting.verticalAlign) parts.push(`<w:vAlign w:val="${formatting.verticalAlign}"/>`);
253
- if (formatting.hideMark !== void 0) parts.push(formatting.hideMark ? "<w:hideMark/>" : "<w:hideMark w:val=\"false\"/>");
253
+ if (formatting.hideMark === true) parts.push("<w:hideMark/>");
254
+ else if (formatting.hideMark === false) parts.push("<w:hideMark w:val=\"off\"/>");
254
255
  }
255
256
  if (structuralChange) {
256
257
  if (structuralChange.type === "tableCellInsertion") parts.push(`<w:cellIns ${serializeTrackedChangeAttributes(structuralChange.info)}/>`);
@@ -276,11 +277,30 @@ function serializeTableCellPropertyChange(change) {
276
277
  return `<w:tcPrChange ${attrs}>${previousTcPrInner.length > 0 ? `<w:tcPr>${previousTcPrInner}</w:tcPr>` : "<w:tcPr/>"}</w:tcPrChange>`;
277
278
  }
278
279
  /**
279
- * Serialize table grid (w:tblGrid)
280
+ * Columns the grid has to declare: the widest row's span total, because a grid
281
+ * narrower than a row leaves cells with no column to sit in.
280
282
  */
281
- function serializeTableGrid(columnWidths) {
282
- if (!columnWidths || columnWidths.length === 0) return "";
283
- return `<w:tblGrid>${columnWidths.map((w) => `<w:gridCol w:w="${intAttr(w)}"/>`).join("")}</w:tblGrid>`;
283
+ function gridColumnCount(table) {
284
+ let widest = 0;
285
+ for (const row of table.rows) {
286
+ let columns = 0;
287
+ for (const cell of row.cells) columns += cell.formatting?.gridSpan ?? 1;
288
+ widest = Math.max(widest, columns);
289
+ }
290
+ return widest;
291
+ }
292
+ /**
293
+ * Serialize table grid (w:tblGrid).
294
+ *
295
+ * `w:tblGrid` is required on every `w:tbl` and `w:w` is optional on a
296
+ * `w:gridCol`, so a table whose column widths were never measured — one the
297
+ * comparison creates, say — still declares a grid, just without widths.
298
+ */
299
+ function serializeTableGrid(table) {
300
+ const columnWidths = table.columnWidths;
301
+ if (columnWidths && columnWidths.length > 0) return `<w:tblGrid>${columnWidths.map((w) => `<w:gridCol w:w="${intAttr(w)}"/>`).join("")}</w:tblGrid>`;
302
+ const columns = gridColumnCount(table);
303
+ return columns === 0 ? "<w:tblGrid/>" : `<w:tblGrid>${"<w:gridCol/>".repeat(columns)}</w:tblGrid>`;
284
304
  }
285
305
  /**
286
306
  * Serialize cell content (paragraphs, nested tables)
@@ -319,11 +339,7 @@ function serializeTableRow(row, serializeParagraph) {
319
339
  * @returns XML string for the table
320
340
  */
321
341
  function serializeTable(table, serializeParagraph) {
322
- const parts = [];
323
- const tblPrXml = serializeTableFormatting(table.formatting, table.propertyChanges);
324
- if (tblPrXml) parts.push(tblPrXml);
325
- const tblGridXml = serializeTableGrid(table.columnWidths);
326
- if (tblGridXml) parts.push(tblGridXml);
342
+ const parts = [serializeTableFormatting(table.formatting, table.propertyChanges) || "<w:tblPr/>", serializeTableGrid(table)];
327
343
  for (const row of table.rows) parts.push(serializeTableRow(row, serializeParagraph));
328
344
  return `<w:tbl>${parts.join("")}</w:tbl>`;
329
345
  }
@@ -2,8 +2,8 @@ import { loadDocxArchive } from "./boundedArchive.js";
2
2
  import { FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, InvalidFolioDocxXmlPatchProposalError, evaluateDocxXmlPatchProposal, parseFolioDocxXmlPatchProposal } from "./evaluateDocxXmlPatchProposal.js";
3
3
  import { FOLIO_DOCX_CONFORMANCE_PROFILE, validateDocxConformance } from "./validateDocxConformance.js";
4
4
  import { TaggedError, panic } from "better-result";
5
- import JSZip from "jszip";
6
5
  import { createHash } from "node:crypto";
6
+ import JSZip from "jszip";
7
7
  //#region src/docx/server/applyDocxXmlPatchProposal.ts
8
8
  const FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION = 1;
9
9
  const FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE = "folio-xml-patch-application-v1";
@@ -1,4 +1,5 @@
1
1
  import { FolioDocxReviewer } from "../../ai-edits/headless.js";
2
+ import { isFolioAIContentBlock } from "../../ai-edits/snapshot.js";
2
3
  import { toArrayBuffer } from "../../utils/docxInput.js";
3
4
  import { ensureParaIds } from "../ensureParaIds.js";
4
5
  import { parseDocx } from "../parser.js";
@@ -14,7 +15,7 @@ import { createBilingualDocument, readBilingualDocument } from "./createBilingua
14
15
  */
15
16
  async function createBilingualDocx(input, options) {
16
17
  const stampedBuffer = await toArrayBuffer((await ensureParaIds(input)).docx);
17
- const editableParagraphIds = new Set((await FolioDocxReviewer.fromBuffer(stampedBuffer)).snapshot().blocks.map(({ id }) => id));
18
+ const editableParagraphIds = new Set((await FolioDocxReviewer.fromBuffer(stampedBuffer)).snapshot().blocks.filter(isFolioAIContentBlock).map(({ id }) => id));
18
19
  const { document, warnings } = createBilingualDocument(await parseDocx(stampedBuffer, { preloadFonts: false }), {
19
20
  ...options,
20
21
  editableParagraphIds
@@ -29,7 +30,7 @@ async function createBilingualDocx(input, options) {
29
30
  /** Bytes-in form of {@link readBilingualDocument}. */
30
31
  async function readBilingualDocx(input) {
31
32
  const buffer = await toArrayBuffer(input);
32
- return readBilingualDocument(await parseDocx(buffer, { preloadFonts: false }), new Set((await FolioDocxReviewer.fromBuffer(buffer)).snapshot().blocks.map(({ id }) => id)));
33
+ return readBilingualDocument(await parseDocx(buffer, { preloadFonts: false }), new Set((await FolioDocxReviewer.fromBuffer(buffer)).snapshot().blocks.filter(isFolioAIContentBlock).map(({ id }) => id)));
33
34
  }
34
35
  //#endregion
35
36
  export { createBilingualDocx, readBilingualDocx };
@@ -11,6 +11,18 @@ type ParseXmlResult = {
11
11
  * pass. Unsupported or malformed constructs return a sentinel so callers can
12
12
  * retain the general-purpose parser as a compatibility fallback.
13
13
  */
14
+ type AttributeValueSpan = {
15
+ start: number;
16
+ end: number;
17
+ };
18
+ type OpenTagVisitor = (element: XmlElement, attributeValueSpans: ReadonlyMap<string, AttributeValueSpan>) => ReadonlyMap<string, string> | null;
14
19
  declare const parseStreamingXml: (xml: string) => ParseXmlResult;
20
+ /** Rewrite selected decimal attribute values while preserving every other source byte. */
21
+ declare const rewriteStreamingXmlDecimalAttributes: (xml: string, visitOpenTag: OpenTagVisitor) => {
22
+ status: "rewritten";
23
+ value: string;
24
+ } | {
25
+ status: "unsupported";
26
+ };
15
27
  //#endregion
16
- export { parseStreamingXml };
28
+ export { parseStreamingXml, rewriteStreamingXmlDecimalAttributes };
@@ -1,3 +1,4 @@
1
+ import { attachXmlNamespaceContext } from "./xmlParser.js";
1
2
  import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
2
3
  //#region src/docx/streamingXmlParser.ts
3
4
  const BUILT_IN_ENTITIES = {
@@ -7,14 +8,10 @@ const BUILT_IN_ENTITIES = {
7
8
  lt: "<",
8
9
  quot: "\""
9
10
  };
10
- /**
11
- * Parse ordinary OOXML into Folio's existing element representation in one
12
- * pass. Unsupported or malformed constructs return a sentinel so callers can
13
- * retain the general-purpose parser as a compatibility fallback.
14
- */
15
- const parseStreamingXml = (xml) => {
11
+ const parseStreamingXmlInternal = (xml, visitOpenTag) => {
16
12
  const root = { elements: [] };
17
13
  const stack = [];
14
+ const replacements = [];
18
15
  let cursor = 0;
19
16
  let mergeAdjacentText = false;
20
17
  while (cursor < xml.length) {
@@ -55,9 +52,19 @@ const parseStreamingXml = (xml) => {
55
52
  mergeAdjacentText = false;
56
53
  continue;
57
54
  }
58
- const parsedTag = parseOpenTag(xml, open + 1, close);
55
+ const parsedTag = parseOpenTag(xml, open + 1, close, visitOpenTag !== void 0);
59
56
  if (parsedTag.status === "unsupported") return parsedTag;
60
57
  const parent = stack.at(-1)?.element ?? root;
58
+ attachXmlNamespaceContext(parsedTag.element, parent.namespaceScope);
59
+ const rewritten = visitOpenTag?.(parsedTag.element, parsedTag.attributeValueSpans ?? /* @__PURE__ */ new Map());
60
+ if (rewritten) for (const [attributeName, value] of rewritten) {
61
+ const span = parsedTag.attributeValueSpans?.get(attributeName);
62
+ if (!span) return { status: "unsupported" };
63
+ replacements.push({
64
+ ...span,
65
+ value
66
+ });
67
+ }
61
68
  appendElement(parent, parsedTag.element);
62
69
  if (!parsedTag.selfClosing) {
63
70
  if (stack.length >= FOLIO_XML_RESOURCE_LIMITS.maxDepth) return { status: "unsupported" };
@@ -72,10 +79,35 @@ const parseStreamingXml = (xml) => {
72
79
  if (stack.length > 0) return { status: "unsupported" };
73
80
  return {
74
81
  status: "parsed",
75
- value: root
82
+ value: root,
83
+ replacements
84
+ };
85
+ };
86
+ const parseStreamingXml = (xml) => {
87
+ const parsed = parseStreamingXmlInternal(xml);
88
+ return parsed.status === "parsed" ? {
89
+ status: "parsed",
90
+ value: parsed.value
91
+ } : { status: "unsupported" };
92
+ };
93
+ /** Rewrite selected decimal attribute values while preserving every other source byte. */
94
+ const rewriteStreamingXmlDecimalAttributes = (xml, visitOpenTag) => {
95
+ const parsed = parseStreamingXmlInternal(xml, visitOpenTag);
96
+ if (parsed.status === "unsupported") return parsed;
97
+ const chunks = [];
98
+ let cursor = 0;
99
+ for (const replacement of parsed.replacements.toSorted((left, right) => left.start - right.start)) {
100
+ if (!/^\d+$/u.test(replacement.value)) return { status: "unsupported" };
101
+ chunks.push(xml.slice(cursor, replacement.start), replacement.value);
102
+ cursor = replacement.end;
103
+ }
104
+ chunks.push(xml.slice(cursor));
105
+ return {
106
+ status: "rewritten",
107
+ value: chunks.join("")
76
108
  };
77
109
  };
78
- const parseOpenTag = (xml, start, close) => {
110
+ const parseOpenTag = (xml, start, close, captureAttributeSpans) => {
79
111
  let cursor = skipWhitespace(xml, start, close);
80
112
  const nameStart = cursor;
81
113
  cursor = scanName(xml, cursor, close);
@@ -83,6 +115,7 @@ const parseOpenTag = (xml, start, close) => {
83
115
  const name = xml.slice(nameStart, cursor);
84
116
  if (isUnsafePropertyName(name)) return { status: "unsupported" };
85
117
  let attributes;
118
+ const attributeValueSpans = captureAttributeSpans ? /* @__PURE__ */ new Map() : void 0;
86
119
  let selfClosing = false;
87
120
  while (cursor < close) {
88
121
  cursor = skipWhitespace(xml, cursor, close);
@@ -110,6 +143,10 @@ const parseOpenTag = (xml, start, close) => {
110
143
  if (decoded === null) return { status: "unsupported" };
111
144
  attributes ??= {};
112
145
  attributes[attributeName] = decoded;
146
+ attributeValueSpans?.set(attributeName, {
147
+ start: valueStart,
148
+ end: cursor
149
+ });
113
150
  cursor += 1;
114
151
  }
115
152
  const element = {
@@ -121,7 +158,8 @@ const parseOpenTag = (xml, start, close) => {
121
158
  status: "parsed",
122
159
  element,
123
160
  name,
124
- selfClosing
161
+ selfClosing,
162
+ attributeValueSpans
125
163
  };
126
164
  };
127
165
  const appendElement = (parent, child) => {
@@ -246,4 +284,4 @@ const isUnsafePropertyName = (name) => {
246
284
  }
247
285
  };
248
286
  //#endregion
249
- export { parseStreamingXml };
287
+ export { parseStreamingXml, rewriteStreamingXmlDecimalAttributes };
@@ -59,7 +59,6 @@ type RawDocxContent = {
59
59
  * @returns Promise resolving to extracted content
60
60
  */
61
61
  declare function unzipDocx(buffer: ArrayBuffer, options?: DocxUnzipOptions): Promise<RawDocxContent>;
62
- declare function isPreservableDocxEntry(path: string): boolean;
63
62
  /**
64
63
  * Get a list of all files in the DOCX
65
64
  *
@@ -120,4 +119,4 @@ declare function getContentSummary(content: RawDocxContent): {
120
119
  totalFiles: number;
121
120
  };
122
121
  //#endregion
123
- export { DocxSecurityError, DocxUnzipLimits, DocxUnzipOptions, RawDocxContent, extractFile, getContentSummary, getFileList, getMediaMimeType, hasFile, isPreservableDocxEntry, mediaToDataUrl, unzipDocx };
122
+ export { DocxSecurityError, DocxUnzipLimits, DocxUnzipOptions, RawDocxContent, extractFile, getContentSummary, getFileList, getMediaMimeType, hasFile, mediaToDataUrl, unzipDocx };
@@ -137,13 +137,13 @@ async function unzipDocx(buffer, options = {}) {
137
137
  const extractionTasks = [];
138
138
  for (const [path, file] of entries) {
139
139
  if (!isSafeDocxPath(path)) throw new DocxSecurityError("DOCX file contains an unsafe entry path");
140
- if (!isPreservableDocxEntry(path)) continue;
141
- const lowerPath = path.toLowerCase();
142
140
  const declaredSize = getEntryUncompressedSize(file);
143
141
  if (declaredSize !== null) {
144
142
  totalUncompressedBytes += declaredSize;
145
143
  if (totalUncompressedBytes > limits.maxTotalUncompressedBytes) throw new DocxSecurityError("DOCX file expands beyond the maximum allowed size");
146
144
  }
145
+ if (!isParsedDocxEntry(path)) continue;
146
+ const lowerPath = path.toLowerCase();
147
147
  if (lowerPath.endsWith(".xml") || lowerPath.endsWith(".rels")) {
148
148
  assertEntrySize(path, declaredSize, limits.maxXmlBytes);
149
149
  if (options.extractAllXml === false && !shouldExtractXmlPart(lowerPath)) continue;
@@ -328,9 +328,28 @@ function isSafeDocxPath(path) {
328
328
  if (!path || path.startsWith("/") || path.includes("\\")) return false;
329
329
  return !path.split("/").some((part) => part === "..");
330
330
  }
331
- function isPreservableDocxEntry(path) {
331
+ /**
332
+ * Parts folio never looks inside. A macro project and an ActiveX control carry
333
+ * code; folio models neither, so it reads neither — not into the document
334
+ * model, not even into the raw XML map. The save path carries them anyway,
335
+ * straight from the source archive, which is what keeps a `.docm` a `.docm`.
336
+ */
337
+ const UNINTERPRETED_PART_NAMES = /* @__PURE__ */ new Set(["word/vbaproject.bin", "word/vbadata.xml"]);
338
+ const isUninterpretedPart = (lowerPath) => UNINTERPRETED_PART_NAMES.has(lowerPath) || lowerPath.startsWith("word/activex/");
339
+ /**
340
+ * Whether this entry is one the PARSER reads into the document model. It says
341
+ * nothing about what a save keeps: a repack hands back every part of the
342
+ * package (see `packageParts.ts`), including the ones this returns false for.
343
+ *
344
+ * That asymmetry is the point. A macro project, an ActiveX control and an OLE
345
+ * embedding are opaque bytes to folio — never decoded, never interpreted,
346
+ * never executed — and they travel from the source archive to the saved one
347
+ * without any code here looking inside them.
348
+ */
349
+ function isParsedDocxEntry(path) {
332
350
  if (!isSafeDocxPath(path)) return false;
333
351
  const lowerPath = path.toLowerCase();
352
+ if (isUninterpretedPart(lowerPath)) return false;
334
353
  if (lowerPath.startsWith("word/media/")) return PRESERVABLE_MEDIA_MIME_TYPES.has(getMediaMimeType(path));
335
354
  if (lowerPath.startsWith("docprops/thumbnail.")) return PRESERVABLE_MEDIA_MIME_TYPES.has(getMediaMimeType(path));
336
355
  if (lowerPath.startsWith("word/fonts/")) return true;
@@ -374,7 +393,7 @@ function isMediaContentAllowed(data, mimeType) {
374
393
  */
375
394
  function getFileList(content) {
376
395
  const files = [];
377
- for (const path of Object.keys(content.originalZip.files)) if (!content.originalZip.files[path]?.dir && isPreservableDocxEntry(path)) files.push(path);
396
+ for (const path of Object.keys(content.originalZip.files)) if (!content.originalZip.files[path]?.dir && isParsedDocxEntry(path)) files.push(path);
378
397
  return files.toSorted();
379
398
  }
380
399
  /**
@@ -425,7 +444,7 @@ function mediaToDataUrl(data, mimeType) {
425
444
  */
426
445
  function extractFile(content, path) {
427
446
  const file = content.originalZip.file(path);
428
- if (!file || !isPreservableDocxEntry(path)) return Promise.resolve(null);
447
+ if (!file || !isParsedDocxEntry(path)) return Promise.resolve(null);
429
448
  const lowerPath = path.toLowerCase();
430
449
  if (lowerPath.endsWith(".xml") || lowerPath.endsWith(".rels")) return file.async("text");
431
450
  return file.async("arraybuffer");
@@ -464,4 +483,4 @@ function getContentSummary(content) {
464
483
  };
465
484
  }
466
485
  //#endregion
467
- export { DocxSecurityError, extractFile, getContentSummary, getFileList, getMediaMimeType, hasFile, isPreservableDocxEntry, mediaToDataUrl, unzipDocx };
486
+ export { DocxSecurityError, extractFile, getContentSummary, getFileList, getMediaMimeType, hasFile, mediaToDataUrl, unzipDocx };
@@ -47,6 +47,8 @@ type XmlNamespaceScope = {
47
47
  bindings: ReadonlyMap<string, string>;
48
48
  parent?: XmlNamespaceScope;
49
49
  };
50
+ /** Attach the element's resolved namespace metadata from its in-scope declarations. */
51
+ declare const attachXmlNamespaceContext: (element: XmlElement, inheritedNamespaceScope?: XmlNamespaceScope) => XmlNamespaceScope;
50
52
  /**
51
53
  * Common OOXML namespace URIs — re-exported from @stll/docx-utils.
52
54
  */
@@ -344,4 +346,4 @@ declare function mergeXmlnsDeclarations(inherited: Record<string, string>, eleme
344
346
  */
345
347
  declare function cloneWithXmlnsDeclarations(element: XmlElement, xmlnsDecls: Record<string, string>): XmlElement;
346
348
  //#endregion
347
- export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, XmlAttributeMatch, XmlElement, XmlNamespaceScope, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
349
+ export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, XmlAttributeMatch, XmlElement, XmlNamespaceScope, attachXmlNamespaceContext, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
@@ -64,6 +64,37 @@ const resolveNamespaceUri = (scope, prefix) => {
64
64
  current = current.parent;
65
65
  }
66
66
  };
67
+ /** Attach the element's resolved namespace metadata from its in-scope declarations. */
68
+ const attachXmlNamespaceContext = (element, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE) => {
69
+ let localBindings = null;
70
+ if (element.attributes) for (const [attribute, value] of Object.entries(element.attributes)) {
71
+ if (typeof value !== "string" || attribute !== "xmlns" && !attribute.startsWith("xmlns:")) continue;
72
+ localBindings ??= /* @__PURE__ */ new Map();
73
+ const prefix = attribute === "xmlns" ? "" : attribute.slice(6);
74
+ localBindings.set(prefix, value);
75
+ }
76
+ const namespaceScope = localBindings === null ? inheritedNamespaceScope : {
77
+ bindings: localBindings,
78
+ parent: inheritedNamespaceScope
79
+ };
80
+ Object.defineProperty(element, "namespaceScope", {
81
+ configurable: false,
82
+ enumerable: false,
83
+ value: namespaceScope,
84
+ writable: false
85
+ });
86
+ const name = element.name ?? "";
87
+ const colonIndex = name.indexOf(":");
88
+ const prefix = colonIndex === -1 ? "" : name.slice(0, colonIndex);
89
+ const namespaceUri = resolveNamespaceUri(namespaceScope, prefix);
90
+ if (namespaceUri !== void 0) Object.defineProperty(element, "namespaceUri", {
91
+ configurable: false,
92
+ enumerable: false,
93
+ value: namespaceUri,
94
+ writable: false
95
+ });
96
+ return namespaceScope;
97
+ };
67
98
  /**
68
99
  * Convert a fast-xml-parser preserveOrder node into an XmlElement.
69
100
  *
@@ -85,32 +116,7 @@ function fxpNodeToElement(node, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE)
85
116
  name: key
86
117
  };
87
118
  if (attrs) element.attributes = attrs;
88
- let localBindings = null;
89
- if (attrs) for (const [attribute, value] of Object.entries(attrs)) {
90
- if (attribute !== "xmlns" && !attribute.startsWith("xmlns:")) continue;
91
- localBindings ??= /* @__PURE__ */ new Map();
92
- const prefix = attribute === "xmlns" ? "" : attribute.slice(6);
93
- localBindings.set(prefix, value);
94
- }
95
- const namespaceScope = localBindings === null ? inheritedNamespaceScope : {
96
- bindings: localBindings,
97
- parent: inheritedNamespaceScope
98
- };
99
- Object.defineProperty(element, "namespaceScope", {
100
- configurable: false,
101
- enumerable: false,
102
- value: namespaceScope,
103
- writable: false
104
- });
105
- const colonIndex = key.indexOf(":");
106
- const prefix = colonIndex === -1 ? "" : key.slice(0, colonIndex);
107
- const namespaceUri = resolveNamespaceUri(namespaceScope, prefix);
108
- if (namespaceUri !== void 0) Object.defineProperty(element, "namespaceUri", {
109
- configurable: false,
110
- enumerable: false,
111
- value: namespaceUri,
112
- writable: false
113
- });
119
+ const namespaceScope = attachXmlNamespaceContext(element, inheritedNamespaceScope);
114
120
  if (children.length > 0) {
115
121
  for (let index = 0; index < children.length; index += 1) children[index] = fxpNodeToElement(children[index], namespaceScope);
116
122
  element.elements = children;
@@ -745,4 +751,4 @@ function cloneWithXmlnsDeclarations(element, xmlnsDecls) {
745
751
  return element;
746
752
  }
747
753
  //#endregion
748
- export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
754
+ export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, attachXmlNamespaceContext, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
@@ -0,0 +1,43 @@
1
+ import { DocxInput } from "./utils/docxInput.js";
2
+ import { DisplayMetadata, DisplayUnsupported } from "./display-list/types.js";
3
+ import { HeadlessFontSource, HeadlessFontSubstitution } from "./fonts/headlessMeasure.js";
4
+ import { HeadlessLayoutGap } from "./headless-layout.js";
5
+ import { PdfSubstitution } from "./pdf/fonts.js";
6
+ import { PdfUnencodable } from "./pdf/writePdf.js";
7
+ import { Result } from "better-result";
8
+ //#region src/export-pdf.d.ts
9
+ declare const ExportPdfError_base: import("better-result").TaggedErrorClass<"ExportPdfError">;
10
+ declare class ExportPdfError extends ExportPdfError_base<{
11
+ message: string;
12
+ cause?: unknown;
13
+ }> {}
14
+ type ExportDocxToPdfOptions = {
15
+ /** Supplies face binaries for measurement and for embedding alike. */
16
+ readonly fonts: HeadlessFontSource;
17
+ /**
18
+ * ISO-8601 instant stamped as the PDF's creation and modification date.
19
+ * Required rather than defaulted so a caller cannot get a nondeterministic
20
+ * document by omission; pass the source package's own timestamp or a fixed
21
+ * epoch.
22
+ */
23
+ readonly timestamp: string;
24
+ readonly metadata?: DisplayMetadata;
25
+ readonly producer?: string;
26
+ };
27
+ type ExportDocxToPdfResult = {
28
+ readonly bytes: Uint8Array;
29
+ readonly pageCount: number;
30
+ /** Constructs the display list could not represent. */
31
+ readonly unsupported: readonly DisplayUnsupported[];
32
+ /** Stories the headless pipeline does not paginate. */
33
+ readonly layoutGaps: readonly HeadlessLayoutGap[];
34
+ /** Faces measured with a stand-in. */
35
+ readonly measurementSubstitutions: readonly HeadlessFontSubstitution[];
36
+ /** Faces embedded as a stand-in. */
37
+ readonly embeddingSubstitutions: readonly PdfSubstitution[];
38
+ /** Code points painted as `.notdef` because no supplied face covers them. */
39
+ readonly unencodable: readonly PdfUnencodable[];
40
+ };
41
+ declare const exportDocxToPdf: (input: DocxInput, options: ExportDocxToPdfOptions) => Promise<Result<ExportDocxToPdfResult, ExportPdfError>>;
42
+ //#endregion
43
+ export { ExportDocxToPdfOptions, ExportDocxToPdfResult, ExportPdfError, exportDocxToPdf };