@stll/folio-core 0.11.0 → 0.13.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 (263) hide show
  1. package/dist/ai-edits/apply.d.ts +2 -1
  2. package/dist/ai-edits/apply.js +693 -63
  3. package/dist/ai-edits/block-identity.d.ts +4 -0
  4. package/dist/ai-edits/block-identity.js +9 -0
  5. package/dist/ai-edits/blockRange.d.ts +28 -1
  6. package/dist/ai-edits/blockRange.js +68 -2
  7. package/dist/ai-edits/clean-text.js +2 -1
  8. package/dist/ai-edits/headless.d.ts +4 -4
  9. package/dist/ai-edits/headless.js +9 -9
  10. package/dist/ai-edits/index.d.ts +2 -2
  11. package/dist/ai-edits/index.js +2 -2
  12. package/dist/ai-edits/read.d.ts +7 -7
  13. package/dist/ai-edits/read.js +110 -3
  14. package/dist/ai-edits/snapshot.js +21 -3
  15. package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
  16. package/dist/ai-edits/table-cell-mutations.js +238 -0
  17. package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
  18. package/dist/ai-edits/table-mutation-plan.js +90 -0
  19. package/dist/ai-edits/table-row-column-mutations.d.ts +108 -0
  20. package/dist/ai-edits/table-row-column-mutations.js +300 -0
  21. package/dist/ai-edits/table-targets.d.ts +32 -0
  22. package/dist/ai-edits/table-targets.js +70 -0
  23. package/dist/ai-edits/types.d.ts +62 -1
  24. package/dist/ai-edits/word-diff.js +21 -0
  25. package/dist/compat/eigenpal.d.ts +3 -3
  26. package/dist/compat/eigenpal.js +3 -3
  27. package/dist/content-controls/mutateContentControls.js +1 -1
  28. package/dist/controller/activeEditorStory.d.ts +36 -0
  29. package/dist/controller/activeEditorStory.js +30 -0
  30. package/dist/controller/contentControlWidgetController.d.ts +55 -0
  31. package/dist/controller/contentControlWidgetController.js +141 -0
  32. package/dist/controller/fontReadiness.js +2 -0
  33. package/dist/controller/headerFooterEditorManager.js +2 -2
  34. package/dist/controller/hiddenEditorApi.js +2 -2
  35. package/dist/controller/hiddenEditorManager.d.ts +12 -1
  36. package/dist/controller/hiddenEditorManager.js +44 -12
  37. package/dist/controller/layoutPipeline.d.ts +2 -0
  38. package/dist/controller/layoutPipeline.js +17 -2
  39. package/dist/controller/noteEditorManager.d.ts +36 -0
  40. package/dist/controller/noteEditorManager.js +210 -0
  41. package/dist/document-operations.d.ts +43 -11
  42. package/dist/document-operations.js +186 -18
  43. package/dist/docx/blockContentParser.js +21 -7
  44. package/dist/docx/commentParser.js +6 -1
  45. package/dist/docx/commentRangeIntegrity.d.ts +6 -0
  46. package/dist/docx/commentRangeIntegrity.js +186 -0
  47. package/dist/docx/conformance.js +62 -1
  48. package/dist/docx/documentGrid.d.ts +8 -0
  49. package/dist/docx/documentGrid.js +8 -0
  50. package/dist/docx/drawingUtils.js +1 -1
  51. package/dist/docx/encryption/agileDecryption.js +12 -2
  52. package/dist/docx/encryption/compoundFile.js +6 -1
  53. package/dist/docx/encryption/encryptionInfo.d.ts +9 -1
  54. package/dist/docx/encryption/encryptionInfo.js +18 -2
  55. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  56. package/dist/docx/ensureParaIds.js +8 -7
  57. package/dist/docx/fieldParser.js +1 -1
  58. package/dist/docx/groupDrawingParser.js +10 -5
  59. package/dist/docx/hyperlinkParser.js +1 -1
  60. package/dist/docx/imageParser.js +10 -6
  61. package/dist/docx/metadataPrivacy.js +17 -3
  62. package/dist/docx/notePropertiesParser.js +1 -1
  63. package/dist/docx/numberingParser.js +1 -1
  64. package/dist/docx/paragraphParser.js +70 -61
  65. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  66. package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
  67. package/dist/docx/paragraphTraversal.js +1 -1
  68. package/dist/docx/parser.js +3 -3
  69. package/dist/docx/rezip.js +20 -9
  70. package/dist/docx/runParser.js +17 -6
  71. package/dist/docx/sdtProperties.js +1 -1
  72. package/dist/docx/sectionParser.js +18 -5
  73. package/dist/docx/selectiveSave.js +5 -1
  74. package/dist/docx/selectiveXmlPatch.d.ts +22 -1
  75. package/dist/docx/selectiveXmlPatch.js +61 -1
  76. package/dist/docx/serializer/blockSdtSerializer.js +3 -2
  77. package/dist/docx/serializer/commentSerializer.js +5 -6
  78. package/dist/docx/serializer/documentSerializer.js +2 -2
  79. package/dist/docx/serializer/headerFooterSerializer.js +2 -2
  80. package/dist/docx/serializer/noteSerializer.js +2 -2
  81. package/dist/docx/serializer/paragraphSerializer.js +23 -15
  82. package/dist/docx/serializer/runSerializer.js +25 -21
  83. package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -1
  84. package/dist/docx/serializer/stylesSerializer.js +1 -1
  85. package/dist/docx/serializer/tableSerializer.d.ts +5 -4
  86. package/dist/docx/serializer/tableSerializer.js +26 -20
  87. package/dist/docx/serializer/xmlUtils.d.ts +13 -1
  88. package/dist/docx/serializer/xmlUtils.js +27 -1
  89. package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
  90. package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
  91. package/dist/docx/server/boundedArchive.d.ts +14 -3
  92. package/dist/docx/server/boundedArchive.js +56 -11
  93. package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
  94. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
  95. package/dist/docx/server/extractDocxText.js +55 -8
  96. package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
  97. package/dist/docx/server/inspectDocxPackage.js +217 -0
  98. package/dist/docx/server/validateDocxConformance.d.ts +41 -0
  99. package/dist/docx/server/validateDocxConformance.js +336 -0
  100. package/dist/docx/settingsParser.js +26 -3
  101. package/dist/docx/shapeParser.js +1 -1
  102. package/dist/docx/styleParser.js +4 -2
  103. package/dist/docx/tableParser.d.ts +8 -1
  104. package/dist/docx/tableParser.js +31 -10
  105. package/dist/docx/textBoxParser.d.ts +3 -3
  106. package/dist/docx/textBoxParser.js +16 -12
  107. package/dist/docx/unzip.js +1 -0
  108. package/dist/docx/vmlImageParser.js +11 -3
  109. package/dist/docx/xmlParser.js +11 -1
  110. package/dist/docx/xmlSafety.d.ts +5 -0
  111. package/dist/docx/xmlSafety.js +9 -0
  112. package/dist/fields/resolveFieldValues.js +1 -3
  113. package/dist/fonts/embeddedFonts.d.ts +33 -5
  114. package/dist/fonts/embeddedFonts.js +37 -8
  115. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  116. package/dist/i18n/messages/catalogs.gen.js +17 -0
  117. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  118. package/dist/index.d.ts +3 -3
  119. package/dist/index.js +3 -3
  120. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  121. package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
  122. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  123. package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
  124. package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
  125. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
  126. package/dist/layout-bridge/convert/toFlowBlocks.js +151 -20
  127. package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
  128. package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
  129. package/dist/layout-bridge/engine/clickToPosition.js +2 -2
  130. package/dist/layout-engine/index.d.ts +2 -2
  131. package/dist/layout-engine/index.js +58 -30
  132. package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
  133. package/dist/layout-engine/justificationCompatibility.js +8 -0
  134. package/dist/layout-engine/keep-together.js +11 -1
  135. package/dist/layout-engine/measure/cache.js +23 -5
  136. package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +8 -2
  137. package/dist/layout-engine/measure/lineBreakProvider.d.ts +2 -2
  138. package/dist/layout-engine/measure/lineBreakProvider.js +56 -37
  139. package/dist/layout-engine/measure/measureBlocks.js +15 -4
  140. package/dist/layout-engine/measure/measureContainer.js +1 -1
  141. package/dist/layout-engine/measure/measureHelpers.d.ts +11 -2
  142. package/dist/layout-engine/measure/measureHelpers.js +15 -4
  143. package/dist/layout-engine/measure/measureParagraph.js +85 -25
  144. package/dist/layout-engine/measure/measureProvider.js +4 -3
  145. package/dist/layout-engine/measure/tableCellFlow.js +5 -2
  146. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
  147. package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
  148. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
  149. package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
  150. package/dist/layout-engine/paginator.js +5 -1
  151. package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
  152. package/dist/layout-engine/paragraphSpacing.js +107 -13
  153. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  154. package/dist/layout-engine/types.d.ts +48 -15
  155. package/dist/layout-painter/registry/modules/textBox.js +5 -1
  156. package/dist/layout-painter/renderImage.d.ts +12 -1
  157. package/dist/layout-painter/renderImage.js +21 -4
  158. package/dist/layout-painter/renderPage.d.ts +5 -2
  159. package/dist/layout-painter/renderPage.js +59 -14
  160. package/dist/layout-painter/renderParagraph.js +55 -15
  161. package/dist/layout-painter/renderTable.d.ts +5 -1
  162. package/dist/layout-painter/renderTable.js +7 -3
  163. package/dist/layout-painter/renderTextBox.d.ts +2 -1
  164. package/dist/layout-painter/renderTextBox.js +25 -14
  165. package/dist/layout-painter/renderWatermark.d.ts +1 -1
  166. package/dist/layout-painter/renderWatermark.js +2 -1
  167. package/dist/managers/DocumentLoaderManager.js +1 -1
  168. package/dist/markdown/fromMarkdown.js +1 -1
  169. package/dist/model.js +1 -1
  170. package/dist/prosemirror/attrs/index.d.ts +4 -2
  171. package/dist/prosemirror/attrs/index.js +175 -18
  172. package/dist/prosemirror/commands/comments.d.ts +79 -4
  173. package/dist/prosemirror/commands/comments.js +622 -13
  174. package/dist/prosemirror/commands/contentControls.js +1 -1
  175. package/dist/prosemirror/commands/hyperlink.js +15 -1
  176. package/dist/prosemirror/commands/image.js +7 -1
  177. package/dist/prosemirror/commands/index.d.ts +2 -2
  178. package/dist/prosemirror/commands/index.js +2 -2
  179. package/dist/prosemirror/commands/pageBreak.js +1 -1
  180. package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
  181. package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
  182. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
  183. package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
  184. package/dist/prosemirror/commentIdAllocator.js +13 -3
  185. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
  186. package/dist/prosemirror/conversion/fromProseDoc.js +404 -62
  187. package/dist/prosemirror/conversion/index.js +1 -1
  188. package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
  189. package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
  190. package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
  191. package/dist/prosemirror/conversion/toProseDoc.js +364 -103
  192. package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
  193. package/dist/prosemirror/extensions/StarterKit.js +6 -1
  194. package/dist/prosemirror/extensions/core/ParagraphExtension.js +6 -2
  195. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
  196. package/dist/prosemirror/extensions/features/ImagePasteExtension.js +10 -2
  197. package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
  198. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
  199. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
  200. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
  201. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
  202. package/dist/prosemirror/extensions/features/pasteCleanup.js +15 -2
  203. package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
  204. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +30 -4
  205. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
  206. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +70 -11
  207. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  208. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
  209. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
  210. package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
  211. package/dist/prosemirror/extensions/nodes/TableExtension.js +14 -6
  212. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
  213. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
  214. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
  215. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
  216. package/dist/prosemirror/index.d.ts +1 -1
  217. package/dist/prosemirror/index.js +1 -1
  218. package/dist/prosemirror/plugins/contentControlWidgets.d.ts +36 -10
  219. package/dist/prosemirror/plugins/contentControlWidgets.js +128 -94
  220. package/dist/prosemirror/plugins/revisionIds.d.ts +23 -0
  221. package/dist/prosemirror/plugins/revisionIds.js +57 -0
  222. package/dist/prosemirror/plugins/selectionTracker.js +1 -0
  223. package/dist/prosemirror/plugins/suggestionMode.js +5 -4
  224. package/dist/prosemirror/runFormattingMarkNames.d.ts +14 -0
  225. package/dist/prosemirror/runFormattingMarkNames.js +40 -0
  226. package/dist/prosemirror/schema/index.d.ts +2 -2
  227. package/dist/prosemirror/schema/marks.d.ts +30 -3
  228. package/dist/prosemirror/schema/marks.js +15 -0
  229. package/dist/prosemirror/schema/nodes.d.ts +111 -6
  230. package/dist/prosemirror/selectionState.js +1 -0
  231. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  232. package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
  233. package/dist/prosemirror/tableCellMergeRevision.js +22 -0
  234. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
  235. package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
  236. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
  237. package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
  238. package/dist/prosemirror/validation.js +9 -1
  239. package/dist/render-dom/RemoteSelectionOverlay.js +2 -2
  240. package/dist/server.d.ts +7 -3
  241. package/dist/server.js +7 -3
  242. package/dist/utils/clipboard.d.ts +10 -1
  243. package/dist/utils/clipboard.js +33 -6
  244. package/dist/utils/colorResolver.d.ts +8 -1
  245. package/dist/utils/colorResolver.js +35 -5
  246. package/dist/utils/fontFamilyMerge.js +9 -11
  247. package/dist/utils/fontLoader.js +3 -2
  248. package/dist/utils/fontResolver.d.ts +2 -1
  249. package/dist/utils/fontResolver.js +31 -7
  250. package/dist/utils/fontWeights.d.ts +1 -1
  251. package/dist/utils/fontWeights.js +1 -1
  252. package/dist/utils/hexId.d.ts +11 -1
  253. package/dist/utils/hexId.js +11 -1
  254. package/dist/utils/paragraphFormattingMerge.js +1 -0
  255. package/dist/utils/sanitizeImageSrc.d.ts +21 -0
  256. package/dist/utils/sanitizeImageSrc.js +30 -0
  257. package/dist/utils/stripXmlDeclarations.d.ts +11 -0
  258. package/dist/utils/stripXmlDeclarations.js +35 -0
  259. package/dist/version-comparison.d.ts +22 -2
  260. package/dist/version-comparison.js +41 -12
  261. package/dist/watermark/index.d.ts +11 -1
  262. package/dist/watermark/index.js +22 -1
  263. package/package.json +3 -2
