@stll/folio-core 0.54.1 → 0.55.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 (251) hide show
  1. package/dist/ai-edits/apply.d.ts +1 -1
  2. package/dist/ai-edits/apply.js +40 -32
  3. package/dist/ai-edits/headless.d.ts +1 -2
  4. package/dist/ai-edits/headless.js +13 -27
  5. package/dist/ai-edits/read.d.ts +1 -0
  6. package/dist/ai-edits/read.js +7 -2
  7. package/dist/ai-edits/table-mutation-plan.d.ts +2 -1
  8. package/dist/ai-edits/table-mutation-plan.js +39 -1
  9. package/dist/ai-suggestions/apply.d.ts +1 -1
  10. package/dist/ai-suggestions/apply.js +3 -2
  11. package/dist/compare/compare.js +2 -1
  12. package/dist/compare/content-types.d.ts +3 -15
  13. package/dist/controller/canonicalComposition.d.ts +36 -0
  14. package/dist/controller/canonicalComposition.js +168 -0
  15. package/dist/controller/canonicalInput.d.ts +45 -0
  16. package/dist/controller/canonicalInput.js +338 -0
  17. package/dist/controller/canonicalOperations.d.ts +22 -0
  18. package/dist/controller/canonicalOperations.js +85 -0
  19. package/dist/controller/canonicalReview.d.ts +12 -0
  20. package/dist/controller/canonicalReview.js +19 -0
  21. package/dist/controller/canonicalSession.d.ts +149 -0
  22. package/dist/controller/canonicalSession.js +1038 -0
  23. package/dist/controller/canonicalStoryEditor.d.ts +34 -0
  24. package/dist/controller/canonicalStoryEditor.js +82 -0
  25. package/dist/controller/canonicalStructure.d.ts +16 -0
  26. package/dist/controller/canonicalStructure.js +269 -0
  27. package/dist/controller/folioEditor.d.ts +1 -1
  28. package/dist/controller/folioEditor.js +9 -0
  29. package/dist/controller/headerFooterEditorManager.d.ts +4 -0
  30. package/dist/controller/headerFooterEditorManager.js +71 -3
  31. package/dist/controller/hiddenEditorApi.d.ts +35 -0
  32. package/dist/controller/hiddenEditorApi.js +25 -8
  33. package/dist/controller/hiddenEditorManager.d.ts +11 -2
  34. package/dist/controller/hiddenEditorManager.js +456 -30
  35. package/dist/controller/layoutPipeline.d.ts +1 -1
  36. package/dist/controller/layoutPipeline.js +2 -0
  37. package/dist/controller/noteEditorManager.d.ts +4 -0
  38. package/dist/controller/noteEditorManager.js +113 -15
  39. package/dist/docx/attributeRemainder.js +1 -0
  40. package/dist/docx/bookmarkIds.d.ts +11 -0
  41. package/dist/docx/bookmarkIds.js +33 -0
  42. package/dist/docx/canonicalSessionInput.d.ts +11 -0
  43. package/dist/docx/canonicalSessionInput.js +27 -0
  44. package/dist/docx/canonicalStoryRepack.d.ts +10 -0
  45. package/dist/docx/canonicalStoryRepack.js +41 -0
  46. package/dist/docx/contentTypeRegistry.d.ts +8 -0
  47. package/dist/docx/contentTypeRegistry.js +64 -0
  48. package/dist/docx/documentParser.d.ts +14 -1
  49. package/dist/docx/documentParser.js +17 -4
  50. package/dist/docx/drawingIdNormalization.js +23 -8
  51. package/dist/docx/ensureParaIds.d.ts +12 -1
  52. package/dist/docx/ensureParaIds.js +173 -213
  53. package/dist/docx/foldedListNumberFields.d.ts +85 -0
  54. package/dist/docx/foldedListNumberFields.js +267 -0
  55. package/dist/docx/headerFooterVerbatim.d.ts +14 -1
  56. package/dist/docx/headerFooterVerbatim.js +95 -2
  57. package/dist/docx/listNumberingInstances.js +11 -9
  58. package/dist/docx/noteIds.d.ts +9 -0
  59. package/dist/docx/noteIds.js +17 -0
  60. package/dist/docx/noteReferenceMark.d.ts +3 -1
  61. package/dist/docx/noteReferenceMark.js +15 -14
  62. package/dist/docx/numberingIds.d.ts +14 -0
  63. package/dist/docx/numberingIds.js +16 -0
  64. package/dist/docx/numberingParser.d.ts +3 -1
  65. package/dist/docx/numberingParser.js +17 -3
  66. package/dist/docx/numericIdAllocator.d.ts +12 -0
  67. package/dist/docx/numericIdAllocator.js +33 -0
  68. package/dist/docx/numericIdNormalization.d.ts +16 -0
  69. package/dist/docx/numericIdNormalization.js +174 -0
  70. package/dist/docx/packageParts.d.ts +3 -1
  71. package/dist/docx/packageParts.js +65 -16
  72. package/dist/docx/paragraphMarkPropertyPresence.d.ts +9 -0
  73. package/dist/docx/paragraphMarkPropertyPresence.js +21 -0
  74. package/dist/docx/paragraphParser.js +101 -53
  75. package/dist/docx/paragraphPropertySource.d.ts +15 -1
  76. package/dist/docx/paragraphPropertySource.js +76 -22
  77. package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
  78. package/dist/docx/parser.js +19 -6
  79. package/dist/docx/rasterMime.d.ts +64 -0
  80. package/dist/docx/rasterMime.js +116 -0
  81. package/dist/docx/replyToComment.js +13 -6
  82. package/dist/docx/reviewHistoryNamespace.d.ts +4 -0
  83. package/dist/docx/reviewHistoryNamespace.js +4 -0
  84. package/dist/docx/reviewResolutionProvenance.d.ts +25 -0
  85. package/dist/docx/reviewResolutionProvenance.js +153 -0
  86. package/dist/docx/rezip.d.ts +7 -3
  87. package/dist/docx/rezip.js +121 -52
  88. package/dist/docx/runConsolidator.d.ts +2 -58
  89. package/dist/docx/runConsolidator.js +3 -175
  90. package/dist/docx/runParser.d.ts +2 -1
  91. package/dist/docx/runParser.js +156 -116
  92. package/dist/docx/saveDiagnostics.d.ts +11 -0
  93. package/dist/docx/saveDiagnostics.js +0 -0
  94. package/dist/docx/selectiveSave.d.ts +2 -1
  95. package/dist/docx/selectiveSave.js +14 -1
  96. package/dist/docx/selectiveXmlPatch.d.ts +16 -8
  97. package/dist/docx/selectiveXmlPatch.js +74 -2
  98. package/dist/docx/serializer/headerFooterSerializer.d.ts +5 -1
  99. package/dist/docx/serializer/headerFooterSerializer.js +19 -11
  100. package/dist/docx/serializer/noteSerializer.d.ts +2 -2
  101. package/dist/docx/serializer/noteSerializer.js +24 -8
  102. package/dist/docx/serializer/paragraphSerializer.js +41 -4
  103. package/dist/docx/serializer/partNamespaces.js +6 -3
  104. package/dist/docx/serializer/runSerializer.js +8 -5
  105. package/dist/docx/serializer/trackedChangeAttributes.js +7 -1
  106. package/dist/docx/server/build.js +14 -3
  107. package/dist/docx/server/createBilingualDocument.js +10 -9
  108. package/dist/docx/settingsHeaderFooterUpdate.d.ts +5 -0
  109. package/dist/docx/settingsHeaderFooterUpdate.js +90 -0
  110. package/dist/docx/settingsParser.d.ts +1 -2
  111. package/dist/docx/settingsParser.js +5 -6
  112. package/dist/docx/storyBlockReplay.d.ts +11 -0
  113. package/dist/docx/storyBlockReplay.js +367 -0
  114. package/dist/docx/streamingXmlParser.d.ts +31 -1
  115. package/dist/docx/streamingXmlParser.js +136 -21
  116. package/dist/docx/structuralXmlPatch.js +8 -1
  117. package/dist/docx/unzip.d.ts +3 -1
  118. package/dist/docx/unzip.js +36 -16
  119. package/dist/docx/verbatimCapture.d.ts +3 -1
  120. package/dist/docx/verbatimCapture.js +49 -6
  121. package/dist/i18n/messages/ar.gen.d.ts +5 -0
  122. package/dist/i18n/messages/ar.gen.js +656 -0
  123. package/dist/i18n/messages/cs.gen.d.ts +5 -0
  124. package/dist/i18n/messages/cs.gen.js +656 -0
  125. package/dist/i18n/messages/de.gen.d.ts +5 -0
  126. package/dist/i18n/messages/de.gen.js +656 -0
  127. package/dist/i18n/messages/en.gen.d.ts +5 -0
  128. package/dist/i18n/messages/en.gen.js +656 -0
  129. package/dist/i18n/messages/es.gen.d.ts +5 -0
  130. package/dist/i18n/messages/es.gen.js +656 -0
  131. package/dist/i18n/messages/et.gen.d.ts +5 -0
  132. package/dist/i18n/messages/et.gen.js +656 -0
  133. package/dist/i18n/messages/fr.gen.d.ts +5 -0
  134. package/dist/i18n/messages/fr.gen.js +656 -0
  135. package/dist/i18n/messages/he.gen.d.ts +5 -0
  136. package/dist/i18n/messages/he.gen.js +656 -0
  137. package/dist/i18n/messages/hi.gen.d.ts +5 -0
  138. package/dist/i18n/messages/hi.gen.js +656 -0
  139. package/dist/i18n/messages/hu.gen.d.ts +5 -0
  140. package/dist/i18n/messages/hu.gen.js +656 -0
  141. package/dist/i18n/messages/locales.d.ts +5 -0
  142. package/dist/i18n/messages/locales.js +22 -0
  143. package/dist/i18n/messages/lt.gen.d.ts +5 -0
  144. package/dist/i18n/messages/lt.gen.js +656 -0
  145. package/dist/i18n/messages/lv.gen.d.ts +5 -0
  146. package/dist/i18n/messages/lv.gen.js +656 -0
  147. package/dist/i18n/messages/pl.gen.d.ts +5 -0
  148. package/dist/i18n/messages/pl.gen.js +656 -0
  149. package/dist/i18n/messages/pt-BR.gen.d.ts +5 -0
  150. package/dist/i18n/messages/pt-BR.gen.js +656 -0
  151. package/dist/i18n/messages/sk.gen.d.ts +5 -0
  152. package/dist/i18n/messages/sk.gen.js +656 -0
  153. package/dist/i18n/messages/tr.gen.d.ts +5 -0
  154. package/dist/i18n/messages/tr.gen.js +656 -0
  155. package/dist/i18n/messages/zh-CN.gen.d.ts +5 -0
  156. package/dist/i18n/messages/zh-CN.gen.js +656 -0
  157. package/dist/i18n/messages.d.ts +2 -3
  158. package/dist/i18n/messages.js +1 -19
  159. package/dist/internal/noteMarkerAttrs.d.ts +8 -0
  160. package/dist/internal/noteMarkerAttrs.js +16 -0
  161. package/dist/internal/pageBreakRunSourceDescendantIndex.js +1 -0
  162. package/dist/internal/prosemirrorAttrBoundary.d.ts +21 -0
  163. package/dist/internal/prosemirrorAttrBoundary.js +50 -0
  164. package/dist/layout-bridge/convert/fixedTableColumnWidths.d.ts +6 -0
  165. package/dist/layout-bridge/convert/fixedTableColumnWidths.js +23 -0
  166. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  167. package/dist/layout-bridge/convert/footnoteLayout.js +3 -3
  168. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  169. package/dist/layout-bridge/convert/headerFooterLayout.js +1 -0
  170. package/dist/layout-bridge/convert/noteReferences.d.ts +1 -3
  171. package/dist/layout-bridge/convert/noteReferences.js +1 -6
  172. package/dist/layout-bridge/convert/paragraphRuns.js +2 -4
  173. package/dist/layout-bridge/convert/tableConversion.js +4 -1
  174. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +2 -2
  175. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -2
  176. package/dist/layout-painter/index.js +2 -5
  177. package/dist/managers/DocumentLoaderManager.d.ts +1 -0
  178. package/dist/managers/DocumentLoaderManager.js +8 -1
  179. package/dist/markdown/renderBlock.js +15 -1
  180. package/dist/markdown/renderParagraph.js +9 -13
  181. package/dist/markdown/renderRuns.js +3 -2
  182. package/dist/markdown/renderTable.js +2 -0
  183. package/dist/markdown/types.d.ts +3 -9
  184. package/dist/paged-layout/editorScrollRoot.d.ts +15 -0
  185. package/dist/paged-layout/editorScrollRoot.js +72 -0
  186. package/dist/paged-layout/scrollToPmPosition.js +8 -22
  187. package/dist/prosemirror/attrs/index.d.ts +2 -12
  188. package/dist/prosemirror/attrs/index.js +22 -48
  189. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +2 -1
  190. package/dist/prosemirror/canonicalCommands.d.ts +97 -0
  191. package/dist/prosemirror/canonicalCommands.js +28 -0
  192. package/dist/prosemirror/commands/clearParagraphIndent.d.ts +6 -0
  193. package/dist/prosemirror/commands/clearParagraphIndent.js +25 -0
  194. package/dist/prosemirror/commands/pageBreak.js +15 -1
  195. package/dist/prosemirror/commentIdAllocator.d.ts +15 -22
  196. package/dist/prosemirror/commentIdAllocator.js +50 -25
  197. package/dist/prosemirror/commentReferenceAttrs.d.ts +2 -1
  198. package/dist/prosemirror/conversion/fromProseDoc.js +91 -19
  199. package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +6 -0
  200. package/dist/prosemirror/conversion/listRenderingDefinition.js +97 -0
  201. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -3
  202. package/dist/prosemirror/conversion/toProseDoc.js +51 -11
  203. package/dist/prosemirror/executeEditorCommand.d.ts +11 -0
  204. package/dist/prosemirror/executeEditorCommand.js +26 -0
  205. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  206. package/dist/prosemirror/extensions/core/HistoryExtension.js +97 -5
  207. package/dist/prosemirror/extensions/core/ParagraphExtension.js +53 -20
  208. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +22 -28
  209. package/dist/prosemirror/extensions/features/ListExtension.js +18 -5
  210. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +5 -2
  211. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +13 -1
  212. package/dist/prosemirror/extensions/marks/TextColorExtension.js +3 -2
  213. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
  214. package/dist/prosemirror/extensions/marks/markUtils.js +70 -8
  215. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.d.ts +6 -0
  216. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.js +36 -0
  217. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +4 -2
  218. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -6
  219. package/dist/prosemirror/listMarker.d.ts +1 -0
  220. package/dist/prosemirror/listMarker.js +16 -7
  221. package/dist/prosemirror/listNumbering.js +26 -5
  222. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  223. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  224. package/dist/prosemirror/markupViewNotes.js +3 -2
  225. package/dist/prosemirror/moveRangeBoundaryAttrs.d.ts +2 -1
  226. package/dist/prosemirror/noteReferenceReview.js +5 -3
  227. package/dist/prosemirror/plugins/revisionIds.d.ts +23 -19
  228. package/dist/prosemirror/plugins/revisionIds.js +110 -29
  229. package/dist/prosemirror/plugins/suggestionMode.d.ts +2 -2
  230. package/dist/prosemirror/plugins/suggestionMode.js +246 -42
  231. package/dist/prosemirror/rangeAnchorAttrs.d.ts +2 -1
  232. package/dist/prosemirror/runFormattingFromMarks.d.ts +4 -1
  233. package/dist/prosemirror/runFormattingFromMarks.js +15 -1
  234. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +2 -1
  235. package/dist/prosemirror/runFormattingInlineCarriers.js +4 -1
  236. package/dist/prosemirror/schema/marks.d.ts +2 -0
  237. package/dist/prosemirror/schema/nodes.d.ts +10 -2
  238. package/dist/prosemirror/storyListNumbering.js +11 -9
  239. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +2 -1
  240. package/dist/prosemirror/textInput.d.ts +6 -1
  241. package/dist/prosemirror/textInput.js +14 -3
  242. package/dist/prosemirror/trackedRevisionPath.js +2 -1
  243. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -0
  244. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -0
  245. package/dist/prosemirror/utils/visualLineNavigation.js +6 -8
  246. package/dist/prosemirror/validation.js +4 -0
  247. package/dist/types/content.d.ts +2 -2
  248. package/dist/utils/createDocument.js +2 -2
  249. package/dist/utils/mergeDocumentContent.d.ts +2 -2
  250. package/dist/utils/mergeDocumentContent.js +11 -8
  251. package/package.json +14 -2
