@stll/folio-core 0.7.0 → 0.9.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 (193) hide show
  1. package/README.md +39 -0
  2. package/dist/ai-edits/apply.js +18 -4
  3. package/dist/ai-edits/blockRange.d.ts +13 -2
  4. package/dist/ai-edits/blockRange.js +23 -2
  5. package/dist/ai-edits/headless.d.ts +31 -4
  6. package/dist/ai-edits/headless.js +148 -35
  7. package/dist/ai-edits/index.d.ts +6 -4
  8. package/dist/ai-edits/index.js +4 -1
  9. package/dist/ai-edits/scoped-reading.d.ts +9 -0
  10. package/dist/ai-edits/scoped-reading.js +60 -0
  11. package/dist/ai-edits/snapshot.js +53 -7
  12. package/dist/ai-edits/types.d.ts +46 -3
  13. package/dist/compat/eigenpal.d.ts +8 -3
  14. package/dist/compat/eigenpal.js +7 -2
  15. package/dist/controller/fontReadiness.d.ts +29 -0
  16. package/dist/controller/fontReadiness.js +139 -0
  17. package/dist/controller/latestRequestGate.d.ts +13 -0
  18. package/dist/controller/latestRequestGate.js +16 -0
  19. package/dist/controller/layoutPipeline.js +88 -10
  20. package/dist/document-operations.d.ts +10 -3
  21. package/dist/document-operations.js +29 -14
  22. package/dist/docx/blockContentParser.js +53 -10
  23. package/dist/docx/commentParser.js +1 -1
  24. package/dist/docx/compatibility.d.ts +30 -2
  25. package/dist/docx/compatibility.js +118 -39
  26. package/dist/docx/conformance.d.ts +6 -0
  27. package/dist/docx/conformance.js +18 -0
  28. package/dist/docx/encryption/agileDecryption.js +1 -1
  29. package/dist/docx/encryption/encryptionInfo.js +1 -1
  30. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  31. package/dist/docx/ensureParaIds.d.ts +29 -0
  32. package/dist/docx/ensureParaIds.js +423 -0
  33. package/dist/docx/fontTableParser.d.ts +6 -0
  34. package/dist/docx/fontTableParser.js +72 -0
  35. package/dist/docx/groupDrawingParser.js +7 -2
  36. package/dist/docx/headerFooterParser.js +1 -1
  37. package/dist/docx/normalizeBaseDirection.js +1 -1
  38. package/dist/docx/numberingParser.js +2 -0
  39. package/dist/docx/paragraphParser.js +21 -1
  40. package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
  41. package/dist/docx/parser.d.ts +1 -1
  42. package/dist/docx/parser.js +14 -9
  43. package/dist/docx/rezip.js +70 -5
  44. package/dist/docx/runConsolidator.js +4 -0
  45. package/dist/docx/runParser.js +19 -1
  46. package/dist/docx/selectiveSave.js +3 -3
  47. package/dist/docx/selectiveXmlPatch.d.ts +10 -1
  48. package/dist/docx/selectiveXmlPatch.js +1 -1
  49. package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
  50. package/dist/docx/serializer/fontTableSerializer.js +26 -0
  51. package/dist/docx/serializer/headerFooterSerializer.js +1 -1
  52. package/dist/docx/serializer/paragraphSerializer.js +8 -0
  53. package/dist/docx/serializer/runSerializer.js +14 -2
  54. package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
  55. package/dist/docx/serializer/settingsSerializer.js +16 -0
  56. package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
  57. package/dist/docx/serializer/stylesSerializer.js +56 -0
  58. package/dist/docx/serializer/tableSerializer.js +29 -26
  59. package/dist/docx/serializer/themeSerializer.d.ts +6 -0
  60. package/dist/docx/serializer/themeSerializer.js +36 -0
  61. package/dist/docx/settingsParser.js +46 -0
  62. package/dist/docx/styleParser.js +26 -1
  63. package/dist/docx/tableParser.js +19 -1
  64. package/dist/docx/textBoxParser.js +6 -1
  65. package/dist/docx/vmlImageParser.js +146 -1
  66. package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
  67. package/dist/i18n/messages/catalogs.gen.js +5910 -5910
  68. package/dist/index.d.ts +8 -3
  69. package/dist/index.js +7 -2
  70. package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
  71. package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
  72. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
  73. package/dist/layout-bridge/convert/headerFooterLayout.js +22 -3
  74. package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
  75. package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
  76. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
  77. package/dist/layout-bridge/convert/toFlowBlocks.js +93 -17
  78. package/dist/layout-bridge/engine/hitTest.js +2 -1
  79. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  80. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  81. package/dist/layout-bridge/engine/selectionRects.js +2 -1
  82. package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
  83. package/dist/layout-engine/headerFooterRefs.js +40 -0
  84. package/dist/layout-engine/index.d.ts +3 -2
  85. package/dist/layout-engine/index.js +68 -106
  86. package/dist/layout-engine/measure/cache.js +11 -3
  87. package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
  88. package/dist/layout-engine/measure/index.d.ts +2 -1
  89. package/dist/layout-engine/measure/index.js +2 -1
  90. package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
  91. package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
  92. package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
  93. package/dist/layout-engine/measure/lineBreaks.js +11 -18
  94. package/dist/layout-engine/measure/listMarkerWidth.d.ts +4 -1
  95. package/dist/layout-engine/measure/listMarkerWidth.js +33 -8
  96. package/dist/layout-engine/measure/measureBlocks.d.ts +4 -2
  97. package/dist/layout-engine/measure/measureBlocks.js +138 -86
  98. package/dist/layout-engine/measure/measureContainer.js +32 -9
  99. package/dist/layout-engine/measure/measureHelpers.js +5 -2
  100. package/dist/layout-engine/measure/measureParagraph.js +346 -74
  101. package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
  102. package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
  103. package/dist/layout-engine/measure/measureWorker.js +8 -19
  104. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
  105. package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
  106. package/dist/layout-engine/measure/tableCellFloating.js +37 -29
  107. package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
  108. package/dist/layout-engine/measure/tableCellFlow.js +73 -0
  109. package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
  110. package/dist/layout-engine/measure/tableCellGrid.js +62 -0
  111. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
  112. package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
  113. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
  114. package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
  115. package/dist/layout-engine/paginator.d.ts +2 -1
  116. package/dist/layout-engine/paginator.js +7 -6
  117. package/dist/layout-engine/paragraphFrame.d.ts +22 -0
  118. package/dist/layout-engine/paragraphFrame.js +17 -0
  119. package/dist/layout-engine/paragraphSequence.d.ts +7 -0
  120. package/dist/layout-engine/paragraphSequence.js +25 -0
  121. package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
  122. package/dist/layout-engine/paragraphSpacing.js +30 -0
  123. package/dist/layout-engine/renderedBreakReconciliation.d.ts +63 -0
  124. package/dist/layout-engine/renderedBreakReconciliation.js +86 -0
  125. package/dist/layout-engine/tableRowBreak.d.ts +5 -2
  126. package/dist/layout-engine/tableRowBreak.js +62 -29
  127. package/dist/layout-engine/types.d.ts +54 -12
  128. package/dist/layout-engine/types.js +15 -2
  129. package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
  130. package/dist/layout-painter/anchoredImagePosition.js +95 -0
  131. package/dist/layout-painter/borderStroke.d.ts +23 -0
  132. package/dist/layout-painter/borderStroke.js +39 -0
  133. package/dist/layout-painter/renderImage.d.ts +5 -4
  134. package/dist/layout-painter/renderImage.js +18 -4
  135. package/dist/layout-painter/renderPage.d.ts +2 -27
  136. package/dist/layout-painter/renderPage.js +15 -101
  137. package/dist/layout-painter/renderParagraph.js +45 -26
  138. package/dist/layout-painter/renderTable.d.ts +2 -0
  139. package/dist/layout-painter/renderTable.js +257 -47
  140. package/dist/layout-painter/renderTextBox.js +17 -7
  141. package/dist/managers/DocumentLoaderManager.d.ts +1 -1
  142. package/dist/managers/DocumentLoaderManager.js +2 -2
  143. package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
  144. package/dist/paged-layout/sectionBlockWidths.js +9 -0
  145. package/dist/paged-layout/sectionGeometry.js +1 -1
  146. package/dist/prosemirror/attrs/index.d.ts +4 -2
  147. package/dist/prosemirror/attrs/index.js +32 -2
  148. package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
  149. package/dist/prosemirror/conversion/fromProseDoc.js +35 -18
  150. package/dist/prosemirror/conversion/toProseDoc.js +73 -37
  151. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  152. package/dist/prosemirror/extensions/core/ParagraphExtension.js +8 -2
  153. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
  154. package/dist/prosemirror/extensions/features/ListExtension.js +2 -0
  155. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
  156. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
  157. package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
  158. package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
  159. package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
  160. package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
  161. package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
  162. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
  163. package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
  164. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  165. package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
  166. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
  167. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
  168. package/dist/prosemirror/schema/index.d.ts +2 -2
  169. package/dist/prosemirror/schema/marks.d.ts +6 -1
  170. package/dist/prosemirror/schema/nodes.d.ts +16 -12
  171. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  172. package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
  173. package/dist/prosemirror/utils/tabCalculator.js +12 -10
  174. package/dist/prosemirror/validation.js +4 -1
  175. package/dist/redline.js +13 -3
  176. package/dist/server.d.ts +9 -4
  177. package/dist/server.js +7 -2
  178. package/dist/style-sets/extract.d.ts +35 -0
  179. package/dist/style-sets/extract.js +123 -0
  180. package/dist/style-sets/stellaStyle.d.ts +13 -0
  181. package/dist/style-sets/stellaStyle.js +516 -0
  182. package/dist/style-sets/types.d.ts +31 -0
  183. package/dist/style-sets/types.js +5 -0
  184. package/dist/types/index.d.ts +2 -1
  185. package/dist/utils/createDocument.d.ts +12 -2
  186. package/dist/utils/createDocument.js +55 -36
  187. package/dist/utils/fontResolver.js +6 -6
  188. package/dist/utils/formatToStyle.js +1 -1
  189. package/dist/utils/hexId.d.ts +8 -1
  190. package/dist/utils/hexId.js +11 -3
  191. package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
  192. package/dist/utils/paragraphFormattingMerge.js +14 -4
  193. package/package.json +5 -1
