@stll/folio-core 0.10.0 → 0.12.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 (217) hide show
  1. package/dist/ai-edits/apply.js +602 -50
  2. package/dist/ai-edits/block-identity.d.ts +4 -0
  3. package/dist/ai-edits/block-identity.js +9 -0
  4. package/dist/ai-edits/blockRange.d.ts +28 -1
  5. package/dist/ai-edits/blockRange.js +67 -1
  6. package/dist/ai-edits/headless.d.ts +4 -4
  7. package/dist/ai-edits/headless.js +9 -9
  8. package/dist/ai-edits/index.d.ts +2 -2
  9. package/dist/ai-edits/index.js +2 -2
  10. package/dist/ai-edits/read.d.ts +7 -7
  11. package/dist/ai-edits/read.js +110 -3
  12. package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
  13. package/dist/ai-edits/table-cell-mutations.js +237 -0
  14. package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
  15. package/dist/ai-edits/table-mutation-plan.js +90 -0
  16. package/dist/ai-edits/table-row-column-mutations.d.ts +100 -0
  17. package/dist/ai-edits/table-row-column-mutations.js +300 -0
  18. package/dist/ai-edits/table-targets.d.ts +32 -0
  19. package/dist/ai-edits/table-targets.js +70 -0
  20. package/dist/ai-edits/types.d.ts +34 -0
  21. package/dist/compat/eigenpal.js +1 -1
  22. package/dist/content-controls/mutateContentControls.js +1 -1
  23. package/dist/controller/activeEditorStory.d.ts +36 -0
  24. package/dist/controller/activeEditorStory.js +30 -0
  25. package/dist/controller/collaborationModules.d.ts +7 -0
  26. package/dist/controller/collaborationModules.js +15 -0
  27. package/dist/controller/contentControlWidgetController.d.ts +55 -0
  28. package/dist/controller/contentControlWidgetController.js +141 -0
  29. package/dist/controller/folioEditor.d.ts +1 -1
  30. package/dist/controller/fontReadiness.js +2 -0
  31. package/dist/controller/headerFooterEditorManager.d.ts +41 -0
  32. package/dist/controller/headerFooterEditorManager.js +182 -0
  33. package/dist/controller/hiddenEditorApi.js +2 -2
  34. package/dist/controller/hiddenEditorManager.d.ts +13 -2
  35. package/dist/controller/hiddenEditorManager.js +32 -11
  36. package/dist/controller/layoutPipeline.d.ts +2 -0
  37. package/dist/controller/layoutPipeline.js +16 -1
  38. package/dist/controller/noteEditorManager.d.ts +36 -0
  39. package/dist/controller/noteEditorManager.js +210 -0
  40. package/dist/document-operations.d.ts +35 -3
  41. package/dist/document-operations.js +161 -3
  42. package/dist/docx/blockContentParser.js +2 -2
  43. package/dist/docx/commentParser.js +6 -1
  44. package/dist/docx/conformance.js +8 -1
  45. package/dist/docx/documentGrid.d.ts +8 -0
  46. package/dist/docx/documentGrid.js +8 -0
  47. package/dist/docx/drawingUtils.js +1 -1
  48. package/dist/docx/ensureParaIds.js +1 -1
  49. package/dist/docx/fieldParser.js +1 -1
  50. package/dist/docx/notePropertiesParser.js +1 -1
  51. package/dist/docx/numberingParser.js +1 -1
  52. package/dist/docx/paragraphParser.js +63 -58
  53. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  54. package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
  55. package/dist/docx/paragraphTraversal.js +1 -1
  56. package/dist/docx/parser.js +3 -3
  57. package/dist/docx/rezip.js +1 -1
  58. package/dist/docx/runParser.js +4 -2
  59. package/dist/docx/sdtProperties.js +1 -1
  60. package/dist/docx/sectionParser.js +1 -1
  61. package/dist/docx/selectiveSave.js +1 -1
  62. package/dist/docx/serializer/commentSerializer.js +1 -2
  63. package/dist/docx/serializer/documentSerializer.js +2 -2
  64. package/dist/docx/serializer/headerFooterSerializer.js +2 -2
  65. package/dist/docx/serializer/noteSerializer.js +2 -2
  66. package/dist/docx/serializer/paragraphSerializer.js +7 -1
  67. package/dist/docx/serializer/runSerializer.js +4 -2
  68. package/dist/docx/serializer/stylesSerializer.js +1 -1
  69. package/dist/docx/serializer/tableSerializer.d.ts +5 -4
  70. package/dist/docx/serializer/tableSerializer.js +17 -13
  71. package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
  72. package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
  73. package/dist/docx/server/boundedArchive.d.ts +14 -3
  74. package/dist/docx/server/boundedArchive.js +56 -11
  75. package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
  76. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
  77. package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
  78. package/dist/docx/server/inspectDocxPackage.js +217 -0
  79. package/dist/docx/server/validateDocxConformance.d.ts +41 -0
  80. package/dist/docx/server/validateDocxConformance.js +336 -0
  81. package/dist/docx/settingsParser.js +11 -1
  82. package/dist/docx/shapeParser.js +1 -1
  83. package/dist/docx/styleParser.js +4 -2
  84. package/dist/docx/tableParser.js +18 -7
  85. package/dist/docx/textBoxParser.d.ts +3 -3
  86. package/dist/docx/textBoxParser.js +16 -12
  87. package/dist/docx/xmlSafety.d.ts +5 -0
  88. package/dist/docx/xmlSafety.js +9 -0
  89. package/dist/fields/resolveFieldValues.js +1 -3
  90. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  91. package/dist/i18n/messages/catalogs.gen.js +17 -0
  92. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  93. package/dist/index.js +1 -1
  94. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  95. package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
  96. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  97. package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
  98. package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
  99. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
  100. package/dist/layout-bridge/convert/toFlowBlocks.js +136 -20
  101. package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
  102. package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
  103. package/dist/layout-bridge/engine/clickToPosition.js +2 -2
  104. package/dist/layout-engine/index.d.ts +2 -2
  105. package/dist/layout-engine/index.js +43 -28
  106. package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
  107. package/dist/layout-engine/justificationCompatibility.js +8 -0
  108. package/dist/layout-engine/keep-together.js +11 -1
  109. package/dist/layout-engine/measure/cache.js +21 -5
  110. package/dist/layout-engine/measure/lineBreakProvider.js +53 -34
  111. package/dist/layout-engine/measure/measureBlocks.js +6 -3
  112. package/dist/layout-engine/measure/measureContainer.js +1 -1
  113. package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
  114. package/dist/layout-engine/measure/measureHelpers.js +3 -3
  115. package/dist/layout-engine/measure/measureParagraph.js +69 -25
  116. package/dist/layout-engine/measure/measureProvider.js +4 -3
  117. package/dist/layout-engine/measure/tableCellFlow.js +5 -2
  118. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
  119. package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
  120. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
  121. package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
  122. package/dist/layout-engine/paginator.js +5 -1
  123. package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
  124. package/dist/layout-engine/paragraphSpacing.js +107 -13
  125. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  126. package/dist/layout-engine/types.d.ts +26 -13
  127. package/dist/layout-painter/registry/modules/textBox.js +5 -1
  128. package/dist/layout-painter/renderPage.d.ts +5 -2
  129. package/dist/layout-painter/renderPage.js +56 -12
  130. package/dist/layout-painter/renderParagraph.js +22 -6
  131. package/dist/layout-painter/renderTable.d.ts +5 -1
  132. package/dist/layout-painter/renderTable.js +5 -2
  133. package/dist/layout-painter/renderTextBox.d.ts +2 -1
  134. package/dist/layout-painter/renderTextBox.js +25 -14
  135. package/dist/managers/DocumentLoaderManager.js +1 -1
  136. package/dist/managers/FindReplaceManager.d.ts +8 -0
  137. package/dist/managers/FindReplaceManager.js +18 -0
  138. package/dist/managers/TableSelectionManager.d.ts +6 -0
  139. package/dist/managers/TableSelectionManager.js +14 -0
  140. package/dist/model.js +1 -1
  141. package/dist/prosemirror/attrs/index.d.ts +4 -2
  142. package/dist/prosemirror/attrs/index.js +154 -18
  143. package/dist/prosemirror/commands/comments.d.ts +3 -3
  144. package/dist/prosemirror/commands/comments.js +248 -12
  145. package/dist/prosemirror/commands/contentControls.js +1 -1
  146. package/dist/prosemirror/commands/index.js +1 -1
  147. package/dist/prosemirror/commands/pageBreak.js +1 -1
  148. package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
  149. package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
  150. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
  151. package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
  152. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
  153. package/dist/prosemirror/conversion/fromProseDoc.js +286 -61
  154. package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
  155. package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
  156. package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
  157. package/dist/prosemirror/conversion/toProseDoc.js +347 -98
  158. package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
  159. package/dist/prosemirror/extensions/StarterKit.js +6 -1
  160. package/dist/prosemirror/extensions/core/ParagraphExtension.js +4 -1
  161. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
  162. package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
  163. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
  164. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
  165. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
  166. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
  167. package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
  168. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
  169. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +24 -1
  170. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  171. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
  172. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
  173. package/dist/prosemirror/extensions/nodes/TableExtension.js +9 -3
  174. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
  175. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
  176. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
  177. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
  178. package/dist/prosemirror/findReplaceSelection.d.ts +6 -1
  179. package/dist/prosemirror/findReplaceSelection.js +57 -13
  180. package/dist/prosemirror/plugins/contentControlWidgets.d.ts +34 -8
  181. package/dist/prosemirror/plugins/contentControlWidgets.js +125 -91
  182. package/dist/prosemirror/plugins/selectionTracker.js +1 -0
  183. package/dist/prosemirror/plugins/suggestionMode.js +1 -1
  184. package/dist/prosemirror/schema/index.d.ts +2 -2
  185. package/dist/prosemirror/schema/marks.d.ts +5 -1
  186. package/dist/prosemirror/schema/nodes.d.ts +64 -7
  187. package/dist/prosemirror/selectionState.js +1 -0
  188. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  189. package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
  190. package/dist/prosemirror/tableCellMergeRevision.js +22 -0
  191. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
  192. package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
  193. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
  194. package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
  195. package/dist/prosemirror/validation.js +9 -1
  196. package/dist/render-dom/BodySelectionOverlay.d.ts +31 -0
  197. package/dist/render-dom/BodySelectionOverlay.js +76 -0
  198. package/dist/render-dom/HeaderFooterSelectionOverlay.d.ts +28 -0
  199. package/dist/render-dom/HeaderFooterSelectionOverlay.js +121 -0
  200. package/dist/render-dom/RemoteSelectionOverlay.d.ts +30 -0
  201. package/dist/render-dom/RemoteSelectionOverlay.js +67 -0
  202. package/dist/render-dom/RenderedDomContext.d.ts +40 -0
  203. package/dist/render-dom/RenderedDomContext.js +126 -0
  204. package/dist/render-dom/resolveSidebarItemPositions.d.ts +33 -0
  205. package/dist/render-dom/resolveSidebarItemPositions.js +45 -0
  206. package/dist/server.d.ts +6 -2
  207. package/dist/server.js +5 -1
  208. package/dist/utils/findReplace.d.ts +32 -0
  209. package/dist/utils/findReplace.js +118 -0
  210. package/dist/utils/fontFamilyMerge.js +9 -11
  211. package/dist/utils/fontLoader.js +3 -2
  212. package/dist/utils/fontResolver.js +22 -4
  213. package/dist/utils/fontWeights.d.ts +1 -1
  214. package/dist/utils/fontWeights.js +1 -1
  215. package/dist/utils/headerFooter.js +24 -1
  216. package/dist/utils/paragraphFormattingMerge.js +1 -0
  217. package/package.json +1 -1