@@ -4,6 +4,7 @@ import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
4
4
  import { appVersionInSchemaForm, normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
5
5
  import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
6
6
  import { applyReplyThreadMarkers } from "./commentReplyMarkers.js";
7
+ import { registerContentTypeParts } from "./contentTypeRegistry.js";
7
8
  import { readDocumentSectionFacts } from "./documentSectionFacts.js";
8
9
  import { normalizeDrawingIds } from "./drawingIdNormalization.js";
9
10
  import { rebindDrawingImageRelationship } from "./drawingRelationships.js";
@@ -15,6 +16,7 @@ import { parseNumbering } from "./numberingParser.js";
15
16
  import { paragraphNumberingReferenceId as paragraphNumberingReferenceId$1 } from "./numberingReference.js";
16
17
  import { isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries } from "./packageParts.js";
17
18
  import { normalizeParaIdRangeInXmlParts } from "./paraIdRangeNormalization.js";
19
+ import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
18
20
  import { BLOCK_TREE_DESCENT, visitBlockTreeRecords } from "./paragraphTraversal.js";
19
21
  import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
20
22
  import { removeResolvedHeaderFooterParts } from "./removeHeaderFooterParts.js";
@@ -30,10 +32,12 @@ import { readRootNamespaceBindings, serializePartElement } from "./serializer/pa
30
32
  import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
31
33
  import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
32
34
  import { serializeThemeXml } from "./serializer/themeSerializer.js";
35
+ import { updateEvenAndOddHeaders } from "./settingsHeaderFooterUpdate.js";
36
+ import { parseSettings } from "./settingsParser.js";
33
37
  import { hasCanonicalWordprocessingPrefixes } from "./wordprocessingPrefixes.js";
34
38
  import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
35
39
  import { TaggedError, panic } from "better-result";
36
- import { escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
40
+ import { assertValidOoxmlNumericIds, escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
37
41
  import { mintRelationshipId } from "@stll/docx-core/model";
38
42
  import JSZip from "jszip";
39
43
  //#region src/docx/rezip.ts
@@ -529,12 +533,19 @@ async function processNewHyperlinks(parts, zip, compressionLevel) {
529
533
  */
530
534
  const normalizePackageIdsInZip = async (zip, compressionLevel) => {
531
535
  const xmlParts = /* @__PURE__ */ new Map();
532
- for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.startsWith("word/") && path.endsWith(".xml")) xmlParts.set(path, await file.async("text"));
536
+ for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.startsWith("word/") && path.endsWith(".xml")) {
537
+ const xml = await file.async("text");
538
+ assertValidOoxmlNumericIds(xml, path);
539
+ xmlParts.set(path, xml);
540
+ }
533
541
  const normalizedParts = normalizeParaIdRangeInXmlParts(normalizeRevisionIdsInXmlParts(xmlParts));
534
- for (const [path, xml] of normalizedParts) if (xml !== xmlParts.get(path)) zip.file(path, xml, {
535
- compression: "DEFLATE",
536
- compressionOptions: { level: compressionLevel }
537
- });
542
+ for (const [path, xml] of normalizedParts) if (xml !== xmlParts.get(path)) {
543
+ assertValidOoxmlNumericIds(xml, path);
544
+ zip.file(path, xml, {
545
+ compression: "DEFLATE",
546
+ compressionOptions: { level: compressionLevel }
547
+ });
548
+ }
538
549
  };
539
550
  const EXTENDED_PROPERTIES_PATH = "docProps/app.xml";
540
551
  /**
@@ -628,8 +639,8 @@ const normalizeExportDrawingIds = ({ package: docxPackage }) => {
628
639
  ...docxPackage.endnotes !== void 0 ? { endnotes: docxPackage.endnotes } : {}
629
640
  });
630
641
  };
631
- const finishRepack = async ({ document, originalZip, outputZip, originalDocument, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, sectionEndpointRemoval, sectionReferenceRemovals }) => {
632
- await materializeNewHeaderFooterParts(document, outputZip, compressionLevel);
642
+ const finishRepack = async ({ document, originalZip, outputZip, originalDocument, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, onDiagnostic, sectionEndpointRemoval, sectionReferenceRemovals }) => {
643
+ document = await materializeNewHeaderFooterParts(document, outputZip, compressionLevel);
633
644
  await materializeEmbeddedMedia({
634
645
  document,
635
646
  zip: outputZip,
@@ -660,7 +671,12 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
660
671
  compressionOptions: { level: compressionLevel }
661
672
  });
662
673
  await rebindWatermarkRelIds(document, outputZip, compressionLevel);
663
- await serializeHeadersFootersToZip(document, outputZip, compressionLevel);
674
+ await serializeHeadersFootersToZip({
675
+ doc: document,
676
+ zip: outputZip,
677
+ compressionLevel,
678
+ onDiagnostic
679
+ });
664
680
  await serializeNotesToZip({
665
681
  doc: document,
666
682
  originalZip,
@@ -670,6 +686,7 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
670
686
  });
671
687
  await serializeNumberingIntoZip(document, originalZip, outputZip, compressionLevel);
672
688
  await serializeAddedStylesIntoZip(document, originalZip, outputZip, compressionLevel);
689
+ await serializeHeaderFooterSettingsIntoZip(document, outputZip, compressionLevel);
673
690
  await serializeCommentsToZip(document, outputZip, compressionLevel);
674
691
  await dropAttachedTemplateReference(outputZip, compressionLevel);
675
692
  if (updateModifiedDate && originalCorePropertiesXml) {
@@ -700,6 +717,7 @@ async function repackDocxWithSectionEndpointRemoval({ document: doc, options, se
700
717
  originalCorePropertiesXml,
701
718
  compressionLevel,
702
719
  updateModifiedDate,
720
+ onDiagnostic: options.onDiagnostic,
703
721
  ...modifiedBy !== void 0 ? { modifiedBy } : {},
704
722
  ...changedNoteParaIds !== void 0 ? { changedNoteParaIds } : {},
705
723
  ...sectionEndpointRemoval !== void 0 ? { sectionEndpointRemoval } : {},
@@ -726,7 +744,7 @@ function repackDocx(doc, options = {}) {
726
744
  */
727
745
  async function repackDocxFromRaw(doc, rawContent, options = {}) {
728
746
  const { compressionLevel = 6, updateModifiedDate = true, modifiedBy, changedNoteParaIds } = options;
729
- const exportDocument = withoutOrphanCommentRanges(doc);
747
+ let exportDocument = withoutOrphanCommentRanges(doc);
730
748
  const newZip = new JSZip();
731
749
  for (const [path, file] of Object.entries(rawContent.originalZip.files)) {
732
750
  if (file.dir) {
@@ -740,7 +758,7 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
740
758
  compressionOptions: { level: compressionLevel }
741
759
  });
742
760
  }
743
- await materializeNewHeaderFooterParts(exportDocument, newZip, compressionLevel);
761
+ exportDocument = await materializeNewHeaderFooterParts(exportDocument, newZip, compressionLevel);
744
762
  await materializeEmbeddedMedia({
745
763
  document: exportDocument,
746
764
  zip: newZip,
@@ -763,7 +781,13 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
763
781
  compressionOptions: { level: compressionLevel }
764
782
  });
765
783
  await rebindWatermarkRelIds(exportDocument, newZip, compressionLevel);
766
- await serializeHeadersFootersToZip(exportDocument, newZip, compressionLevel);
784
+ await serializeHeadersFootersToZip({
785
+ doc: exportDocument,
786
+ zip: newZip,
787
+ compressionLevel,
788
+ onDiagnostic: options.onDiagnostic
789
+ });
790
+ await serializeHeaderFooterSettingsIntoZip(exportDocument, newZip, compressionLevel);
767
791
  await serializeNotesToZip({
768
792
  doc: exportDocument,
769
793
  originalZip: rawContent.originalZip,
@@ -1108,11 +1132,19 @@ function findMaxHeaderFooterNum(zip, prefix) {
1108
1132
  * relationship.
1109
1133
  */
1110
1134
  async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
1111
- if (!hasUnmaterializedHeaderFooter(doc)) return;
1112
- doc.package.relationships ??= /* @__PURE__ */ new Map();
1113
- const rels = doc.package.relationships;
1135
+ if (!doc.package.headers?.size && !doc.package.footers?.size) return doc;
1136
+ doc = {
1137
+ ...doc,
1138
+ package: {
1139
+ ...doc.package,
1140
+ relationships: new Map(doc.package.relationships),
1141
+ ...doc.package.headers === void 0 ? {} : { headers: new Map(doc.package.headers) },
1142
+ ...doc.package.footers === void 0 ? {} : { footers: new Map(doc.package.footers) }
1143
+ }
1144
+ };
1145
+ const rels = doc.package.relationships ?? panic("The export owns its relationship map.");
1114
1146
  const relEntries = [];
1115
- const overrides = [];
1147
+ const contentTypeParts = [];
1116
1148
  let maxHeaderNum = findMaxHeaderFooterNum(zip, "header");
1117
1149
  let maxFooterNum = findMaxHeaderFooterNum(zip, "footer");
1118
1150
  const relsPath = "word/_rels/document.xml.rels";
@@ -1135,8 +1167,18 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
1135
1167
  const materialize = (map, relType, prefix, contentType, isHeader) => {
1136
1168
  if (!map) return;
1137
1169
  for (const rId of [...map.keys()]) {
1138
- const existing = rels.get(rId) ?? zipRels.get(rId);
1139
- if (existing && existing.type === relType && existing.target) continue;
1170
+ const registered = zipRels.get(rId);
1171
+ const existing = registered ?? rels.get(rId);
1172
+ if (existing && existing.type === relType && existing.target) {
1173
+ rels.set(rId, existing);
1174
+ const path = resolveRelativePath(relsPath, existing.target);
1175
+ contentTypeParts.push({
1176
+ partName: `/${path}`,
1177
+ contentType
1178
+ });
1179
+ if (!registered) relEntries.push(`<Relationship Id="${escapeXmlAttribute(rId)}" Type="${relType}" Target="${escapeXmlAttribute(existing.target)}"/>`);
1180
+ continue;
1181
+ }
1140
1182
  let effectiveRId = rId;
1141
1183
  if (existing) {
1142
1184
  effectiveRId = `rId${++maxRId}`;
@@ -1145,6 +1187,12 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
1145
1187
  map.delete(rId);
1146
1188
  map.set(effectiveRId, headerFooter);
1147
1189
  }
1190
+ const body = doc.package.document;
1191
+ doc.package.document = {
1192
+ ...body,
1193
+ content: body.content.map((block) => block.type === "paragraph" && block.sectionProperties !== void 0 ? cloneParagraphWithPropertySource(block, { sectionProperties: structuredClone(block.sectionProperties) }) : block),
1194
+ ...body.finalSectionProperties === void 0 ? {} : { finalSectionProperties: structuredClone(body.finalSectionProperties) }
1195
+ };
1148
1196
  for (const block of doc.package.document.content) if (block.type === "paragraph") remapRefs(isHeader ? block.sectionProperties?.headerReferences : block.sectionProperties?.footerReferences, rId, effectiveRId);
1149
1197
  const finalProps = doc.package.document.finalSectionProperties;
1150
1198
  remapRefs(isHeader ? finalProps?.headerReferences : finalProps?.footerReferences, rId, effectiveRId);
@@ -1156,32 +1204,29 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
1156
1204
  target: filename
1157
1205
  });
1158
1206
  relEntries.push(`<Relationship Id="${escapeXmlAttribute(effectiveRId)}" Type="${relType}" Target="${filename}"/>`);
1159
- overrides.push(`<Override PartName="/word/${filename}" ContentType="${contentType}"/>`);
1207
+ contentTypeParts.push({
1208
+ partName: `/word/${filename}`,
1209
+ contentType
1210
+ });
1160
1211
  }
1161
1212
  };
1162
1213
  materialize(doc.package.headers, RELATIONSHIP_TYPES.header, "header", HEADER_CONTENT_TYPE, true);
1163
1214
  materialize(doc.package.footers, RELATIONSHIP_TYPES.footer, "footer", FOOTER_CONTENT_TYPE, false);
1164
- if (relEntries.length === 0) return;
1165
1215
  const compressionOptions = { level: compressionLevel };
1166
- zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
1216
+ if (relEntries.length > 0) zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
1167
1217
  compression: "DEFLATE",
1168
1218
  compressionOptions
1169
1219
  });
1170
1220
  const ctFile = zip.file("[Content_Types].xml");
1171
1221
  if (ctFile) {
1172
- let ctXml = await ctFile.async("text");
1173
- const missing = overrides.filter((override) => {
1174
- const partName = /PartName="(?<partName>[^"]+)"/u.exec(override)?.groups?.["partName"];
1175
- return partName ? !ctXml.includes(`PartName="${partName}"`) : true;
1222
+ const original = await ctFile.async("text");
1223
+ const updated = registerContentTypeParts(original, contentTypeParts);
1224
+ if (updated !== original) zip.file("[Content_Types].xml", updated, {
1225
+ compression: "DEFLATE",
1226
+ compressionOptions
1176
1227
  });
1177
- if (missing.length > 0) {
1178
- ctXml = ctXml.replace("</Types>", `${missing.join("")}</Types>`);
1179
- zip.file("[Content_Types].xml", ctXml, {
1180
- compression: "DEFLATE",
1181
- compressionOptions
1182
- });
1183
- }
1184
1228
  }
1229
+ return doc;
1185
1230
  }