@@ -0,0 +1,17 @@
1
+ import { ParagraphBlock } from "./types.js";
2
+
3
+ //#region src/layout-engine/paragraphSpacing.d.ts
4
+ /** Whether a paragraph has no visible run content. */
5
+ declare function isEmptyParagraph(block: ParagraphBlock): boolean;
6
+ /**
7
+ * Resolve leading paragraph spacing after applying the empty-paragraph
8
+ * inherited-spacing collapse rule.
9
+ */
10
+ declare function getParagraphSpacingBefore(block: ParagraphBlock): number;
11
+ /**
12
+ * Resolve trailing paragraph spacing after applying the empty-paragraph
13
+ * inherited-spacing collapse rule.
14
+ */
15
+ declare function getParagraphSpacingAfter(block: ParagraphBlock): number;
16
+ //#endregion
17
+ export { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph };
@@ -0,0 +1,30 @@
1
+ //#region src/layout-engine/paragraphSpacing.ts
2
+ /** Whether a paragraph has no visible run content. */
3
+ function isEmptyParagraph(block) {
4
+ if (block.runs.length === 0) return true;
5
+ if (block.runs.length !== 1) return false;
6
+ const run = block.runs.at(0);
7
+ return run?.kind === "text" && run.text === "";
8
+ }
9
+ /**
10
+ * Resolve leading paragraph spacing after applying the empty-paragraph
11
+ * inherited-spacing collapse rule.
12
+ */
13
+ function getParagraphSpacingBefore(block) {
14
+ const value = block.attrs?.spacing?.before ?? 0;
15
+ if (value === 0) return 0;
16
+ if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.hasDirectParagraphFormatting && !block.attrs?.hasDirectParagraphMarkFormatting && !block.attrs?.spacingExplicit?.before) return 0;
17
+ return value;
18
+ }
19
+ /**
20
+ * Resolve trailing paragraph spacing after applying the empty-paragraph
21
+ * inherited-spacing collapse rule.
22
+ */
23
+ function getParagraphSpacingAfter(block) {
24
+ const value = block.attrs?.spacing?.after ?? 0;
25
+ if (value === 0) return 0;
26
+ if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.hasDirectParagraphFormatting && !block.attrs?.hasDirectParagraphMarkFormatting && !block.attrs?.spacingExplicit?.after) return 0;
27
+ return value;
28
+ }
29
+ //#endregion
30
+ export { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph };
@@ -0,0 +1,63 @@
1
+ import { FlowBlock, Page } from "./types.js";
2
+
3
+ //#region src/layout-engine/renderedBreakReconciliation.d.ts
4
+ type RenderedBreakState = {
5
+ type: "noPageAdvance";
6
+ } | {
7
+ type: "pageAdvance";
8
+ reason: "ordinary" | "reflowBoundary";
9
+ } | {
10
+ type: "pageAdvance";
11
+ reason: "authoredBoundary";
12
+ boundary: "hardBreak" | "sectionBreak";
13
+ };
14
+ declare const INITIAL_RENDERED_BREAK_STATE: RenderedBreakState;
15
+ type ReconcileBeforeBlockOptions = {
16
+ state: RenderedBreakState;
17
+ block: FlowBlock;
18
+ previousBlock: FlowBlock | undefined;
19
+ page: Page;
20
+ blocksById: ReadonlyMap<string, FlowBlock>;
21
+ hasExplicitPageBreak: boolean;
22
+ renderedBreakNeedsSnap: boolean;
23
+ };
24
+ type BreakDecision = {
25
+ forcePageBreak: boolean;
26
+ suppressSpaceBefore: boolean;
27
+ state: RenderedBreakState;
28
+ };
29
+ /**
30
+ * Reconcile an authored or cached break before laying out one block.
31
+ *
32
+ * Cached markers describe a boundary at a paragraph, not an ordinal page.
33
+ * Natural reflow may therefore satisfy the marker before layout reaches it.
34
+ * Every marker consumes the accumulated advance state so later markers make
35
+ * independent decisions.
36
+ */
37
+ declare const reconcileBreakBeforeBlock: ({
38
+ state,
39
+ block,
40
+ previousBlock,
41
+ page,
42
+ blocksById,
43
+ hasExplicitPageBreak,
44
+ renderedBreakNeedsSnap
45
+ }: ReconcileBeforeBlockOptions) => BreakDecision;
46
+ declare const recordReflowBoundary: (state: RenderedBreakState, crossedPageBoundary: boolean) => RenderedBreakState;
47
+ type ReconcileAfterBlockOptions = {
48
+ state: RenderedBreakState;
49
+ block: FlowBlock;
50
+ pageNumberBefore: number;
51
+ pageNumberAfter: number;
52
+ previousPage: Page | undefined;
53
+ };
54
+ /** Record page movement and carry authored boundaries across pagination-empty paragraphs. */
55
+ declare const reconcileAfterBlock: ({
56
+ state,
57
+ block,
58
+ pageNumberBefore,
59
+ pageNumberAfter,
60
+ previousPage
61
+ }: ReconcileAfterBlockOptions) => RenderedBreakState;
62
+ //#endregion
63
+ export { INITIAL_RENDERED_BREAK_STATE, RenderedBreakState, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary };
@@ -0,0 +1,86 @@
1
+ import { continuesNumberedSequence, continuesTabbedParagraphSequence } from "./paragraphSequence.js";
2
+ //#region src/layout-engine/renderedBreakReconciliation.ts
3
+ const INITIAL_RENDERED_BREAK_STATE = { type: "noPageAdvance" };
4
+ /**
5
+ * Reconcile an authored or cached break before laying out one block.
6
+ *
7
+ * Cached markers describe a boundary at a paragraph, not an ordinal page.
8
+ * Natural reflow may therefore satisfy the marker before layout reaches it.
9
+ * Every marker consumes the accumulated advance state so later markers make
10
+ * independent decisions.
11
+ */
12
+ const reconcileBreakBeforeBlock = ({ state, block, previousBlock, page, blocksById, hasExplicitPageBreak, renderedBreakNeedsSnap }) => {
13
+ if (hasExplicitPageBreak) return {
14
+ forcePageBreak: true,
15
+ suppressSpaceBefore: false,
16
+ state: INITIAL_RENDERED_BREAK_STATE
17
+ };
18
+ if (block.kind !== "paragraph" || block.attrs?.renderedPageBreakBefore !== true) return {
19
+ forcePageBreak: false,
20
+ suppressSpaceBefore: false,
21
+ state
22
+ };
23
+ const markerAlreadySatisfied = pageStartsWithPreviousParagraphContinuation(page, previousBlock) || state.type === "pageAdvance" && state.reason === "authoredBoundary" || state.type === "pageAdvance" && state.reason === "reflowBoundary" || state.type === "pageAdvance" && isPaginationEmptyParagraph(block) || state.type === "pageAdvance" && (continuesNumberedSequence(previousBlock, block) || continuesTabbedParagraphSequence(previousBlock, block));
24
+ const forcePageBreak = (renderedBreakNeedsSnap || block.attrs?.keepNext === true) && !markerAlreadySatisfied && pageHasVisibleBodyContent(page, blocksById);
25
+ const followsAuthoredPageBreak = previousBlock?.kind === "pageBreak";
26
+ const followsSectionBreak = previousBlock?.kind === "sectionBreak";
27
+ const followsPageBreakingSection = followsSectionBreak && previousBlock.type !== "continuous";
28
+ const followsCarriedSectionBoundary = state.type === "pageAdvance" && state.reason === "authoredBoundary" && state.boundary === "sectionBreak";
29
+ const preserveSectionLeadingSpacing = (followsSectionBreak || followsCarriedSectionBoundary) && block.attrs?.spacingExplicit?.before === true;
30
+ return {
31
+ forcePageBreak,
32
+ suppressSpaceBefore: (forcePageBreak || markerAlreadySatisfied || followsAuthoredPageBreak || followsPageBreakingSection) && !preserveSectionLeadingSpacing,
33
+ state: INITIAL_RENDERED_BREAK_STATE
34
+ };
35
+ };
36
+ const recordReflowBoundary = (state, crossedPageBoundary) => crossedPageBoundary ? {
37
+ type: "pageAdvance",
38
+ reason: "reflowBoundary"
39
+ } : state;
40
+ /** Record page movement and carry authored boundaries across pagination-empty paragraphs. */
41
+ const reconcileAfterBlock = ({ state, block, pageNumberBefore, pageNumberAfter, previousPage }) => {
42
+ if (isStructuralBreak(block)) {
43
+ if (pageNumberAfter <= pageNumberBefore) {
44
+ if (state.type === "pageAdvance" && state.reason === "authoredBoundary") return block.kind === "sectionBreak" ? {
45
+ ...state,
46
+ boundary: "sectionBreak"
47
+ } : state;
48
+ return INITIAL_RENDERED_BREAK_STATE;
49
+ }
50
+ return {
51
+ type: "pageAdvance",
52
+ reason: "authoredBoundary",
53
+ boundary: block.kind === "sectionBreak" ? "sectionBreak" : "hardBreak"
54
+ };
55
+ }
56
+ if (block.kind === "paragraph" && isPaginationEmptyParagraph(block)) return state;
57
+ if (pageNumberAfter <= pageNumberBefore) return state.type === "pageAdvance" && state.reason === "authoredBoundary" ? INITIAL_RENDERED_BREAK_STATE : state;
58
+ return {
59
+ type: "pageAdvance",
60
+ reason: previousPage?.fragments.some(({ blockId }) => blockId === block.id) ? "reflowBoundary" : "ordinary"
61
+ };
62
+ };
63
+ const isPaginationEmptyParagraph = (block) => !hasVisibleEmptyParagraphDecoration(block) && block.runs.every((run) => {
64
+ if (run.kind === "text") return run.text.trim().length === 0;
65
+ return run.kind === "tab" || run.kind === "lineBreak";
66
+ });
67
+ const hasVisibleEmptyParagraphDecoration = (block) => {
68
+ if (block.attrs?.listMarker !== void 0 && block.attrs.listMarker.length > 0 && block.attrs.listMarkerHidden !== true) return true;
69
+ if (block.attrs?.shading) return true;
70
+ return Object.values(block.attrs?.borders ?? {}).some((border) => border !== void 0 && border.style !== "none" && border.style !== "nil" && border.width !== 0);
71
+ };
72
+ const pageHasVisibleBodyContent = (page, blocksById) => {
73
+ for (const fragment of page.fragments) {
74
+ if (fragment.kind !== "paragraph") return true;
75
+ const block = blocksById.get(String(fragment.blockId));
76
+ if (block?.kind !== "paragraph" || !isPaginationEmptyParagraph(block)) return true;
77
+ }
78
+ return false;
79
+ };
80
+ const pageStartsWithPreviousParagraphContinuation = (page, previousBlock) => {
81
+ if (previousBlock?.kind !== "paragraph") return false;
82
+ return page.fragments.some((fragment) => fragment.kind === "paragraph" && String(fragment.blockId) === String(previousBlock.id) && fragment.continuesFromPrev === true);
83
+ };
84
+ const isStructuralBreak = (block) => block.kind === "pageBreak" || block.kind === "columnBreak" || block.kind === "sectionBreak";
85
+ //#endregion
86
+ export { INITIAL_RENDERED_BREAK_STATE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary };
@@ -9,10 +9,13 @@ type TableRowBreakInfo = {
9
9
  * at which a break is clean. Always includes the row's full height as the
10
10
  * final boundary.
11
11
  */
12
- breakOffsets: number[][];
12
+ breakOffsets: number[][]; /** Suppressible leading paragraph whitespace after each matching break offset. */
13
+ continuationSkips: number[][];
13
14
  };