@@ -1,15 +1,17 @@
1
1
  import { NUMBER_FORMAT_VALUES } from "../../types/documentEnumValues.js";
2
- import { AUTO_PARAGRAPH_SPACING_PX, halfPointsToPixels, halfPointsToPoints, pointsToPixels } from "../../utils/units.js";
3
- import { convertBulletToUnicode } from "../../docx/bulletMarkers.js";
4
- import { padDecimal } from "../../docx/numberingParser.js";
5
- import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
6
2
  import { directionIsRtl } from "../../prosemirror/paragraphDirection.js";
7
3
  import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunShadingMarkAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../../prosemirror/attrs/index.js";
4
+ import { padDecimal } from "../../docx/numberingParser.js";
5
+ import { AUTO_PARAGRAPH_SPACING_PX, halfPointsToPixels, halfPointsToPoints, pointsToPixels } from "../../utils/units.js";
6
+ import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
8
7
  import { assertValidProseMirrorDocument } from "../../prosemirror/validation.js";
9
8
  import { runShadingAttrsToShading } from "../../prosemirror/conversion/runShadingMark.js";
10
9
  import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.js";
10
+ import { resolveThemeFont } from "../../utils/fontResolver.js";
11
11
  import { resolveShadingFill } from "../../utils/formatToStyle.js";
12
+ import { convertBulletToUnicode } from "../../docx/bulletMarkers.js";
12
13
  import { DEFAULT_TEXTBOX_MARGINS } from "../../layout-engine/types.js";