@@ -1,3 +1,4 @@
1
+ import { toPaintedText } from "./textMeasurementPolicy.js";
1
2
  import { panic } from "better-result";
2
3
  //#region src/layout-engine/measure/measureProvider.ts
3
4
  /**
@@ -28,8 +29,8 @@ const resetMeasureProvider = () => {
28
29
  activeMeasureProvider = throwingMeasureProvider;
29
30
  };
30
31
  const getFontMetrics = (style) => activeMeasureProvider.getFontMetrics(style);
31
- const measureTextWidth = (text, style) => activeMeasureProvider.measureTextWidth(text, style);
32
- const measureText = (text, style) => activeMeasureProvider.measureText(text, style);
33
- const measureRun = (text, style) => activeMeasureProvider.measureRun(text, style);
32
+ const measureTextWidth = (text, style) => activeMeasureProvider.measureTextWidth(toPaintedText(text), style);
33
+ const measureText = (text, style) => activeMeasureProvider.measureText(toPaintedText(text), style);
34
+ const measureRun = (text, style) => activeMeasureProvider.measureRun(toPaintedText(text), style);
34
35
  //#endregion
35
36
  export { getFontMetrics, getMeasureProvider, measureRun, measureText, measureTextWidth, resetMeasureProvider, setMeasureProvider };
@@ -1,6 +1,6 @@
1
1
  import { measuredLineRangeHeight } from "../lineFlow.js";
2
2
  import { isFloatingTextBoxBlock } from "../types.js";
3
- import { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph } from "../paragraphSpacing.js";
3
+ import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph } from "../paragraphSpacing.js";
4
4
  //#region src/layout-engine/measure/tableCellFlow.ts
5
5
  const createTableCellFlowState = () => ({
6
6
  height: 0,
@@ -57,7 +57,10 @@ const placeTableCellBlock = (state, block, measure) => {
57
57
  }
58
58
  const spacing = paragraphSpacing(block, measure);
59
59
  const empty = isEmptyParagraph(block);
60
- const leadingSpacing = empty && state.previousParagraphWasEmpty ? spacing.before + state.trailingSpacing : Math.max(spacing.before, state.trailingSpacing);
60
+ const leadingSpacing = empty && state.previousParagraphWasEmpty ? spacing.before + state.trailingSpacing : collapseParagraphSpacing({
61
+ before: spacing.before,
62
+ after: state.trailingSpacing
63
+ });
61
64
  const contentTop = top + leadingSpacing;
62
65
  state.height = contentTop + contentHeight;
63
66
  state.previousParagraphWasEmpty = empty;
@@ -1,18 +1,20 @@
1
- import { ParagraphBlock, ParagraphMeasure } from "../types.js";
1
+ import { ParagraphBlock, ParagraphMeasure, TableBlock, TableMeasure } from "../types.js";
2
2
 
3
3
  //#region src/layout-engine/measure/textBoxParagraphLayout.d.ts
4
- type TextBoxParagraphPlacement = {
4
+ type TextBoxContentPlacement = {
5
5
  leadingSpacing: number;
6
6
  contentHeight: number;
7
7
  };
8
- type TextBoxParagraphLayout = {
9
- placements: TextBoxParagraphPlacement[];
8
+ type TextBoxContentLayout = {
9
+ placements: TextBoxContentPlacement[];
10
10
  totalHeight: number;
11
11
  };
12
+ type TextBoxContentBlock = ParagraphBlock | TableBlock;
13
+ type TextBoxContentMeasure = ParagraphMeasure | TableMeasure;
12
14
  /**
13
- * Lay out a text box's paragraph story as one unpaginated flow. Adjacent
15
+ * Lay out a text box's block story as one unpaginated flow. Adjacent paragraph
14
16
  * before/after spacing collapses to the larger value, matching body flow.
15
17
  */