14
15
  /** Build break geometry for a table from its block + measure. */
15
16
  declare function buildTableRowBreakInfo(block: TableBlock, measure: TableMeasure): TableRowBreakInfo;
17
+ /** Leading empty-paragraph whitespace to consume when a row resumes after `offset`. */
18
+ declare function getRowContinuationSkip(info: TableRowBreakInfo, rowIndex: number, offset: number): number;
16
19
  /**
17
20
  * Given a row and how much of it has already been placed (`fromOffset`), return
18
21
  * how many more px can be placed ending on a whole line, without exceeding
@@ -20,4 +23,4 @@ declare function buildTableRowBreakInfo(block: TableBlock, measure: TableMeasure
20
23
  */
21
24
  declare function snapRowBreak(info: TableRowBreakInfo, rowIndex: number, fromOffset: number, maxSlice: number): number;
22
25
  //#endregion
23
- export { TableRowBreakInfo, buildTableRowBreakInfo, snapRowBreak };
26
+ export { TableRowBreakInfo, buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak };
@@ -1,28 +1,24 @@
1
- import { isFloatingTextBoxBlock } from "./types.js";
2
1
  import { measureParagraph } from "./measure/measureParagraph.js";
2
+ import { isEmptyParagraph } from "./paragraphSpacing.js";
3
+ import { createTableCellFlowState, placeTableCellBlock } from "./measure/tableCellFlow.js";
3
4
  import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "./measure/tableCellFloating.js";