14
+ import { resolveDocumentGridLinePitch } from "../../docx/documentGrid.js";
13
15
  import { setTextBoxGroupId } from "../../layout-engine/textBoxGroup.js";
14
16
  import { setParagraphFrame } from "../../layout-engine/paragraphFrame.js";
15
17
  import { getColumns } from "../sectionColumns.js";
@@ -239,9 +241,10 @@ function extractRunFormatting(marks, theme) {
239
241
  break;
240
242
  case "fontFamily": {
241
243
  const attrs = expectFontFamilyMarkAttrs(mark);
242
- const font = attrs.ascii || attrs.hAnsi;
244
+ const font = resolveWesternThemeFont(attrs, theme);
243
245
  if (font) formatting.fontFamily = font;
244
- if (attrs.eastAsia) formatting.eastAsiaFontFamily = attrs.eastAsia;
246
+ const eastAsiaFont = resolveEastAsiaThemeFont(attrs, theme);
247
+ if (eastAsiaFont) formatting.eastAsiaFontFamily = eastAsiaFont;
245
248
  break;
246
249
  }
247
250
  case "language": {
@@ -345,6 +348,13 @@ function extractRunFormatting(marks, theme) {
345
348
  if (hasNoteRef && formatting.subscript) delete formatting.superscript;
346
349
  return formatting;
347
350
  }
351
+ const resolveWesternThemeFont = (fontFamily, theme) => {
352
+ const themeRef = fontFamily.asciiTheme ?? fontFamily.hAnsiTheme;
353
+ return (themeRef ? resolveThemeFont(themeRef, theme?.fontScheme) : null) ?? fontFamily.ascii ?? fontFamily.hAnsi ?? void 0;
354
+ };
355
+ const resolveEastAsiaThemeFont = (fontFamily, theme) => {
356
+ return (fontFamily.eastAsiaTheme ? resolveThemeFont(fontFamily.eastAsiaTheme, theme?.fontScheme) : null) ?? fontFamily.eastAsia ?? void 0;
357
+ };
348
358
  function isAutomaticTextColorValue(color) {
349
359
  const rgb = color.rgb?.trim().toLowerCase();
350
360
  return color.auto === true || rgb === "auto" || !rgb && !color.themeColor;
@@ -381,9 +391,10 @@ function paragraphRunDefaults(pmAttrs, theme) {
381
391
  const defaultTextFormatting = pmAttrs.defaultTextFormatting;
382
392
  if (!defaultTextFormatting) return {};
383
393
  const result = {};
384
- const fontFamily = defaultTextFormatting.fontFamily?.ascii ?? defaultTextFormatting.fontFamily?.hAnsi;
394
+ const fontFamily = defaultTextFormatting.fontFamily ? resolveWesternThemeFont(defaultTextFormatting.fontFamily, theme) : void 0;
385
395
  if (fontFamily) result.fontFamily = fontFamily;
386
- if (defaultTextFormatting.fontFamily?.eastAsia) result.eastAsiaFontFamily = defaultTextFormatting.fontFamily.eastAsia;
396
+ const eastAsiaFontFamily = defaultTextFormatting.fontFamily ? resolveEastAsiaThemeFont(defaultTextFormatting.fontFamily, theme) : void 0;
397
+ if (eastAsiaFontFamily) result.eastAsiaFontFamily = eastAsiaFontFamily;
387
398
  if (defaultTextFormatting.language) result.language = { ...defaultTextFormatting.language };
388
399
  if (defaultTextFormatting.fontSize !== void 0) result.fontSize = defaultTextFormatting.fontSize / 2;
389
400
  if (defaultTextFormatting.bold !== void 0) result.bold = defaultTextFormatting.bold;
@@ -488,7 +499,21 @@ function paragraphToRuns(node, startPos, _options) {
488
499
  const paraDefaults = paragraphRunDefaults(pmAttrs, theme);
489
500
  const paragraphStyleId = pmAttrs.styleId;
490
501
  const inTocParagraph = typeof paragraphStyleId === "string" && TOC_STYLE_ID.test(paragraphStyleId);
502
+ let leadingRenderedPageBreakPending = pmAttrs.renderedPageBreakBefore === true;
491
503
  function pushRunsForChild(child, childPos) {
504
+ if (child.type.name === "renderedPageBreak") {
505
+ if (leadingRenderedPageBreakPending) {
506
+ leadingRenderedPageBreakPending = false;
507
+ return;
508
+ }
509
+ runs.push({
510
+ kind: "renderedPageBreak",
511
+ pmStart: childPos,
512
+ pmEnd: childPos + child.nodeSize
513
+ });
514
+ return;
515
+ }
516
+ if (child.type.name !== "sdt") leadingRenderedPageBreakPending = false;
492
517
  if (child.isText && child.text) {
493
518
  const formatting = extractRunFormatting(child.marks, theme);
494
519
  if (inTocParagraph) stripTocHyperlinkStyle(formatting);
@@ -700,6 +725,7 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
700
725
  const spaceBefore = pmAttrs.spaceBefore;
701
726
  const spaceAfter = pmAttrs.spaceAfter;
702
727
  const lineSpacing = pmAttrs.lineSpacing;
728
+ if (typeof pmAttrs.snapToGrid === "boolean") attrs.snapToGrid = pmAttrs.snapToGrid;
703
729
  const autoBefore = autospacingMatchesBase(pmAttrs._autospacingBase, "before", spaceBefore);
704
730
  const autoAfter = autospacingMatchesBase(pmAttrs._autospacingBase, "after", spaceAfter);
705
731
  if (autoBefore || autoAfter || typeof spaceBefore === "number" || typeof spaceAfter === "number" || typeof lineSpacing === "number") {
@@ -714,9 +740,10 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
714
740
  };
715
741
  const pmSpacingExplicit = pmAttrs.spacingExplicit;
716
742
  const spacingFromDocDefaults = pmAttrs.spacingFromDocDefaults;
743
+ const spacingFromImplicitDefaultStyle = pmAttrs.spacingFromImplicitDefaultStyle;
717
744
  const explicit = {};
718
- if (autoBefore || pmSpacingExplicit?.before || spacingFromDocDefaults?.before) explicit.before = true;
719
- if (autoAfter || pmSpacingExplicit?.after || spacingFromDocDefaults?.after) explicit.after = true;
745
+ if (autoBefore || pmSpacingExplicit?.before || spacingFromDocDefaults?.before || spacingFromImplicitDefaultStyle?.before) explicit.before = true;
746
+ if (autoAfter || pmSpacingExplicit?.after || spacingFromDocDefaults?.after || spacingFromImplicitDefaultStyle?.after) explicit.after = true;
720
747
  if (explicit.before !== void 0 || explicit.after !== void 0) attrs.spacingExplicit = explicit;
721
748
  if (typeof lineSpacing === "number") if (pmAttrs.lineSpacingRule === "exact" || pmAttrs.lineSpacingRule === "atLeast") {
722
749
  attrs.spacing.line = twipsToPixels(lineSpacing);
@@ -832,7 +859,7 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
832
859
  if (dtf) {
833
860
  if (dtf.fontSize !== void 0) attrs.defaultFontSize = dtf.fontSize / 2;
834
861
  if (dtf.fontFamily) {
835
- const resolvedFamily = dtf.fontFamily.ascii || dtf.fontFamily.hAnsi;
862
+ const resolvedFamily = resolveWesternThemeFont(dtf.fontFamily, theme);
836
863
  if (resolvedFamily) attrs.defaultFontFamily = resolvedFamily;
837
864
  }
838
865
  }
@@ -856,19 +883,23 @@ function mapTabAlignment(align) {
856
883
  /**
857
884
  * Convert a paragraph node to a ParagraphBlock.
858
885
  */
886
+ function hasOnlyVisuallyEmptyTextRuns(runs) {
887
+ return runs.length > 0 && runs.every((run) => run.kind === "text" && run.text.replace(/\u00a0/gu, " ").trim().length === 0);
888
+ }
859
889
  function convertParagraph(node, startPos, options) {
860
890
  const pmAttrs = expectParagraphAttrs(node);
861
891
  const runs = paragraphToRuns(node, startPos, options);
862
892
  const attrs = convertParagraphAttrs(pmAttrs, options.theme, options.listCounters, options.listAbstractCounters, options.listSeenNumIds, options.defaultTabStopTwips, options.originalListCounters, options.originalListAbstractCounters, options.originalListSeenNumIds);
863
893
  if (options.lineBreakRules) attrs.lineBreakRules = options.lineBreakRules;
894
+ if (options.justificationCompatibility) attrs.justificationCompatibility = options.justificationCompatibility;
864
895
  if (options.automaticHyphenation) attrs.automaticHyphenation = options.automaticHyphenation;
865
896
  const defaultTextFormatting = pmAttrs.defaultTextFormatting;
866
- if (runs.length === 0) {
897
+ if (runs.length === 0 || hasOnlyVisuallyEmptyTextRuns(runs)) {
867
898
  if (pmAttrs._originalFormatting && Object.entries(pmAttrs._originalFormatting).some(([key, value]) => key !== "runProperties" && value !== void 0 && value !== null)) attrs.hasDirectParagraphFormatting = true;
868
899
  const paragraphMarkFormatting = pmAttrs._originalFormatting?.runProperties;
869
900
  if (paragraphMarkFormatting && Object.values(paragraphMarkFormatting).some((value) => value !== void 0 && value !== null)) attrs.hasDirectParagraphMarkFormatting = true;
870
901
  if (paragraphMarkFormatting?.fontSize !== void 0) attrs.defaultFontSize = paragraphMarkFormatting.fontSize / 2;
871
- const paragraphMarkFontFamily = paragraphMarkFormatting?.fontFamily?.ascii ?? paragraphMarkFormatting?.fontFamily?.hAnsi;
902
+ const paragraphMarkFontFamily = paragraphMarkFormatting?.fontFamily ? resolveWesternThemeFont(paragraphMarkFormatting.fontFamily, options.theme) : void 0;
872
903
  if (paragraphMarkFontFamily) attrs.defaultFontFamily = paragraphMarkFontFamily;
873
904
  }
874
905
  if (runs.length === 0 && defaultTextFormatting?.hidden === true) {
@@ -1037,12 +1068,13 @@ function convertTableCell(node, startPos, options, tableCellMargins) {
1037
1068
  suppressEmptyParagraphHeight: true
1038
1069
  };
1039
1070
  const attrs = expectTableCellAttrs(node);
1071
+ if (attrs.hideMark && trailingBlock?.kind === "paragraph" && trailingBlock.runs.every((run) => run.kind === "text" && run.text.length === 0)) trailingBlock.attrs = {
1072
+ ...trailingBlock.attrs,
1073
+ suppressEmptyParagraphHeight: true
1074
+ };
1040
1075
  const margins = attrs.margins;
1041
1076
  const resolvePaddingSide = (side, cellTwips, tableTwips) => {
1042
- if (cellTwips !== void 0) {
1043
- const px = twipsToPixels(cellTwips);
1044
- if (px > 0) return px;
1045
- }
1077
+ if (cellTwips !== void 0) return twipsToPixels(cellTwips);
1046
1078
  if (tableTwips !== void 0) return twipsToPixels(tableTwips);
1047
1079
  return twipsToPixels(DEFAULT_TABLE_CELL_MARGIN_TWIPS[side]);
1048
1080
  };
@@ -1191,10 +1223,12 @@ function convertTextBoxNode(node, startPos, opts) {
1191
1223
  const attrs = expectTextBoxAttrs(node);
1192
1224
  const contentBlocks = [];
1193
1225
  node.forEach((child, offset) => {
1226
+ const childPos = startPos + 1 + offset;
1194
1227
  if (child.type.name === "paragraph") {
1195
- const block = convertParagraph(child, startPos + 1 + offset, opts);
1196
- contentBlocks.push(block);
1228
+ contentBlocks.push(convertParagraph(child, childPos, opts));
1229
+ return;
1197
1230
  }
1231
+ if (child.type.name === "table") contentBlocks.push(convertTable(child, childPos, opts));
1198
1232
  });
1199
1233
  const textBox = {
1200
1234
  kind: "textBox",
@@ -1271,6 +1305,23 @@ function toFlowBlocks(doc, options = {}) {
1271
1305
  tagBlockWithSdtStack(block);
1272
1306
  blocks.push(block);
1273
1307
  };
1308
+ const trailingPageBreakSectionPositions = /* @__PURE__ */ new Set();
1309
+ const consumedPageBreakPositions = /* @__PURE__ */ new Set();
1310
+ const collectTrailingPageBreakSections = (parent, contentStart) => {
1311
+ let childStart = contentStart;
1312
+ for (let index = 0; index < parent.childCount; index += 1) {
1313
+ const child = parent.child(index);
1314
+ const next = index + 1 < parent.childCount ? parent.child(index + 1) : void 0;
1315
+ const paragraphAttrs = child.type.name === "paragraph" ? expectParagraphAttrs(child) : void 0;
1316
+ if (paragraphAttrs && next?.type.name === "pageBreak" && paragraphAttrs._trailingPageBreak === true && (paragraphAttrs._sectionProperties || paragraphAttrs.sectionBreakType)) {
1317
+ trailingPageBreakSectionPositions.add(childStart);
1318
+ consumedPageBreakPositions.add(childStart + child.nodeSize);
1319
+ }
1320
+ if (child.type.name === "blockSdt") collectTrailingPageBreakSections(child, childStart + 1);
1321
+ childStart += child.nodeSize;
1322
+ }
1323
+ };
1324
+ collectTrailingPageBreakSections(doc, offset);
1274
1325
  const visit = (node, pos) => {
1275
1326
  switch (node.type.name) {
1276
1327
  case "blockSdt": {
@@ -1356,6 +1407,41 @@ function toFlowBlocks(doc, options = {}) {
1356
1407
  trackedPush(paragraph);
1357
1408
  } else if (node.content.size > 0 || hasListFormatting || !hasSectionBreak) trackedPush(convertParagraph(node, pos, opts));
1358
1409
  if (hasSectionBreak) {
1410
+ if (trailingPageBreakSectionPositions.has(pos)) {
1411
+ const sourceParagraph = blocks.at(-1);
1412
+ if (sourceParagraph?.kind === "paragraph" && sourceParagraph.pmStart === pos) {
1413
+ const pageBreak = {
1414
+ kind: "pageBreak",
1415
+ id: nextBlockId(),
1416
+ pmStart: pos + node.nodeSize,
1417
+ pmEnd: pos + node.nodeSize + 1
1418
+ };
1419
+ trackedPush(pageBreak);
1420
+ const sourceAttrs = sourceParagraph.attrs;
1421
+ const carrierSpacing = sourceAttrs?.spacing ? { ...sourceAttrs.spacing } : void 0;
1422
+ if (carrierSpacing) delete carrierSpacing.before;
1423
+ const carrierAttrs = {
1424
+ ...carrierSpacing ? { spacing: carrierSpacing } : {},
1425
+ ...sourceAttrs?.automaticSpacing?.after === true ? { automaticSpacing: { after: true } } : {},
1426
+ ...sourceAttrs?.spacingExplicit?.after === true ? { spacingExplicit: { after: true } } : {},
1427
+ ...sourceAttrs?.hasDirectParagraphFormatting === true ? { hasDirectParagraphFormatting: true } : {},
1428
+ ...sourceAttrs?.hasDirectParagraphMarkFormatting === true ? { hasDirectParagraphMarkFormatting: true } : {},
1429
+ ...sourceAttrs?.snapToGrid !== void 0 ? { snapToGrid: sourceAttrs.snapToGrid } : {},
1430
+ ...sourceAttrs?.documentGridLinePitch !== void 0 ? { documentGridLinePitch: sourceAttrs.documentGridLinePitch } : {},
1431
+ ...sourceAttrs?.defaultFontSize !== void 0 ? { defaultFontSize: sourceAttrs.defaultFontSize } : {},
1432
+ ...sourceAttrs?.defaultFontFamily !== void 0 ? { defaultFontFamily: sourceAttrs.defaultFontFamily } : {}
1433
+ };
1434
+ const carrier = {
1435
+ kind: "paragraph",
1436
+ id: nextBlockId(),
1437
+ runs: [],
1438
+ attrs: carrierAttrs,
1439
+ pmStart: pos + node.nodeSize,
1440
+ pmEnd: pos + node.nodeSize
1441
+ };
1442
+ trackedPush(carrier);
1443
+ }
1444
+ }
1359
1445
  const sectionBreak = {
1360
1446
  kind: "sectionBreak",
1361
1447
  id: nextBlockId()
@@ -1363,6 +1449,8 @@ function toFlowBlocks(doc, options = {}) {
1363
1449
  const breakType = secProps?.sectionStart ?? pmAttrs.sectionBreakType;
1364
1450
  if (breakType) sectionBreak.type = breakType;
1365
1451
  if (secProps) {
1452
+ const documentGridLinePitchTwips = resolveDocumentGridLinePitch(secProps.docGrid);
1453
+ if (documentGridLinePitchTwips !== void 0) sectionBreak.documentGridLinePitchTwips = documentGridLinePitchTwips;
1366
1454
  if (secProps.pageWidth || secProps.pageHeight) sectionBreak.pageSize = {
1367
1455
  w: twipsToPixels(secProps.pageWidth ?? 12240),
1368
1456
  h: twipsToPixels(secProps.pageHeight ?? 15840)
@@ -1401,6 +1489,7 @@ function toFlowBlocks(doc, options = {}) {
1401
1489
  break;
1402
1490
  case "horizontalRule":
1403
1491
  case "pageBreak": {
1492
+ if (consumedPageBreakPositions.has(pos)) break;
1404
1493
  const pb = {
1405
1494
  kind: "pageBreak",
1406
1495
  id: nextBlockId(),
@@ -1419,7 +1508,34 @@ function toFlowBlocks(doc, options = {}) {
1419
1508
  reserveLeadingEmptyOutlineHeight(blocks);
1420
1509
  suppressTerminalEmptyParagraphsAfterTable(blocks);
1421
1510
  suppressFinalParagraphInRepeatedEmptySuffix(blocks);
1422
- return groupParagraphFrames(mergeRunInParagraphs(blocks), nextBlockId);
1511
+ return groupParagraphFrames(applySectionDocumentGrid(mergeRunInParagraphs(blocks), opts.finalSectionDocumentGridLinePitchTwips), nextBlockId);
1512
+ }
1513
+ function applySectionDocumentGrid(blocks, finalLinePitchTwips) {
1514
+ const result = [...blocks];
1515
+ let sectionStart = 0;
1516
+ const stampSection = (end, linePitchTwips) => {
1517
+ if (linePitchTwips === void 0 || linePitchTwips <= 0) return;
1518
+ const linePitch = twipsToPixels(linePitchTwips);
1519
+ for (let index = sectionStart; index < end; index += 1) {
1520
+ const block = result[index];
1521
+ if (block?.kind !== "paragraph") continue;
1522
+ result[index] = {
1523
+ ...block,
1524
+ attrs: {
1525
+ ...block.attrs,
1526
+ documentGridLinePitch: linePitch
1527
+ }
1528
+ };
1529
+ }
1530
+ };
1531
+ for (let index = 0; index < blocks.length; index += 1) {
1532
+ const block = blocks[index];
1533
+ if (block?.kind !== "sectionBreak") continue;
1534
+ stampSection(index, block.documentGridLinePitchTwips);
1535
+ sectionStart = index + 1;
1536
+ }
1537
+ stampSection(blocks.length, finalLinePitchTwips);
1538
+ return result;
1423
1539
  }
1424
1540
  /**
1425
1541
  * Merge consecutive paragraph blocks where the first carries
@@ -0,0 +1,6 @@
1
+ import { NoteStoryKey } from "../../controller/noteEditorManager.js";
2
+
3
+ //#region src/layout-bridge/dom/noteStoryDom.d.ts
4
+ declare const findNoteStoryForTarget: (target: unknown) => NoteStoryKey | null;
5
+ //#endregion
6
+ export { findNoteStoryForTarget };
@@ -0,0 +1,17 @@
1
+ //#region src/layout-bridge/dom/noteStoryDom.ts
2
+ const isClosestNoteTarget = (value) => typeof value === "object" && value !== null && "closest" in value && typeof value.closest === "function";
3
+ const findNoteStoryForTarget = (target) => {
4
+ if (!isClosestNoteTarget(target)) return null;
5
+ const noteTarget = target.closest("[data-note-kind][data-note-id]");
6
+ const kind = noteTarget?.dataset["noteKind"];
7
+ const rawNoteId = noteTarget?.dataset["noteId"] ?? "";
8
+ if (!/^-?\d+$/u.test(rawNoteId)) return null;
9
+ const noteId = Number(rawNoteId);
10
+ if (kind !== "footnote" && kind !== "endnote" || !Number.isSafeInteger(noteId)) return null;
11
+ return {
12
+ kind,
13
+ noteId
14
+ };
15
+ };
16
+ //#endregion
17
+ export { findNoteStoryForTarget };
@@ -72,7 +72,7 @@ function computeLinePmRange(block, line) {
72
72
  if (!run) continue;
73
73
  if (runIndex < line.fromRun) {
74
74
  if (run.kind === "text") charOffset += run.text.length;
75
- else if (run.kind === "tab" || run.kind === "lineBreak") charOffset += 1;
75
+ else if (run.kind === "tab" || run.kind === "lineBreak" || run.kind === "renderedPageBreak") charOffset += 1;
76
76
  else if (run.kind === "image") charOffset += 1;
77
77
  } else if (runIndex === line.fromRun) {
78
78
  charOffset += line.fromChar;
@@ -90,7 +90,7 @@ function computeLinePmRange(block, line) {
90
90
  const start = runIndex === line.fromRun ? line.fromChar : 0;
91
91
  const end = runIndex === line.toRun ? line.toChar : text.length;
92
92
  lineLength += end - start;
93
- } else if (run.kind === "tab" || run.kind === "lineBreak") lineLength += 1;
93
+ } else if (run.kind === "tab" || run.kind === "lineBreak" || run.kind === "renderedPageBreak") lineLength += 1;
94
94
  else if (run.kind === "image") lineLength += 1;
95
95
  }
96
96
  pmEnd = pmStart + lineLength;
@@ -1,4 +1,4 @@
1
- import { 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 } from "./types.js";
1
+ import { 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, RenderedPageBreakRun, 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 } from "./types.js";
2
2
  import { PageState, Paginator, PaginatorOptions, createPaginator } from "./paginator.js";
3
3
  import { KeepNextChain, calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
4
4
  import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
@@ -44,4 +44,4 @@ declare function layoutDocument(blocks: FlowBlock[], measures: Measure[], option
44
44
  */
45
45
  declare function getHeaderRowsHeight(measure: TableMeasure, headerRowCount: number): number;
46
46
  //#endregion
47
- export { BlockId, BorderStyle, type BreakDecision, 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, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, scheduleSectionBreak, tableColumnsArePinned };
47
+ export { BlockId, BorderStyle, type BreakDecision, 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, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, scheduleSectionBreak, tableColumnsArePinned };
@@ -1,9 +1,8 @@
1
1
  import { emuToPixels } from "../utils/units.js";
2
2
  import { measuredLineAdvance } from "./lineFlow.js";
3
3
  import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
4
- import { getParagraphSpacingAfter, getParagraphSpacingBefore } from "./paragraphSpacing.js";
4
+ import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, paragraphsShareStyle, resolveEffectiveParagraphSpacingTree } from "./paragraphSpacing.js";
5
5
  import { calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
6
- import { continuesNumberedSequence } from "./paragraphSequence.js";
7
6
  import { createPaginator } from "./paginator.js";
8
7
  import { getParagraphFragmentPmRange } from "./paragraphFragmentRange.js";
9
8
  import { INITIAL_RENDERED_BREAK_STATE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary } from "./renderedBreakReconciliation.js";
@@ -19,6 +18,7 @@ import { panic } from "better-result";
19
18
  *
20
19
  * Converts blocks + measures into positioned fragments on pages.
21
20
  */
21
+ const RENDERED_BREAK_REFLOW_TOLERANCE_LINES = 3;
22
22
  const DEFAULT_COLUMNS = {
23
23
  count: 1,
24
24
  gap: 0
@@ -58,7 +58,10 @@ function balancedParagraphSectionHeight({ blocks, measures, startIndex, endIndex
58
58
  const measure = measures[index];
59
59
  if (block?.kind !== "paragraph" || measure?.kind !== "paragraph") return;
60
60
  if (block.attrs?.keepNext === true || block.attrs?.keepLines === true || block.runs.some((run) => run.kind === "text" && run.footnoteRefId !== void 0)) return;
61
- const leadingSpacing = Math.max(getParagraphSpacingBefore(block), trailingSpacing);
61
+ const leadingSpacing = collapseParagraphSpacing({
62
+ before: getParagraphSpacingBefore(block),
63
+ after: trailingSpacing
64
+ });
62
65
  if (measure.lines.length === 0) {
63
66
  if (leadingSpacing > 0) {
64
67
  lineUnits.push(leadingSpacing);
@@ -117,7 +120,7 @@ function applyContextualSpacing(blocks) {
117
120
  if (curr.kind !== "paragraph" || next.kind !== "paragraph") continue;
118
121
  const currAttrs = curr.attrs;
119
122
  const nextAttrs = next.attrs;
120
- if (currAttrs?.styleId !== nextAttrs?.styleId) continue;
123
+ if (!paragraphsShareStyle(curr, next)) continue;
121
124
  if (currAttrs?.contextualSpacing && currAttrs.spacing) currAttrs.spacing = {
122
125
  ...currAttrs.spacing,
123
126
  after: 0
@@ -130,24 +133,6 @@ function applyContextualSpacing(blocks) {
130
133
  for (const block of blocks) if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) applyContextualSpacing(cell.blocks);
131
134
  else if (block.kind === "textBox") applyContextualSpacing(block.content);
132
135
  }
133
- /** Suppress automatic inter-item spacing within one numbered sequence. */
134
- const applyAutomaticListSpacing = (blocks) => {
135
- for (let index = 1; index < blocks.length; index++) {
136
- const previous = blocks[index - 1];
137
- const current = blocks[index];
138
- if (previous?.kind !== "paragraph" || current?.kind !== "paragraph" || !continuesNumberedSequence(previous, current)) continue;
139
- if (previous.attrs?.automaticSpacing?.after && previous.attrs.spacing) previous.attrs.spacing = {
140
- ...previous.attrs.spacing,
141
- after: 0
142
- };
143
- if (current.attrs?.automaticSpacing?.before && current.attrs.spacing) current.attrs.spacing = {
144
- ...current.attrs.spacing,
145
- before: 0
146
- };
147
- }
148
- for (const block of blocks) if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) applyAutomaticListSpacing(cell.blocks);
149
- else if (block.kind === "textBox") applyAutomaticListSpacing(block.content);
150
- };
151
136
  /**
152
137
  * Layout a document: convert blocks + measures into pages with positioned fragments.
153
138
  *
@@ -196,8 +181,7 @@ function layoutDocument(blocks, measures, options) {
196
181
  ...options.footnoteReservedHeights !== void 0 ? { footnoteReservedHeights: options.footnoteReservedHeights } : {},
197
182
  ...options.sectionHeaderFooterRefs !== void 0 ? { sectionHeaderFooterRefs: options.sectionHeaderFooterRefs } : {}
198
183
  });
199
- applyContextualSpacing(blocks);
200
- applyAutomaticListSpacing(blocks);
184
+ blocks = resolveEffectiveParagraphSpacingTree(blocks);
201
185
  const keepNextChains = computeKeepNextChains(blocks);
202
186
  const midChainIndices = getMidChainIndices(keepNextChains);
203
187
  const blocksById = /* @__PURE__ */ new Map();
@@ -210,7 +194,9 @@ function layoutDocument(blocks, measures, options) {
210
194
  for (let i = 0; i < blocks.length; i++) {
211
195
  const block = blocks[i];
212
196
  const measure = measures[i];
213
- const renderedBreakNeedsSnap = measure.kind === "paragraph" && !paginator.fits(measure.totalHeight);
197
+ const firstLine = measure.kind === "paragraph" ? measure.lines.at(0) : void 0;
198
+ const firstLineAdvance = firstLine ? measuredLineAdvance(firstLine) : 0;
199
+ const renderedBreakNeedsSnap = measure.kind === "paragraph" && (!paginator.fits(measure.totalHeight) || firstLineAdvance > 0 && paginator.getAvailableHeight() <= firstLineAdvance * RENDERED_BREAK_REFLOW_TOLERANCE_LINES);
214
200
  const hasExplicitPageBreak = hasPageBreakBefore(block);
215
201
  const breakDecision = reconcileBreakBeforeBlock({
216
202
  state: renderedBreakState,
@@ -370,11 +356,20 @@ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeig
370
356
  while (currentLineIndex < lines.length) {
371
357
  const state = paginator.getCurrentState();
372
358
  const availableHeight = paginator.getAvailableHeight();
359
+ const currentLine = lines.at(currentLineIndex);
360
+ const markerAlreadySatisfied = state.columnIndex === 0 && state.cursorY === state.topMargin && state.page.fragments.length === 0;
361
+ if (currentLine?.renderedPageBreakBefore === true && !markerAlreadySatisfied && availableHeight <= measuredLineAdvance(currentLine) * RENDERED_BREAK_REFLOW_TOLERANCE_LINES) {
362
+ paginator.forcePageBreak();
363
+ continue;
364
+ }
373
365
  if (currentLineIndex === 0 && state.trailingSpacing > 0 && state.cursorY !== state.topMargin) {
374
366
  const firstLine = lines[0];
375
367
  const firstLineRefs = getLineFootnoteRefs(block, firstLine.fromRun, firstLine.toRun, footnoteHeightById);
376
368
  const firstLineHeight = measuredLineAdvance(firstLine) + firstLineRefs.height;
377
- const collapsedLead = Math.max(spaceBefore, state.trailingSpacing);
369
+ const collapsedLead = collapseParagraphSpacing({
370
+ before: spaceBefore,
371
+ after: state.trailingSpacing
372
+ });
378
373
  const columnCapacity = state.contentBottom - state.topMargin;
379
374
  if (collapsedLead + firstLineHeight > availableHeight && spaceBefore + firstLineHeight <= columnCapacity) {
380
375
  paginator.ensureFits(collapsedLead + firstLineHeight);
@@ -385,10 +380,18 @@ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeig
385
380
  let linesFnHeight = 0;
386
381
  const linesFnIds = [];
387
382
  let fittingLines = 0;
388
- const firstFragmentSpaceBefore = currentLineIndex === 0 ? Math.max(spaceBefore, state.trailingSpacing) : 0;
383
+ let forcePageBreakAfterFragment = false;
384
+ const firstFragmentSpaceBefore = currentLineIndex === 0 ? collapseParagraphSpacing({
385
+ before: spaceBefore,
386
+ after: state.trailingSpacing
387
+ }) : 0;
389
388
  for (let j = currentLineIndex; j < lines.length; j++) {
390
389
  const line = lines[j];
391
390
  const lineAdvance = measuredLineAdvance(line);
391
+ if (j > currentLineIndex && line.renderedPageBreakBefore === true && availableHeight - (linesHeight + firstFragmentSpaceBefore + linesFnHeight) <= lineAdvance * RENDERED_BREAK_REFLOW_TOLERANCE_LINES) {
392
+ forcePageBreakAfterFragment = true;
393
+ break;
394
+ }
392
395
  const lineRefs = getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById);
393
396
  const totalWithLine = linesHeight + lineAdvance;
394
397
  if (totalWithLine + firstFragmentSpaceBefore + linesFnHeight + lineRefs.height <= availableHeight || fittingLines === 0) {
@@ -447,7 +450,8 @@ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeig
447
450
  fragment.y = paginator.addFragment(fragment, linesHeight, effectiveSpaceBefore, effectiveSpaceAfter).y;
448
451
  if (linesFnHeight > 0) paginator.addFootnoteHeight(linesFnHeight, linesFnIds);
449
452
  currentLineIndex += fittingLines;
450
- if (currentLineIndex < lines.length) if (forceBreakAfterFragment) paginator.forceColumnBreak();
453
+ if (currentLineIndex < lines.length) if (forcePageBreakAfterFragment) paginator.forcePageBreak();
454
+ else if (forceBreakAfterFragment) paginator.forceColumnBreak();
451
455
  else paginator.ensureFits(measuredLineAdvance(lines[currentLineIndex]));
452
456
  }
453
457
  }
@@ -469,6 +473,10 @@ function getHeaderRowsHeight(measure, headerRowCount) {
469
473
  for (let i = 0; i < headerRowCount && i < measure.rows.length; i++) height += measure.rows[i].height;
470
474
  return height;
471
475
  }
476
+ const tableRowStartsWithRenderedPageBreak = (block, rowIndex) => block.rows[rowIndex]?.cells.some((cell) => {
477
+ const firstBlock = cell.blocks.at(0);
478
+ return firstBlock?.kind === "paragraph" && firstBlock.attrs?.renderedPageBreakBefore === true;
479
+ }) ?? false;
472
480
  /**
473
481
  * Layout a table block onto pages.
474
482
  */
@@ -511,6 +519,13 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
511
519
  return oversized || row.height > currentAvailableHeight;
512
520
  };
513
521
  while (currentRowIndex < rows.length) {
522
+ const rowState = paginator.getCurrentState();
523
+ const rowHeaderOverhead = shouldRepeatHeaderRows(currentRowIndex, 0, rowState) ? headerRowsHeight : 0;
524
+ const rowAvailableHeight = paginator.getAvailableHeight() - rowHeaderOverhead - rowState.trailingSpacing;
525
+ if (!(rowState.cursorY === rowState.topMargin && rowState.page.fragments.length === 0) && tableRowStartsWithRenderedPageBreak(block, currentRowIndex) && rows[currentRowIndex].height > rowAvailableHeight) {
526
+ paginator.forcePageBreak();
527
+ continue;
528
+ }
514
529
  const splittableRow = rows[currentRowIndex];
515
530
  if (canSplitRow(currentRowIndex)) {
516
531
  let consumed = 0;
@@ -0,0 +1,6 @@
1
+ import { ParagraphAttrs } from "./types.js";
2
+
3
+ //#region src/layout-engine/justificationCompatibility.d.ts
4
+ declare function resolveJustificationCompatibility(compatibilityMode: number | undefined): NonNullable<ParagraphAttrs["justificationCompatibility"]> | undefined;
5
+ //#endregion
6
+ export { resolveJustificationCompatibility };
@@ -0,0 +1,8 @@
1
+ //#region src/layout-engine/justificationCompatibility.ts
2
+ const LEGACY_JUSTIFICATION_MAX_COMPATIBILITY_MODE = 14;
3
+ function resolveJustificationCompatibility(compatibilityMode) {
4
+ if (compatibilityMode === void 0 || compatibilityMode > LEGACY_JUSTIFICATION_MAX_COMPATIBILITY_MODE) return;
5
+ return { type: "legacy" };
6
+ }
7
+ //#endregion
8
+ export { resolveJustificationCompatibility };
@@ -1,4 +1,11 @@
1
+ import { collapseParagraphSpacing } from "./paragraphSpacing.js";
1
2
  //#region src/layout-engine/keep-together.ts
3
+ /**
4
+ * Keep Together Logic - Handle keepNext and keepLines paragraph properties
5
+ *
6
+ * DOCX paragraphs can have keepNext (keep with next paragraph) and keepLines
7
+ * (keep all lines together) properties that affect pagination.
8
+ */
2
9
  function isVisuallyEmptyParagraph(block) {
3
10
  if (block.runs.length === 0) return true;
4
11
  if (block.runs.length !== 1) return false;
@@ -87,7 +94,10 @@ function calculateChainHeight(chain, blocks, measures) {
87
94
  const successorBlock = blocks[successorIndex];
88
95
  const successorMeasure = measures[successorIndex];
89
96
  if (successorBlock?.kind !== "paragraph" || successorMeasure?.kind !== "paragraph") return totalHeight;
90
- totalHeight += Math.max(trailingSpacing, successorBlock.attrs?.spacing?.before ?? 0);
97
+ totalHeight += collapseParagraphSpacing({
98
+ before: successorBlock.attrs?.spacing?.before ?? 0,
99
+ after: trailingSpacing
100
+ });
91
101
  const firstLine = successorMeasure.lines.at(0);
92
102
  if (!firstLine) return totalHeight;
93
103
  const isAnchor = index === successorIndices.length - 1 && chain.anchorIndex !== -1;
@@ -2,9 +2,16 @@ import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
2
2
  import { lineBreakPolicyCacheParts } from "./effectiveLineBreakPolicy.js";
3
3
  //#region src/layout-engine/measure/cache.ts
4
4
  /**
5
+ * Default max entries for text width cache
6
+ * Large documents (30+ pages) can generate 20,000+ unique text measurements.
7
+ * A generous default avoids cache thrashing on big docs.
8
+ */
9
+ const DEFAULT_TEXT_CACHE_SIZE = 2e4;
10
+ const TEXT_CACHE_REFRESH_AFTER_HITS = 16;
11
+ /**
5
12
  * Current max size for text width cache
6
13
  */
7
- let textCacheMaxSize = 2e4;
14
+ let textCacheMaxSize = DEFAULT_TEXT_CACHE_SIZE;
8
15
  /**
9
16
  * LRU cache for text width measurements
10
17
  * Key format: "text|font|letterSpacing"
@@ -34,8 +41,12 @@ function getCachedTextWidth(text, font, letterSpacing = 0) {
34
41
  const key = makeTextKey(text, font, letterSpacing);
35
42
  const entry = textWidthCache.get(key);
36
43
  if (entry !== void 0) {
37
- textWidthCache.delete(key);
38
- textWidthCache.set(key, entry);
44
+ entry.hitsSinceRefresh += 1;
45
+ if (entry.hitsSinceRefresh >= TEXT_CACHE_REFRESH_AFTER_HITS) {
46
+ entry.hitsSinceRefresh = 0;
47
+ textWidthCache.delete(key);
48
+ textWidthCache.set(key, entry);
49
+ }
39
50
  return entry.width;
40
51
  }
41
52
  }
@@ -44,8 +55,11 @@ function getCachedTextWidth(text, font, letterSpacing = 0) {
44
55
  */
45
56
  function setCachedTextWidth(text, font, letterSpacing, width) {
46
57
  const key = makeTextKey(text, font, letterSpacing);
47
- textWidthCache.set(key, { width });
48
- evictTextEntries();
58
+ textWidthCache.set(key, {
59
+ hitsSinceRefresh: 0,
60
+ width
61
+ });
62
+ if (textWidthCache.size > textCacheMaxSize) evictTextEntries();
49
63
  }
50
64
  /**
51
65
  * Clear the text width cache
@@ -169,6 +183,8 @@ function hashParagraphBlock(block) {
169
183
  if (attrs.defaultFontFamily != null) parts.push(`dff:${attrs.defaultFontFamily}`);
170
184
  if (attrs.suppressEmptyParagraphHeight) parts.push("sup");
171
185
  if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
186
+ if (attrs.documentGridLinePitch !== void 0) parts.push(`documentGrid:${attrs.documentGridLinePitch}|${attrs.snapToGrid !== false}`);
187
+ if (attrs.justificationCompatibility) parts.push(`justify-compat:${attrs.justificationCompatibility.type}`);
172
188
  parts.push(...lineBreakPolicyCacheParts(attrs));
173
189
  const borders = attrs.borders;
174
190
  if (borders) {