16
- declare function layoutTextBoxParagraphs(blocks: readonly ParagraphBlock[], measures: readonly ParagraphMeasure[]): TextBoxParagraphLayout;
18
+ declare function layoutTextBoxContent(blocks: readonly TextBoxContentBlock[], measures: readonly TextBoxContentMeasure[]): TextBoxContentLayout;
17
19
  //#endregion
18
- export { TextBoxParagraphLayout, TextBoxParagraphPlacement, layoutTextBoxParagraphs };
20
+ export { TextBoxContentLayout, TextBoxContentPlacement, layoutTextBoxContent };
@@ -1,20 +1,36 @@
1
1
  import { measuredLineRangeHeight } from "../lineFlow.js";
2
- import { getParagraphSpacingAfter, getParagraphSpacingBefore } from "../paragraphSpacing.js";
2
+ import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore } from "../paragraphSpacing.js";
3
3
  import { panic } from "better-result";
4
4
  //#region src/layout-engine/measure/textBoxParagraphLayout.ts
5
5
  /**
6
- * Lay out a text box's paragraph story as one unpaginated flow. Adjacent
6
+ * Lay out a text box's block story as one unpaginated flow. Adjacent paragraph
7
7
  * before/after spacing collapses to the larger value, matching body flow.
8
8
  */