1186
1231
  /**
1187
1232
  * A picture watermark is "model-driven" when its raw VML was cleared (so the
@@ -1292,13 +1337,7 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
1292
1337
  });
1293
1338
  }
1294
1339
  }
1295
- /**
1296
- * Re-serialize every header/footer the model still owns, keyed by part path.
1297
- *
1298
- * `sourceZip` supplies each part as it stands before the save so the rebuilt
1299
- * root can keep any prefix binding only the source document declared.
1300
- */
1301
- async function collectHeaderFooterUpdates(doc, sourceZip) {
1340
+ async function collectHeaderFooterUpdates(doc, { sourceZip, onDiagnostic }) {
1302
1341
  const updates = /* @__PURE__ */ new Map();
1303
1342
  const rels = doc.package.relationships;
1304
1343
  if (!rels) return updates;
@@ -1320,19 +1359,21 @@ async function collectHeaderFooterUpdates(doc, sourceZip) {
1320
1359
  const bindings = sourceFile ? readRootNamespaceBindings(await sourceFile.async("text")) : /* @__PURE__ */ new Map();
1321
1360
  updates.set(path, serializeHeaderFooter(headerFooter, {
1322
1361
  path,
1323
- bindings
1362
+ bindings,
1363
+ onDiagnostic,
1364
+ originalBuffer: doc.originalBuffer
1324
1365
  }));
1325
1366
  }
1326
1367
  }
1327
1368
  }