4
5
  //#region src/layout-engine/tableRowBreak.ts
5
6
  /**
6
7
  * Table row-break geometry. Ported from eigenpal/docx-editor#698 (folio subset).
7
8
  *
8
- * Word lets a table row break across a page boundary ("allow row to break
9
- * across pages", on by default). When a row is taller than a whole page it
10
- * cannot fit anywhere, so the portion that fits stays and the rest continues on
11
- * the next page broken between whole text lines, never through a glyph.
12
- * Without this folio forced the oversized row onto one page and the overflow
13
- * was clipped (content lost; upstream #570).
9
+ * Table rows may break across flow regions unless `cantSplit` is set. When a
10
+ * permitted row exceeds the current region, the portion that fits stays and
11
+ * the rest continues in the next region, broken between whole text lines and
12
+ * never through a glyph. This also prevents oversized rows from overflowing
13
+ * one page and clipping content (upstream #570).
14
14
  *
15
15
  * This computes, per row, the safe break offsets (the y of every line bottom in
16
16
  * the row's cells) so the paginator can snap a break to the deepest whole line
17
17
  * that still fits. Vertically-merged cells that span into a row are not modeled
18
18
  * here yet (a separate follow-up); each row uses its own cells' content.
19
19
  */
20
+ const BREAK_OFFSET_EPSILON = .01;
20
21
  const DEFAULT_TABLE_CELL_PADDING_TOP = 1;