9
- function layoutTextBoxParagraphs(blocks, measures) {
10
- if (blocks.length !== measures.length) panic("layoutTextBoxParagraphs: block and measure counts must match");
9
+ function layoutTextBoxContent(blocks, measures) {
10
+ if (blocks.length !== measures.length) panic("layoutTextBoxContent: block and measure counts must match");
11
11
  const placements = [];
12
12
  let totalHeight = 0;
13
13
  let trailingSpacing = 0;
14
14
  for (let index = 0; index < blocks.length; index += 1) {
15
15
  const block = blocks[index];
16
16
  const measure = measures[index];
17
- const leadingSpacing = Math.max(getParagraphSpacingBefore(block), trailingSpacing);
17
+ if (block.kind === "table") {
18
+ if (measure.kind !== "table") panic("layoutTextBoxContent: block and measure kinds must match");
19
+ const leadingSpacing = trailingSpacing;
20
+ const contentHeight = measure.totalHeight;
21
+ placements.push({
22
+ leadingSpacing,
23
+ contentHeight
24
+ });
25
+ totalHeight += leadingSpacing + contentHeight;
26
+ trailingSpacing = 0;
27
+ continue;
28
+ }
29
+ if (measure.kind !== "paragraph") panic("layoutTextBoxContent: block and measure kinds must match");
30
+ const leadingSpacing = collapseParagraphSpacing({
31
+ before: getParagraphSpacingBefore(block),
32
+ after: trailingSpacing
33
+ });
18
34
  const contentHeight = measuredLineRangeHeight(measure.lines, 0, measure.lines.length);
19
35
  placements.push({
20
36
  leadingSpacing,
@@ -29,4 +45,4 @@ function layoutTextBoxParagraphs(blocks, measures) {
29
45
  };
30
46
  }
31
47
  //#endregion
32
- export { layoutTextBoxParagraphs };
48
+ export { layoutTextBoxContent };
@@ -14,5 +14,7 @@ declare function getRunFontKerningMode(run: RunKerningInput, fallbackFontSize: n
14
14
  declare function getFontKerningMode(style: Pick<FontStyle, "kerning">): FontKerningMode;
15
15
  /** Count ordinary ASCII spaces that justification may compress. */
16
16
  declare function countCompressibleSpaces(text: string): number;
17
+ /** Paint OOXML no-break hyphens with the ordinary hyphen glyph. */
18
+ declare function toPaintedText(text: string): string;
17
19
  //#endregion
18
- export { FONT_KERNING_MODE, FontKerningMode, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode };
20
+ export { FONT_KERNING_MODE, FontKerningMode, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode, toPaintedText };
@@ -3,6 +3,8 @@ const FONT_KERNING_MODE = {
3
3
  enabled: "normal",
4
4
  disabled: "none"
5
5
  };
6
+ const NO_BREAK_HYPHEN = "‑";
7
+ const PAINTED_HYPHEN = "-";
6
8
  /** Resolve an authored kerning threshold against the effective run size. */
7
9
  function getRunFontKerningMode(run, fallbackFontSize) {
8
10
  const threshold = run.kerningMinPt;
@@ -20,5 +22,9 @@ function countCompressibleSpaces(text) {
20
22
  for (const char of text) if (char === " ") count += 1;
21
23
  return count;
22
24
  }
25
+ /** Paint OOXML no-break hyphens with the ordinary hyphen glyph. */
26
+ function toPaintedText(text) {
27
+ return text.replaceAll(NO_BREAK_HYPHEN, PAINTED_HYPHEN);
28
+ }
23
29
  //#endregion
24
- export { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode };
30
+ export { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode, toPaintedText };
@@ -1,4 +1,5 @@
1
1
  import { FOOTNOTE_SEPARATOR_HEIGHT } from "./types.js";
2
+ import { collapseParagraphSpacing } from "./paragraphSpacing.js";
2
3
  import { panic } from "better-result";
3
4
  //#region src/layout-engine/paginator.ts
4
5
  /**
@@ -178,7 +179,10 @@ function createPaginator(options) {
178
179
  function addFragment(fragment, height, spaceBefore = 0, spaceAfter = 0) {
179
180
  const initialState = getCurrentState();
180
181
  const initialColumnIndex = initialState.columnIndex;
181
- const effectiveSpaceBefore = Math.max(spaceBefore, initialState.trailingSpacing);
182
+ const effectiveSpaceBefore = collapseParagraphSpacing({
183
+ before: spaceBefore,
184
+ after: initialState.trailingSpacing
185
+ });
182
186
  const state = ensureFits(effectiveSpaceBefore + height);
183
187
  const actualSpaceBefore = state === initialState && state.columnIndex === initialColumnIndex ? effectiveSpaceBefore : spaceBefore;
184
188
  const x = getColumnX(state.columnIndex);
@@ -1,17 +1,41 @@
1
- import { ParagraphBlock } from "./types.js";
1
+ import { FlowBlock, ParagraphBlock } from "./types.js";
2
2
 
3
3
  //#region src/layout-engine/paragraphSpacing.d.ts
4
+ type EffectiveParagraphSpacing = Readonly<{
5
+ before: number;
6
+ after: number;
7
+ }>;
8
+ type CollapseParagraphSpacingOptions = {
9
+ before: number;
10
+ after: number;
11
+ };
12
+ /** Collapse adjacent paragraph spacing to the larger authored side. */
13
+ declare const collapseParagraphSpacing: ({
14
+ before,
15
+ after
16
+ }: CollapseParagraphSpacingOptions) => number;
4
17
  /** Whether a paragraph has no visible run content. */
5
18
  declare function isEmptyParagraph(block: ParagraphBlock): boolean;
6
19
  /**
7
20
  * Resolve leading paragraph spacing after applying the empty-paragraph
8
21
  * inherited-spacing collapse rule.
9
22
  */
10
- declare function getParagraphSpacingBefore(block: ParagraphBlock): number;
23
+ declare const getParagraphSpacingBefore: (block: ParagraphBlock) => number;
11
24
  /**
12
25
  * Resolve trailing paragraph spacing after applying the empty-paragraph
13
26
  * inherited-spacing collapse rule.
14
27
  */
15
- declare function getParagraphSpacingAfter(block: ParagraphBlock): number;
28
+ declare const getParagraphSpacingAfter: (block: ParagraphBlock) => number;
29
+ /** Resolve empty-paragraph spacing without modifying the authored block. */
30
+ declare const resolveEffectiveParagraphSpacing: (block: ParagraphBlock) => EffectiveParagraphSpacing;
31
+ /**
32
+ * Derive sequence-level spacing suppression with structural sharing.
33
+ *
34
+ * Contextual and automatic list spacing affect layout values, not authored
35
+ * OOXML formatting. Only changed branches are cloned; the input tree remains
36
+ * untouched and repeated resolution is idempotent.
37
+ */
38
+ declare const resolveEffectiveParagraphSpacingTree: (blocks: FlowBlock[]) => FlowBlock[];
39
+ declare const paragraphsShareStyle: (previous: ParagraphBlock, current: ParagraphBlock) => boolean;
16
40
  //#endregion
17
- export { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph };
41
+ export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree };
@@ -1,4 +1,7 @@
1
+ import { continuesNumberedSequence } from "./paragraphSequence.js";
1
2
  //#region src/layout-engine/paragraphSpacing.ts
3
+ /** Collapse adjacent paragraph spacing to the larger authored side. */
4
+ const collapseParagraphSpacing = ({ before, after }) => Math.max(before, after);
2
5
  /** Whether a paragraph has no visible run content. */
3
6
  function isEmptyParagraph(block) {
4
7
  if (block.runs.length === 0) return true;
@@ -10,21 +13,112 @@ function isEmptyParagraph(block) {
10
13
  * Resolve leading paragraph spacing after applying the empty-paragraph
11
14
  * inherited-spacing collapse rule.
12
15
  */
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
- }
16
+ const getParagraphSpacingBefore = (block) => resolveEffectiveParagraphSpacing(block).before;
19
17
  /**
20
18
  * Resolve trailing paragraph spacing after applying the empty-paragraph
21
19
  * inherited-spacing collapse rule.
22
20
  */
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
- }
21
+ const getParagraphSpacingAfter = (block) => resolveEffectiveParagraphSpacing(block).after;
22
+ /** Resolve empty-paragraph spacing without modifying the authored block. */
23
+ const resolveEffectiveParagraphSpacing = (block) => {
24
+ const before = block.attrs?.spacing?.before ?? 0;
25
+ const after = block.attrs?.spacing?.after ?? 0;
26
+ if (!isEmptyParagraph(block) || preservesInheritedEmptySpacing(block)) return {
27
+ before,
28
+ after
29
+ };
30
+ return {
31
+ before: block.attrs?.spacingExplicit?.before ? before : 0,
32
+ after: block.attrs?.spacingExplicit?.after ? after : 0
33
+ };
34
+ };
35
+ /**
36
+ * Derive sequence-level spacing suppression with structural sharing.
37
+ *
38
+ * Contextual and automatic list spacing affect layout values, not authored
39
+ * OOXML formatting. Only changed branches are cloned; the input tree remains
40
+ * untouched and repeated resolution is idempotent.
41
+ */
42
+ const resolveEffectiveParagraphSpacingTree = (blocks) => {
43
+ let changed = false;
44
+ const resolved = blocks.map((block, index) => {
45
+ const next = resolveBlockSpacing(blocks, index);
46
+ if (next !== block) changed = true;
47
+ return next;
48
+ });
49
+ return changed ? resolved : blocks;
50
+ };
51
+ const paragraphsShareStyle = (previous, current) => previous.attrs?.styleId === current.attrs?.styleId;
52
+ const preservesInheritedEmptySpacing = (block) => Boolean(block.attrs?.styleId || block.attrs?.hasDirectParagraphFormatting || block.attrs?.hasDirectParagraphMarkFormatting);
53
+ const resolveBlockSpacing = (blocks, index) => {
54
+ const block = blocks[index];
55
+ if (block.kind === "paragraph") return resolveParagraphSequenceSpacing(blocks, index, block);
56
+ if (block.kind === "table") return resolveTableSpacing(block);
57
+ if (block.kind === "textBox") return resolveTextBoxSpacing(block);
58
+ return block;
59
+ };
60
+ const resolveParagraphSequenceSpacing = (blocks, index, block) => {
61
+ const spacing = block.attrs?.spacing;
62
+ if (!spacing) return block;
63
+ const previous = blocks[index - 1];
64
+ const next = blocks[index + 1];
65
+ const suppressBefore = previous?.kind === "paragraph" && (block.attrs?.contextualSpacing === true && paragraphsShareStyle(previous, block) || block.attrs?.automaticSpacing?.before === true && continuesNumberedSequence(previous, block));
66
+ const suppressAfter = next?.kind === "paragraph" && (block.attrs?.contextualSpacing === true && paragraphsShareStyle(block, next) || block.attrs?.automaticSpacing?.after === true && continuesNumberedSequence(block, next));
67
+ const beforeChanged = suppressBefore && spacing.before !== 0;
68
+ const afterChanged = suppressAfter && spacing.after !== 0;
69
+ if (!beforeChanged && !afterChanged) return block;
70
+ const effectiveSpacing = { ...spacing };
71
+ if (beforeChanged) effectiveSpacing.before = 0;
72
+ if (afterChanged) effectiveSpacing.after = 0;
73
+ return {
74
+ ...block,
75
+ attrs: {
76
+ ...block.attrs,
77
+ spacing: effectiveSpacing
78
+ }
79
+ };
80
+ };
81
+ const resolveTextBoxContentSpacing = (blocks) => {
82
+ let changed = false;
83
+ const resolved = blocks.map((block, index) => {
84
+ const next = block.kind === "table" ? resolveTableSpacing(block) : resolveParagraphSequenceSpacing(blocks, index, block);
85
+ if (next !== block) changed = true;
86
+ return next;
87
+ });
88
+ return changed ? resolved : blocks;
89
+ };
90
+ const resolveTableSpacing = (block) => {
91
+ let changed = false;
92
+ const rows = block.rows.map((row) => {
93
+ let rowChanged = false;
94
+ const cells = row.cells.map((cell) => {
95
+ const blocks = resolveEffectiveParagraphSpacingTree(cell.blocks);
96
+ if (blocks === cell.blocks) return cell;
97
+ rowChanged = true;
98
+ return {
99
+ ...cell,
100
+ blocks
101
+ };
102
+ });
103
+ if (!rowChanged) return row;
104
+ changed = true;
105
+ return {
106
+ ...row,
107
+ cells
108
+ };
109
+ });
110
+ return changed ? {
111
+ ...block,
112
+ rows
113
+ } : block;
114
+ };
115
+ const resolveTextBoxSpacing = (block) => {
116
+ const content = resolveTextBoxContentSpacing(block.content);
117
+ if (content === block.content) return block;
118
+ return {
119
+ ...block,
120
+ content
121
+ };
122
+ };
29
123
  //#endregion
30
- export { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph };
124
+ export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree };
@@ -21,7 +21,7 @@ const reconcileBreakBeforeBlock = ({ state, block, previousBlock, page, blocksBy
21
21
  state
22
22
  };
23
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);
24
+ const forcePageBreak = (renderedBreakNeedsSnap || block.attrs?.keepNext === true || (block.attrs?.spacing?.before ?? 0) > 0 && previousBlock?.kind !== "sectionBreak" || previousBlock?.kind === "table") && !markerAlreadySatisfied && pageHasVisibleBodyContent(page, blocksById);
25
25
  const followsAuthoredPageBreak = previousBlock?.kind === "pageBreak";
26
26
  const followsSectionBreak = previousBlock?.kind === "sectionBreak";
27
27
  const followsPageBreakingSection = followsSectionBreak && previousBlock.type !== "continuous";
@@ -3,7 +3,7 @@ import { ImagePosition, ImageWrap, SdtProperties, SdtType, ShapeTextBody, TableC
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, getTableRowLeadingWidth, 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, 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 };
7
7
  }
8
8
  /**
9
9
  * Unique identifier for a block in the document.
@@ -87,6 +87,14 @@ type RunFormatting = {
87
87
  changeAuthor?: string; /** Date of the tracked change */
88
88
  changeDate?: string; /** Revision ID of the tracked change (for sidebar matching) */
89
89
  changeRevisionId?: number;
90
+ /**
91
+ * Whether this tracked insertion/deletion is an AI-proposed *suggestion*
92
+ * rather than a user change. Suggestions render with the same grammar but a
93
+ * dotted stroke and a dedicated hue; they are stripped from serialized DOCX
94
+ * until accepted.
95
+ */
96
+ isSuggestion?: boolean; /** Groups a suggestion's runs for accept/reject and scroll-to. */
97
+ suggestionId?: string;
90
98
  };
91
99
  /**
92
100
  * Hyperlink information for a run.
@@ -179,7 +187,10 @@ type ImageRun = {
179
187
  cropTop?: number;
180
188
  cropRight?: number;
181
189
  cropBottom?: number;
182
- cropLeft?: number; /** Whether this picture is itself a tracked insertion (`<w:ins>`). eigenpal #641. */
190
+ cropLeft?: number; /** CSS border width in pixels. eigenpal #1096. */
191
+ borderWidth?: number; /** CSS border color. eigenpal #1096. */
192
+ borderColor?: string; /** CSS border style. eigenpal #1096. */
193
+ borderStyle?: string; /** Whether this picture is itself a tracked insertion (`<w:ins>`). eigenpal #641. */
183
194
  isInsertion?: boolean; /** Whether this picture is itself a tracked deletion (`<w:del>`). eigenpal #641. */
184
195
  isDeletion?: boolean; /** Author of the tracked change wrapping the picture. eigenpal #641. */
185
196
  changeAuthor?: string; /** Date of the tracked change wrapping the picture. eigenpal #641. */
@@ -196,6 +207,12 @@ type LineBreakRun = {
196
207
  pmStart?: number;
197
208
  pmEnd?: number;
198
209
  };
210
+ /** Zero-width cached page boundary inside a paragraph. */
211
+ type RenderedPageBreakRun = RunFormatting & {
212
+ kind: "renderedPageBreak";
213
+ pmStart?: number;
214
+ pmEnd?: number;
215
+ };
199
216
  /**
200
217
  * A field run (PAGE, NUMPAGES, etc.) that gets substituted at render time.
201
218
  */
@@ -230,7 +247,7 @@ type MathRun = RunFormatting & {
230
247
  /**
231
248
  * Union of all run types.
232
249
  */
233
- type Run = TextRun | TabRun | ImageRun | LineBreakRun | FieldRun | MathRun;
250
+ type Run = TextRun | TabRun | ImageRun | LineBreakRun | RenderedPageBreakRun | FieldRun | MathRun;
234
251
  /**
235
252
  * Paragraph spacing configuration.
236
253
  */
@@ -293,7 +310,10 @@ type ListNumPr = {
293
310
  * Paragraph block attributes.
294
311
  */
295
312
  type ParagraphAttrs = {
296
- alignment?: "left" | "center" | "right" | "justify"; /** East Asian first/last-character restrictions (`w:kinsoku`). */
313
+ alignment?: "left" | "center" | "right" | "justify"; /** Document-generation policy for justified line fitting. */
314
+ justificationCompatibility?: {
315
+ type: "legacy";
316
+ }; /** East Asian first/last-character restrictions (`w:kinsoku`). */
297
317
  kinsoku?: boolean; /** Hanging punctuation (`w:overflowPunct`); defaults to enabled when omitted. */
298
318
  overflowPunctuation?: boolean; /** Exempt this paragraph from document automatic hyphenation. */
299
319
  suppressAutoHyphens?: boolean; /** Document-wide automatic hyphenation controls retained for line layout. */
@@ -315,7 +335,9 @@ type ParagraphAttrs = {
315
335
  useLegacyEthiopicAmharicRules?: boolean;
316
336
  }; /** OOXML outline level (`w:outlineLvl`), where zero is the top level. */
317
337
  outlineLevel?: number;
318
- spacing?: ParagraphSpacing; /** Spacing sides resolved from OOXML automatic paragraph spacing. */
338
+ spacing?: ParagraphSpacing; /** Whether this paragraph participates in the active section line grid. */
339
+ snapToGrid?: boolean; /** Active section line pitch in pixels; present only on top-level body paragraphs. */
340
+ documentGridLinePitch?: number; /** Spacing sides resolved from OOXML automatic paragraph spacing. */
319
341
  automaticSpacing?: {
320
342
  before?: boolean;
321
343
  after?: boolean;
@@ -343,7 +365,7 @@ type ParagraphAttrs = {
343
365
  keepNext?: boolean;
344
366
  keepLines?: boolean;
345
367
  widowControl?: boolean;
346
- pageBreakBefore?: boolean; /** Word's cached pagination hint (`w:lastRenderedPageBreak`). */
368
+ pageBreakBefore?: boolean; /** Cached OOXML pagination hint (`w:lastRenderedPageBreak`). */
347
369
  renderedPageBreakBefore?: boolean;
348
370
  styleId?: string;
349
371
  contextualSpacing?: boolean;
@@ -366,6 +388,12 @@ type ParagraphAttrs = {
366
388
  listMarkerHidden?: boolean;
367
389
  listMarkerFontFamily?: string;
368
390
  listMarkerFontSize?: number;
391
+ /**
392
+ * Effective paragraph-mark size used only to measure a visible list
393
+ * paragraph's final line. Word lets the inherited complex-script size raise
394
+ * that line box without enlarging the marker or Western text glyphs.
395
+ */
396
+ listParagraphMarkFontSize?: number;
369
397
  listMarkerBold?: boolean; /** Horizontal alignment of the marker around the paragraph's list anchor. */
370
398
  listMarkerAlignment?: "left" | "center" | "right";
371
399
  /**
@@ -555,7 +583,10 @@ type ImageBlock = {
555
583
  cropTop?: number;
556
584
  cropRight?: number;
557
585
  cropBottom?: number;
558
- cropLeft?: number;
586
+ cropLeft?: number; /** CSS border width in pixels. eigenpal #1096. */
587
+ borderWidth?: number; /** CSS border color. eigenpal #1096. */
588
+ borderColor?: string; /** CSS border style. eigenpal #1096. */
589
+ borderStyle?: string;
559
590
  pmStart?: number;
560
591
  pmEnd?: number;
561
592
  };
@@ -572,7 +603,8 @@ type SectionBreakBlock = {
572
603
  };
573
604
  orientation?: "portrait" | "landscape";
574
605
  margins?: PageMargins;
575
- columns?: ColumnLayout;
606
+ columns?: ColumnLayout; /** Line pitch authored by the section properties that end at this boundary, in twips. */
607
+ documentGridLinePitchTwips?: number;
576
608
  };
577
609
  type PageHeaderFooterRefs = {
578
610
  titlePg?: boolean;
@@ -612,7 +644,7 @@ declare const DEFAULT_TEXTBOX_MARGINS: {
612
644
  /** Default text box width in pixels when no width is specified */
613
645
  declare const DEFAULT_TEXTBOX_WIDTH = 200;
614
646
  /**
615
- * Text box block — positioned container with paragraph content.
647
+ * Text box block — positioned container with block content.
616
648
  */
617
649
  type TextBoxBlock = {
618
650
  kind: "textBox";
@@ -629,8 +661,8 @@ type TextBoxBlock = {
629
661
  bottom: number;
630
662
  left: number;
631
663
  right: number;
632
- }; /** Paragraph blocks inside the text box */
633
- content: ParagraphBlock[]; /** Display mode copied from the ProseMirror text box node. */
664
+ }; /** Flow blocks inside the text box */
665
+ content: (ParagraphBlock | TableBlock)[]; /** Display mode copied from the ProseMirror text box node. */
634
666
  displayMode?: "inline" | "float" | "block"; /** CSS float direction copied from the ProseMirror text box node. */
635
667
  cssFloat?: "left" | "right" | "none"; /** OOXML wrap type for anchored text boxes. */
636
668
  wrapType?: ImageWrap["type"]; /** OOXML wrapText direction for anchored text boxes. */
@@ -704,7 +736,8 @@ type MeasuredLine = {
704
736
  floatSkipBefore?: number; /** Decorative hyphen inserted at a dictionary break without changing source text. */
705
737
  discretionaryHyphen?: {
706
738
  runIndex: number;
707
- };
739
+ }; /** A cached pagination boundary occurs immediately before this line. */
740
+ renderedPageBreakBefore?: boolean;
708
741
  };
709
742
  /**
710
743
  * Measurement result for a paragraph block.
@@ -782,8 +815,8 @@ type ColumnBreakMeasure = {
782
815
  type TextBoxMeasure = {
783
816
  kind: "textBox";
784
817
  width: number;
785
- height: number; /** Pre-measured inner paragraph measures (avoids re-measuring during render) */
786
- innerMeasures: ParagraphMeasure[];
818
+ height: number; /** Pre-measured inner content (avoids re-measuring during render) */
819
+ innerMeasures: (ParagraphMeasure | TableMeasure)[];
787
820
  };
788
821
  /**
789
822
  * Union of all measurement types.
@@ -1112,4 +1145,4 @@ declare function tableColumnsArePinned(table: TableBlock): boolean;
1112
1145
  /** Return the leading visual offset for a row with omitted grid columns. */
1113
1146
  declare const getTableRowLeadingWidth: (row: TableRow, columnWidths: readonly number[]) => number;
1114
1147
  //#endregion
1115
- 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 };
1148
+ 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, 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, types_d_exports };
@@ -1,4 +1,5 @@
1
1
  import { renderTextBoxFragment } from "../../renderTextBox.js";
2
+ import { renderNestedTable } from "../../renderTable.js";
2
3
  //#region src/layout-painter/registry/modules/textBox.ts
3
4
  /**
4
5
  * Text-box feature module — renders `TextBoxFragment`.
@@ -6,7 +7,10 @@ import { renderTextBoxFragment } from "../../renderTextBox.js";
6
7
  const textBoxModule = {
7
8
  kind: "textBox",
8
9
  render({ fragment, block, measure, context, doc }) {
9
- return renderTextBoxFragment(fragment, block, measure, context, { document: doc });
10
+ return renderTextBoxFragment(fragment, block, measure, context, {
11
+ document: doc,
12
+ renderTable: renderNestedTable
13
+ });
10
14
  }
11
15
  };
12
16
  //#endregion
@@ -21,6 +21,17 @@ type ImageVisualAttrs = {
21
21
  cropBottom?: number;
22
22
  cropLeft?: number;
23
23
  };
24
+ type ImageBorderAttrs = {
25
+ borderWidth?: number;
26
+ borderColor?: string;
27
+ borderStyle?: string;
28
+ };
29
+ /**
30
+ * Paint image borders carried through the layout model. Folio's PM schema
31
+ * uses `borderStyle` (not upstream's `borderKind`); the painter mirrors the
32
+ * editor DOM serialization while keeping the authored image box size stable.
33
+ */
34
+ declare function applyImageBorder(element: HTMLElement, border: ImageBorderAttrs): void;
24
35
  /**
25
36
  * True when any visual attribute is set. Cheap call-site guard so the no-op
26
37
  * common case skips the helper call.
@@ -86,4 +97,4 @@ type RenderImageFragmentOptions = {
86
97
  */
87
98
  declare function renderImageFragment(fragment: ImageFragment, block: ImageBlock, _measure: ImageMeasure, _context: RenderContext, options?: RenderImageFragmentOptions): HTMLElement;
88
99
  //#endregion
89
- export { IMAGE_CLASS_NAMES, ImageVisualAttrs, RenderImageFragmentOptions, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
100
+ export { IMAGE_CLASS_NAMES, ImageBorderAttrs, ImageVisualAttrs, RenderImageFragmentOptions, applyImageBorder, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
@@ -1,3 +1,5 @@
1
+ import { sanitizeExternalUrl } from "../utils/urlSecurity.js";
2
+ import { applySanitizedImageSrc } from "../utils/sanitizeImageSrc.js";
1
3
  //#region src/layout-painter/renderImage.ts
2
4
  /**
3
5
  * CSS class names for image elements
@@ -7,6 +9,18 @@ const IMAGE_CLASS_NAMES = {
7
9
  imageAnchored: "layout-image-anchored"
8
10
  };
9
11
  /**
12
+ * Paint image borders carried through the layout model. Folio's PM schema
13
+ * uses `borderStyle` (not upstream's `borderKind`); the painter mirrors the
14
+ * editor DOM serialization while keeping the authored image box size stable.
15
+ */
16
+ function applyImageBorder(element, border) {
17
+ if (border.borderWidth == null || border.borderWidth <= 0) return;
18
+ const borderStyle = border.borderStyle || "solid";
19
+ const borderColor = border.borderColor || "#000000";
20
+ element.style.border = `${border.borderWidth}px ${borderStyle} ${borderColor}`;
21
+ element.style.boxSizing = "border-box";
22
+ }
23
+ /**
10
24
  * True when any visual attribute is set. Cheap call-site guard so the no-op
11
25
  * common case skips the helper call.
12
26
  *
@@ -118,7 +132,7 @@ function renderImageFragment(fragment, block, _measure, _context, options = {})
118
132
  if (fragment.pmStart !== void 0) containerEl.dataset["pmStart"] = String(fragment.pmStart);
119
133
  if (fragment.pmEnd !== void 0) containerEl.dataset["pmEnd"] = String(fragment.pmEnd);
120
134
  const imgEl = doc.createElement("img");
121
- imgEl.src = block.src;
135
+ applySanitizedImageSrc(imgEl, block.src);
122
136
  imgEl.alt = block.alt ?? "";
123
137
  imgEl.style.width = "100%";
124
138
  imgEl.style.height = "100%";
@@ -127,9 +141,10 @@ function renderImageFragment(fragment, block, _measure, _context, options = {})
127
141
  if (block.transform) imgEl.style.transform = block.transform;
128
142
  if (hasImageVisualAttrs(block)) applyImageVisualAttrs(imgEl, block);
129
143
  imgEl.draggable = false;
130
- if (block.hlinkHref) {
144
+ const hlinkHref = sanitizeExternalUrl(block.hlinkHref);
145
+ if (hlinkHref) {
131
146
  const linkEl = doc.createElement("a");
132
- linkEl.href = block.hlinkHref;
147
+ linkEl.href = hlinkHref;
133
148
  linkEl.target = "_blank";
134
149
  linkEl.rel = "noopener noreferrer";
135
150
  linkEl.style.display = "block";
@@ -138,7 +153,9 @@ function renderImageFragment(fragment, block, _measure, _context, options = {})
138
153
  linkEl.append(imgEl);
139
154
  containerEl.append(linkEl);
140
155
  } else containerEl.append(imgEl);
156
+ if (hasImageCrop(block)) applyImageBorder(containerEl, block);
157
+ else applyImageBorder(imgEl, block);
141
158
  return containerEl;
142
159
  }
143
160
  //#endregion
144
- export { IMAGE_CLASS_NAMES, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
161
+ export { IMAGE_CLASS_NAMES, applyImageBorder, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };