@stll/folio-core 0.48.0 → 0.50.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 (170) hide show
  1. package/dist/ai-edits/apply.js +565 -121
  2. package/dist/ai-edits/headless.d.ts +37 -1
  3. package/dist/ai-edits/headless.js +47 -16
  4. package/dist/ai-edits/minimal-replacement.d.ts +64 -0
  5. package/dist/ai-edits/minimal-replacement.js +282 -0
  6. package/dist/controller/layoutPipeline.js +11 -1
  7. package/dist/display-list/build/paragraphPrimitives.js +63 -7
  8. package/dist/display-list/build/textBoxPrimitives.js +1 -1
  9. package/dist/display-list/build/unsupported.d.ts +2 -2
  10. package/dist/display-list/build/unsupported.js +0 -0
  11. package/dist/display-list/dom/renderDisplayListToDom.d.ts +10 -1
  12. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  13. package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
  14. package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
  15. package/dist/display-list/selectDisplayPages.d.ts +6 -0
  16. package/dist/display-list/selectDisplayPages.js +44 -0
  17. package/dist/docx/blockContentParser.js +2 -2
  18. package/dist/docx/bulletMarkers.d.ts +18 -2
  19. package/dist/docx/bulletMarkers.js +27 -2
  20. package/dist/docx/drawingGroupChildren.d.ts +60 -0
  21. package/dist/docx/drawingGroupChildren.js +176 -0
  22. package/dist/docx/drawingUtils.js +3 -0
  23. package/dist/docx/groupDrawingParser.d.ts +7 -1
  24. package/dist/docx/groupDrawingParser.js +123 -32
  25. package/dist/docx/packageParts.d.ts +45 -1
  26. package/dist/docx/packageParts.js +93 -2
  27. package/dist/docx/paraIdAttribute.d.ts +3 -2
  28. package/dist/docx/paragraphParser.js +91 -7
  29. package/dist/docx/paragraphTextBoxEnrichment.js +126 -4
  30. package/dist/docx/runParser.js +10 -0
  31. package/dist/docx/selectiveSaveFlags.d.ts +10 -3
  32. package/dist/docx/selectiveSaveFlags.js +1 -1
  33. package/dist/docx/selectiveXmlPatch.d.ts +56 -34
  34. package/dist/docx/selectiveXmlPatch.js +222 -115
  35. package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
  36. package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
  37. package/dist/docx/serializer/paragraphSerializer.js +5 -4
  38. package/dist/docx/serializer/runSerializer.d.ts +5 -1
  39. package/dist/docx/serializer/runSerializer.js +4 -3
  40. package/dist/docx/shapeAlternateContent.d.ts +25 -0
  41. package/dist/docx/shapeAlternateContent.js +52 -0
  42. package/dist/docx/textBoxParser.d.ts +10 -2
  43. package/dist/docx/textBoxParser.js +9 -3
  44. package/dist/export-pdf.d.ts +35 -2
  45. package/dist/export-pdf.js +51 -20
  46. package/dist/fonts/fontsourceFaces.d.ts +47 -0
  47. package/dist/fonts/fontsourceFaces.js +230 -0
  48. package/dist/internal/headlessRevisionResolution.js +50 -13
  49. package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
  50. package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
  51. package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
  52. package/dist/layout-bridge/convert/flowBorders.js +31 -0
  53. package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
  54. package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
  55. package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
  56. package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
  57. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
  58. package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
  59. package/dist/layout-bridge/convert/imageConversion.js +118 -0
  60. package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
  61. package/dist/layout-bridge/convert/listMarkers.js +98 -0
  62. package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
  63. package/dist/layout-bridge/convert/noteReferences.js +29 -0
  64. package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
  65. package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
  66. package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
  67. package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
  68. package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
  69. package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
  70. package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
  71. package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
  72. package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
  73. package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
  74. package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
  75. package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
  76. package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
  77. package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
  78. package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
  79. package/dist/layout-bridge/convert/tableConversion.js +303 -0
  80. package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
  81. package/dist/layout-bridge/convert/textBoxFill.js +42 -0
  82. package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
  83. package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
  84. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
  85. package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2060
  86. package/dist/layout-bridge/engine/hitTest.js +1 -1
  87. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  88. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  89. package/dist/layout-bridge/engine/selectionRects.js +1 -1
  90. package/dist/layout-engine/imageLayout.d.ts +9 -0
  91. package/dist/layout-engine/imageLayout.js +48 -0
  92. package/dist/layout-engine/index.d.ts +4 -19
  93. package/dist/layout-engine/index.js +14 -910
  94. package/dist/layout-engine/keep-together.d.ts +5 -2
  95. package/dist/layout-engine/keep-together.js +5 -1
  96. package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
  97. package/dist/layout-engine/layoutFlowShared.js +8 -0
  98. package/dist/layout-engine/measure/advanceComposition.js +49 -3
  99. package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
  100. package/dist/layout-engine/measure/crossRunWords.js +172 -0
  101. package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
  102. package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
  103. package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
  104. package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
  105. package/dist/layout-engine/measure/justification.d.ts +32 -0
  106. package/dist/layout-engine/measure/justification.js +83 -0
  107. package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
  108. package/dist/layout-engine/measure/lineFitting.js +145 -0
  109. package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
  110. package/dist/layout-engine/measure/lineTypography.js +124 -0
  111. package/dist/layout-engine/measure/measureBlocks.js +7 -4
  112. package/dist/layout-engine/measure/measureContainer.js +46 -3
  113. package/dist/layout-engine/measure/measureHelpers.js +13 -3
  114. package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
  115. package/dist/layout-engine/measure/measureParagraph.js +41 -780
  116. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
  117. package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
  118. package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
  119. package/dist/layout-engine/measure/paragraphTabs.js +78 -0
  120. package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
  121. package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
  122. package/dist/layout-engine/paginator.d.ts +2 -0
  123. package/dist/layout-engine/paginator.js +5 -1
  124. package/dist/layout-engine/paragraphLayout.d.ts +23 -0
  125. package/dist/layout-engine/paragraphLayout.js +204 -0
  126. package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
  127. package/dist/layout-engine/paragraphSpacing.js +2 -1
  128. package/dist/layout-engine/sectionLayout.d.ts +45 -0
  129. package/dist/layout-engine/sectionLayout.js +216 -0
  130. package/dist/layout-engine/tableLayout.d.ts +17 -0
  131. package/dist/layout-engine/tableLayout.js +369 -0
  132. package/dist/layout-engine/tableRowBreak.d.ts +8 -1
  133. package/dist/layout-engine/tableRowBreak.js +22 -1
  134. package/dist/layout-engine/textBoxLayout.d.ts +15 -0
  135. package/dist/layout-engine/textBoxLayout.js +137 -0
  136. package/dist/layout-engine/types.d.ts +21 -2
  137. package/dist/layout-painter/renderPage.js +1 -0
  138. package/dist/layout-painter/renderParagraph.js +41 -4
  139. package/dist/layout-painter/renderTextBox.d.ts +12 -2
  140. package/dist/layout-painter/renderTextBox.js +26 -2
  141. package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
  142. package/dist/paged-layout/sectionBlockWidths.js +1 -1
  143. package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
  144. package/dist/prosemirror/alternateContentAttrs.js +35 -0
  145. package/dist/prosemirror/attrs/index.js +90 -0
  146. package/dist/prosemirror/commands/comments.js +51 -41
  147. package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
  148. package/dist/prosemirror/contentControlRevisions.js +84 -0
  149. package/dist/prosemirror/conversion/fromProseDoc.js +46 -22
  150. package/dist/prosemirror/conversion/toProseDoc.js +42 -19
  151. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
  152. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
  153. package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
  154. package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
  155. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  156. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +3 -0
  157. package/dist/prosemirror/listMarker.js +2 -2
  158. package/dist/prosemirror/paragraphDirection.d.ts +29 -2
  159. package/dist/prosemirror/paragraphDirection.js +21 -2
  160. package/dist/prosemirror/plugins/suggestionMode.js +37 -8
  161. package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
  162. package/dist/prosemirror/rejoinRunCarriers.js +66 -0
  163. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
  164. package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
  165. package/dist/prosemirror/schema/nodes.d.ts +37 -5
  166. package/dist/server.d.ts +3 -2
  167. package/dist/types/content.d.ts +2 -2
  168. package/package.json +2 -2
  169. package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
  170. package/dist/layout-engine/justifiedLineFit.js +0 -6
@@ -1,76 +1,24 @@
1
- import { convertBulletToUnicode } from "../../docx/bulletMarkers.js";
2
1
  import { resolveDocumentGridLinePitch } from "../../docx/documentGrid.js";
3
- import { getFontAlternate } from "../../fonts/fontAlternates.js";
4
2
  import { buildPageBreakRunDescendantIndex } from "../../internal/pageBreakRunDescendantIndex.js";
5
- import { partitionRunsAtPageBreaks } from "../../internal/pageBreakRunPartition.js";
6
- import { setHyperlinkInstanceIndex } from "../../layout-engine/measure/hyperlinkInstance.js";
7
- import { setParagraphFrame } from "../../layout-engine/paragraphFrame.js";
8
- import { setTextBoxGroupId } from "../../layout-engine/textBoxGroup.js";
9
- import { DEFAULT_TEXTBOX_MARGINS, isListNumPr } from "../../layout-engine/types.js";
10
3
  import { getPageNumbering } from "../../paged-layout/sectionGeometry.js";
11
- import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectInlineWrapperMarkAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectParagraphAttrs, expectPreservedXmlAttrs, expectRunFormattingOverrideMarkAttrs, expectRunShadingMarkAttrs, expectSymbolAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../../prosemirror/attrs/index.js";
12
- import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
13
- import { runShadingAttrsToShading } from "../../prosemirror/conversion/runShadingMark.js";
14
- import { advanceListMarker, advanceVisibleListMarker, cloneListCounterState, formatCounter, resolveListTemplate } from "../../prosemirror/listMarker.js";
4
+ import { expectBlockSdtAttrs, expectHardBreakAttrs, expectParagraphAttrs, expectTextBoxAttrs } from "../../prosemirror/attrs/index.js";
5
+ import { cloneListCounterState, formatCounter, resolveListTemplate } from "../../prosemirror/listMarker.js";
15
6
  import { resolveNumberedRefFields } from "../../prosemirror/numberedRefFields.js";
16
- import { directionIsRtl, directionToBidi } from "../../prosemirror/paragraphDirection.js";
17
- import { resolveEffectiveRunStyleFormatting } from "../../prosemirror/runStyleFormatting.js";
18
- import { parseSectionBreakType } from "../../prosemirror/sectionCarrier.js";
19
7
  import { expectTextBoxAnchorAttrs } from "../../prosemirror/textBoxAnchorAttrs.js";
20
- import { textBoxHostParagraph } from "../../prosemirror/textBoxHostParagraph.js";
21
8
  import { assertValidProseMirrorDocument } from "../../prosemirror/validation.js";
22
9
  import { createStyleEngine } from "../../style-engine/styleEngine.js";
23
- import { normalizeShapeTextAnchor } from "../../types/documentEnumValues.js";
24
- import { cssBorderStyle, cssBorderStyleOf } from "../../utils/borderCss.js";
25
- import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.js";
26
- import { resolveThemeFont } from "../../utils/fontResolver.js";
27
- import { resolveShadingFill } from "../../utils/formatToStyle.js";
28
- import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
29
- import { decodeOoxmlSymbolCharacter } from "../../utils/ooxmlSymbol.js";
30
- import { mergeParagraphTabStops } from "../../utils/paragraphFormattingMerge.js";
31
- import { sanitizeImageSrc } from "../../utils/sanitizeImageSrc.js";
32
- import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
33
- import { STYLE_TOGGLE_KEYS, mergeTextFormatting } from "../../utils/textFormattingMerge.js";
34
- import { AUTO_PARAGRAPH_SPACING_PX, halfPointsToPixels, halfPointsToPoints, pointsToPixels } from "../../utils/units.js";
35
10
  import { getColumns } from "../sectionColumns.js";
11
+ import { mergeRunInParagraphs, reserveLeadingEmptyOutlineHeight, suppressFinalEmptyParagraphAfterTable, suppressFinalParagraphInRepeatedEmptySuffix } from "./flowBlockPostprocessing.js";
12
+ import { convertBorderSpecToLayout } from "./flowBorders.js";
13
+ import { nextBlockId, resetBlockIdCounter, twipsToPixels } from "./flowConversionShared.js";
14
+ import { convertImage } from "./imageConversion.js";
15
+ import { collectNoteRefs, isNoteReferenceMarkXml } from "./noteReferences.js";
16
+ import { splitParagraphAtPageBreaks } from "./pageBreakSplitting.js";
17
+ import { convertParagraph, drawsParagraphBorder } from "./paragraphConversion.js";
36
18
  import { groupParagraphFrames } from "./paragraphFrames.js";
37
- import { resolveParagraphMarkFormatting, resolveParagraphMarkOwnFormatting } from "./paragraphMarkFormatting.js";
38
- import { panic } from "better-result";
39
- import { headingLevelOf, paragraphNumberingLevel, paragraphNumberingReferenceId, sameStatedParagraphNumbering, statesNoBorder } from "@stll/docx-core/model";
19
+ import { applySectionDocumentGrid, applySectionStartsToBoundaries, coalesceTrailingPageBreakBeforeContinuousSection, getLastMapKey, readFinalSectionStart } from "./sectionBoundaries.js";
20
+ import { convertTable, convertTextBoxNode } from "./tableConversion.js";
40
21
  //#region src/layout-bridge/convert/toFlowBlocks.ts
41
- const DETACHED_WATERMARK_HOST_ATTR = "_detachedWatermarkHost";
42
- const expectDetachedWatermarkHostAttr = (attrs) => {
43
- const value = Reflect.get(attrs, DETACHED_WATERMARK_HOST_ATTR);
44
- if (value === null || value === void 0) return false;
45
- if (typeof value !== "boolean") panic("Invalid ProseMirror detached watermark host attrs:\nparagraph.attrs._detachedWatermarkHost: Expected a boolean.");
46
- return value;
47
- };
48
- const NOTE_REFERENCE_MARK_XML = /^<(?:[^\s:/>]+:)?(?:footnoteRef|endnoteRef)[\s/>]/u;
49
- /**
50
- * Runs with `idKey` set, in document order: the note references a block list
51
- * makes, through table cells (and tables within cells) and text boxes.
52
- */
53
- function collectNoteRefs(blocks, idKey) {
54
- const refs = [];
55
- const walk = (containerBlocks) => {
56
- for (const block of containerBlocks) if (block.kind === "paragraph") for (const run of block.runs) {
57
- if (run.kind !== "text") continue;
58
- const noteId = run[idKey];
59
- if (noteId !== void 0) refs.push({
60
- noteId,
61
- pmPos: run.pmStart ?? 0
62
- });
63
- }
64
- else if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) walk(cell.blocks);
65
- else if (block.kind === "textBox") walk(block.content);
66
- };
67
- walk(blocks);
68
- return refs;
69
- }
70
- /** Whether captured run-child markup is a note story's `w:footnoteRef`/`w:endnoteRef`. */
71
- function isNoteReferenceMarkXml(xml) {
72
- return NOTE_REFERENCE_MARK_XML.test(xml);
73
- }
74
22
  /**
75
23
  * Word's layout fallback when an imported OOXML style hierarchy never supplies
76
24
  * `w:sz`. ECMA-376 deliberately leaves that terminal fallback to the consumer;
@@ -79,1905 +27,6 @@ function isNoteReferenceMarkXml(xml) {
79
27
  * default.
80
28
  */
81
29
  const WORD_UNSPECIFIED_FONT_SIZE = 10;