21
- function getAtomicBlockHeight(measure) {
22
- if ("totalHeight" in measure) return measure.totalHeight;
23
- if ("height" in measure) return measure.height;
24
- return 0;
25
- }
26
22
  function isInsideRange(offset, range) {
27
23
  return offset > range.top && offset < range.bottom;
28
24
  }
@@ -39,6 +35,10 @@ function shiftCellGeometry(geometry, offset) {
39
35
  unsafeRanges: geometry.unsafeRanges.map((range) => ({
40
36
  top: range.top + offset,
41
37
  bottom: range.bottom + offset
38
+ })),
39
+ suppressibleLeadingRanges: geometry.suppressibleLeadingRanges.map((range) => ({
40
+ top: range.top + offset,
41
+ bottom: range.bottom + offset
42
42
  }))
43
43
  };
44
44
  }
@@ -46,22 +46,28 @@ function shiftCellGeometry(geometry, offset) {
46
46
  function cellBreakGeometry(cell, measure) {
47
47
  const bottoms = [];
48
48
  const unsafeRanges = [];
49
+ const suppressibleLeadingRanges = [];
49
50
  const cellBlocks = cell?.blocks;
50
51
  const blockMeasures = measure.blocks;
51
52
  const padTop = cell?.padding?.top ?? DEFAULT_TABLE_CELL_PADDING_TOP;
52
53
  const contentWidth = getTableCellContentWidth(cell, measure);
53
54
  const floatingZones = buildTableCellFloatingZones(cell !== void 0 ? getTableCellFloatingImages(cell, measure, contentWidth) : [], contentWidth);
54
- let y = padTop;
55
- let paragraphY = 0;
55
+ const flowState = createTableCellFlowState();
56
56
  for (let i = 0; i < blockMeasures.length; i++) {
57
57
  let blockMeasure = blockMeasures[i];
58
58
  if (blockMeasure?.kind === "paragraph") {
59
59
  const block = cellBlocks?.[i];
60
- if (block?.kind === "paragraph" && floatingZones.length > 0) blockMeasure = measureParagraph(block, contentWidth, {
60
+ if (block?.kind !== "paragraph") continue;
61
+ if (floatingZones.length > 0) blockMeasure = measureParagraph(block, contentWidth, {
61
62
  floatingZones,
62
- paragraphYOffset: paragraphY
63
+ paragraphYOffset: placeTableCellBlock({ ...flowState }, block, blockMeasure).contentTop
64
+ });
65
+ const placement = placeTableCellBlock(flowState, block, blockMeasure);
66
+ if (placement.leadingSpacing > 0 && isEmptyParagraph(block)) suppressibleLeadingRanges.push({
67
+ top: padTop + placement.top,
68
+ bottom: padTop + placement.contentTop
63
69
  });
64
- const blockTop = y;
70
+ let y = padTop + placement.contentTop;
65
71
  for (const line of blockMeasure.lines) {
66
72
  y += line.floatSkipBefore ?? 0;
67
73
  const top = y;
@@ -72,16 +78,15 @@ function cellBreakGeometry(cell, measure) {
72
78
  });
73
79
  bottoms.push(y);
74
80
  }
75
- y = blockTop + blockMeasure.totalHeight;
76
- paragraphY += blockMeasure.totalHeight;
81
+ bottoms.push(padTop + flowState.height);
77
82
  } else if (blockMeasure) {
78
83
  const block = cellBlocks?.[i];
79
- if (block?.kind === "textBox" && isFloatingTextBoxBlock(block)) continue;
80
- const blockHeight = getAtomicBlockHeight(blockMeasure);
84
+ if (!block) continue;
85
+ const placement = placeTableCellBlock(flowState, block, blockMeasure);
86
+ const blockHeight = placement.contentHeight;
81
87
  if (blockHeight > 0) {
82
- const top = y;
83
- y += blockHeight;
84
- paragraphY += blockHeight;
88
+ const top = padTop + placement.contentTop;
89
+ const y = top + blockHeight;
85
90
  unsafeRanges.push({
86
91
  top,
87
92
  bottom: y
@@ -92,9 +97,26 @@ function cellBreakGeometry(cell, measure) {
92
97
  }
93
98
  return {
94
99
  bottoms,
95
- unsafeRanges
100
+ unsafeRanges,
101
+ suppressibleLeadingRanges
96
102
  };
97
103
  }
104
+ const continuationSkipForCell = (geometry, offset) => {
105
+ if (!geometry.unsafeRanges.some(({ bottom }) => bottom > offset + BREAK_OFFSET_EPSILON)) return;
106
+ const leadingRange = geometry.suppressibleLeadingRanges.find(({ top, bottom }) => top <= offset + BREAK_OFFSET_EPSILON && bottom > offset + BREAK_OFFSET_EPSILON);
107
+ return leadingRange ? leadingRange.bottom - offset : 0;
108
+ };
109
+ /**
110
+ * Leading spacing before an empty paragraph is consumed when a split row
111
+ * resumes at the top of a fresh flow region. Only a whitespace band shared by
112
+ * every cell with remaining content is suppressible; non-empty paragraphs,
113
+ * line boxes, cell padding, and explicit row height retain authored space.
114
+ */
115
+ const continuationSkipAfter = (geometries, offset) => {
116
+ const skips = geometries.map((geometry) => continuationSkipForCell(geometry, offset)).filter((skip) => skip !== void 0);
117
+ if (skips.length === 0 || skips.some((skip) => skip <= BREAK_OFFSET_EPSILON)) return 0;
118
+ return Math.min(...skips);
119
+ };
98
120
  /** Build break geometry for a table from its block + measure. */
99
121
  function buildTableRowBreakInfo(block, measure) {
100
122
  const rowCount = measure.rows.length;
@@ -106,6 +128,7 @@ function buildTableRowBreakInfo(block, measure) {
106
128
  }
107
129
  rowTops.push(acc);
108
130
  const breakOffsets = [];
131
+ const continuationSkips = [];
109
132
  for (let r = 0; r < rowCount; r++) {
110
133
  const rowHeight = measure.rows[r]?.height ?? 0;
111
134
  const offsets = /* @__PURE__ */ new Set();
@@ -121,14 +144,24 @@ function buildTableRowBreakInfo(block, measure) {
121
144
  cellGeometries.push(geometry);
122
145
  for (const b of geometry.bottoms) if (b > 0 && b < rowHeight) offsets.add(b);
123
146
  }
124
- const safeOffsets = [...offsets].filter((offset) => offset === rowHeight || cellGeometries.every((geometry) => geometry.unsafeRanges.every((range) => !isInsideRange(offset, range))));
125
- breakOffsets.push(safeOffsets.sort((a, b) => a - b));
147
+ const sortedOffsets = [...offsets].filter((offset) => offset === rowHeight || cellGeometries.every((geometry) => geometry.unsafeRanges.every((range) => !isInsideRange(offset, range)))).sort((a, b) => a - b);
148
+ breakOffsets.push(sortedOffsets);
149
+ continuationSkips.push(sortedOffsets.map((offset) => continuationSkipAfter(cellGeometries, offset)));
126
150
  }
127
151
  return {
128
152
  rowTops,
129
- breakOffsets
153
+ breakOffsets,
154
+ continuationSkips
130
155
  };
131
156
  }
157
+ /** Leading empty-paragraph whitespace to consume when a row resumes after `offset`. */
158
+ function getRowContinuationSkip(info, rowIndex, offset) {
159
+ const offsets = info.breakOffsets[rowIndex];
160
+ const skips = info.continuationSkips[rowIndex];
161
+ const index = offsets?.findIndex((candidate) => Math.abs(candidate - offset) <= BREAK_OFFSET_EPSILON);
162
+ if (index === void 0 || index < 0) return 0;
163
+ return skips?.[index] ?? 0;
164
+ }
132
165
  /**
133
166
  * Given a row and how much of it has already been placed (`fromOffset`), return
134
167
  * how many more px can be placed ending on a whole line, without exceeding
@@ -147,4 +180,4 @@ function snapRowBreak(info, rowIndex, fromOffset, maxSlice) {
147
180
  return best;
148
181
  }
149
182
  //#endregion
150
- export { buildTableRowBreakInfo, snapRowBreak };
183
+ export { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak };
@@ -1,9 +1,9 @@
1
1
  import { OutlineStyleAttr } from "../types/documentEnumValues.js";
2
- import { ImagePosition, ImageWrap, SdtProperties, SdtType, TableWidthType } from "@stll/docx-core/model";
2
+ import { ImagePosition, ImageWrap, SdtProperties, SdtType, ShapeTextBody, TableCellFormatting, TableWidthType } from "@stll/docx-core/model";
3
3
 
4
4
  //#region src/layout-engine/types.d.ts
5
5
  declare namespace types_d_exports {
6
- export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned };
6
+ export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned };
7
7
  }
8
8
  /**
9
9
  * Unique identifier for a block in the document.
@@ -38,7 +38,12 @@ type RunFormatting = {
38
38
  * `segmentByScript`) so wrapping stays correct. Absent means CJK falls back
39
39
  * to `fontFamily`.
40
40
  */
41
- eastAsiaFontFamily?: string;
41
+ eastAsiaFontFamily?: string; /** Run language metadata resolved from `w:lang`. */
42
+ language?: {
43
+ val?: string;
44
+ eastAsia?: string;
45
+ bidi?: string;
46
+ };
42
47
  fontSize?: number;
43
48
  letterSpacing?: number;
44
49
  superscript?: boolean;
@@ -164,7 +169,8 @@ type ImageRun = {
164
169
  distTop?: number;
165
170
  distBottom?: number;
166
171
  distLeft?: number;
167
- distRight?: number;
172
+ distRight?: number; /** Use the image box as the exact line height for an embedded-object preview. */
173
+ exactLineHeight?: boolean;
168
174
  /**
169
175
  * wp:srcRect crop fractions in [0, 1]; emit as CSS `clip-path: inset(...)`
170
176
  * to match Word's visible region. eigenpal #424 (image-crop subset).
@@ -286,7 +292,26 @@ type ListNumPr = {
286
292
  * Paragraph block attributes.
287
293
  */
288
294
  type ParagraphAttrs = {
289
- alignment?: "left" | "center" | "right" | "justify"; /** OOXML outline level (`w:outlineLvl`), where zero is the top level. */
295
+ alignment?: "left" | "center" | "right" | "justify"; /** East Asian first/last-character restrictions (`w:kinsoku`). */
296
+ kinsoku?: boolean; /** Hanging punctuation (`w:overflowPunct`), retained for layout policy. */
297
+ overflowPunctuation?: boolean; /** Exempt this paragraph from document automatic hyphenation. */
298
+ suppressAutoHyphens?: boolean; /** Document-wide automatic hyphenation controls retained for line layout. */
299
+ automaticHyphenation?: {
300
+ enabled: true;
301
+ doNotHyphenateCaps?: boolean;
302
+ consecutiveLineLimit?: number;
303
+ }; /** Document-wide custom line-edge characters and compatibility behavior. */
304
+ lineBreakRules?: {
305
+ noLineBreaksBefore?: {
306
+ language?: string;
307
+ characters: string;
308
+ };
309
+ noLineBreaksAfter?: {
310
+ language?: string;
311
+ characters: string;
312
+ };
313
+ useLegacyEthiopicAmharicRules?: boolean;
314
+ }; /** OOXML outline level (`w:outlineLvl`), where zero is the top level. */
290
315
  outlineLevel?: number;
291
316
  spacing?: ParagraphSpacing; /** Spacing sides resolved from OOXML automatic paragraph spacing. */
292
317
  automaticSpacing?: {
@@ -310,7 +335,8 @@ type ParagraphAttrs = {
310
335
  * source `w:pPr`. Word treats such a blank as authored and keeps its
311
336
  * inherited spacing; a bare empty paragraph still collapses that spacing.
312
337
  */
313
- hasDirectParagraphFormatting?: boolean;
338
+ hasDirectParagraphFormatting?: boolean; /** Whether an empty paragraph carries direct formatting on its paragraph mark. */
339
+ hasDirectParagraphMarkFormatting?: boolean;
314
340
  indent?: ParagraphIndent;
315
341
  keepNext?: boolean;
316
342
  keepLines?: boolean;
@@ -338,6 +364,8 @@ type ParagraphAttrs = {
338
364
  listMarkerHidden?: boolean;
339
365
  listMarkerFontFamily?: string;
340
366
  listMarkerFontSize?: number;
367
+ listMarkerBold?: boolean; /** Horizontal alignment of the marker around the paragraph's list anchor. */
368
+ listMarkerAlignment?: "left" | "center" | "right";
341
369
  /**
342
370
  * `w:suff` (§17.9.25) — what follows the marker before body text.
343
371
  * `tab` (default) grows the marker to the next tab stop; `space` adds
@@ -411,6 +439,8 @@ type CellBorders = {
411
439
  bottom?: CellBorderSpec;
412
440
  left?: CellBorderSpec;
413
441
  right?: CellBorderSpec;
442
+ topLeftToBottomRight?: CellBorderSpec;
443
+ topRightToBottomLeft?: CellBorderSpec;
414
444
  };
415
445
  /**
416
446
  * A table cell with content.
@@ -422,6 +452,7 @@ type TableCell = {
422
452
  rowSpan?: number;
423
453
  width?: number;
424
454
  verticalAlign?: "top" | "center" | "bottom";
455
+ textDirection?: NonNullable<TableCellFormatting["textDirection"]>;
425
456
  background?: string;
426
457
  borders?: CellBorders; /** Per-cell padding in pixels (from w:tcMar or table-level w:tblCellMar) */
427
458
  padding?: {
@@ -443,9 +474,12 @@ type TableCell = {
443
474
  type TableRow = {
444
475
  id: BlockId;
445
476
  cells: TableCell[];
477
+ gridBefore?: number;
478
+ gridAfter?: number;
446
479
  height?: number;
447
480
  heightRule?: "auto" | "atLeast" | "exact";
448
- isHeader?: boolean;
481
+ isHeader?: boolean; /** `w:cantSplit`: keep this row in one flow region. */
482
+ cantSplit?: boolean;
449
483
  hidden?: boolean;
450
484
  };
451
485
  /**
@@ -582,7 +616,8 @@ type TextBoxBlock = {
582
616
  kind: "textBox";
583
617
  id: BlockId; /** Width in pixels */
584
618
  width: number; /** Height in pixels (may be auto-calculated) */
585
- height?: number; /** Fill/background color */
619
+ height?: number; /** Text fitting behavior */
620
+ autoFit?: ShapeTextBody["autoFit"]; /** Fill/background color */
586
621
  fillColor?: string; /** Border width in pixels */
587
622
  outlineWidth?: number; /** Border color */
588
623
  outlineColor?: string; /** Outline dash style, or `"none"` for an explicit no-outline. */
@@ -664,7 +699,10 @@ type MeasuredLine = {
664
699
  * no usable horizontal room at the natural line Y. Painters render this
665
700
  * as marginTop on the line element; measurement adds it to totalHeight.
666
701
  */
667
- floatSkipBefore?: number;
702
+ floatSkipBefore?: number; /** Decorative hyphen inserted at a dictionary break without changing source text. */
703
+ discretionaryHyphen?: {
704
+ runIndex: number;
705
+ };
668
706
  };
669
707
  /**
670
708
  * Measurement result for a paragraph block.
@@ -913,7 +951,8 @@ type LayoutOptions = {
913
951
  * overflowing first-page header without forcing pages 2+ to inherit the
914
952
  * same extension.
915
953
  */
916
- firstPageMargins?: PageMargins; /** Body-level final section page size. */
954
+ firstPageMargins?: PageMargins; /** Per-section body margins used on even section pages. */
955
+ sectionEvenPageMargins?: (PageMargins | undefined)[]; /** Body-level final section page size. */
917
956
  finalPageSize?: {
918
957
  w: number;
919
958
  h: number;
@@ -1016,7 +1055,8 @@ type HeaderFooterContent = {
1016
1055
  /**
1017
1056
  * `true` when the image run is anchored at page-level coordinates instead of
1018
1057
  * participating in inline flow. Covers the OOXML floating wrap types
1019
- * (`square`, `tight`, `through`, `behind`, `inFront`) and the legacy
1058
+ * (`square`, `tight`, `through`, `behind`, `inFront`), explicitly positioned
1059
+ * `topAndBottom` artwork, and the legacy
1020
1060
  * `displayMode === "float"` escape hatch used by ProseMirror nodes that
1021
1061
  * pre-date the wrap-type round-trip.
1022
1062
  *
@@ -1067,5 +1107,7 @@ declare function floatingTextBoxReservesBand(block: TextBoxFlowAttrs): boolean;
1067
1107
  * and the painter both consult this so their line count and `white-space` agree.
1068
1108
  */
1069
1109
  declare function tableColumnsArePinned(table: TableBlock): boolean;
1110
+ /** Return the leading visual offset for a row with omitted grid columns. */
1111
+ declare const getTableRowLeadingWidth: (row: TableRow, columnWidths: readonly number[]) => number;
1070
1112
  //#endregion
1071
- export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_d_exports };
1113
+ export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_d_exports };
@@ -9,6 +9,7 @@ var types_exports = /* @__PURE__ */ __exportAll({
9
9
  FOOTNOTE_SEPARATOR_HEIGHT: () => 12,
10
10
  floatingTextBoxReservesBand: () => floatingTextBoxReservesBand,
11
11
  floatingTextBoxWrapsText: () => floatingTextBoxWrapsText,
12
+ getTableRowLeadingWidth: () => getTableRowLeadingWidth,
12
13
  isFloatingImageRun: () => isFloatingImageRun,
13
14
  isFloatingTextBoxBlock: () => isFloatingTextBoxBlock,
14
15
  isTextWrappingFloatingImageRun: () => isTextWrappingFloatingImageRun,
@@ -46,7 +47,8 @@ const FOOTNOTE_FALLBACK_LINE_HEIGHT = 13;
46
47
  /**
47
48
  * `true` when the image run is anchored at page-level coordinates instead of
48
49
  * participating in inline flow. Covers the OOXML floating wrap types
49
- * (`square`, `tight`, `through`, `behind`, `inFront`) and the legacy
50
+ * (`square`, `tight`, `through`, `behind`, `inFront`), explicitly positioned
51
+ * `topAndBottom` artwork, and the legacy
50
52
  * `displayMode === "float"` escape hatch used by ProseMirror nodes that
51
53
  * pre-date the wrap-type round-trip.
52
54
  *
@@ -55,6 +57,7 @@ const FOOTNOTE_FALLBACK_LINE_HEIGHT = 13;
55
57
  */
56
58
  const isFloatingImageRun = (run) => {
57
59
  const wrapType = run.wrapType;
60
+ if (wrapType === "topAndBottom" && run.position !== void 0) return true;
58
61
  if (wrapType === "square" || wrapType === "tight" || wrapType === "through" || wrapType === "behind" || wrapType === "inFront") return true;
59
62
  return run.displayMode === "float";
60
63
  };
@@ -114,5 +117,15 @@ function tableColumnsArePinned(table) {
114
117
  if (table.layout === "fixed") return true;
115
118
  return table.widthType === "dxa" || table.widthType === "pct";
116
119
  }
120
+ const tableRowOwnsNestedGrid = (row) => {
121
+ const cell = row.cells.length === 1 ? row.cells.at(0) : void 0;
122
+ if (!cell?.blocks.some((block) => block.kind === "table")) return false;
123
+ return cell.blocks.every((block) => block.kind === "table" || block.kind === "paragraph" && block.runs.length === 0);
124
+ };
125
+ /** Return the leading visual offset for a row with omitted grid columns. */
126
+ const getTableRowLeadingWidth = (row, columnWidths) => {
127
+ if (tableRowOwnsNestedGrid(row)) return 0;
128
+ return columnWidths.slice(0, row.gridBefore ?? 0).reduce((sum, columnWidth) => sum + columnWidth, 0);
129
+ };
117
130
  //#endregion
118
- export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_exports };
131
+ export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_exports };
@@ -0,0 +1,23 @@
1
+ import { ImageRun } from "../layout-engine/types.js";
2
+
3
+ //#region src/layout-painter/anchoredImagePosition.d.ts
4
+ type PageGeometry = {
5
+ pageWidth: number;
6
+ pageHeight: number;
7
+ marginLeft: number;
8
+ marginTop: number;
9
+ marginRight: number;
10
+ marginBottom: number;
11
+ contentWidth: number;
12
+ contentHeight: number;
13
+ };
14
+ /** Coordinates relative to the page content area's top-left corner. */
15
+ type AnchoredImagePosition = {
16
+ x: number;
17
+ y: number;
18
+ side: "left" | "right";
19
+ };
20
+ /** Resolve an anchored image into page-content-relative coordinates. */
21
+ declare function resolveAnchoredImagePosition(imgRun: ImageRun, fragmentY: number, geometry: PageGeometry): AnchoredImagePosition;
22
+ //#endregion
23
+ export { AnchoredImagePosition, PageGeometry, resolveAnchoredImagePosition };