1328
1369
  return updates;
1329
1370
  }
1330
- /**
1331
- * Serialize modified headers and footers into the ZIP
1332
- */
1333
- async function serializeHeadersFootersToZip(doc, zip, compressionLevel) {
1371
+ async function serializeHeadersFootersToZip({ doc, zip, compressionLevel, onDiagnostic }) {
1334
1372
  const compressionOptions = { level: compressionLevel };
1335
- for (const [filename, xml] of await collectHeaderFooterUpdates(doc, zip)) zip.file(filename, xml, {
1373
+ for (const [filename, xml] of await collectHeaderFooterUpdates(doc, {
1374
+ sourceZip: zip,
1375
+ onDiagnostic
1376
+ })) zip.file(filename, xml, {
1336
1377
  compression: "DEFLATE",
1337
1378
  compressionOptions
1338
1379
  });
@@ -1343,7 +1384,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
1343
1384
  if (footnotes.length > 0 || originalFootnotes) if (originalFootnotes) await patchNotePartIntoZip({
1344
1385
  conventionalLowerPath: "word/footnotes.xml",
1345
1386
  currentXml: serializeFootnotes(footnotes),
1346
- replacementXml: serializeNewFootnotesPart(footnotes),
1387
+ replacementXml: serializeNewFootnotesPart(footnotes, doc),
1347
1388
  baselineFrom: (xml) => serializeFootnotes(parseFootnotes(xml).getNormalFootnotes()),
1348
1389
  elementName: "footnote",
1349
1390
  changedNoteParaIds,
@@ -1356,7 +1397,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
1356
1397
  newZip,
1357
1398
  partPath: "word/footnotes.xml",
1358
1399
  relationshipType: RELATIONSHIP_TYPES.footnotes,
1359
- serializedPart: serializeNewFootnotesPart(footnotes),
1400
+ serializedPart: serializeNewFootnotesPart(footnotes, doc),
1360
1401
  compressionLevel
1361
1402
  });
1362
1403
  const endnotes = doc.package.endnotes ?? [];
@@ -1364,7 +1405,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
1364
1405
  if (endnotes.length > 0 || originalEndnotes) if (originalEndnotes) await patchNotePartIntoZip({
1365
1406
  conventionalLowerPath: "word/endnotes.xml",
1366
1407
  currentXml: serializeEndnotes(endnotes),
1367
- replacementXml: serializeNewEndnotesPart(endnotes),
1408
+ replacementXml: serializeNewEndnotesPart(endnotes, doc),
1368
1409
  baselineFrom: (xml) => serializeEndnotes(parseEndnotes(xml).getNormalEndnotes()),
1369
1410
  elementName: "endnote",
1370
1411
  changedNoteParaIds,
@@ -1377,7 +1418,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
1377
1418
  newZip,
1378
1419
  partPath: "word/endnotes.xml",
1379
1420
  relationshipType: RELATIONSHIP_TYPES.endnotes,
1380
- serializedPart: serializeNewEndnotesPart(endnotes),
1421
+ serializedPart: serializeNewEndnotesPart(endnotes, doc),
1381
1422
  compressionLevel
1382
1423
  });
1383
1424
  }
@@ -1406,6 +1447,32 @@ async function materializeNewNotePart({ contentType, newZip, partPath, relations
1406
1447
  compressionOptions
1407
1448
  });
1408
1449
  }
1450
+ /** Only this modeled switch changes on story lifecycle edits; preserve all other settings. */
1451
+ async function serializeHeaderFooterSettingsIntoZip(doc, zip, compressionLevel) {
1452
+ if (!doc.package.settings) return;
1453
+ const enabled = doc.package.settings.evenAndOddHeaders;
1454
+ const file = zip.file(SETTINGS_PART);
1455
+ if (!file) {
1456
+ if (enabled === void 0) return;
1457
+ await materializeNewNotePart({
1458
+ contentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
1459
+ newZip: zip,
1460
+ partPath: "word/settings.xml",
1461
+ relationshipType: RELATIONSHIP_TYPES.settings,
1462
+ serializedPart: serializeSettingsXml(doc.package.settings),
1463
+ compressionLevel
1464
+ });
1465
+ return;
1466
+ }
1467
+ const xml = await file.async("text");
1468
+ if (parseSettings(xml).evenAndOddHeaders === enabled) return;
1469
+ const patched = updateEvenAndOddHeaders(xml, enabled);
1470
+ if (patched === null) throw new DocxPackageFidelityError("Cannot update malformed header/footer settings");
1471
+ zip.file(file.name, patched, {
1472
+ compression: "DEFLATE",
1473
+ compressionOptions: { level: compressionLevel }
1474
+ });
1475
+ }
1409
1476
  const numberingBaselines = /* @__PURE__ */ new WeakMap();
1410
1477
  async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLevel) {
1411
1478
  const numbering = doc.package.numbering;
@@ -1600,7 +1667,7 @@ const notePartXmlFor = ({ patch, originalXml, replacementXml, hasDirtyParagraph,
1600
1667
  return null;
1601
1668
  };
1602
1669
  switch (patch.type) {
1603
- case "patched": return patch.xml === originalXml ? keepOriginal() : patch.xml;
1670
+ case "patched": return patch.xml === originalXml ? null : patch.xml;
1604
1671
  case "refused": switch (patch.reason) {
1605
1672
  case "comment-range-balance": return replacementXml;
1606
1673
  case "unroutable-paragraph": return keepOriginal();
@@ -1800,7 +1867,8 @@ async function createDocx(doc, properties = {}) {
1800
1867
  originalDocument: source.document,
1801
1868
  originalCorePropertiesXml: source.corePropertiesXml,
1802
1869
  compressionLevel: 6,
1803
- updateModifiedDate: true
1870
+ updateModifiedDate: true,
1871
+ onDiagnostic: properties.onDiagnostic
1804
1872
  });
1805
1873
  }
1806
1874
  const zip = await createDocumentSeedZip(doc, properties);
@@ -1811,7 +1879,8 @@ async function createDocx(doc, properties = {}) {
1811
1879
  originalDocument: void 0,
1812
1880
  originalCorePropertiesXml: await zip.file("docProps/core.xml")?.async("text"),
1813
1881
  compressionLevel: 6,
1814
- updateModifiedDate: true
1882
+ updateModifiedDate: true,
1883
+ onDiagnostic: properties.onDiagnostic
1815
1884
  });
1816
1885
  }
1817
1886
  const createDocumentSeedZip = async (doc, properties) => {
@@ -1,62 +1,6 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { formattingEquals, isTextOnlyRun, runsMergeable } from "@stll/docx-core/ops";
2
3
  //#region src/docx/runConsolidator.d.ts
3
- /**
4
- * Every `TextFormatting` field, named so the comparison below is total.
5
- *
6
- * A field this comparison forgets is a field one of two merged runs loses,
7
- * silently and at parse time. Naming them turns a field added to the model
8
- * without a comparison into a compile error rather than a fidelity defect.
9
- */
10
- type ComparedTextFormattingField = "bold" | "boldCs" | "italic" | "italicCs" | "underline" | "strike" | "doubleStrike" | "vertAlign" | "smallCaps" | "allCaps" | "hidden" | "noProof" | "color" | "highlight" | "shading" | "fontSize" | "fontSizeCs" | "fontFamily" | "language" | "spacing" | "position" | "scale" | "kerning" | "effect" | "emphasisMark" | "emboss" | "imprint" | "outline" | "shadow" | "rtl" | "cs" | "styleId" | "preserved";
11
- type ComparedTextFormatting = document_d_exports.ExhaustiveFields<document_d_exports.TextFormatting, ComparedTextFormattingField>;
12
- /**
13
- * Check if two TextFormatting objects are equivalent
14
- *
15
- * Uses deep comparison of all properties to determine if runs
16
- * can be merged without losing formatting information.
17
- */
18
- declare function formattingEquals(a: ComparedTextFormatting | undefined, b: ComparedTextFormatting | undefined): boolean;
19
- /**
20
- * Check if a run contains only text content
21
- * (runs with special content like images, fields, etc. should not be merged)
22
- */
23
- declare function isTextOnlyRun(run: document_d_exports.Run): boolean;
24
- /**
25
- * Check if a run can be merged with another run
26
- * Runs with breaks, tabs, images, fields, etc. act as merge boundaries
27
- */
28
- declare function canMergeRun(run: document_d_exports.Run): boolean;
29
- /**
30
- * Every `Run` field, named by what a merge does with it.
31
- *
32
- * The merged run is the survivor spread whole, so a field added to the model
33
- * is carried over from one side by default. Naming each one here forces the
34
- * next field to state whether that default is right before it can compile.
35
- */
36
- type MergeDecidedRunField =
37
- /** Identical for every run. */
38
- "type" |
39
- /** Concatenated; see {@link mergeRunContent}. */
40
- "content" |
41
- /** Must be equal, and the survivor keeps it. */
42
- "formatting" |
43
- /** Must be equal, and the survivor keeps it. */
44
- "preservedAttributes" |
45
- /** Refuses the merge outright; see {@link canMergeRun}. */
46
- "propertyChanges";
47
- type MergeDecidedRun = document_d_exports.ExhaustiveFields<document_d_exports.Run, MergeDecidedRunField>;
48
- /**
49
- * May two adjacent runs become one?
50
- *
51
- * Every consolidation site asks this one question, because a merge that any
52
- * one site decides differently is a merge the next parse undoes. A run holds
53
- * three records a merged run can hold only one of: its typed formatting, the
54
- * attributes `w:r` carried that the model has no field for, and the `w:rPr`
55
- * children no reader took a value from. Merging two runs that disagree on any
56
- * of them discards the loser's copy, which is how both runs' `w:rsid*` used to
57
- * vanish at parse time — before the editor, before any gate could see it.
58
- */
59
- declare function runsMergeable(a: MergeDecidedRun, b: MergeDecidedRun): boolean;
60
4
  /**
61
5
  * Consolidate an array of runs by merging consecutive runs with identical formatting
62
6
  *
@@ -93,4 +37,4 @@ declare function getConsolidationStats(originalParagraphs: document_d_exports.Pa
93
37
  reductionPercentage: number;
94
38
  };
95
39
  //#endregion
96
- export { canMergeRun, consolidateParagraph, consolidateParagraphContent, consolidateRuns, countRuns, formattingEquals, getConsolidationStats, isTextOnlyRun, runsMergeable };
40
+ export { consolidateParagraph, consolidateParagraphContent, consolidateRuns, countRuns, formattingEquals, getConsolidationStats, isTextOnlyRun, runsMergeable };
@@ -1,180 +1,8 @@
1
1
  import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
2
2
  import { runHoldsPayload } from "./runPayload.js";
3
+ import { formattingEquals, isTextOnlyRun, mergeRunContent, runsMergeable, runsMergeable as runsMergeable$1 } from "@stll/docx-core/ops";
3
4
  //#region src/docx/runConsolidator.ts
4
5
  /**
5
- * Check if two TextFormatting objects are equivalent
6
- *
7
- * Uses deep comparison of all properties to determine if runs
8
- * can be merged without losing formatting information.
9
- */
10
- function formattingEquals(a, b) {
11
- if (!a && !b) return true;
12
- if (!a || !b) return false;
13
- if (a.bold !== b.bold) return false;
14
- if (a.boldCs !== b.boldCs) return false;
15
- if (a.italic !== b.italic) return false;
16
- if (a.italicCs !== b.italicCs) return false;
17
- if (a.strike !== b.strike) return false;
18
- if (a.doubleStrike !== b.doubleStrike) return false;
19
- if (a.smallCaps !== b.smallCaps) return false;
20
- if (a.allCaps !== b.allCaps) return false;
21
- if (a.hidden !== b.hidden) return false;
22
- if (a.noProof !== b.noProof) return false;
23
- if (a.emboss !== b.emboss) return false;
24
- if (a.imprint !== b.imprint) return false;
25
- if (a.outline !== b.outline) return false;
26
- if (a.shadow !== b.shadow) return false;
27
- if (a.rtl !== b.rtl) return false;
28
- if (a.cs !== b.cs) return false;
29
- if (a.fontSize !== b.fontSize) return false;
30
- if (a.fontSizeCs !== b.fontSizeCs) return false;
31
- if (a.spacing !== b.spacing) return false;
32
- if (a.position !== b.position) return false;
33
- if (a.scale !== b.scale) return false;
34
- if (a.kerning !== b.kerning) return false;
35
- if (a.vertAlign !== b.vertAlign) return false;
36
- if (a.highlight !== b.highlight) return false;
37
- if (a.effect !== b.effect) return false;
38
- if (a.emphasisMark !== b.emphasisMark) return false;
39
- if (a.styleId !== b.styleId) return false;
40
- if (!underlineEquals(a.underline, b.underline)) return false;
41
- if (!colorEquals(a.color, b.color)) return false;
42
- if (!shadingEquals(a.shading, b.shading)) return false;
43
- if (!fontFamilyEquals(a.fontFamily, b.fontFamily)) return false;
44
- if (!languageEquals(a.language, b.language)) return false;
45
- if (!preservedMarkupEquals(a.preserved, b.preserved)) return false;
46
- return true;
47
- }
48
- /**
49
- * Compare two verbatim sinks position by position.
50
- *
51
- * The sink is ordered — by `index`, then by source order within an index — and
52
- * the order is what puts the markup back between the same modelled siblings,
53
- * so equality is by sequence, not by set.
54
- */
55
- function preservedMarkupEquals(a, b) {
56
- const left = a?.children ?? [];
57
- const right = b?.children ?? [];
58
- if (left.length !== right.length) return false;
59
- return left.every(({ index, xml }, position) => {
60
- const other = right[position];
61
- return other !== void 0 && other.index === index && other.xml === xml;
62
- });
63
- }
64
- /**
65
- * Compare two attribute remainders as sets.
66
- *
67
- * An element cannot carry one expanded name twice, so (namespace, name) orders
68
- * a remainder totally and the sorted keys compare exactly. Order is a set
69
- * comparison rather than a sequence one because attribute order in XML says
70
- * nothing: two runs that spelled the same attributes in a different order
71
- * carry the same remainder and may still become one run.
72
- */
73
- function preservedAttributesEqual(a, b) {
74
- const left = canonicalRemainderKeys(a);
75
- const right = canonicalRemainderKeys(b);
76
- return left.length === right.length && left.every((key, index) => key === right[index]);
77
- }
78
- /** `\u0000` cannot appear in an XML name, namespace URI or attribute value. */
79
- const REMAINDER_KEY_SEPARATOR = "\0";
80
- const canonicalRemainderKeys = (attributes) => (attributes ?? []).map(({ namespace, name, value }) => [
81
- namespace ?? "",
82
- name,
83
- value
84
- ].join(REMAINDER_KEY_SEPARATOR)).sort();
85
- function languageEquals(a, b) {
86
- return a?.val === b?.val && a?.eastAsia === b?.eastAsia && a?.bidi === b?.bidi;
87
- }
88
- /**
89
- * Compare underline settings
90
- */
91
- function underlineEquals(a, b) {
92
- if (!a && !b) return true;
93
- if (!a || !b) return false;
94
- if (a.style !== b.style) return false;
95
- return colorEquals(a.color, b.color);
96
- }
97
- /**
98
- * Compare color values
99
- */
100
- function colorEquals(a, b) {
101
- if (!a && !b) return true;
102
- if (!a || !b) return false;
103
- return a.rgb === b.rgb && a.auto === b.auto && a.themeColor === b.themeColor && a.themeTint === b.themeTint && a.themeShade === b.themeShade;
104
- }
105
- /**
106
- * Compare shading properties
107
- */
108
- function shadingEquals(a, b) {
109
- if (!a && !b) return true;
110
- if (!a || !b) return false;
111
- if (a.pattern !== b.pattern) return false;
112
- if (!colorEquals(a.color, b.color)) return false;
113
- if (!colorEquals(a.fill, b.fill)) return false;
114
- return true;
115
- }
116
- /**
117
- * Compare font family settings
118
- */
119
- function fontFamilyEquals(a, b) {
120
- if (!a && !b) return true;
121
- if (!a || !b) return false;
122
- return a.ascii === b.ascii && a.hAnsi === b.hAnsi && a.eastAsia === b.eastAsia && a.cs === b.cs && a.asciiTheme === b.asciiTheme && a.hAnsiTheme === b.hAnsiTheme && a.eastAsiaTheme === b.eastAsiaTheme && a.csTheme === b.csTheme;
123
- }
124
- /**
125
- * Check if a run contains only text content
126
- * (runs with special content like images, fields, etc. should not be merged)
127
- */
128
- function isTextOnlyRun(run) {
129
- return run.content.every((c) => c.type === "text" || c.type === "softHyphen" || c.type === "noBreakHyphen");
130
- }
131
- /**
132
- * Check if run content can be merged (simple text types)
133
- */
134
- function isMergeableContent(content) {
135
- return content.type === "text" || content.type === "softHyphen" || content.type === "noBreakHyphen";
136
- }
137
- /**
138
- * Check if a run can be merged with another run
139
- * Runs with breaks, tabs, images, fields, etc. act as merge boundaries
140
- */
141
- function canMergeRun(run) {
142
- if (run.propertyChanges && run.propertyChanges.length > 0) return false;
143
- if (!runHoldsPayload(run)) return false;
144
- return run.content.every(isMergeableContent);
145
- }
146
- /**
147
- * May two adjacent runs become one?
148
- *
149
- * Every consolidation site asks this one question, because a merge that any
150
- * one site decides differently is a merge the next parse undoes. A run holds
151
- * three records a merged run can hold only one of: its typed formatting, the
152
- * attributes `w:r` carried that the model has no field for, and the `w:rPr`
153
- * children no reader took a value from. Merging two runs that disagree on any
154
- * of them discards the loser's copy, which is how both runs' `w:rsid*` used to
155
- * vanish at parse time — before the editor, before any gate could see it.
156
- */
157
- function runsMergeable(a, b) {
158
- return canMergeRun(a) && canMergeRun(b) && formattingEquals(a.formatting, b.formatting) && preservedAttributesEqual(a.preservedAttributes, b.preservedAttributes);
159
- }
160
- /**
161
- * Merge the content of two runs into a single content array
162
- */
163
- function mergeRunContent(content1, content2) {
164
- const result = [];
165
- for (const c of content1) result.push(c);
166
- const lastText = result.at(-1);
167
- const firstText = content2[0];
168
- if (lastText?.type === "text" && firstText?.type === "text") {
169
- result[result.length - 1] = {
170
- type: "text",
171
- text: lastText.text + firstText.text
172
- };
173
- for (let i = 1; i < content2.length; i++) result.push(content2[i]);
174
- } else for (const c of content2) result.push(c);
175
- return result;
176
- }
177
- /**
178
6
  * Consolidate an array of runs by merging consecutive runs with identical formatting
179
7
  *
180
8
  * @param runs - Array of runs to consolidate
@@ -200,7 +28,7 @@ function consolidateRuns(runs) {
200
28
  };
201
29
  continue;
202
30
  }
203
- if (runsMergeable(current, run)) current = {
31
+ if (runsMergeable$1(current, run)) current = {
204
32
  ...current,
205
33
  content: mergeRunContent(current.content, run.content)
206
34
  };
@@ -276,4 +104,4 @@ function getConsolidationStats(originalParagraphs, consolidatedParagraphs) {
276
104
  };
277
105
  }
278
106
  //#endregion
279
- export { canMergeRun, consolidateParagraph, consolidateParagraphContent, consolidateRuns, countRuns, formattingEquals, getConsolidationStats, isTextOnlyRun, runsMergeable };
107
+ export { consolidateParagraph, consolidateParagraphContent, consolidateRuns, countRuns, formattingEquals, getConsolidationStats, isTextOnlyRun, runsMergeable };
@@ -60,7 +60,8 @@ declare const isBaselineVertAlign: (vertAlign: document_d_exports.TextFormatting
60
60
  * it beat are not kept.
61
61
  *
62
62
  * An element that states nothing at all still yields a record: `undefined` here
63
- * means the owner carried no `w:rPr`, never that the one it carried was empty.
63
+ * means the owner carried no `w:rPr`, or a paragraph-mark carrier contained
64
+ * only metadata owned by the paragraph. An authored empty set remains {}.
64
65
  */
65
66
  declare function parseRunProperties(rPr: XmlElement | null, theme: document_d_exports.Theme | null, owner: RunPropertyOwner): document_d_exports.TextFormatting | undefined;
66
67
  /**