82
- const TEXT_BOX_ANCHOR_BLOCK_ID = Symbol.for("stll.textBoxAnchorBlockId");
83
- const DEFAULT_TABLE_CELL_MARGIN_TWIPS = {
84
- top: 0,
85
- right: 108,
86
- bottom: 0,
87
- left: 108
88
- };
89
- const DEFAULT_BLACK_TEXT_COLOR_VALUES = /* @__PURE__ */ new Set(["000000", "000"]);
90
- function normalizeResolvedTextColor(color) {
91
- return color.trim().toLowerCase().replace(/^#/u, "");
92
- }
93
- function isDefaultBlackResolvedTextColor(color) {
94
- return DEFAULT_BLACK_TEXT_COLOR_VALUES.has(normalizeResolvedTextColor(color));
95
- }
96
- function areResolvedTextColorsEqual(left, right) {
97
- return normalizeResolvedTextColor(left) === normalizeResolvedTextColor(right);
98
- }
99
- /**
100
- * Constrain image dimensions to fit within the page content area.
101
- * Scales proportionally if height exceeds pageContentHeight.
102
- */
103
- function constrainImageToPage(width, height, pageContentHeight) {
104
- if (!pageContentHeight || height <= pageContentHeight) return {
105
- width,
106
- height
107
- };
108
- const scale = pageContentHeight / height;
109
- return {
110
- width: Math.round(width * scale),
111
- height: pageContentHeight
112
- };
113
- }
114
- /**
115
- * Convert twips to pixels (1 twip = 1/1440 inch, 1 inch = 96 CSS px).
116
- * No rounding — precision prevents cumulative layout drift across paragraphs.
117
- */
118
- function twipsToPixels(twips) {
119
- return twips / 1440 * 96;
120
- }
121
- /**
122
- * Generate a unique block ID.
123
- */
124
- let blockIdCounter = 0;
125
- function nextBlockId() {
126
- return `block-${++blockIdCounter}`;
127
- }
128
- function applyMarkerAllCaps(marker, allCaps) {
129
- if (marker === null || !allCaps) return marker;
130
- return marker.toLocaleUpperCase();
131
- }
132
- function computeListMarker(pmAttrs, state) {
133
- return advanceListMarker(pmAttrs, state);
134
- }
135
- /**
136
- * Reset the block ID counter (useful for testing).
137
- */
138
- function resetBlockIdCounter() {
139
- blockIdCounter = 0;
140
- }
141
- /**
142
- * What the painter does with each mark the schema declares.
143
- *
144
- * The switch below used to end in `default: break`, so a mark that reached it
145
- * was silently not painted whether that was the decision or an omission. The
146
- * table is total over {@link SchemaMarkName}: a mark added to the schema
147
- * without a paint decision does not compile, and a mark this table says is
148
- * painted but the switch does not handle panics rather than disappearing.
149
- */
150
- const MARK_PAINT_DISPOSITIONS = {
151
- bold: "runFormatting",
152
- italic: "runFormatting",
153
- underline: "runFormatting",
154
- strike: "runFormatting",
155
- textColor: "runFormatting",
156
- runShading: "runFormatting",
157
- highlight: "runFormatting",
158
- fontSize: "runFormatting",
159
- fontFamily: "runFormatting",
160
- language: "runFormatting",
161
- superscript: "runFormatting",
162
- subscript: "runFormatting",
163
- hyperlink: "runFormatting",
164
- allCaps: "runFormatting",
165
- smallCaps: "runFormatting",
166
- footnoteRef: "runFormatting",
167
- characterSpacing: "runFormatting",
168
- emboss: "runFormatting",
169
- imprint: "runFormatting",
170
- hidden: "runFormatting",
171
- textShadow: "runFormatting",
172
- emphasisMark: "runFormatting",
173
- textOutline: "runFormatting",
174
- rtl: "runFormatting",
175
- textEffect: "runFormatting",
176
- runFormattingOverride: "runFormatting",
177
- comment: "runFormatting",
178
- insertion: "runFormatting",
179
- deletion: "runFormatting",
180
- inlineWrapper: "runFormatting",
181
- characterStyle: "resolvedElsewhere",
182
- runIdentity: "notPainted",
183
- runPropertyChange: "notPainted"
184
- };
185
- const markPaintDisposition = (name) => {
186
- return MARK_PAINT_DISPOSITIONS[name];
187
- };
188
- /**
189
- * Whether a layer states how its content is laid out.
190
- *
191
- * A smart tag and a custom-XML wrapper name an element in another vocabulary
192
- * and say nothing about layout, so the painter draws the text exactly as it
193
- * would without them. The `switch` is here rather than inline so a kind added
194
- * to the model has to answer the question.
195
- */
196
- const paintedDirection = (layer) => {
197
- switch (layer.kind) {
198
- case "bidi": return layer.direction === void 0 ? { control: layer.control } : {
199
- control: layer.control,
200
- direction: layer.direction
201
- };
202
- case "smartTag":
203
- case "customXml": return;
204
- default: return;
205
- }
206
- };
207
- /**
208
- * The direction the innermost bidirectional layer of a stack lays its content out in.
209
- *
210
- * Innermost wins: a bidirectional wrapper inside another is the one the text is
211
- * laid out in, and the outer layers are what the save leg rebuilds the nesting
212
- * from. The layers that state no direction are skipped rather than ending the
213
- * search, so a smart tag inside a `w:dir` still reads right to left.
214
- */
215
- const innermostBidiWrapper = (stack) => {
216
- for (const layer of stack.toReversed()) {
217
- const direction = paintedDirection(layer);
218
- if (direction !== void 0) return direction;
219
- }
220
- };
221
- /**
222
- * Extract run formatting from ProseMirror marks.
223
- */
224
- function extractRunFormatting(marks, theme, fontAlternates) {
225
- const formatting = {};
226
- let hasNoteRef = false;
227
- let runFormattingOverride;
228
- for (const mark of marks) switch (mark.type.name) {
229
- case "bold":
230
- formatting.bold = true;
231
- break;
232
- case "italic":
233
- formatting.italic = true;
234
- break;
235
- case "underline": {
236
- const attrs = expectUnderlineMarkAttrs(mark);
237
- if (attrs.style || attrs.color) {
238
- const underlineObj = {};
239
- if (attrs.style) underlineObj.style = attrs.style;
240
- if (attrs.color) underlineObj.color = resolveColor(attrs.color, theme);
241
- formatting.underline = underlineObj;
242
- } else formatting.underline = true;
243
- break;
244
- }
245
- case "strike":
246
- formatting.strike = true;
247
- break;
248
- case "textColor": {
249
- const attrs = expectTextColorMarkAttrs(mark);
250
- if (attrs.themeColor || attrs.rgb) {
251
- const colorArg = {};
252
- if (attrs.rgb) colorArg.rgb = attrs.rgb;
253
- if (attrs.themeColor) colorArg.themeColor = attrs.themeColor;
254
- if (attrs.themeTint) colorArg.themeTint = attrs.themeTint;
255
- if (attrs.themeShade) colorArg.themeShade = attrs.themeShade;
256
- if (!isAutomaticTextColorValue(colorArg)) {
257
- formatting.color = resolveColor(colorArg, theme);
258
- formatting.textColorSource = "direct";
259
- }
260
- }
261
- break;
262
- }
263
- case "highlight":
264
- formatting.highlight = resolveHighlightToCss(expectHighlightMarkAttrs(mark).color);
265
- break;
266
- case "runShading": {
267
- const shadingCss = resolveShadingFill(runShadingAttrsToShading(expectRunShadingMarkAttrs(mark)), theme);
268
- if (shadingCss) formatting.shading = shadingCss;
269
- break;
270
- }
271
- case "fontSize":
272
- formatting.fontSize = expectFontSizeMarkAttrs(mark).size / 2;
273
- break;
274
- case "fontFamily": {
275
- const attrs = expectFontFamilyMarkAttrs(mark);
276
- const font = resolveWesternThemeFont(attrs, theme);
277
- if (font) {
278
- formatting.fontFamily = font;
279
- const alternate = getFontAlternate(font, fontAlternates);
280
- if (alternate) formatting.alternateFontFamily = alternate;
281
- }
282
- const eastAsiaFont = resolveEastAsiaThemeFont(attrs, theme);
283
- if (eastAsiaFont) {
284
- formatting.eastAsiaFontFamily = eastAsiaFont;
285
- const alternate = getFontAlternate(eastAsiaFont, fontAlternates);
286
- if (alternate) formatting.eastAsiaAlternateFontFamily = alternate;
287
- }
288
- if (attrs.hint) formatting.eastAsiaHint = attrs.hint === "eastAsia";
289
- const complexScriptFont = resolveComplexScriptThemeFont(attrs, theme);
290
- if (complexScriptFont) {
291
- formatting.complexScriptFontFamily = complexScriptFont;
292
- const alternate = getFontAlternate(complexScriptFont, fontAlternates);
293
- if (alternate) formatting.complexScriptAlternateFontFamily = alternate;
294
- }
295
- break;
296
- }
297
- case "language": {
298
- const attrs = expectLanguageMarkAttrs(mark);
299
- formatting.language = {
300
- ...attrs.val ? { val: attrs.val } : {},
301
- ...attrs.eastAsia ? { eastAsia: attrs.eastAsia } : {},
302
- ...attrs.bidi ? { bidi: attrs.bidi } : {}
303
- };
304
- break;
305
- }
306
- case "characterSpacing": {
307
- const attrs = expectCharacterSpacingMarkAttrs(mark);
308
- if (attrs.spacing !== void 0) formatting.letterSpacing = twipsToPixels(attrs.spacing);
309
- if (attrs.position !== void 0 && attrs.position !== 0) formatting.positionPx = halfPointsToPixels(attrs.position);
310
- const horizontalScale = normalizeHorizontalScalePercent(attrs.scale);
311
- if (horizontalScale !== void 0) formatting.horizontalScale = horizontalScale;
312
- if (attrs.kerning !== void 0 && attrs.kerning > 0) formatting.kerningMinPt = halfPointsToPoints(attrs.kerning);
313
- break;
314
- }
315
- case "allCaps":
316
- formatting.allCaps = true;
317
- break;
318
- case "smallCaps":
319
- formatting.smallCaps = true;
320
- break;
321
- case "emboss":
322
- formatting.emboss = true;
323
- break;
324
- case "imprint":
325
- formatting.imprint = true;
326
- break;
327
- case "hidden":
328
- formatting.hidden = true;
329
- break;
330
- case "textShadow":
331
- formatting.textShadow = true;
332
- break;
333
- case "textOutline":
334
- formatting.textOutline = true;
335
- break;
336
- case "rtl":
337
- formatting.rtl = true;
338
- break;
339
- case "textEffect":
340
- formatting.textEffect = expectTextEffectMarkAttrs(mark).effect;
341
- break;
342
- case "runFormattingOverride":
343
- runFormattingOverride = expectRunFormattingOverrideMarkAttrs(mark);
344
- break;
345
- case "emphasisMark":
346
- formatting.emphasisMark = expectEmphasisMarkAttrs(mark).type ?? "dot";
347
- break;
348
- case "superscript":
349
- formatting.superscript = true;
350
- break;
351
- case "subscript":
352
- formatting.subscript = true;
353
- break;
354
- case "hyperlink": {
355
- const attrs = expectHyperlinkMarkAttrs(mark);
356
- const link = { href: attrs.href };
357
- if (attrs.tooltip !== void 0) link.tooltip = attrs.tooltip;
358
- if (attrs._docxHyperlinkIndex !== void 0) setHyperlinkInstanceIndex(link, attrs._docxHyperlinkIndex);
359
- formatting.hyperlink = link;
360
- break;
361
- }
362
- case "footnoteRef": {
363
- hasNoteRef = true;
364
- const attrs = expectFootnoteRefMarkAttrs(mark);
365
- if (attrs.vertAlign === "superscript") formatting.superscript = true;
366
- const id = typeof attrs.id === "string" ? Number.parseInt(attrs.id, 10) : attrs.id;
367
- if (attrs.noteType === "endnote") formatting.endnoteRefId = id;
368
- else formatting.footnoteRefId = id;
369
- break;
370
- }
371
- case "comment": {
372
- const commentId = expectCommentMarkAttrs(mark).commentId;
373
- if (commentId) {
374
- if (!formatting.commentIds) formatting.commentIds = [];
375
- formatting.commentIds.push(commentId);
376
- }
377
- break;
378
- }
379
- case "insertion": {
380
- const attrs = expectTrackedChangeMarkAttrs(mark);
381
- formatting.isInsertion = true;
382
- formatting.changeAuthor = attrs.author;
383
- if (attrs.date !== void 0) formatting.changeDate = attrs.date;
384
- formatting.changeRevisionId = attrs.revisionId;
385
- if (attrs.provenance === "suggested") {
386
- formatting.isSuggestion = true;
387
- if (attrs.suggestionId) formatting.suggestionId = attrs.suggestionId;
388
- }
389
- break;
390
- }
391
- case "deletion": {
392
- const attrs = expectTrackedChangeMarkAttrs(mark);
393
- formatting.isDeletion = true;
394
- if (attrs._historicalFormatting) formatting.usesHistoricalFormatting = true;
395
- formatting.changeAuthor = attrs.author;
396
- if (attrs.date !== void 0) formatting.changeDate = attrs.date;
397
- formatting.changeRevisionId = attrs.revisionId;
398
- if (attrs.provenance === "suggested") {
399
- formatting.isSuggestion = true;
400
- if (attrs.suggestionId) formatting.suggestionId = attrs.suggestionId;
401
- }
402
- break;
403
- }
404
- case "inlineWrapper": {
405
- const bidiWrapper = innermostBidiWrapper(expectInlineWrapperMarkAttrs(mark).stack);
406
- if (bidiWrapper) formatting.bidiWrapper = bidiWrapper;
407
- break;
408
- }
409
- default:
410
- if (markPaintDisposition(mark.type.name) === "runFormatting") panic(`Mark ${mark.type.name} is painted as run formatting but has no branch here`);
411
- break;
412
- }
413
- if (runFormattingOverride) applyRunFormattingOverrides(formatting, runFormattingOverride);
414
- if (hasNoteRef && formatting.subscript) delete formatting.superscript;
415
- return formatting;
416
- }
417
- const resolveWesternThemeFont = (fontFamily, theme) => {
418
- const themeRef = fontFamily.asciiTheme ?? fontFamily.hAnsiTheme;
419
- return (themeRef ? resolveThemeFont(themeRef, theme?.fontScheme) : null) ?? fontFamily.ascii ?? fontFamily.hAnsi ?? void 0;
420
- };
421
- const resolveComplexScriptThemeFont = (fontFamily, theme) => {
422
- return (fontFamily.csTheme ? resolveThemeFont(fontFamily.csTheme, theme?.fontScheme) : null) ?? fontFamily.cs ?? void 0;
423
- };
424
- const resolveEastAsiaThemeFont = (fontFamily, theme) => {
425
- return (fontFamily.eastAsiaTheme ? resolveThemeFont(fontFamily.eastAsiaTheme, theme?.fontScheme) : null) ?? fontFamily.eastAsia ?? void 0;
426
- };
427
- function isAutomaticTextColorValue(color) {
428
- const rgb = color.rgb?.trim().toLowerCase();
429
- return color.auto === true || rgb === "auto" || !rgb && !color.themeColor;
430
- }
431
- function markDefaultBlackTextColorSource(formatting, paraDefaults) {
432
- if (formatting.textColorSource === "direct" || formatting.color === void 0 || paraDefaults.color === void 0 || !isDefaultBlackResolvedTextColor(formatting.color) || !areResolvedTextColorsEqual(formatting.color, paraDefaults.color)) return formatting;
433
- return {
434
- ...formatting,
435
- textColorSource: "paragraphDefault"
436
- };
437
- }
438
- function mergeRunFormatting(paraDefaults, formatting) {
439
- if (formatting.isDeletion && formatting.usesHistoricalFormatting) return formatting;
440
- const merged = {
441
- ...paraDefaults,
442
- ...markDefaultBlackTextColorSource(formatting, paraDefaults)
443
- };
444
- if (formatting.fontFamily !== void 0 && formatting.alternateFontFamily === void 0) delete merged.alternateFontFamily;
445
- if (formatting.eastAsiaFontFamily !== void 0 && formatting.eastAsiaAlternateFontFamily === void 0) delete merged.eastAsiaAlternateFontFamily;
446
- if (formatting.complexScriptFontFamily !== void 0 && formatting.complexScriptAlternateFontFamily === void 0) delete merged.complexScriptAlternateFontFamily;
447
- if (merged.letterSpacing === 0) delete merged.letterSpacing;
448
- if (formatting.horizontalScale === 100 && paraDefaults.horizontalScale === void 0) delete merged.horizontalScale;
449
- return merged;
450
- }
451
- const CHARACTER_STYLE_TOGGLE_LAYOUT_PROPERTIES = {
452
- allCaps: "allCaps",
453
- bold: "bold",
454
- boldCs: "complexScriptBold",
455
- emboss: "emboss",
456
- hidden: "hidden",
457
- imprint: "imprint",
458
- italic: "italic",
459
- italicCs: "complexScriptItalic",
460
- outline: "textOutline",
461
- shadow: "textShadow",
462
- smallCaps: "smallCaps",
463
- strike: "strike"
464
- };
465
- /** Restore character-style toggle values that plain visual marks cannot represent. */
466
- function applyCharacterStyleToggleFormatting({ formatting, marks, paragraphFormatting, styleResolver }) {
467
- const characterStyleMark = marks.find((mark) => mark.type.name === "characterStyle");
468
- const styleRPr = characterStyleMark ? styleResolver.getRunStyleOwnProperties(expectCharacterStyleMarkAttrs(characterStyleMark).styleId) : styleResolver.getDefaultCharacterStyle()?.rPr;
469
- if (!styleRPr) return;
470
- const effectiveStyleFormatting = resolveEffectiveRunStyleFormatting({
471
- marks,
472
- paragraphFormatting,
473
- styleResolver
474
- });
475
- for (const property of STYLE_TOGGLE_KEYS) {
476
- if (styleRPr[property] === void 0) continue;
477
- const layoutProperty = CHARACTER_STYLE_TOGGLE_LAYOUT_PROPERTIES[property];
478
- if (formatting[layoutProperty] === void 0) Reflect.set(formatting, layoutProperty, effectiveStyleFormatting?.[property] ?? false);
479
- }
480
- }
481
- const suppressInheritedRunFormatting = (formatting, property) => {
482
- Reflect.set(formatting, property, void 0);
483
- };
484
- const RUN_FORMATTING_OVERRIDE_HANDLER_ENTRIES = Object.entries({
485
- _authoredOff() {},
486
- _authoredOn() {},
487
- _authoredValues() {},
488
- allCaps(formatting, value) {
489
- if (typeof value === "boolean") formatting.allCaps = value;
490
- },
491
- bold(formatting, value) {
492
- if (typeof value === "boolean") formatting.bold = value;
493
- },
494
- boldCs(formatting, value) {
495
- if (typeof value === "boolean") formatting.complexScriptBold = value;
496
- },
497
- color(formatting, value) {
498
- if (value === "auto") {
499
- suppressInheritedRunFormatting(formatting, "color");
500
- suppressInheritedRunFormatting(formatting, "textColorSource");
501
- }
502
- },
503
- complexScriptPropertyAbsences() {},
504
- cs(formatting, value) {
505
- if (typeof value === "boolean") formatting.forceComplexScript = value;
506
- },
507
- directFontProperties() {},
508
- doubleStrike(formatting, value) {
509
- if (value === false && formatting.strike === void 0) formatting.strike = false;
510
- },
511
- effect(formatting, value) {
512
- if (value === "none") suppressInheritedRunFormatting(formatting, "textEffect");
513
- },
514
- emboss(formatting, value) {
515
- if (typeof value === "boolean") formatting.emboss = value;
516
- },
517
- emphasisMark(formatting, value) {
518
- if (value === "none") suppressInheritedRunFormatting(formatting, "emphasisMark");
519
- },
520
- fontSizeCs(formatting, value) {
521
- if (typeof value === "number") formatting.complexScriptFontSize = value / 2;
522
- },
523
- noProof() {},
524
- hidden(formatting, value) {
525
- if (typeof value === "boolean") formatting.hidden = value;
526
- },
527
- highlight(formatting, value) {
528
- if (value === "none") suppressInheritedRunFormatting(formatting, "highlight");
529
- },
530
- imprint(formatting, value) {
531
- if (typeof value === "boolean") formatting.imprint = value;
532
- },
533
- italic(formatting, value) {
534
- if (typeof value === "boolean") formatting.italic = value;
535
- },
536
- italicCs(formatting, value) {
537
- if (typeof value === "boolean") formatting.complexScriptItalic = value;
538
- },
539
- kerning(formatting, value) {
540
- if (value === 0) formatting.kerningMinPt = 0;
541
- },
542
- outline(formatting, value) {
543
- if (typeof value === "boolean") formatting.textOutline = value;
544
- },
545
- position(formatting, value) {
546
- if (value === 0) formatting.positionPx = 0;
547
- },
548
- rtl(formatting, value) {
549
- if (value === false) formatting.rtl = false;
550
- },
551
- scale(formatting, value) {
552
- if (value === 100) formatting.horizontalScale = 100;
553
- },
554
- shading(formatting, value) {
555
- if (typeof value === "object" && value !== null && Reflect.get(value, "pattern") === "nil") suppressInheritedRunFormatting(formatting, "shading");
556
- },
557
- shadow(formatting, value) {
558
- if (typeof value === "boolean") formatting.textShadow = value;
559
- },
560
- smallCaps(formatting, value) {
561
- if (typeof value === "boolean") formatting.smallCaps = value;
562
- },
563
- spacing(formatting, value) {
564
- if (value === 0) formatting.letterSpacing = 0;
565
- },
566
- strike(formatting, value) {
567
- if (typeof value === "boolean") formatting.strike = value;
568
- },
569
- underline(formatting, value) {
570
- if (value === "none") formatting.underline = false;
571
- },
572
- vertAlign(formatting, value) {
573
- if (value === "baseline") {
574
- formatting.superscript = false;
575
- formatting.subscript = false;
576
- }
577
- }
578
- });
579
- function applyRunFormattingOverrides(formatting, attrs) {
580
- for (const [key, handler] of RUN_FORMATTING_OVERRIDE_HANDLER_ENTRIES) handler(formatting, Reflect.get(attrs, key));
581
- }
582
- function textFormattingToRunFormatting(defaultTextFormatting, theme, fontAlternates) {
583
- if (!defaultTextFormatting) return {};
584
- const result = {};
585
- const fontFamily = defaultTextFormatting.fontFamily ? resolveWesternThemeFont(defaultTextFormatting.fontFamily, theme) : void 0;
586
- if (fontFamily) {
587
- result.fontFamily = fontFamily;
588
- const alternate = getFontAlternate(fontFamily, fontAlternates);
589
- if (alternate) result.alternateFontFamily = alternate;
590
- }
591
- const eastAsiaFontFamily = defaultTextFormatting.fontFamily ? resolveEastAsiaThemeFont(defaultTextFormatting.fontFamily, theme) : void 0;
592
- if (eastAsiaFontFamily) {
593
- result.eastAsiaFontFamily = eastAsiaFontFamily;
594
- const alternate = getFontAlternate(eastAsiaFontFamily, fontAlternates);
595
- if (alternate) result.eastAsiaAlternateFontFamily = alternate;
596
- }
597
- if (defaultTextFormatting.fontFamily?.hint) result.eastAsiaHint = defaultTextFormatting.fontFamily.hint === "eastAsia";
598
- const complexScriptFontFamily = defaultTextFormatting.fontFamily ? resolveComplexScriptThemeFont(defaultTextFormatting.fontFamily, theme) : void 0;
599
- if (complexScriptFontFamily) {
600
- result.complexScriptFontFamily = complexScriptFontFamily;
601
- const alternate = getFontAlternate(complexScriptFontFamily, fontAlternates);
602
- if (alternate) result.complexScriptAlternateFontFamily = alternate;
603
- }
604
- if (defaultTextFormatting.language) result.language = { ...defaultTextFormatting.language };
605
- if (defaultTextFormatting.fontSize !== void 0) result.fontSize = defaultTextFormatting.fontSize / 2;
606
- if (defaultTextFormatting.fontSizeCs !== void 0) result.complexScriptFontSize = defaultTextFormatting.fontSizeCs / 2;
607
- if (defaultTextFormatting.bold !== void 0) result.bold = defaultTextFormatting.bold;
608
- if (defaultTextFormatting.boldCs !== void 0) result.complexScriptBold = defaultTextFormatting.boldCs;
609
- if (defaultTextFormatting.italic !== void 0) result.italic = defaultTextFormatting.italic;
610
- if (defaultTextFormatting.italicCs !== void 0) result.complexScriptItalic = defaultTextFormatting.italicCs;
611
- if (defaultTextFormatting.rtl !== void 0) result.rtl = defaultTextFormatting.rtl;
612
- if (defaultTextFormatting.cs !== void 0) result.forceComplexScript = defaultTextFormatting.cs;
613
- if (defaultTextFormatting.underline && defaultTextFormatting.underline.style !== "none") {
614
- result.underline = { style: defaultTextFormatting.underline.style };
615
- if (defaultTextFormatting.underline.color) result.underline.color = resolveColor(defaultTextFormatting.underline.color, theme);
616
- }
617
- if (defaultTextFormatting.strike !== void 0) result.strike = defaultTextFormatting.strike;
618
- if (defaultTextFormatting.color && !isAutomaticTextColorValue(defaultTextFormatting.color)) {
619
- result.color = resolveColor(defaultTextFormatting.color, theme);
620
- result.textColorSource = "paragraphDefault";
621
- }
622
- if (defaultTextFormatting.highlight) {
623
- const highlight = resolveHighlightToCss(defaultTextFormatting.highlight);
624
- if (highlight) result.highlight = highlight;
625
- }
626
- if (defaultTextFormatting.vertAlign === "superscript") result.superscript = true;
627
- if (defaultTextFormatting.vertAlign === "subscript") result.subscript = true;
628
- if (defaultTextFormatting.allCaps !== void 0) result.allCaps = defaultTextFormatting.allCaps;
629
- if (defaultTextFormatting.smallCaps !== void 0) result.smallCaps = defaultTextFormatting.smallCaps;
630
- if (defaultTextFormatting.spacing !== void 0 && defaultTextFormatting.spacing !== 0) result.letterSpacing = twipsToPixels(defaultTextFormatting.spacing);
631
- if (defaultTextFormatting.position !== void 0 && defaultTextFormatting.position !== 0) result.positionPx = halfPointsToPixels(defaultTextFormatting.position);
632
- const horizontalScale = normalizeHorizontalScalePercent(defaultTextFormatting.scale);
633
- if (horizontalScale !== void 0 && horizontalScale !== 100) result.horizontalScale = horizontalScale;
634
- if (defaultTextFormatting.kerning !== void 0 && defaultTextFormatting.kerning > 0) result.kerningMinPt = halfPointsToPoints(defaultTextFormatting.kerning);
635
- if (defaultTextFormatting.emboss !== void 0) result.emboss = defaultTextFormatting.emboss;
636
- if (defaultTextFormatting.imprint !== void 0) result.imprint = defaultTextFormatting.imprint;
637
- if (defaultTextFormatting.shadow !== void 0) result.textShadow = defaultTextFormatting.shadow;
638
- if (defaultTextFormatting.outline !== void 0) result.textOutline = defaultTextFormatting.outline;
639
- if (defaultTextFormatting.emphasisMark && defaultTextFormatting.emphasisMark !== "none") result.emphasisMark = defaultTextFormatting.emphasisMark;
640
- return result;
641
- }
642
- const LIST_MARKER_FORMATTING_KEYS = [
643
- "fontFamily",
644
- "alternateFontFamily",
645
- "eastAsiaFontFamily",
646
- "eastAsiaAlternateFontFamily",
647
- "complexScriptFontFamily",
648
- "complexScriptAlternateFontFamily",
649
- "fontSize",
650
- "complexScriptFontSize",
651
- "bold",
652
- "complexScriptBold",
653
- "italic",
654
- "complexScriptItalic",
655
- "rtl",
656
- "forceComplexScript",
657
- "color"
658
- ];
659
- /**
660
- * The numbering symbol's typography: the numbering level's `w:rPr` applied
661
- * over the paragraph mark's run properties (ECMA-376 §17.9.24, §17.3.1.29).
662
- * Text runs of the paragraph play no part.
663
- */
664
- function listMarkerFormattingFor(levelFormatting, paragraphMarkFormatting, theme, fontAlternates) {
665
- const runFormatting = textFormattingToRunFormatting(mergeTextFormatting(paragraphMarkFormatting, levelFormatting), theme, fontAlternates);
666
- const formatting = {};
667
- for (const key of LIST_MARKER_FORMATTING_KEYS) if (runFormatting[key] !== void 0) Reflect.set(formatting, key, runFormatting[key]);
668
- return Object.keys(formatting).length > 0 ? formatting : void 0;
669
- }
670
- function paragraphRunDefaults(pmAttrs, theme, fontAlternates) {
671
- return textFormattingToRunFormatting(pmAttrs.defaultTextFormatting, theme, fontAlternates);
672
- }
673
- /**
674
- * Build an ImageRun from ProseMirror node attrs, applying conditional property assignment
675
- * to satisfy exactOptionalPropertyTypes.
676
- */
677
- function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange, forceLayoutInCell) {
678
- const run = {
679
- kind: "image",
680
- src: attrs.src,
681
- ...attrs.preview ? { preview: attrs.preview } : {},
682
- width: constrained.width,
683
- height: constrained.height,
684
- pmStart,
685
- pmEnd
686
- };
687
- if (attrs.alt !== void 0) run.alt = attrs.alt;
688
- if (attrs.transform !== void 0) run.transform = attrs.transform;
689
- if (attrs.opacity != null) run.opacity = attrs.opacity;
690
- if (attrs.brightness != null) run.brightness = attrs.brightness;
691
- if (attrs.contrast != null) run.contrast = attrs.contrast;
692
- if (attrs.wrapType !== void 0) run.wrapType = attrs.wrapType;
693
- if (attrs.displayMode !== void 0) run.displayMode = attrs.displayMode;
694
- if (attrs.cssFloat !== void 0) run.cssFloat = attrs.cssFloat;
695
- if (attrs.distTop !== void 0) run.distTop = attrs.distTop;
696
- if (attrs.distBottom !== void 0) run.distBottom = attrs.distBottom;
697
- if (attrs.distLeft !== void 0) run.distLeft = attrs.distLeft;
698
- if (attrs.distRight !== void 0) run.distRight = attrs.distRight;
699
- if (attrs._docxObjectPreview === true) run.exactLineHeight = true;
700
- if (attrs.cropTop != null) run.cropTop = attrs.cropTop;
701
- if (attrs.cropRight != null) run.cropRight = attrs.cropRight;
702
- if (attrs.cropBottom != null) run.cropBottom = attrs.cropBottom;
703
- if (attrs.cropLeft != null) run.cropLeft = attrs.cropLeft;
704
- if (attrs.borderWidth != null) run.borderWidth = attrs.borderWidth;
705
- if (attrs.borderColor) run.borderColor = attrs.borderColor;
706
- const runBorderStyle = cssBorderStyleOf(attrs.borderStyle);
707
- if (runBorderStyle) run.borderStyle = runBorderStyle;
708
- if (attrs.position !== void 0) run.position = attrs.position;
709
- if (forceLayoutInCell) run.layoutInCell = true;
710
- else if (attrs.anchor?.layoutInCell !== void 0) run.layoutInCell = attrs.anchor.layoutInCell;
711
- if (trackedChange?.isInsertion) run.isInsertion = true;
712
- if (trackedChange?.isDeletion) run.isDeletion = true;
713
- if (trackedChange?.changeAuthor !== void 0) run.changeAuthor = trackedChange.changeAuthor;
714
- if (trackedChange?.changeDate !== void 0) run.changeDate = trackedChange.changeDate;
715
- if (trackedChange?.changeRevisionId !== void 0) run.changeRevisionId = trackedChange.changeRevisionId;
716
- return run;
717
- }
718
- /** A package image whose bytes cannot paint still owns its authored line box. */
719
- const hasRelationshipBackedImageBox = (attrs) => typeof attrs.rId === "string" && attrs.rId.trim().length > 0 && typeof attrs.width === "number" && Number.isFinite(attrs.width) && attrs.width >= 0 && typeof attrs.height === "number" && Number.isFinite(attrs.height) && attrs.height >= 0;
720
- /**
721
- * A preview counts as a source: the bytes are not in the attrs, but the
722
- * display list can produce them without I/O when it interns the descriptor.
723
- */
724
- const hasPaintableImageSource = (attrs) => attrs.preview !== void 0 || sanitizeImageSrc(attrs.src) !== void 0;
725
- /**
726
- * In TOC paragraphs, strip the resolved Hyperlink character-style colour and
727
- * underline so the painter's link fallback doesn't fire. The PM doc keeps the
728
- * original marks so copy/paste out of a TOC still carries the Hyperlink
729
- * styling like Word does. Applies to both text and field runs — a TOC entry's
730
- * page number is a PAGEREF field inside the entry's hyperlink.
731
- *
732
- * Mutates `formatting` in place; cheaper than re-cloning per run.
733
- */
734
- function stripTocHyperlinkStyle(formatting) {
735
- if (!formatting.hyperlink) return;
736
- formatting.hyperlink.noDefaultStyle = true;
737
- delete formatting.color;
738
- delete formatting.underline;
739
- }
740
- const isTrackedRunMark = ({ type }) => type.name === "insertion" || type.name === "deletion";
741
- /**
742
- * Inline wrappers own marks that apply to every projected descendant. A
743
- * descendant's mark of the same type is more specific, and any descendant
744
- * revision replaces (rather than combines with) an inherited revision.
745
- */
746
- const mergeProjectedInlineMarks = (inheritedMarks, ownMarks) => {
747
- if (inheritedMarks.length === 0) return ownMarks;
748
- const ownMarkTypes = new Set(ownMarks.map(({ type }) => type));
749
- const hasOwnTrackedMark = ownMarks.some(isTrackedRunMark);
750
- return [...inheritedMarks.filter((mark) => !ownMarkTypes.has(mark.type) && !(hasOwnTrackedMark && isTrackedRunMark(mark))), ...ownMarks];
751
- };
752
- function paragraphToRuns(node, startPos, _options, pageBreaks) {
753
- const runs = [];
754
- const offset = startPos + 1;
755
- const theme = _options.theme;
756
- const fontAlternates = _options.fontAlternates;
757
- const pmAttrs = expectParagraphAttrs(node);
758
- const paraDefaults = paragraphRunDefaults(pmAttrs, theme, fontAlternates);
759
- const paragraphStyleId = pmAttrs.styleId;
760
- const inTocParagraph = pmAttrs._tableOfContentsLevel !== void 0 || tableOfContentsStyleLevel({ styleId: paragraphStyleId }) !== void 0;
761
- let leadingRenderedPageBreakPending = pmAttrs.renderedPageBreakBefore === true;
762
- function pushRunsForChild(child, childPos, inheritedMarks = []) {
763
- if (child.type.name === "bookmarkBoundary") return;
764
- if (child.type.name === "renderedPageBreak") {
765
- if (leadingRenderedPageBreakPending) {
766
- leadingRenderedPageBreakPending = false;
767
- return;
768
- }
769
- runs.push({
770
- kind: "renderedPageBreak",
771
- pmStart: childPos,
772
- pmEnd: childPos + child.nodeSize
773
- });
774
- return;
775
- }
776
- const effectiveMarks = mergeProjectedInlineMarks(inheritedMarks, child.marks);
777
- if (child.type.name === "pageBreakRun") {
778
- const trackedChange = extractRunFormatting(effectiveMarks, theme, fontAlternates);
779
- if (trackedChange.isDeletion) {
780
- runs.push({
781
- kind: "text",
782
- text: "",
783
- isDeletion: true,
784
- ...trackedChange.changeAuthor !== void 0 ? { changeAuthor: trackedChange.changeAuthor } : {},
785
- ...trackedChange.changeDate !== void 0 ? { changeDate: trackedChange.changeDate } : {},
786
- ...trackedChange.changeRevisionId !== void 0 ? { changeRevisionId: trackedChange.changeRevisionId } : {},
787
- ...trackedChange.isSuggestion === true ? { isSuggestion: true } : {},
788
- ...trackedChange.suggestionId !== void 0 ? { suggestionId: trackedChange.suggestionId } : {},
789
- pmStart: childPos,
790
- pmEnd: childPos + child.nodeSize
791
- });
792
- return;
793
- }
794
- pageBreaks?.push({
795
- pmStart: childPos,
796
- pmEnd: childPos + child.nodeSize,
797
- ...trackedChange.isInsertion ? { trackedChange: {
798
- isInsertion: true,
799
- ...trackedChange.changeAuthor !== void 0 ? { changeAuthor: trackedChange.changeAuthor } : {},
800
- ...trackedChange.changeDate !== void 0 ? { changeDate: trackedChange.changeDate } : {},
801
- ...trackedChange.changeRevisionId !== void 0 ? { changeRevisionId: trackedChange.changeRevisionId } : {},
802
- ...trackedChange.isSuggestion === true ? { isSuggestion: true } : {},
803
- ...trackedChange.suggestionId !== void 0 ? { suggestionId: trackedChange.suggestionId } : {}
804
- } } : {}
805
- });
806
- return;
807
- }
808
- if (child.type.name !== "sdt") leadingRenderedPageBreakPending = false;
809
- if (child.isText && child.text) {
810
- const formatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
811
- applyCharacterStyleToggleFormatting({
812
- formatting,
813
- marks: effectiveMarks,
814
- paragraphFormatting: pmAttrs.defaultTextFormatting,
815
- styleResolver: _options.styleResolver
816
- });
817
- if (inTocParagraph) stripTocHyperlinkStyle(formatting);
818
- const run = {
819
- kind: "text",
820
- text: child.text,
821
- ...mergeRunFormatting(paraDefaults, formatting),
822
- pmStart: childPos,
823
- pmEnd: childPos + child.nodeSize
824
- };
825
- runs.push(run);
826
- return;
827
- }
828
- if (child.type.name === "symbol") {
829
- const attrs = expectSymbolAttrs(child);
830
- const text = decodeOoxmlSymbolCharacter(attrs.char);
831
- if (text === null) return;
832
- const formatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
833
- applyCharacterStyleToggleFormatting({
834
- formatting,
835
- marks: effectiveMarks,
836
- paragraphFormatting: pmAttrs.defaultTextFormatting,
837
- styleResolver: _options.styleResolver
838
- });
839
- if (inTocParagraph) stripTocHyperlinkStyle(formatting);
840
- const alternateFontFamily = getFontAlternate(attrs.font, fontAlternates);
841
- formatting.fontFamily = attrs.font;
842
- if (alternateFontFamily) formatting.alternateFontFamily = alternateFontFamily;
843
- runs.push({
844
- kind: "text",
845
- text,
846
- ...mergeRunFormatting(paraDefaults, formatting),
847
- pmStart: childPos,
848
- pmEnd: childPos + child.nodeSize
849
- });
850
- return;
851
- }
852
- if (child.type.name === "preservedXml") {
853
- const { xml, text: capturedText } = expectPreservedXmlAttrs(child);
854
- const text = capturedText === "" && _options.noteReferenceMarkText !== void 0 && isNoteReferenceMarkXml(xml) ? _options.noteReferenceMarkText : capturedText;
855
- if (text === "") return;
856
- const formatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
857
- applyCharacterStyleToggleFormatting({
858
- formatting,
859
- marks: effectiveMarks,
860
- paragraphFormatting: pmAttrs.defaultTextFormatting,
861
- styleResolver: _options.styleResolver
862
- });
863
- runs.push({
864
- kind: "text",
865
- text,
866
- ...mergeRunFormatting(paraDefaults, formatting),
867
- pmStart: childPos,
868
- pmEnd: childPos + child.nodeSize
869
- });
870
- return;
871
- }
872
- if (child.type.name === "hardBreak") {
873
- runs.push({
874
- kind: "lineBreak",
875
- pmStart: childPos,
876
- pmEnd: childPos + child.nodeSize
877
- });
878
- return;
879
- }
880
- if (child.type.name === "tab") {
881
- const formatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
882
- applyCharacterStyleToggleFormatting({
883
- formatting,
884
- marks: effectiveMarks,
885
- paragraphFormatting: pmAttrs.defaultTextFormatting,
886
- styleResolver: _options.styleResolver
887
- });
888
- const run = {
889
- kind: "tab",
890
- ...mergeRunFormatting(paraDefaults, formatting),
891
- pmStart: childPos,
892
- pmEnd: childPos + child.nodeSize
893
- };
894
- runs.push(run);
895
- return;
896
- }
897
- if (child.type.name === "image") {
898
- const attrs = expectImageAttrs(child);
899
- if (!hasPaintableImageSource(attrs) && !hasRelationshipBackedImageBox(attrs)) return;
900
- const constrained = constrainImageToPage(attrs.width ?? 100, attrs.height ?? 100, _options.pageContentHeight);
901
- const trackedFmt = extractRunFormatting(effectiveMarks, theme, fontAlternates);
902
- const run = buildImageRun(attrs, constrained, childPos, childPos + child.nodeSize, trackedFmt, _options.forceAnchorLayoutInCell);
903
- runs.push(run);
904
- return;
905
- }
906
- if (child.type.name === "structuredField") {
907
- let containsPageBreak = false;
908
- child.descendants((descendant) => {
909
- if (descendant.type.name === "pageBreakRun") {
910
- containsPageBreak = true;
911
- return false;
912
- }
913
- return true;
914
- });
915
- if (containsPageBreak) {
916
- const fieldContentStart = childPos + 1;
917
- child.forEach((fieldChild, fieldChildOffset) => {
918
- pushRunsForChild(fieldChild, fieldContentStart + fieldChildOffset, effectiveMarks);
919
- });
920
- return;
921
- }
922
- }
923
- if (child.type.name === "field" || child.type.name === "structuredField") {
924
- const attrs = expectFieldAttrs(child);
925
- const ft = attrs.fieldType;
926
- let mappedType = "OTHER";
927
- if (ft === "PAGE") mappedType = "PAGE";
928
- else if (ft === "NUMPAGES") mappedType = "NUMPAGES";
929
- else if (ft === "DATE") mappedType = "DATE";
930
- else if (ft === "TIME") mappedType = "TIME";
931
- const extractedFieldFormatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
932
- applyCharacterStyleToggleFormatting({
933
- formatting: extractedFieldFormatting,
934
- marks: effectiveMarks,
935
- paragraphFormatting: pmAttrs.defaultTextFormatting,
936
- styleResolver: _options.styleResolver
937
- });
938
- if (inTocParagraph) stripTocHyperlinkStyle(extractedFieldFormatting);
939
- const fieldFormatting = markDefaultBlackTextColorSource(extractedFieldFormatting, paraDefaults);
940
- const run = {
941
- kind: "field",
942
- fieldType: mappedType,
943
- instruction: attrs.instruction,
944
- fallback: _options.numberedRefResults?.get(child) ?? attrs.displayText ?? "",
945
- ...attrs.fldLock ? { fldLock: true } : {},
946
- pmStart: childPos,
947
- pmEnd: childPos + child.nodeSize,
948
- ...fieldFormatting
949
- };
950
- runs.push(run);
951
- return;
952
- }
953
- if (child.type.name === "math") {
954
- const attrs = expectMathAttrs(child);
955
- const plainText = attrs.plainText || "[equation]";
956
- runs.push({
957
- kind: "math",
958
- display: attrs.display ?? "inline",
959
- ommlXml: attrs.ommlXml,
960
- plainText,
961
- italic: true,
962
- fontFamily: "Cambria Math",
963
- pmStart: childPos,
964
- pmEnd: childPos + child.nodeSize
965
- });
966
- return;
967
- }
968
- if (child.type.name === "sdt") {
969
- const sdtInnerOffset = childPos + 1;
970
- child.forEach((sdtChild, sdtChildOffset) => {
971
- pushRunsForChild(sdtChild, sdtInnerOffset + sdtChildOffset, effectiveMarks);
972
- });
973
- }
974
- }
975
- node.forEach((child, childOffset) => {
976
- pushRunsForChild(child, offset + childOffset);
977
- });
978
- const { defaultFont, defaultSize } = _options;
979
- if (defaultFont !== void 0 || defaultSize !== void 0) for (const run of runs) switch (run.kind) {
980
- case "text":
981
- case "tab":
982
- case "renderedPageBreak":
983
- case "field":
984
- case "math":
985
- if (defaultFont !== void 0) run.fontFamily ??= defaultFont;
986
- if (defaultSize !== void 0) run.fontSize ??= defaultSize;
987
- break;
988
- case "image":
989
- case "lineBreak": break;
990
- default:
991
- }
992
- return runs;
993
- }
994
- function toListMarkerRevision(kind, info) {
995
- const revision = { kind };
996
- if (typeof info?.author === "string") revision.author = info.author;
997
- if (typeof info?.date === "string") revision.date = info.date;
998
- if (typeof info?.id === "number") revision.revisionId = info.id;
999
- return revision;
1000
- }
1001
- function isAddedNumberingChange(change) {
1002
- const previousFormatting = change.previousFormatting;
1003
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && previousFormatting.numPr == null;
1004
- }
1005
- function isRemovedNumberingChange(change) {
1006
- const previousFormatting = change.previousFormatting;
1007
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting.numPr);
1008
- }
1009
- function isChangedNumberingChange(currentNumPr, change) {
1010
- const previousFormatting = change.previousFormatting;
1011
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting.numPr) && !sameStatedParagraphNumbering(previousFormatting.numPr, currentNumPr);
1012
- }
1013
- function toPreviousListAttrs(previousFormatting) {
1014
- const attrs = {};
1015
- const numPr = previousFormatting.numPr;
1016
- if (isListNumPr(numPr)) attrs.numPr = numPr;
1017
- const listIsBullet = previousFormatting.listIsBullet;
1018
- if (listIsBullet !== void 0) attrs.listIsBullet = listIsBullet;
1019
- const listIsLegal = previousFormatting.listIsLegal;
1020
- if (listIsLegal !== void 0) attrs.listIsLegal = listIsLegal;
1021
- const listMarker = previousFormatting.listMarker;
1022
- if (listMarker !== void 0) attrs.listMarker = listMarker;
1023
- const listMarkerTemplate = previousFormatting.listMarkerTemplate;
1024
- if (listMarkerTemplate !== void 0) attrs.listMarkerTemplate = listMarkerTemplate;
1025
- const listNumFmt = previousFormatting.listNumFmt;
1026
- if (listNumFmt !== void 0) attrs.listNumFmt = listNumFmt;
1027
- const listLevelNumFmts = previousFormatting.listLevelNumFmts;
1028
- if (listLevelNumFmts !== void 0) attrs.listLevelNumFmts = listLevelNumFmts;
1029
- const listLevelStarts = previousFormatting.listLevelStarts;
1030
- if (listLevelStarts !== void 0) attrs.listLevelStarts = listLevelStarts;
1031
- const listAbstractNumId = previousFormatting.listAbstractNumId;
1032
- if (listAbstractNumId !== void 0) attrs.listAbstractNumId = listAbstractNumId;
1033
- const listStartOverride = previousFormatting.listStartOverride;
1034
- if (listStartOverride !== void 0) attrs.listStartOverride = listStartOverride;
1035
- const listMarkerHidden = previousFormatting.listMarkerHidden;
1036
- if (listMarkerHidden !== void 0) attrs.listMarkerHidden = listMarkerHidden;
1037
- const listMarkerFormatting = previousFormatting.listMarkerFormatting;
1038
- if (listMarkerFormatting !== void 0) attrs.listMarkerFormatting = listMarkerFormatting;
1039
- const listMarkerAlignment = previousFormatting.listMarkerAlignment;
1040
- if (listMarkerAlignment !== void 0) attrs.listMarkerAlignment = listMarkerAlignment;
1041
- const listMarkerSuffix = previousFormatting.listMarkerSuffix;
1042
- if (listMarkerSuffix !== void 0) attrs.listMarkerSuffix = listMarkerSuffix;
1043
- const listMarkerAllCaps = previousFormatting.listMarkerAllCaps;
1044
- if (listMarkerAllCaps !== void 0) attrs.listMarkerAllCaps = listMarkerAllCaps;
1045
- const listImplicitChildLevelAdvances = previousFormatting.listImplicitChildLevelAdvances;
1046
- if (listImplicitChildLevelAdvances !== void 0) attrs.listImplicitChildLevelAdvances = listImplicitChildLevelAdvances;
1047
- const listMarkerSecondSlotOffsetTwips = previousFormatting.listMarkerSecondSlotOffsetTwips;
1048
- if (listMarkerSecondSlotOffsetTwips !== void 0) attrs.listMarkerSecondSlotOffsetTwips = listMarkerSecondSlotOffsetTwips;
1049
- return attrs;
1050
- }
1051
- function resolveDeletedListMarker(previousListAttrs, listCounterState) {
1052
- if (listCounterState && previousListAttrs.numPr) {
1053
- const marker = computeListMarker(previousListAttrs, listCounterState);
1054
- if (marker) return marker;
1055
- }
1056
- if (previousListAttrs.listMarker) return previousListAttrs.listIsBullet ? convertBulletToUnicode(previousListAttrs.listMarker) : previousListAttrs.listMarker;
1057
- if (previousListAttrs.listIsBullet) return "•";
1058
- return null;
1059
- }
1060
- function applyDeletedListMarkerAttrs(attrs, change, listCounterState, theme, fontAlternates, paragraphMarkFormatting) {
1061
- const previousListAttrs = toPreviousListAttrs(change.previousFormatting);
1062
- const marker = resolveDeletedListMarker(previousListAttrs, listCounterState);
1063
- if (!marker) return;
1064
- attrs.listMarker = marker;
1065
- attrs.listMarkerRevision = toListMarkerRevision("del", change.info);
1066
- if (previousListAttrs.listIsBullet !== void 0) attrs.listIsBullet = previousListAttrs.listIsBullet;
1067
- if (previousListAttrs.listMarkerHidden !== void 0) attrs.listMarkerHidden = previousListAttrs.listMarkerHidden;
1068
- const listMarkerFormatting = listMarkerFormattingFor(previousListAttrs.listMarkerFormatting, paragraphMarkFormatting(), theme, fontAlternates);
1069
- if (listMarkerFormatting) attrs.listMarkerFormatting = listMarkerFormatting;
1070
- if (previousListAttrs.listMarkerAlignment) attrs.listMarkerAlignment = previousListAttrs.listMarkerAlignment;
1071
- if (previousListAttrs.listMarkerSuffix) attrs.listMarkerSuffix = previousListAttrs.listMarkerSuffix;
1072
- }
1073
- /**
1074
- * Every `ST_Jc` member, and the flow alignment that paints it.
1075
- *
1076
- * `start` and `end` stay unresolved here: they name an edge of the writing
1077
- * direction, not a side of the page, so which of `left` and `right` paints
1078
- * them depends on the paragraph's direction. `numTab` has no flow alignment at
1079
- * all — it aligns the paragraph to the list number's tab stop, which the flow
1080
- * engine does not model — so it falls back to the start edge, which is where a
1081
- * numbered paragraph without one sits.
1082
- */
1083
- const FLOW_ALIGNMENT_BY_PARAGRAPH_ALIGNMENT = {
1084
- start: "start",
1085
- end: "end",
1086
- numTab: "start",
1087
- left: "left",
1088
- center: "center",
1089
- right: "right",
1090
- both: "justify",
1091
- distribute: "justify",
1092
- mediumKashida: "justify",
1093
- highKashida: "justify",
1094
- lowKashida: "justify",
1095
- thaiDistribute: "justify"
1096
- };
1097
- /** Resolve a logical alignment against the direction the paragraph runs in. */
1098
- const resolveFlowAlignment = (alignment, rightToLeft) => {
1099
- const flow = FLOW_ALIGNMENT_BY_PARAGRAPH_ALIGNMENT[alignment];
1100
- switch (flow) {
1101
- case "start": return rightToLeft ? "right" : "left";
1102
- case "end": return rightToLeft ? "left" : "right";
1103
- default: return flow;
1104
- }
1105
- };
1106
- function convertParagraphAttrs(pmAttrs, { theme, fontAlternates, listCounterStreams, defaultTabStopTwips, paragraphMarkFormatting }) {
1107
- const attrs = {};
1108
- if (pmAttrs.alignment) attrs.alignment = resolveFlowAlignment(pmAttrs.alignment, directionIsRtl(pmAttrs.direction));
1109
- if (pmAttrs.outlineLevel !== void 0) attrs.outlineLevel = pmAttrs.outlineLevel;
1110
- const spaceBefore = pmAttrs.spaceBefore;
1111
- const spaceAfter = pmAttrs.spaceAfter;
1112
- const lineSpacing = pmAttrs.lineSpacing;
1113
- if (typeof pmAttrs.snapToGrid === "boolean") attrs.snapToGrid = pmAttrs.snapToGrid;
1114
- const autoBefore = autospacingMatchesBase(pmAttrs._autospacingBase, "before", spaceBefore);
1115
- const autoAfter = autospacingMatchesBase(pmAttrs._autospacingBase, "after", spaceAfter);
1116
- if (autoBefore || autoAfter || typeof spaceBefore === "number" || typeof spaceAfter === "number" || typeof lineSpacing === "number") {
1117
- attrs.spacing = {};
1118
- if (autoBefore) attrs.spacing.before = AUTO_PARAGRAPH_SPACING_PX;
1119
- else if (typeof spaceBefore === "number") attrs.spacing.before = twipsToPixels(spaceBefore);
1120
- if (autoAfter) attrs.spacing.after = AUTO_PARAGRAPH_SPACING_PX;
1121
- else if (typeof spaceAfter === "number") attrs.spacing.after = twipsToPixels(spaceAfter);
1122
- if (autoBefore || autoAfter) attrs.automaticSpacing = {
1123
- ...autoBefore ? { before: true } : {},
1124
- ...autoAfter ? { after: true } : {}
1125
- };
1126
- const pmSpacingExplicit = pmAttrs.spacingExplicit;
1127
- const spacingFromDocDefaults = pmAttrs.spacingFromDocDefaults;
1128
- const spacingFromImplicitDefaultStyle = pmAttrs.spacingFromImplicitDefaultStyle;
1129
- const explicit = {};
1130
- if (autoBefore || pmSpacingExplicit?.before || spacingFromDocDefaults?.before || spacingFromImplicitDefaultStyle?.before) explicit.before = true;
1131
- if (autoAfter || pmSpacingExplicit?.after || spacingFromDocDefaults?.after || spacingFromImplicitDefaultStyle?.after) explicit.after = true;
1132
- if (explicit.before !== void 0 || explicit.after !== void 0) attrs.spacingExplicit = explicit;
1133
- if (typeof lineSpacing === "number") if (pmAttrs.lineSpacingRule === "exact" || pmAttrs.lineSpacingRule === "atLeast") {
1134
- attrs.spacing.line = twipsToPixels(lineSpacing);
1135
- attrs.spacing.lineUnit = "px";
1136
- attrs.spacing.lineRule = pmAttrs.lineSpacingRule;
1137
- } else {
1138
- attrs.spacing.line = lineSpacing / 240;
1139
- attrs.spacing.lineUnit = "multiplier";
1140
- attrs.spacing.lineRule = "auto";
1141
- }
1142
- }
1143
- let indentLeft = typeof pmAttrs.indentLeft === "number" ? pmAttrs.indentLeft : void 0;
1144
- let indentFirstLine = typeof pmAttrs.indentFirstLine === "number" ? pmAttrs.indentFirstLine : void 0;
1145
- let hangingIndent = pmAttrs.hangingIndent;
1146
- if (paragraphNumberingReferenceId(pmAttrs.numPr) !== void 0 && indentLeft === void 0 && indentFirstLine === void 0) {
1147
- indentLeft = ((paragraphNumberingLevel(pmAttrs.numPr) ?? 0) + 1) * 720;
1148
- if (indentFirstLine === void 0) {
1149
- indentFirstLine = -360;
1150
- hangingIndent = true;
1151
- }
1152
- }
1153
- if (indentLeft !== void 0 || typeof pmAttrs.indentRight === "number" || indentFirstLine !== void 0) {
1154
- attrs.indent = {};
1155
- if (indentLeft !== void 0) attrs.indent.left = twipsToPixels(indentLeft);
1156
- if (typeof pmAttrs.indentRight === "number") attrs.indent.right = twipsToPixels(pmAttrs.indentRight);
1157
- if (indentFirstLine !== void 0) if (hangingIndent) attrs.indent.hanging = Math.abs(twipsToPixels(indentFirstLine));
1158
- else attrs.indent.firstLine = twipsToPixels(indentFirstLine);
1159
- }
1160
- if (pmAttrs.styleId) attrs.styleId = pmAttrs.styleId;
1161
- if (pmAttrs.borders) {
1162
- const borders = pmAttrs.borders;
1163
- attrs.borders = {};
1164
- const convertBorder = (border) => border ? convertBorderSpecToLayout(border, theme) : void 0;
1165
- const topBorder = borders.top ? convertBorder(borders.top) : void 0;
1166
- if (topBorder) attrs.borders.top = topBorder;
1167
- const bottomBorder = borders.bottom ? convertBorder(borders.bottom) : void 0;
1168
- if (bottomBorder) attrs.borders.bottom = bottomBorder;
1169
- const leftBorder = borders.left ? convertBorder(borders.left) : void 0;
1170
- if (leftBorder) attrs.borders.left = leftBorder;
1171
- const rightBorder = borders.right ? convertBorder(borders.right) : void 0;
1172
- if (rightBorder) attrs.borders.right = rightBorder;
1173
- const betweenBorder = borders.between ? convertBorder(borders.between) : void 0;
1174
- if (betweenBorder) attrs.borders.between = betweenBorder;
1175
- const barBorder = borders.bar ? convertBorder(borders.bar) : void 0;
1176
- if (barBorder) attrs.borders.bar = barBorder;
1177
- if (!attrs.borders.top && !attrs.borders.bottom && !attrs.borders.left && !attrs.borders.right && !attrs.borders.between && !attrs.borders.bar) delete attrs.borders;
1178
- }
1179
- const shadingRgb = pmAttrs.shading?.fill?.rgb?.toUpperCase();
1180
- if (shadingRgb && shadingRgb !== "FFFFFF" && shadingRgb !== "FFFFFE") attrs.shading = `#${pmAttrs.shading?.fill?.rgb}`;
1181
- const tabs = mergeParagraphTabStops(pmAttrs.listLevelTabs ?? void 0, pmAttrs.tabs ?? void 0);
1182
- if (tabs && tabs.length > 0) {
1183
- const rightToLeft = directionIsRtl(pmAttrs.direction);
1184
- attrs.tabs = tabs.map((tab) => {
1185
- const tabStop = {
1186
- val: resolveTabAlignment(tab.alignment, rightToLeft),
1187
- pos: tab.position
1188
- };
1189
- if (tab.leader) tabStop.leader = tab.leader;
1190
- return tabStop;
1191
- });
1192
- }
1193
- if (pmAttrs.pageBreakBefore) attrs.pageBreakBefore = true;
1194
- if (pmAttrs.kinsoku !== void 0 && pmAttrs.kinsoku !== null) attrs.kinsoku = pmAttrs.kinsoku;
1195
- if (pmAttrs.overflowPunctuation !== void 0 && pmAttrs.overflowPunctuation !== null) attrs.overflowPunctuation = pmAttrs.overflowPunctuation;
1196
- if (pmAttrs.suppressAutoHyphens !== void 0 && pmAttrs.suppressAutoHyphens !== null) attrs.suppressAutoHyphens = pmAttrs.suppressAutoHyphens;
1197
- if (pmAttrs.renderedPageBreakBefore) attrs.renderedPageBreakBefore = true;
1198
- if (pmAttrs.keepNext) attrs.keepNext = true;
1199
- if (pmAttrs.keepLines) attrs.keepLines = true;
1200
- if (pmAttrs.widowControl === false) attrs.widowControl = false;
1201
- if (pmAttrs.contextualSpacing) attrs.contextualSpacing = true;
1202
- if (pmAttrs.runInWithNext) attrs.runInWithNext = true;
1203
- const bidi = directionToBidi(pmAttrs.direction);
1204
- if (bidi !== void 0) attrs.bidi = bidi;
1205
- if (pmAttrs.styleId) attrs.styleId = pmAttrs.styleId;
1206
- const propertyChanges = pmAttrs._propertyChanges ?? [];
1207
- let changedNumberingChange;
1208
- if (pmAttrs.numPr) {
1209
- attrs.numPr = pmAttrs.numPr;
1210
- if (pmAttrs.pPrMark?.kind === "del") attrs.listMarkerRevision = toListMarkerRevision("del", pmAttrs.pPrMark.info);
1211
- else if (pmAttrs.pPrMark?.kind === "ins") attrs.listMarkerRevision = toListMarkerRevision("ins", pmAttrs.pPrMark.info);
1212
- else {
1213
- const numberingAddedChange = propertyChanges.find(isAddedNumberingChange);
1214
- const currentNumPr = pmAttrs.numPr;
1215
- changedNumberingChange = propertyChanges.find((change) => isChangedNumberingChange(currentNumPr, change));
1216
- const numberingInsertionChange = numberingAddedChange ?? changedNumberingChange;
1217
- if (numberingInsertionChange) attrs.listMarkerRevision = toListMarkerRevision("ins", numberingInsertionChange.info);
1218
- }
1219
- }
1220
- const resolvedMarker = applyMarkerAllCaps(advanceVisibleListMarker(pmAttrs, listCounterStreams).marker, pmAttrs.listMarkerAllCaps);
1221
- if (resolvedMarker !== null) attrs.listMarker = resolvedMarker;
1222
- else if (pmAttrs.listMarker) attrs.listMarker = pmAttrs.listIsBullet ? convertBulletToUnicode(pmAttrs.listMarker) : pmAttrs.listMarker;
1223
- if (pmAttrs.listIsBullet !== void 0) attrs.listIsBullet = pmAttrs.listIsBullet;
1224
- if (pmAttrs.listMarkerHidden) attrs.listMarkerHidden = true;
1225
- if (attrs.listMarker !== void 0 || pmAttrs.listMarkerFormatting) {
1226
- const listMarkerFormatting = listMarkerFormattingFor(pmAttrs.listMarkerFormatting, paragraphMarkFormatting(), theme, fontAlternates);
1227
- if (listMarkerFormatting) attrs.listMarkerFormatting = listMarkerFormatting;
1228
- }
1229
- if (pmAttrs.listMarkerAlignment) attrs.listMarkerAlignment = pmAttrs.listMarkerAlignment;
1230
- if (pmAttrs.listMarkerSuffix) attrs.listMarkerSuffix = pmAttrs.listMarkerSuffix;
1231
- if (pmAttrs.listMarkerSecondSlotOffsetTwips !== void 0) attrs.listMarkerSecondSlotOffsetTwips = pmAttrs.listMarkerSecondSlotOffsetTwips;
1232
- if (!pmAttrs.numPr) {
1233
- const numberingRemovedChange = propertyChanges.find(isRemovedNumberingChange);
1234
- if (numberingRemovedChange) {
1235
- applyDeletedListMarkerAttrs(attrs, numberingRemovedChange, void 0, theme, fontAlternates, paragraphMarkFormatting);
1236
- if (resolvedMarker !== null) {
1237
- attrs.listMarker = resolvedMarker;
1238
- attrs.listMarkerRevision = toListMarkerRevision("del", numberingRemovedChange.info);
1239
- }
1240
- }
1241
- }
1242
- if (defaultTabStopTwips !== void 0) attrs.defaultTabStopTwips = defaultTabStopTwips;
1243
- const dtf = pmAttrs.defaultTextFormatting;
1244
- if (dtf) {
1245
- if (dtf.fontSize !== void 0) attrs.defaultFontSize = dtf.fontSize / 2;
1246
- if (attrs.listMarker !== void 0 && !attrs.listMarkerHidden && dtf.fontSizeCs !== void 0 && (dtf.fontSize === void 0 || dtf.fontSizeCs > dtf.fontSize)) attrs.listParagraphMarkFontSize = dtf.fontSizeCs / 2;
1247
- if (dtf.fontFamily) {
1248
- const resolvedFamily = resolveWesternThemeFont(dtf.fontFamily, theme);
1249
- if (resolvedFamily) {
1250
- attrs.defaultFontFamily = resolvedFamily;
1251
- const alternate = getFontAlternate(resolvedFamily, fontAlternates);
1252
- if (alternate) attrs.defaultAlternateFontFamily = alternate;
1253
- }
1254
- }
1255
- }
1256
- return attrs;
1257
- }
1258
- /**
1259
- * Every `ST_TabJc` member, and the layout engine's name for it.
1260
- *
1261
- * The engine's `start` and `end` are its names for the left and right edges of
1262
- * the line, not direction-aware ones, so `w:tab w:val="left"` maps to `start`
1263
- * unconditionally. `ST_TabJc`'s own `start` and `end` *are* direction-aware
1264
- * and stay unresolved here, under names the engine has no member for.
1265
- * `num`, the tab a numbered paragraph's text hangs from, has no engine
1266
- * alignment of its own and falls back to the line's start edge.
1267
- */
1268
- const TAB_ALIGNMENT_BY_TAB_STOP_ALIGNMENT = {
1269
- start: "logicalStart",
1270
- end: "logicalEnd",
1271
- num: "logicalStart",
1272
- left: "start",
1273
- right: "end",
1274
- center: "center",
1275
- decimal: "decimal",
1276
- bar: "bar",
1277
- clear: "clear"
1278
- };
1279
- /** Resolve a logical tab alignment against the direction the paragraph runs in. */
1280
- const resolveTabAlignment = (alignment, rightToLeft) => {
1281
- const mapped = TAB_ALIGNMENT_BY_TAB_STOP_ALIGNMENT[alignment];
1282
- switch (mapped) {
1283
- case "logicalStart": return rightToLeft ? "end" : "start";
1284
- case "logicalEnd": return rightToLeft ? "start" : "end";
1285
- default: return mapped;
1286
- }
1287
- };
1288
- /**
1289
- * Convert a paragraph node to a ParagraphBlock.
1290
- */
1291
- function hasOnlyVisuallyEmptyTextRuns(runs) {
1292
- return runs.length > 0 && runs.every((run) => run.kind === "text" && run.text.replace(/\u00a0/gu, " ").trim().length === 0);
1293
- }
1294
- function hasOnlyHiddenTextRuns(runs) {
1295
- return runs.length > 0 && runs.every((run) => run.kind === "text" && run.hidden === true);
1296
- }
1297
- function hasVisibleParagraphPayload(attrs) {
1298
- return attrs.listMarker !== void 0 && !attrs.listMarkerHidden || attrs.borders?.top !== void 0 || attrs.borders?.bottom !== void 0 || attrs.borders?.left !== void 0 || attrs.borders?.right !== void 0 || attrs.borders?.between !== void 0 || attrs.borders?.bar !== void 0 || attrs.shading !== void 0;
1299
- }
1300
- /** Whether any `w:pBdr` side draws a rule (`none`/`nil` sides draw nothing). */
1301
- function drawsParagraphBorder(borders) {
1302
- if (!borders) return false;
1303
- return [
1304
- borders.top,
1305
- borders.bottom,
1306
- borders.left,
1307
- borders.right,
1308
- borders.between,
1309
- borders.bar
1310
- ].some((border) => border?.style !== void 0 && !statesNoBorder(border.style));
1311
- }
1312
- function convertParagraph(node, startPos, options, pageBreaks) {
1313
- const pmAttrs = expectParagraphAttrs(node);
1314
- const runs = paragraphToRuns(node, startPos, options, pageBreaks);
1315
- const attrs = convertParagraphAttrs(pmAttrs, {
1316
- theme: options.theme,
1317
- fontAlternates: options.fontAlternates,
1318
- listCounterStreams: options.listCounterStreams,
1319
- defaultTabStopTwips: options.defaultTabStopTwips,
1320
- paragraphMarkFormatting: () => resolveParagraphMarkFormatting(pmAttrs, options.styleResolver)
1321
- });
1322
- if (options.numberingTabIgnoresIndent && attrs.listMarker !== void 0) attrs.listNumberingTabIgnoresIndent = true;
1323
- if (options.defaultFont !== void 0) attrs.defaultFontFamily ??= options.defaultFont;
1324
- if (options.defaultSize !== void 0) attrs.defaultFontSize ??= options.defaultSize;
1325
- if (options.lineBreakRules) attrs.lineBreakRules = options.lineBreakRules;
1326
- if (options.justificationCompatibility) attrs.justificationCompatibility = options.justificationCompatibility;
1327
- if (options.automaticHyphenation) attrs.automaticHyphenation = options.automaticHyphenation;
1328
- const defaultTextFormatting = pmAttrs.defaultTextFormatting;
1329
- if (runs.length === 0 || hasOnlyVisuallyEmptyTextRuns(runs)) {
1330
- if (pmAttrs._originalFormatting && Object.entries(pmAttrs._originalFormatting).some(([key, value]) => key !== "runProperties" && value !== void 0 && value !== null)) attrs.hasDirectParagraphFormatting = true;
1331
- const directParagraphMarkFormatting = pmAttrs._originalFormatting?.runProperties;
1332
- if (directParagraphMarkFormatting && Object.values(directParagraphMarkFormatting).some((value) => value !== void 0 && value !== null)) attrs.hasDirectParagraphMarkFormatting = true;
1333
- const paragraphMarkFormatting = resolveParagraphMarkOwnFormatting(directParagraphMarkFormatting, options.styleResolver);
1334
- if (paragraphMarkFormatting?.fontSize !== void 0) attrs.defaultFontSize = paragraphMarkFormatting.fontSize / 2;
1335
- const paragraphMarkFontFamily = paragraphMarkFormatting?.fontFamily ? resolveWesternThemeFont(paragraphMarkFormatting.fontFamily, options.theme) : void 0;
1336
- if (paragraphMarkFontFamily) {
1337
- attrs.defaultFontFamily = paragraphMarkFontFamily;
1338
- const alternate = getFontAlternate(paragraphMarkFontFamily, options.fontAlternates);
1339
- if (alternate) attrs.defaultAlternateFontFamily = alternate;
1340
- }
1341
- }
1342
- const isFullyHiddenParagraph = defaultTextFormatting?.hidden === true && (runs.length === 0 || hasOnlyHiddenTextRuns(runs));
1343
- if (isFullyHiddenParagraph && attrs.listMarker !== void 0) attrs.listMarkerHidden = true;
1344
- if (isFullyHiddenParagraph) attrs.suppressEmptyParagraphHeight = true;
1345
- if (runs.length === 0 && expectDetachedWatermarkHostAttr(node.attrs)) attrs.suppressEmptyParagraphHeight = false;
1346
- if (runs.length === 0 && pmAttrs._pageBreakCarrier === true && !hasVisibleParagraphPayload(attrs)) attrs.suppressEmptyParagraphHeight = true;
1347
- const bookmarkNames = pmAttrs.bookmarks?.map((b) => b.name);
1348
- const block = {
1349
- kind: "paragraph",
1350
- id: nextBlockId(),
1351
- runs,
1352
- attrs,
1353
- ...pmAttrs.paraId ? { paraId: pmAttrs.paraId } : {},
1354
- ...bookmarkNames && bookmarkNames.length > 0 ? { bookmarks: bookmarkNames } : {},
1355
- pmStart: startPos,
1356
- pmEnd: startPos + node.nodeSize
1357
- };
1358
- const frame = pmAttrs._originalFormatting?.frame;
1359
- if (frame !== void 0 && frame.dropCap !== "drop" && frame.dropCap !== "margin") setParagraphFrame(block, {
1360
- ...frame.width !== void 0 ? { width: twipsToPixels(frame.width) } : {},
1361
- ...frame.height !== void 0 ? { height: twipsToPixels(frame.height) } : {},
1362
- ...frame.hSpace !== void 0 ? { hSpace: twipsToPixels(frame.hSpace) } : {},
1363
- ...frame.vSpace !== void 0 ? { vSpace: twipsToPixels(frame.vSpace) } : {},
1364
- ...frame.hAnchor !== void 0 ? { hAnchor: frame.hAnchor } : {},
1365
- ...frame.vAnchor !== void 0 ? { vAnchor: frame.vAnchor } : {},
1366
- ...frame.x !== void 0 ? { x: twipsToPixels(frame.x) } : {},
1367
- ...frame.y !== void 0 ? { y: twipsToPixels(frame.y) } : {},
1368
- ...frame.xAlign !== void 0 ? { xAlign: frame.xAlign } : {},
1369
- ...frame.yAlign !== void 0 ? { yAlign: frame.yAlign } : {},
1370
- ...frame.wrap !== void 0 ? { wrap: frame.wrap } : {}
1371
- });
1372
- node.descendants((child) => {
1373
- if (child.type.name !== "textBoxAnchor") return true;
1374
- options.textBoxAnchorBlockIds.set(expectTextBoxAnchorAttrs(child).anchorId, block.id);
1375
- return false;
1376
- });
1377
- return block;
1378
- }
1379
- function paragraphFragmentAttrs(source, index, count) {
1380
- if (!source) return;
1381
- const attrs = {
1382
- ...source,
1383
- ...source.indent ? { indent: { ...source.indent } } : {},
1384
- ...source.spacing ? { spacing: { ...source.spacing } } : {},
1385
- ...source.automaticSpacing ? { automaticSpacing: { ...source.automaticSpacing } } : {},
1386
- ...source.spacingExplicit ? { spacingExplicit: { ...source.spacingExplicit } } : {}
1387
- };
1388
- if (index > 0) {
1389
- delete attrs.listMarker;
1390
- delete attrs.listIsBullet;
1391
- delete attrs.listMarkerFormatting;
1392
- delete attrs.listMarkerHidden;
1393
- delete attrs.listMarkerAlignment;
1394
- delete attrs.listMarkerSuffix;
1395
- delete attrs.listMarkerRevision;
1396
- delete attrs.listMarkerSecondSlotOffsetTwips;
1397
- delete attrs.reserveEmptyOutlineHeight;
1398
- delete attrs.pageBreakBefore;
1399
- delete attrs.renderedPageBreakBefore;
1400
- if (attrs.indent) {
1401
- delete attrs.indent.firstLine;
1402
- delete attrs.indent.hanging;
1403
- }
1404
- if (attrs.spacing) delete attrs.spacing.before;
1405
- if (attrs.automaticSpacing) delete attrs.automaticSpacing.before;
1406
- if (attrs.spacingExplicit) delete attrs.spacingExplicit.before;
1407
- }
1408
- if (index < count - 1) {
1409
- delete attrs.keepNext;
1410
- delete attrs.runInWithNext;
1411
- delete attrs.listParagraphMarkFontSize;
1412
- if (attrs.spacing) delete attrs.spacing.after;
1413
- if (attrs.automaticSpacing) delete attrs.automaticSpacing.after;
1414
- if (attrs.spacingExplicit) delete attrs.spacingExplicit.after;
1415
- }
1416
- return attrs;
1417
- }
1418
- const runIsZeroWidthBoundaryMarker = (run) => {
1419
- switch (run.kind) {
1420
- case "text": return run.text.length === 0;
1421
- case "field": return (run.fallback ?? "").length === 0;
1422
- case "renderedPageBreak": return true;
1423
- case "image":
1424
- case "lineBreak":
1425
- case "math":
1426
- case "tab": return false;
1427
- default: return run;
1428
- }
1429
- };
1430
- /** Every page-break run under a node, nested tables and text boxes included. */
1431
- const countPageBreakRuns = (node) => {
1432
- let count = node.type.name === "pageBreakRun" ? 1 : 0;
1433
- node.descendants((descendant) => {
1434
- if (descendant.type.name === "pageBreakRun") count += 1;
1435
- return true;
1436
- });
1437
- return count;
1438
- };
1439
- /** Decide leading-break eligibility from the exact projected runs consumed by layout. */
1440
- const hasSingleLeadingProjectedPageBreak = (runs, pageBreaks) => {
1441
- if (pageBreaks.length !== 1) return false;
1442
- const partitioned = partitionRunsAtPageBreaks(runs, pageBreaks);
1443
- if (partitioned.type === "overlap") return false;
1444
- return partitioned.partitions[0]?.before.every(runIsZeroWidthBoundaryMarker) === true;
1445
- };
1446
- function splitParagraphAtPageBreaks({ pageBreaks, paragraph, splitPageBreakAndParagraphMark }) {
1447
- if (pageBreaks.length === 0) return [paragraph];
1448
- const result = [];
1449
- let fragmentStart = paragraph.pmStart ?? pageBreaks[0].pmStart;
1450
- let emittedParagraph = false;
1451
- const appendParagraph = (runs, pmStart, pmEnd) => {
1452
- const fragment = {
1453
- ...paragraph,
1454
- id: emittedParagraph ? nextBlockId() : paragraph.id,
1455
- runs,
1456
- pmStart,
1457
- pmEnd
1458
- };
1459
- if (emittedParagraph) delete fragment.bookmarks;
1460
- result.push(fragment);
1461
- emittedParagraph = true;
1462
- };
1463
- const partitioned = partitionRunsAtPageBreaks(paragraph.runs, pageBreaks);
1464
- if (partitioned.type === "overlap") panic("An inline layout run overlaps an explicit page-break carrier");
1465
- for (const { before, pageBreak } of partitioned.partitions) {
1466
- if (before.length > 0) appendParagraph(before, fragmentStart, pageBreak.pmStart);
1467
- result.push({
1468
- kind: "pageBreak",
1469
- id: nextBlockId(),
1470
- pmStart: pageBreak.pmStart,
1471
- pmEnd: pageBreak.pmEnd,
1472
- ...pageBreak.trackedChange
1473
- });
1474
- fragmentStart = pageBreak.pmEnd;
1475
- }
1476
- const remainingRuns = partitioned.remaining;
1477
- let paragraphMarkCarrier;
1478
- if (remainingRuns.length > 0) appendParagraph(remainingRuns, fragmentStart, paragraph.pmEnd ?? fragmentStart);
1479
- else if (paragraph.runs.length === 0 || splitPageBreakAndParagraphMark && pageBreaks.at(-1)?.pmEnd === (paragraph.pmEnd ?? fragmentStart) - 1) {
1480
- appendParagraph([], fragmentStart, paragraph.pmEnd ?? fragmentStart);
1481
- const carrier = result.at(-1);
1482
- if (carrier?.kind === "paragraph" && !splitPageBreakAndParagraphMark && !hasVisibleParagraphPayload(carrier.attrs ?? {})) {
1483
- carrier.attrs = {
1484
- ...carrier.attrs,
1485
- suppressEmptyParagraphHeight: true
1486
- };
1487
- paragraphMarkCarrier = carrier;
1488
- }
1489
- }
1490
- const fragments = result.filter((block) => block.kind === "paragraph");
1491
- for (let index = 0; index < fragments.length; index += 1) {
1492
- const fragment = fragments[index];
1493
- if (!fragment) continue;
1494
- const attrs = paragraphFragmentAttrs(fragment.attrs, index, fragments.length);
1495
- if (attrs) fragment.attrs = attrs;
1496
- else delete fragment.attrs;
1497
- }
1498
- if (paragraphMarkCarrier?.attrs) paragraphMarkCarrier.attrs = withoutParagraphSpacing(paragraphMarkCarrier.attrs);
1499
- return result;
1500
- }
1501
- function withoutParagraphSpacing(source) {
1502
- const attrs = { ...source };
1503
- if (attrs.spacing) {
1504
- const { before: _before, after: _after, ...lineSpacing } = attrs.spacing;
1505
- if (Object.keys(lineSpacing).length > 0) attrs.spacing = lineSpacing;
1506
- else delete attrs.spacing;
1507
- }
1508
- delete attrs.automaticSpacing;
1509
- delete attrs.spacingExplicit;
1510
- return attrs;
1511
- }
1512
- /**
1513
- * Word keeps a final empty body paragraph after a table as an editable anchor,
1514
- * but that final anchor does not create a page of its own. Earlier authored
1515
- * empty paragraphs retain their height and may carry the document onto a blank
1516
- * page. Preserve every block and PM range while collapsing only the final one.
1517
- */
1518
- function isPaintlessTerminalParagraph(block) {
1519
- if (block?.kind !== "paragraph" || block.runs.length !== 0) return false;
1520
- const attrs = block.attrs;
1521
- if (attrs?.suppressEmptyParagraphHeight === false) return false;
1522
- return !(attrs?.listMarker !== void 0 && !attrs.listMarkerHidden || attrs?.borders?.top || attrs?.borders?.bottom || attrs?.borders?.left || attrs?.borders?.right || attrs?.borders?.between || attrs?.borders?.bar || attrs?.shading || attrs?.spacingExplicit?.before || attrs?.spacingExplicit?.after || attrs?.pageBreakBefore || attrs?.renderedPageBreakBefore);
1523
- }
1524
- function suppressFinalEmptyParagraphAfterTable(blocks) {
1525
- let suffixStart = blocks.length;
1526
- while (suffixStart > 0 && isPaintlessTerminalParagraph(blocks[suffixStart - 1])) suffixStart -= 1;
1527
- if (suffixStart === blocks.length || suffixStart === 0 || blocks[suffixStart - 1]?.kind !== "table") return;
1528
- const finalBlock = blocks.at(-1);
1529
- if (isPaintlessTerminalParagraph(finalBlock)) finalBlock.attrs = {
1530
- ...finalBlock.attrs,
1531
- suppressEmptyParagraphHeight: true
1532
- };
1533
- }
1534
- function suppressFinalParagraphInRepeatedEmptySuffix(blocks) {
1535
- let suffixStart = blocks.length;
1536
- while (suffixStart > 0 && isPaintlessTerminalParagraph(blocks[suffixStart - 1])) suffixStart -= 1;
1537
- if (suffixStart === 0 || blocks.length - suffixStart < 2 || blocks[suffixStart - 1]?.kind === "table") return;
1538
- const finalBlock = blocks.at(-1);
1539
- if (isPaintlessTerminalParagraph(finalBlock)) finalBlock.attrs = {
1540
- ...finalBlock.attrs,
1541
- suppressEmptyParagraphHeight: true
1542
- };
1543
- }
1544
- function reserveLeadingEmptyOutlineHeight(blocks) {
1545
- const firstBlock = blocks.at(0);
1546
- if (firstBlock?.kind !== "paragraph" || firstBlock.runs.length !== 0 || headingLevelOf(firstBlock.attrs?.outlineLevel) !== 0) return;
1547
- firstBlock.attrs = {
1548
- ...firstBlock.attrs,
1549
- reserveEmptyOutlineHeight: true
1550
- };
1551
- }
1552
- /**
1553
- * Convert border width from eighths of a point to pixels.
1554
- * OOXML stores border widths in eighths of a point.
1555
- */
1556
- function borderWidthToPixels(eighthsOfPoint) {
1557
- return pointsToPixels(eighthsOfPoint / 8);
1558
- }
1559
- /**
1560
- * Convert an OOXML BorderSpec to a layout-engine BorderStyle.
1561
- * Shared by paragraph borders, cell borders, and header/footer borders.
1562
- */
1563
- function convertBorderSpecToLayout(border, theme) {
1564
- if (border.style === void 0 || statesNoBorder(border.style)) return;
1565
- const result = {
1566
- style: cssBorderStyle(border.style),
1567
- width: border.size === void 0 ? 1 : borderWidthToPixels(border.size),
1568
- color: border.color ? resolveColor(border.color, theme) : "#000000"
1569
- };
1570
- if (border.space !== void 0) result.space = pointsToPixels(border.space);
1571
- return result;
1572
- }
1573
- /**
1574
- * Extract cell borders from ProseMirror attributes.
1575
- * Borders are full BorderSpec objects with style/size/color.
1576
- */
1577
- function extractCellBorders(borders, theme) {
1578
- if (!borders) return;
1579
- const result = {};
1580
- for (const side of [
1581
- "top",
1582
- "bottom",
1583
- "left",
1584
- "right"
1585
- ]) {
1586
- const border = borders[side];
1587
- const converted = border ? convertBorderSpecToLayout(border, theme) : void 0;
1588
- if (!converted) {
1589
- result[side] = {
1590
- width: 0,
1591
- style: "none"
1592
- };
1593
- continue;
1594
- }
1595
- result[side] = border?.size === 0 ? {
1596
- ...converted,
1597
- width: 0
1598
- } : converted;
1599
- }
1600
- for (const side of ["topLeftToBottomRight", "topRightToBottomLeft"]) {
1601
- const border = borders[side];
1602
- const converted = border ? convertBorderSpecToLayout(border, theme) : void 0;
1603
- if (converted) result[side] = converted;
1604
- }
1605
- return Object.keys(result).length > 0 ? result : void 0;
1606
- }
1607
- function convertTableCell(node, startPos, options, tableCellMargins) {
1608
- const blocks = [];
1609
- let offset = startPos + 1;
1610
- const authoredPageBreakPosition = options.firstPageBreakRunPosition(node);
1611
- const leadingParagraph = node.firstChild?.type.name === "paragraph" && countPageBreakRuns(node) === 1 && options.firstPageBreakRunPosition(node.firstChild) === authoredPageBreakPosition ? node.firstChild : void 0;
1612
- const pageBreaks = [];
1613
- const convertCellChild = (child, childStart) => {
1614
- if (child.type.name === "paragraph") {
1615
- const block = convertParagraph(child, childStart, options, child === leadingParagraph ? pageBreaks : void 0);
1616
- blocks.push(block);
1617
- } else if (child.type.name === "table") blocks.push(convertTable(child, childStart, options));
1618
- else if (child.type.name === "textBox") blocks.push(convertTextBoxNode(child, childStart, options));
1619
- else if (child.type.name === "blockSdt") {
1620
- let sdtChildStart = childStart + 1;
1621
- child.forEach((sdtChild) => {
1622
- convertCellChild(sdtChild, sdtChildStart);
1623
- sdtChildStart += sdtChild.nodeSize;
1624
- });
1625
- }
1626
- };
1627
- node.forEach((child) => {
1628
- convertCellChild(child, offset);
1629
- offset += child.nodeSize;
1630
- });
1631
- const trailingBlock = blocks.at(-1);
1632
- if (blocks.at(-2)?.kind === "table" && trailingBlock?.kind === "paragraph" && trailingBlock.runs.every((run) => run.kind === "text" && run.text.length === 0)) trailingBlock.attrs = {
1633
- ...trailingBlock.attrs,
1634
- suppressEmptyParagraphHeight: true
1635
- };
1636
- const attrs = expectTableCellAttrs(node);
1637
- if (attrs.hideMark && trailingBlock?.kind === "paragraph" && trailingBlock.runs.every((run) => run.kind === "text" && run.text.length === 0)) trailingBlock.attrs = {
1638
- ...trailingBlock.attrs,
1639
- suppressEmptyParagraphHeight: true
1640
- };
1641
- const margins = attrs.margins;
1642
- const resolvePaddingSide = (side, cellTwips, tableTwips) => {
1643
- if (cellTwips !== void 0) return twipsToPixels(cellTwips);
1644
- if (tableTwips !== void 0) return twipsToPixels(tableTwips);
1645
- return twipsToPixels(DEFAULT_TABLE_CELL_MARGIN_TWIPS[side]);
1646
- };
1647
- const padding = {
1648
- top: resolvePaddingSide("top", margins?.top, tableCellMargins?.top),
1649
- right: resolvePaddingSide("right", margins?.right, tableCellMargins?.right),
1650
- bottom: resolvePaddingSide("bottom", margins?.bottom, tableCellMargins?.bottom),
1651
- left: resolvePaddingSide("left", margins?.left, tableCellMargins?.left)
1652
- };
1653
- const cell = {
1654
- id: nextBlockId(),
1655
- blocks: groupParagraphFrames(blocks, nextBlockId),
1656
- colSpan: attrs.colspan,
1657
- rowSpan: attrs.rowspan,
1658
- padding
1659
- };
1660
- if (attrs.width) cell.width = twipsToPixels(attrs.width);
1661
- if (attrs.verticalAlign) cell.verticalAlign = attrs.verticalAlign;
1662
- if (attrs.textDirection) cell.textDirection = attrs.textDirection;
1663
- if (attrs.backgroundColor) cell.background = `#${attrs.backgroundColor}`;
1664
- const cellBorders = extractCellBorders(attrs.borders, options.theme);
1665
- if (cellBorders) cell.borders = cellBorders;
1666
- if (attrs.noWrap) cell.noWrap = true;
1667
- if (authoredPageBreakPosition === void 0 || pageBreaks.length === 0) return { cell };
1668
- const paragraph = blocks.at(0);
1669
- if (paragraph?.kind !== "paragraph" || !hasSingleLeadingProjectedPageBreak(paragraph.runs, pageBreaks)) return { cell };
1670
- return {
1671
- cell,
1672
- breakBefore: "page"
1673
- };
1674
- }
1675
- /**
1676
- * Resolve an `ST_JcTable` placement to the flow engine's logical alignment.
1677
- *
1678
- * `start` and `end` name an edge of the writing direction, not a side of the
1679
- * page, the same distinction `ST_Jc` draws for a paragraph. The flow engine
1680
- * applies `w:bidiVisual` when it resolves that logical alignment to a physical
1681
- * side, so the bridge must not mirror it first.
1682
- */
1683
- const resolveTablePlacement = (placement) => {
1684
- switch (placement) {
1685
- case "start": return "left";
1686
- case "end": return "right";
1687
- case "left":
1688
- case "center":
1689
- case "right": return placement;
1690
- default: return placement;
1691
- }
1692
- };
1693
- function convertTableRow(node, startPos, options, { cellMargins }) {
1694
- const cells = [];
1695
- let offset = startPos + 1;
1696
- let breakBefore;
1697
- node.forEach((child) => {
1698
- if (child.type.name === "tableCell" || child.type.name === "tableHeader") {
1699
- if (!expectTableCellAttrs(child)._omittedGridSlot) {
1700
- const converted = convertTableCell(child, offset, options, cellMargins);
1701
- if (converted.breakBefore !== void 0) breakBefore = converted.breakBefore;
1702
- cells.push(converted.cell);
1703
- }
1704
- }
1705
- offset += child.nodeSize;
1706
- });
1707
- const attrs = expectTableRowAttrs(node);
1708
- const row = {
1709
- id: nextBlockId(),
1710
- cells
1711
- };
1712
- if (attrs._originalFormatting?.gridBefore) row.gridBefore = attrs._originalFormatting.gridBefore;
1713
- if (attrs._originalFormatting?.gridAfter) row.gridAfter = attrs._originalFormatting.gridAfter;
1714
- if (attrs.height) row.height = twipsToPixels(attrs.height);
1715
- if (attrs.heightRule) row.heightRule = attrs.heightRule;
1716
- if (attrs.isHeader) row.isHeader = attrs.isHeader;
1717
- if (attrs._originalFormatting?.cantSplit) row.cantSplit = true;
1718
- if (breakBefore !== void 0) row.breakBefore = breakBefore;
1719
- if (attrs.hidden) row.hidden = attrs.hidden;
1720
- const effectiveJustification = attrs._originalFormatting?.justification ?? attrs._resolvedJustification;
1721
- if (effectiveJustification) row.justification = resolveTablePlacement(effectiveJustification);
1722
- return row;
1723
- }
1724
- /**
1725
- * Convert a table node to a TableBlock.
1726
- */
1727
- function convertTable(node, startPos, options) {
1728
- const rows = [];
1729
- let offset = startPos + 1;
1730
- const attrs = expectTableAttrs(node);
1731
- const rightToLeft = (attrs._resolvedBidi ?? attrs._originalFormatting?.bidi) === true;
1732
- const rowContext = { ...attrs.cellMargins === void 0 ? {} : { cellMargins: attrs.cellMargins } };
1733
- node.forEach((child) => {
1734
- if (child.type.name === "tableRow") rows.push(convertTableRow(child, offset, options, rowContext));
1735
- offset += child.nodeSize;
1736
- });
1737
- let columnWidths = attrs.columnWidths?.map(twipsToPixels);
1738
- const width = attrs.width;
1739
- const widthType = attrs.widthType;
1740
- if (!columnWidths && rows.length > 0) {
1741
- const cellWidths = rows[0].cells.map((cell) => cell.width);
1742
- if (cellWidths.every((w) => w !== void 0 && w > 0)) columnWidths = cellWidths;
1743
- }
1744
- const authoredJustification = attrs.justification ?? attrs._resolvedJustification;
1745
- const justification = authoredJustification === void 0 ? void 0 : resolveTablePlacement(authoredJustification);
1746
- const originalFormatting = attrs._originalFormatting;
1747
- const effectiveIndent = attrs._resolvedIndent ?? originalFormatting?.indent;
1748
- const indentPx = effectiveIndent?.value !== void 0 && effectiveIndent?.type === "dxa" ? twipsToPixels(effectiveIndent.value) : void 0;
1749
- const dropsAuthoredIndent = effectiveIndent?.value !== void 0 && indentPx === void 0;
1750
- const floating = attrs.floating;
1751
- let floatingPx;
1752
- if (floating) {
1753
- const fp = {};
1754
- if (floating.horzAnchor) fp.horzAnchor = floating.horzAnchor;
1755
- if (floating.vertAnchor) fp.vertAnchor = floating.vertAnchor;
1756
- if (floating.tblpX !== void 0) fp.tblpX = twipsToPixels(floating.tblpX);
1757
- if (floating.tblpXSpec) fp.tblpXSpec = floating.tblpXSpec;
1758
- if (floating.tblpY !== void 0) fp.tblpY = twipsToPixels(floating.tblpY);
1759
- if (floating.tblpYSpec) fp.tblpYSpec = floating.tblpYSpec;
1760
- if (floating.topFromText !== void 0) fp.topFromText = twipsToPixels(floating.topFromText);
1761
- if (floating.bottomFromText !== void 0) fp.bottomFromText = twipsToPixels(floating.bottomFromText);
1762
- if (floating.leftFromText !== void 0) fp.leftFromText = twipsToPixels(floating.leftFromText);
1763
- if (floating.rightFromText !== void 0) fp.rightFromText = twipsToPixels(floating.rightFromText);
1764
- floatingPx = fp;
1765
- }
1766
- const tableBlock = {
1767
- kind: "table",
1768
- id: nextBlockId(),
1769
- rows,
1770
- pmStart: startPos,
1771
- pmEnd: startPos + node.nodeSize
1772
- };
1773
- if (columnWidths) tableBlock.columnWidths = columnWidths;
1774
- if (width !== void 0) tableBlock.width = width;
1775
- if (widthType !== void 0) tableBlock.widthType = widthType;
1776
- if (originalFormatting?.layout !== void 0) tableBlock.layout = originalFormatting.layout;
1777
- if (justification) tableBlock.justification = justification;
1778
- if (indentPx !== void 0) tableBlock.indent = indentPx;
1779
- const cellSpacing = originalFormatting?.cellSpacing;
1780
- if (cellSpacing?.type === "dxa" && cellSpacing.value > 0) tableBlock.cellSpacing = twipsToPixels(cellSpacing.value);
1781
- if (options.tableIndentCompatibility && !dropsAuthoredIndent) tableBlock.indentCompatibility = options.tableIndentCompatibility;
1782
- if (floatingPx) tableBlock.floating = floatingPx;
1783
- if (rightToLeft) tableBlock.bidi = true;
1784
- return tableBlock;
1785
- }
1786
- /**
1787
- * Convert an image node to an ImageBlock.
1788
- */
1789
- function convertImage(node, startPos, pageContentHeight) {
1790
- const attrs = expectImageAttrs(node);
1791
- if (!hasPaintableImageSource(attrs) && !hasRelationshipBackedImageBox(attrs)) return;
1792
- const wrapType = attrs.wrapType;
1793
- const shouldAnchor = wrapType === "behind" || wrapType === "inFront";
1794
- const constrained = constrainImageToPage(attrs.width ?? 100, attrs.height ?? 100, pageContentHeight);
1795
- const imgBlock = {
1796
- kind: "image",
1797
- id: nextBlockId(),
1798
- src: attrs.src,
1799
- ...attrs.preview ? { preview: attrs.preview } : {},
1800
- width: constrained.width,
1801
- height: constrained.height,
1802
- pmStart: startPos,
1803
- pmEnd: startPos + node.nodeSize
1804
- };
1805
- if (attrs.alt) imgBlock.alt = attrs.alt;
1806
- if (attrs.transform) imgBlock.transform = attrs.transform;
1807
- if (attrs.opacity != null) imgBlock.opacity = attrs.opacity;
1808
- if (attrs.brightness != null) imgBlock.brightness = attrs.brightness;
1809
- if (attrs.contrast != null) imgBlock.contrast = attrs.contrast;
1810
- if (shouldAnchor) {
1811
- const anchor = {
1812
- isAnchored: true,
1813
- behindDoc: wrapType === "behind"
1814
- };
1815
- if (attrs.distLeft !== void 0) anchor.offsetH = attrs.distLeft;
1816
- if (attrs.distTop !== void 0) anchor.offsetV = attrs.distTop;
1817
- imgBlock.anchor = anchor;
1818
- }
1819
- if (attrs.hlinkHref) imgBlock.hlinkHref = attrs.hlinkHref;
1820
- if (attrs.cropTop != null) imgBlock.cropTop = attrs.cropTop;
1821
- if (attrs.cropRight != null) imgBlock.cropRight = attrs.cropRight;
1822
- if (attrs.cropBottom != null) imgBlock.cropBottom = attrs.cropBottom;
1823
- if (attrs.cropLeft != null) imgBlock.cropLeft = attrs.cropLeft;
1824
- if (attrs.borderWidth != null) imgBlock.borderWidth = attrs.borderWidth;
1825
- if (attrs.borderColor) imgBlock.borderColor = attrs.borderColor;
1826
- const imageBorderStyle = cssBorderStyleOf(attrs.borderStyle);
1827
- if (imageBorderStyle) imgBlock.borderStyle = imageBorderStyle;
1828
- return imgBlock;
1829
- }
1830
- const EMUS_PER_PIXEL = 9525;
1831
- /**
1832
- * The `wp:effectExtent` of an inline (`wp:inline`) box, in unrounded pixels.
1833
- *
1834
- * An inline object occupies its extent plus the effect extent on every side,
1835
- * so the box itself sits `l`/`t` inside the space the line gives it. On a
1836
- * `wp:anchor` the position offsets already locate the shape's own extent, so
1837
- * the reservation only affects wrapping and is not applied here.
1838
- */
1839
- function inlineEffectExtentPx(wrapType, extent) {
1840
- if (wrapType !== void 0 && wrapType !== "inline" || extent === void 0) return;
1841
- const px = (emu) => Math.max(0, (emu ?? 0) / EMUS_PER_PIXEL);
1842
- const resolved = {
1843
- top: px(extent.top),
1844
- bottom: px(extent.bottom),
1845
- left: px(extent.left),
1846
- right: px(extent.right)
1847
- };
1848
- return resolved.top === 0 && resolved.bottom === 0 && resolved.left === 0 && resolved.right === 0 ? void 0 : resolved;
1849
- }
1850
- /** Convert a textBox PM node to a TextBoxBlock. */
1851
- function convertTextBoxNode(node, startPos, opts) {
1852
- const attrs = expectTextBoxAttrs(node);
1853
- const contentBlocks = [];
1854
- node.forEach((child, offset) => {
1855
- const childPos = startPos + 1 + offset;
1856
- if (child.type.name === "paragraph") {
1857
- contentBlocks.push(convertParagraph(child, childPos, opts));
1858
- return;
1859
- }
1860
- if (child.type.name === "table") contentBlocks.push(convertTable(child, childPos, opts));
1861
- });
1862
- const textBox = {
1863
- kind: "textBox",
1864
- id: nextBlockId(),
1865
- width: attrs.width ?? 200,
1866
- margins: {
1867
- top: attrs.marginTop ?? DEFAULT_TEXTBOX_MARGINS.top,
1868
- bottom: attrs.marginBottom ?? DEFAULT_TEXTBOX_MARGINS.bottom,
1869
- left: attrs.marginLeft ?? DEFAULT_TEXTBOX_MARGINS.left,
1870
- right: attrs.marginRight ?? DEFAULT_TEXTBOX_MARGINS.right
1871
- },
1872
- content: contentBlocks,
1873
- pmStart: startPos,
1874
- pmEnd: startPos + node.nodeSize
1875
- };
1876
- const verticalAlign = normalizeShapeTextAnchor(attrs.verticalAlign);
1877
- if (verticalAlign !== void 0) textBox.verticalAlign = verticalAlign;
1878
- if (attrs.height !== void 0) textBox.height = attrs.height;
1879
- if (attrs.autoFit !== void 0) textBox.autoFit = attrs.autoFit;
1880
- if (attrs.textWrap !== void 0) textBox.textWrap = attrs.textWrap;
1881
- if (attrs.fillColor !== void 0) textBox.fillColor = attrs.fillColor;
1882
- if (attrs.outlineWidth !== void 0) textBox.outlineWidth = attrs.outlineWidth;
1883
- if (attrs.outlineColor !== void 0) textBox.outlineColor = attrs.outlineColor;
1884
- if (attrs.outlineStyle !== void 0) textBox.outlineStyle = attrs.outlineStyle;
1885
- if (attrs.transform !== void 0) textBox.transform = attrs.transform;
1886
- if (attrs.displayMode !== void 0) textBox.displayMode = attrs.displayMode;
1887
- if (attrs.cssFloat !== void 0) textBox.cssFloat = attrs.cssFloat;
1888
- if (attrs.wrapType !== void 0) textBox.wrapType = attrs.wrapType;
1889
- if (attrs.wrapText !== void 0) textBox.wrapText = attrs.wrapText;
1890
- if (attrs.distTop !== void 0) textBox.distTop = attrs.distTop;
1891
- if (attrs.distBottom !== void 0) textBox.distBottom = attrs.distBottom;
1892
- if (attrs.distLeft !== void 0) textBox.distLeft = attrs.distLeft;
1893
- if (attrs.distRight !== void 0) textBox.distRight = attrs.distRight;
1894
- if (attrs.position !== void 0) textBox.position = attrs.position;
1895
- const effectExtent = inlineEffectExtentPx(attrs.wrapType, attrs.wrapEffectExtentSlots?.drawing);
1896
- if (effectExtent !== void 0) textBox.effectExtent = effectExtent;
1897
- const host = textBoxHostParagraph(node);
1898
- if (host && attrs.position === void 0 && (attrs.wrapType ?? "inline") === "inline") {
1899
- const hostAttrs = expectParagraphAttrs(host);
1900
- textBox.hostParagraph = convertParagraphAttrs(hostAttrs, {
1901
- theme: opts.theme,
1902
- fontAlternates: opts.fontAlternates,
1903
- listCounterStreams: opts.listCounterStreams,
1904
- defaultTabStopTwips: opts.defaultTabStopTwips,
1905
- paragraphMarkFormatting: () => resolveParagraphMarkFormatting(hostAttrs, opts.styleResolver)
1906
- });
1907
- }
1908
- if (attrs._docxGroupId !== void 0) setTextBoxGroupId(textBox, attrs._docxGroupId);
1909
- const anchorBlockId = attrs._docxAnchorId ? opts.textBoxAnchorBlockIds.get(attrs._docxAnchorId) : void 0;
1910
- if (anchorBlockId !== void 0) Reflect.set(textBox, TEXT_BOX_ANCHOR_BLOCK_ID, anchorBlockId);
1911
- return textBox;
1912
- }
1913
- function getLastMapKey(map) {
1914
- let lastKey;
1915
- for (const key of map.keys()) lastKey = key;
1916
- return lastKey;
1917
- }
1918
- /**
1919
- * Translate section-owned start modes into the boundary-owned values consumed
1920
- * by the paginator. Each flow section break carries the properties for the
1921
- * section it ends, so its start mode belongs on the preceding boundary.
1922
- */
1923
- function applySectionStartsToBoundaries(blocks, finalSectionStart) {
1924
- const breakIndexes = [];
1925
- for (let index = 0; index < blocks.length; index += 1) if (blocks[index]?.kind === "sectionBreak") breakIndexes.push(index);
1926
- if (breakIndexes.length === 0) return [...blocks];
1927
- const result = [...blocks];
1928
- for (let index = 0; index < breakIndexes.length; index += 1) {
1929
- const boundaryIndex = breakIndexes[index];
1930
- if (boundaryIndex === void 0) continue;
1931
- const boundary = blocks[boundaryIndex];
1932
- if (boundary?.kind !== "sectionBreak") continue;
1933
- const nextBoundaryIndex = breakIndexes[index + 1];
1934
- const nextBoundary = nextBoundaryIndex === void 0 ? void 0 : blocks[nextBoundaryIndex];
1935
- const nextStart = nextBoundary?.kind === "sectionBreak" ? nextBoundary.type : finalSectionStart;
1936
- const translated = { ...boundary };
1937
- if (nextStart === void 0) delete translated.type;
1938
- else translated.type = nextStart;
1939
- result[boundaryIndex] = translated;
1940
- }
1941
- return result;
1942
- }
1943
- /**
1944
- * A trailing page break belongs to its section-ending paragraph. When the
1945
- * paragraph mark stays on the current page, an immediately following
1946
- * continuous section resumes there too. Accept both the legacy generated
1947
- * carrier and the inline atom's one-token paragraph-mark fragment; neither is
1948
- * a physical layout boundary.
1949
- *
1950
- * A section break in `breaksWithoutMarker` ends at an empty w:sectPr paragraph
1951
- * that projected no block, so a page break just before it ends an earlier
1952
- * paragraph and still starts the next page.
1953
- */
1954
- function coalesceTrailingPageBreakBeforeContinuousSection(blocks, splitPageBreakAndParagraphMark, breaksWithoutMarker) {
1955
- if (splitPageBreakAndParagraphMark) return [...blocks];
1956
- const result = [];
1957
- for (let index = 0; index < blocks.length; index += 1) {
1958
- const block = blocks[index];
1959
- const carrier = blocks[index + 1];
1960
- const section = blocks[index + 2];
1961
- const isLegacyGeneratedCarrier = block?.kind === "pageBreak" && carrier?.kind === "paragraph" && carrier.runs.length === 0 && block.pmStart !== void 0 && block.pmStart === carrier.pmStart && carrier.pmStart === carrier.pmEnd;
1962
- const isInlineParagraphMarkCarrier = block?.kind === "pageBreak" && carrier?.kind === "paragraph" && carrier.runs.length === 0 && block.pmEnd !== void 0 && carrier.pmStart === block.pmEnd && carrier.pmEnd === block.pmEnd + 1;
1963
- const isUnresolvedInsertion = block?.kind === "pageBreak" && block.isInsertion === true;
1964
- if (block?.kind === "pageBreak" && !isUnresolvedInsertion && carrier?.kind === "sectionBreak" && carrier.type === "continuous" && !breaksWithoutMarker.has(carrier.id)) continue;
1965
- if (block?.kind === "pageBreak" && !isUnresolvedInsertion && (isLegacyGeneratedCarrier || isInlineParagraphMarkCarrier) && section?.kind === "sectionBreak" && section.type === "continuous" && !breaksWithoutMarker.has(section.id)) {
1966
- index += 1;
1967
- continue;
1968
- }
1969
- if (block) result.push(block);
1970
- }
1971
- return result;
1972
- }
1973
- /**
1974
- * The last section's start mode, off a document attribute the schema types as
1975
- * `any`. `parseSectionBreakType` owns the enumeration, so a member added to
1976
- * `ST_SectionMark` reaches the paginator instead of being dropped here.
1977
- */
1978
- function readFinalSectionStart(doc) {
1979
- return parseSectionBreakType(doc.attrs["_finalSectionStart"]) ?? void 0;
1980
- }
1981
30
  /**
1982
31
  * Convert a ProseMirror document to FlowBlock array.
1983
32
  *
@@ -2358,103 +407,5 @@ function toFlowBlocks(doc, options = {}) {
2358
407
  tableCellLinePitch
2359
408
  }), readFinalSectionStart(doc)), options.splitPageBreakAndParagraphMark === true, sectionBreaksWithoutMarker), nextBlockId);
2360
409
  }
2361
- function applySectionDocumentGrid(blocks, { finalLinePitchTwips, tableCellLinePitch }) {
2362
- const result = [...blocks];
2363
- let sectionStart = 0;
2364
- const stampSection = (end, linePitchTwips) => {
2365
- if (linePitchTwips === void 0 || linePitchTwips <= 0) return;
2366
- const linePitch = twipsToPixels(linePitchTwips);
2367
- const stampBlock = (block) => {
2368
- if (block.kind === "paragraph") return {
2369
- ...block,
2370
- attrs: {
2371
- ...block.attrs,
2372
- documentGridLinePitch: linePitch
2373
- }
2374
- };
2375
- if (block.kind !== "table" || tableCellLinePitch !== "sectionGrid") return block;
2376
- return {
2377
- ...block,
2378
- rows: block.rows.map((row) => ({
2379
- ...row,
2380
- cells: row.cells.map((cell) => ({
2381
- ...cell,
2382
- blocks: cell.blocks.map(stampBlock)
2383
- }))
2384
- }))
2385
- };
2386
- };
2387
- for (let index = sectionStart; index < end; index += 1) {
2388
- const block = result[index];
2389
- if (!block) continue;
2390
- result[index] = stampBlock(block);
2391
- }
2392
- };
2393
- for (let index = 0; index < blocks.length; index += 1) {
2394
- const block = blocks[index];
2395
- if (block?.kind !== "sectionBreak") continue;
2396
- stampSection(index, block.documentGridLinePitchTwips);
2397
- sectionStart = index + 1;
2398
- }
2399
- stampSection(blocks.length, finalLinePitchTwips);
2400
- return result;
2401
- }
2402
- /**
2403
- * Merge consecutive paragraph blocks where the first carries
2404
- * `runInWithNext` (`<w:specVanish/>` on the paragraph mark).
2405
- *
2406
- * Word's run-in heading feature renders the next paragraph inline on
2407
- * the same line, so for layout we collapse the pair into one
2408
- * ParagraphBlock with combined runs. The merged block keeps the first
2409
- * paragraph's attrs (heading formatting, list marker, indent) and
2410
- * extends pmEnd to the second paragraph's range so click-to-position
2411
- * resolution still maps both ranges back to body content.
2412
- *
2413
- * Chains: runInWithNext on the merged block is dropped because the
2414
- * second paragraph's mark wasn't `specVanish`. If a chain of
2415
- * specVanish paragraphs needs collapsing (rare in practice), the loop
2416
- * naturally handles it by re-inspecting the merged block's flag (we
2417
- * preserve runInWithNext only when the second paragraph itself has
2418
- * specVanish).
2419
- */
2420
- function sdtGroupStacksEqual(a, b) {
2421
- const lenA = a?.length ?? 0;
2422
- if (lenA !== (b?.length ?? 0)) return false;
2423
- if (lenA === 0) return true;
2424
- for (let i = 0; i < lenA; i++) if (a?.[i]?.pmPos !== b?.[i]?.pmPos) return false;
2425
- return true;
2426
- }
2427
- function mergeRunInParagraphs(blocks) {
2428
- const out = [];
2429
- for (let i = 0; i < blocks.length; i++) {
2430
- let current = blocks[i];
2431
- if (!current) continue;
2432
- while (current.kind === "paragraph" && current.attrs?.runInWithNext && i + 1 < blocks.length) {
2433
- const next = blocks[i + 1];
2434
- if (!next || next.kind !== "paragraph") break;
2435
- const a = current;
2436
- const b = next;
2437
- if (!sdtGroupStacksEqual(a.sdtGroups, b.sdtGroups)) break;
2438
- const mergedAttrs = { ...a.attrs };
2439
- if (b.attrs?.spacing?.after !== void 0) mergedAttrs.spacing = {
2440
- ...mergedAttrs.spacing,
2441
- after: b.attrs.spacing.after
2442
- };
2443
- if (b.attrs?.runInWithNext) mergedAttrs.runInWithNext = true;
2444
- else delete mergedAttrs.runInWithNext;
2445
- const merged = {
2446
- ...a,
2447
- runs: [...a.runs, ...b.runs],
2448
- attrs: mergedAttrs
2449
- };
2450
- const mergedPmEnd = b.pmEnd ?? a.pmEnd;
2451
- if (mergedPmEnd !== void 0) merged.pmEnd = mergedPmEnd;
2452
- current = merged;
2453
- i += 1;
2454
- }
2455
- out.push(current);
2456
- }
2457
- return out;
2458
- }
2459
410
  //#endregion
2460
411
  export { WORD_UNSPECIFIED_FONT_SIZE, collectNoteRefs, convertBorderSpecToLayout, formatCounter, isNoteReferenceMarkXml, resetBlockIdCounter, resolveListTemplate, toFlowBlocks };