@stll/folio-core 0.32.2 → 0.33.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 (222) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
  2. package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
  3. package/dist/ai-edits/apply.d.ts +49 -3
  4. package/dist/ai-edits/apply.js +649 -85
  5. package/dist/ai-edits/blockRange.d.ts +7 -3
  6. package/dist/ai-edits/blockRange.js +14 -6
  7. package/dist/ai-edits/headless.d.ts +39 -5
  8. package/dist/ai-edits/headless.js +76 -17
  9. package/dist/ai-edits/index.d.ts +6 -6
  10. package/dist/ai-edits/index.js +3 -3
  11. package/dist/ai-edits/read.d.ts +4 -0
  12. package/dist/ai-edits/read.js +30 -6
  13. package/dist/ai-edits/scoped-reading.js +4 -3
  14. package/dist/ai-edits/snapshot.d.ts +42 -2
  15. package/dist/ai-edits/snapshot.js +127 -62
  16. package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
  17. package/dist/ai-edits/table-row-column-mutations.js +98 -6
  18. package/dist/ai-edits/table-targets.d.ts +12 -3
  19. package/dist/ai-edits/table-targets.js +15 -5
  20. package/dist/ai-edits/types.d.ts +219 -15
  21. package/dist/ai-edits/word-diff.d.ts +54 -11
  22. package/dist/ai-edits/word-diff.js +221 -61
  23. package/dist/compare/__fixtures__/body-sequence.d.ts +33 -0
  24. package/dist/compare/__fixtures__/body-sequence.js +67 -0
  25. package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
  26. package/dist/compare/__fixtures__/nested-table.js +50 -0
  27. package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
  28. package/dist/compare/__fixtures__/numbered-list.js +85 -0
  29. package/dist/compare/column-alignment.d.ts +27 -0
  30. package/dist/compare/column-alignment.js +162 -0
  31. package/dist/compare/compare.d.ts +107 -0
  32. package/dist/compare/compare.js +406 -0
  33. package/dist/compare/formatting.d.ts +29 -0
  34. package/dist/compare/formatting.js +84 -0
  35. package/dist/compare/plan.d.ts +22 -0
  36. package/dist/compare/plan.js +1059 -0
  37. package/dist/compare/reproducible-package.d.ts +17 -0
  38. package/dist/compare/reproducible-package.js +30 -0
  39. package/dist/compare/scenario.d.ts +77 -0
  40. package/dist/compare/scenario.js +263 -0
  41. package/dist/compare/types.d.ts +282 -0
  42. package/dist/compare/types.js +32 -0
  43. package/dist/compare/verification.d.ts +55 -0
  44. package/dist/compare/verification.js +146 -0
  45. package/dist/compat/eigenpal.d.ts +9 -6
  46. package/dist/compat/eigenpal.js +6 -3
  47. package/dist/controller/layoutPipeline.d.ts +13 -0
  48. package/dist/controller/layoutPipeline.js +15 -1
  49. package/dist/display-list/build/buildContext.d.ts +39 -0
  50. package/dist/display-list/build/buildContext.js +31 -0
  51. package/dist/display-list/build/buildDisplayList.d.ts +79 -0
  52. package/dist/display-list/build/buildDisplayList.js +283 -0
  53. package/dist/display-list/build/colors.d.ts +20 -0
  54. package/dist/display-list/build/colors.js +136 -0
  55. package/dist/display-list/build/floatingImages.d.ts +24 -0
  56. package/dist/display-list/build/floatingImages.js +41 -0
  57. package/dist/display-list/build/fontTable.d.ts +31 -0
  58. package/dist/display-list/build/fontTable.js +97 -0
  59. package/dist/display-list/build/furniture.d.ts +86 -0
  60. package/dist/display-list/build/furniture.js +98 -0
  61. package/dist/display-list/build/glyphs.d.ts +55 -0
  62. package/dist/display-list/build/glyphs.js +103 -0
  63. package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
  64. package/dist/display-list/build/headerFooterPrimitives.js +40 -0
  65. package/dist/display-list/build/imagePrimitives.d.ts +35 -0
  66. package/dist/display-list/build/imagePrimitives.js +264 -0
  67. package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
  68. package/dist/display-list/build/pageBorderPrimitives.js +111 -0
  69. package/dist/display-list/build/pageFurniture.d.ts +35 -0
  70. package/dist/display-list/build/pageFurniture.js +141 -0
  71. package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
  72. package/dist/display-list/build/paragraphPrimitives.js +819 -0
  73. package/dist/display-list/build/regions.d.ts +46 -0
  74. package/dist/display-list/build/regions.js +56 -0
  75. package/dist/display-list/build/storyPrimitives.d.ts +29 -0
  76. package/dist/display-list/build/storyPrimitives.js +279 -0
  77. package/dist/display-list/build/strokes.d.ts +19 -0
  78. package/dist/display-list/build/strokes.js +78 -0
  79. package/dist/display-list/build/tablePrimitives.d.ts +38 -0
  80. package/dist/display-list/build/tablePrimitives.js +409 -0
  81. package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
  82. package/dist/display-list/build/textBoxPrimitives.js +129 -0
  83. package/dist/display-list/build/textDecorations.d.ts +29 -0
  84. package/dist/display-list/build/textDecorations.js +29 -0
  85. package/dist/display-list/build/unsupported.d.ts +57 -0
  86. package/dist/display-list/build/unsupported.js +0 -0
  87. package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
  88. package/dist/display-list/build/watermarkPrimitives.js +128 -0
  89. package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
  90. package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
  91. package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
  92. package/dist/display-list/editor/displayListPagePainter.js +63 -0
  93. package/dist/display-list/editor/pageRenderer.d.ts +24 -0
  94. package/dist/display-list/editor/pageRenderer.js +36 -0
  95. package/dist/display-list/primitives.d.ts +95 -0
  96. package/dist/display-list/primitives.js +127 -0
  97. package/dist/display-list/types.d.ts +472 -0
  98. package/dist/display-list/types.js +0 -0
  99. package/dist/document-operations.d.ts +39 -8
  100. package/dist/document-operations.js +169 -16
  101. package/dist/document-stories.d.ts +16 -0
  102. package/dist/document-stories.js +48 -4
  103. package/dist/docx/packageParts.d.ts +35 -0
  104. package/dist/docx/packageParts.js +120 -0
  105. package/dist/docx/paragraphParser.js +22 -22
  106. package/dist/docx/paragraphTraversal.js +1 -1
  107. package/dist/docx/renderedPageBreakNormalization.js +3 -1
  108. package/dist/docx/revisionIdNormalization.d.ts +13 -0
  109. package/dist/docx/revisionIdNormalization.js +103 -0
  110. package/dist/docx/rezip.js +26 -17
  111. package/dist/docx/selectiveSave.js +2 -2
  112. package/dist/docx/serializer/commentSerializer.js +6 -1
  113. package/dist/docx/serializer/paragraphSerializer.js +33 -4
  114. package/dist/docx/serializer/runSerializer.js +11 -10
  115. package/dist/docx/serializer/tableSerializer.js +2 -1
  116. package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
  117. package/dist/docx/server/createBilingualDocx.js +3 -2
  118. package/dist/docx/streamingXmlParser.d.ts +13 -1
  119. package/dist/docx/streamingXmlParser.js +49 -11
  120. package/dist/docx/unzip.d.ts +1 -2
  121. package/dist/docx/unzip.js +25 -6
  122. package/dist/docx/xmlParser.d.ts +3 -1
  123. package/dist/docx/xmlParser.js +33 -27
  124. package/dist/export-pdf.d.ts +43 -0
  125. package/dist/export-pdf.js +98 -0
  126. package/dist/fonts/headlessMeasure.d.ts +76 -0
  127. package/dist/fonts/headlessMeasure.js +0 -0
  128. package/dist/fonts/sfnt/parse.d.ts +51 -0
  129. package/dist/fonts/sfnt/parse.js +525 -0
  130. package/dist/fonts/sfnt/subset.d.ts +20 -0
  131. package/dist/fonts/sfnt/subset.js +350 -0
  132. package/dist/fonts/sfnt/tables.d.ts +88 -0
  133. package/dist/fonts/sfnt/tables.js +131 -0
  134. package/dist/fonts/sfnt/woff.d.ts +14 -0
  135. package/dist/fonts/sfnt/woff.js +163 -0
  136. package/dist/generated/text_shaper.js +324 -0
  137. package/dist/generated/text_shaper_bg.wasm +0 -0
  138. package/dist/headless-layout.d.ts +75 -0
  139. package/dist/headless-layout.js +350 -0
  140. package/dist/index.d.ts +9 -6
  141. package/dist/index.js +6 -3
  142. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  143. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  144. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  145. package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
  146. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
  147. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
  148. package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
  149. package/dist/layout-engine/measure/advanceComposition.js +148 -0
  150. package/dist/layout-engine/measure/cache.d.ts +2 -0
  151. package/dist/layout-engine/measure/measureContainer.js +13 -0
  152. package/dist/layout-engine/measure/measureParagraph.js +1 -2
  153. package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
  154. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
  155. package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
  156. package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
  157. package/dist/layout-engine/tableIndentCompatibility.js +15 -0
  158. package/dist/layout-engine/types.d.ts +8 -0
  159. package/dist/layout-painter/renderPage.d.ts +29 -1
  160. package/dist/layout-painter/renderPage.js +47 -19
  161. package/dist/layout-painter/renderParagraph.d.ts +21 -2
  162. package/dist/layout-painter/renderParagraph.js +1 -1
  163. package/dist/layout-painter/renderTable.js +37 -4
  164. package/dist/managers/editorShortcuts.d.ts +33 -1
  165. package/dist/managers/editorShortcuts.js +20 -1
  166. package/dist/markdown/renderRuns.js +7 -2
  167. package/dist/markdown/renderTable.js +5 -1
  168. package/dist/model.d.ts +3 -3
  169. package/dist/model.js +2 -2
  170. package/dist/pdf/contentStream.d.ts +73 -0
  171. package/dist/pdf/contentStream.js +143 -0
  172. package/dist/pdf/fonts.d.ts +96 -0
  173. package/dist/pdf/fonts.js +593 -0
  174. package/dist/pdf/images.d.ts +28 -0
  175. package/dist/pdf/images.js +439 -0
  176. package/dist/pdf/objects.d.ts +93 -0
  177. package/dist/pdf/objects.js +237 -0
  178. package/dist/pdf/pageSpace.d.ts +43 -0
  179. package/dist/pdf/pageSpace.js +57 -0
  180. package/dist/pdf/paint.d.ts +32 -0
  181. package/dist/pdf/paint.js +442 -0
  182. package/dist/pdf/writePdf.d.ts +56 -0
  183. package/dist/pdf/writePdf.js +345 -0
  184. package/dist/prosemirror/commands/comments.js +91 -6
  185. package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
  186. package/dist/prosemirror/conversion/toProseDoc.js +41 -4
  187. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
  188. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
  189. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
  190. package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
  191. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
  192. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
  193. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
  194. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
  195. package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
  196. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
  197. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
  198. package/dist/prosemirror/plugins/suggestionMode.js +1 -0
  199. package/dist/prosemirror/revisionCarriers.js +19 -2
  200. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
  201. package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
  202. package/dist/prosemirror/validation.js +55 -27
  203. package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
  204. package/dist/prosemirror/zeroWidthAnchors.js +23 -0
  205. package/dist/redline.js +12 -79
  206. package/dist/server.d.ts +3 -3
  207. package/dist/server.js +2 -2
  208. package/dist/shaping/placeRun.d.ts +51 -0
  209. package/dist/shaping/placeRun.js +84 -0
  210. package/dist/shaping/shaper.d.ts +92 -0
  211. package/dist/shaping/shaper.js +102 -0
  212. package/dist/types/block-id.d.ts +18 -2
  213. package/dist/types/block-id.js +49 -5
  214. package/dist/types/content.d.ts +2 -2
  215. package/dist/utils/clipboard.js +4 -3
  216. package/dist/utils/fontResolver.js +183 -0
  217. package/dist/utils/htmlComments.d.ts +17 -0
  218. package/dist/utils/htmlComments.js +22 -0
  219. package/dist/utils/scriptSegments.d.ts +12 -1
  220. package/dist/utils/scriptSegments.js +17 -1
  221. package/dist/version-comparison.d.ts +1 -1
  222. package/package.json +7 -3
@@ -0,0 +1,148 @@
1
+ import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
2
+ import { SCRIPT_CLASS, hasComplexScript, scriptClassOf, segmentByScript } from "../../utils/scriptSegments.js";
3
+ import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./complexScriptFormatting.js";
4
+ //#region src/layout-engine/measure/advanceComposition.ts
5
+ /**
6
+ * The arithmetic that turns raw glyph advances into measured widths.
7
+ *
8
+ * Text transform, per-script font selection, letter spacing and horizontal
9
+ * scale are the same calculation whoever supplies the advances: a canvas, a
10
+ * parsed font, a shaper. Only the advance source differs. Keeping the
11
+ * calculation here and parameterizing the source means a second measurement
12
+ * backend can disagree with the canvas one about a glyph's width (which the
13
+ * differential tests measure) but cannot disagree about where letter spacing
14
+ * goes or which font a CJK code point takes.
15
+ *
16
+ * Pure: no canvas, no DOM, no caching.
17
+ */
18
+ const applyMeasurementTextTransform = (text, style) => style.textTransform === "uppercase" ? text.toLocaleUpperCase() : text;
19
+ const countCodePoints = (text) => [...text].length;
20
+ /** Resolve the independent font/style slot a script segment selects. */
21
+ const scriptStyle = (style, script) => {
22
+ if (script === SCRIPT_CLASS.eastAsia) {
23
+ if (style.eastAsiaFontFamily === void 0) return style;
24
+ const result = {
25
+ ...style,
26
+ fontFamily: style.eastAsiaFontFamily
27
+ };
28
+ delete result.alternateFontFamily;
29
+ if (style.eastAsiaAlternateFontFamily !== void 0) result.alternateFontFamily = style.eastAsiaAlternateFontFamily;
30
+ return result;
31
+ }
32
+ if (script === SCRIPT_CLASS.complex) return applyComplexScriptFormatting(style, style);
33
+ return style;
34
+ };
35
+ /**
36
+ * Keep only the properties that change a glyph's advance, so a per-script
37
+ * segment takes the single-font path: letter spacing and horizontal scale are
38
+ * applied once over the whole string, never per segment.
39
+ */
40
+ const glyphAdvanceStyle = (style) => {
41
+ const result = {};
42
+ if (style.fontFamily !== void 0) result.fontFamily = style.fontFamily;
43
+ if (style.alternateFontFamily !== void 0) result.alternateFontFamily = style.alternateFontFamily;
44
+ if (style.fontSize !== void 0) result.fontSize = style.fontSize;
45
+ if (style.bold !== void 0) result.bold = style.bold;
46
+ if (style.italic !== void 0) result.italic = style.italic;
47
+ if (style.fontVariant !== void 0) result.fontVariant = style.fontVariant;
48
+ if (style.kerning !== void 0) result.kerning = style.kerning;
49
+ return result;
50
+ };
51
+ const needsPerScriptFonts = (style, text) => style.eastAsiaFontFamily !== void 0 && [...text].some((c) => isEastAsia(c)) || hasComplexScriptFormatting(style) && hasComplexScript(text);
52
+ const isEastAsia = (char) => {
53
+ return scriptClassOf(char.codePointAt(0)) === SCRIPT_CLASS.eastAsia;
54
+ };
55
+ /**
56
+ * Width of a string: the advance width used for line breaking, never the
57
+ * painted (ink) extent, which includes overhang a break decision must ignore.
58
+ */
59
+ const composeTextWidth = ({ text, style: source, advanceOf }) => {
60
+ if (!text) return 0;
61
+ const style = source.forceComplexScript ? applyComplexScriptFormatting(source, source) : source;
62
+ const transformed = applyMeasurementTextTransform(text, style);
63
+ const letterSpacing = style.letterSpacing ?? 0;
64
+ const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
65
+ const glyphWidth = !source.forceComplexScript && !letterSpacing && needsPerScriptFonts(style, transformed) ? segmentByScript(transformed).reduce((sum, segment) => sum + advanceOf(segment.text, glyphAdvanceStyle(scriptStyle(style, segment.script))), 0) : advanceOf(transformed, style);
66
+ const codePoints = countCodePoints(transformed);
67
+ return (letterSpacing && codePoints > 1 ? glyphWidth + letterSpacing * (codePoints - 1) : glyphWidth) * horizontalScale;
68
+ };
69
+ /**
70
+ * Per-code-point advances for click positioning.
71
+ *
72
+ * Iterates whole code points so an astral ideograph takes the East-Asian font
73
+ * and gets a real width, while `charWidths` stays one entry per UTF-16 unit
74
+ * (the trailing unit of a surrogate pair carries 0) so it keeps aligning with
75
+ * ProseMirror offsets. The total differs from {@link composeTextWidth} for a
76
+ * kerned or ligature-forming run, because each code point is measured alone.
77
+ */
78
+ const composeRunMeasurement = ({ text, style: source, metrics, advanceOf, advancesOf }) => {
79
+ const style = source.forceComplexScript ? applyComplexScriptFormatting(source, source) : source;
80
+ if (!text) return {
81
+ width: 0,
82
+ charWidths: [],
83
+ metrics
84
+ };
85
+ const perScript = !style.letterSpacing;
86
+ const letterSpacing = style.letterSpacing ?? 0;
87
+ const scale = getHorizontalScaleFactor(style.horizontalScale);
88
+ const charWidths = [];
89
+ let totalWidth = 0;
90
+ let offset = 0;
91
+ const shaped = shapedAdvances({
92
+ text,
93
+ style,
94
+ advancesOf,
95
+ perScript
96
+ });
97
+ let codePointIndex = 0;
98
+ for (const char of text) {
99
+ const cp = char.codePointAt(0);
100
+ const charStyle = perScript ? scriptStyle(style, scriptClassOf(cp)) : style;
101
+ let charWidth = shaped.get(codePointIndex) ?? advanceOf(applyMeasurementTextTransform(char, style), charStyle);
102
+ if (letterSpacing && offset + char.length < text.length) charWidth += letterSpacing;
103
+ charWidth *= scale;
104
+ charWidths.push(charWidth);
105
+ if (char.length === 2) charWidths.push(0);
106
+ totalWidth += charWidth;
107
+ offset += char.length;
108
+ codePointIndex += 1;
109
+ }
110
+ return {
111
+ width: totalWidth,
112
+ charWidths,
113
+ metrics
114
+ };
115
+ };
116
+ /**
117
+ * Advances by code-point index for every stretch of the run that shapes.
118
+ *
119
+ * Each such stretch is measured in one call, because that is the only unit
120
+ * shaping has an answer for. A text transform that changes the length of a
121
+ * shaping stretch would break the alignment between the advances and the
122
+ * source's code points, so the transform is applied to the stretch and the
123
+ * result is only used when it did not change.
124
+ */
125
+ const shapedAdvances = ({ text, style, advancesOf, perScript }) => {
126
+ const byIndex = /* @__PURE__ */ new Map();
127
+ if (advancesOf === void 0 || !hasComplexScript(text)) return byIndex;
128
+ const characters = [...text];
129
+ const shapingStyle = perScript ? scriptStyle(style, SCRIPT_CLASS.complex) : style;
130
+ let start = 0;
131
+ while (start < characters.length) {
132
+ if (scriptClassOf(characters[start].codePointAt(0)) !== SCRIPT_CLASS.complex) {
133
+ start += 1;
134
+ continue;
135
+ }
136
+ let end = start + 1;
137
+ while (end < characters.length && scriptClassOf(characters[end].codePointAt(0)) === SCRIPT_CLASS.complex) end += 1;
138
+ const segment = characters.slice(start, end).join("");
139
+ if (applyMeasurementTextTransform(segment, style) === segment) {
140
+ const advances = advancesOf(segment, shapingStyle);
141
+ for (const [offset, advance] of advances.entries()) byIndex.set(start + offset, advance);
142
+ }
143
+ start = end;
144
+ }
145
+ return byIndex;
146
+ };
147
+ //#endregion
148
+ export { applyMeasurementTextTransform, composeRunMeasurement, composeTextWidth, glyphAdvanceStyle, scriptStyle };
@@ -31,6 +31,8 @@ declare function getTextCacheSize(): number;
31
31
  type FontMetricsEntry = {
32
32
  ascent: number;
33
33
  descent: number;
34
+ fontBoxAscent: number;
35
+ fontBoxDescent: number;
34
36
  lineHeight: number;
35
37
  singleLineRatio: number;
36
38
  };
@@ -81,6 +81,8 @@ function canvasGetFontMetrics(style) {
81
81
  fontSize,
82
82
  ascent: cached.ascent,
83
83
  descent: cached.descent,
84
+ fontBoxAscent: cached.fontBoxAscent,
85
+ fontBoxDescent: cached.fontBoxDescent,
84
86
  lineHeight: cached.lineHeight,
85
87
  fontFamily,
86
88
  singleLineRatio: cached.singleLineRatio
@@ -88,6 +90,8 @@ function canvasGetFontMetrics(style) {
88
90
  const fontSizePx = ptToPx(fontSize);
89
91
  let ascent = fontSizePx * DEFAULT_ASCENT_RATIO;
90
92
  let descent = fontSizePx * DEFAULT_DESCENT_RATIO;
93
+ let fontBoxAscent = ascent;
94
+ let fontBoxDescent = descent;
91
95
  let lineHeight = fontSizePx * DEFAULT_LINE_HEIGHT_MULTIPLIER;
92
96
  try {
93
97
  const ctx = getCanvasContext();
@@ -98,6 +102,13 @@ function canvasGetFontMetrics(style) {
98
102
  ascent = metrics.actualBoundingBoxAscent;
99
103
  descent = metrics.actualBoundingBoxDescent;
100
104
  }
105
+ if (typeof metrics.fontBoundingBoxAscent === "number" && typeof metrics.fontBoundingBoxDescent === "number") {
106
+ fontBoxAscent = metrics.fontBoundingBoxAscent;
107
+ fontBoxDescent = metrics.fontBoundingBoxDescent;
108
+ } else {
109
+ fontBoxAscent = ascent;
110
+ fontBoxDescent = descent;
111
+ }
101
112
  } catch {}
102
113
  lineHeight = Math.max(lineHeight, ascent + descent);
103
114
  const singleLineRatio = getResolvedData(fontFamily, alternateFontFamily).singleLineRatio;
@@ -105,6 +116,8 @@ function canvasGetFontMetrics(style) {
105
116
  fontSize,
106
117
  ascent,
107
118
  descent,
119
+ fontBoxAscent,
120
+ fontBoxDescent,
108
121
  lineHeight,
109
122
  fontFamily,
110
123
  singleLineRatio
@@ -369,7 +369,7 @@ function uppercaseLetterRatio(text) {
369
369
  return letters === 0 ? 0 : uppercase / letters;
370
370
  }
371
371
  function resolveJustifyFitStrategy(block, isFirstLine, profile) {
372
- if (block.attrs?.justificationCompatibility?.type === "legacy") return { type: "strict" };
372
+ if (block.attrs?.justificationCompatibility?.type === "legacy") return { type: "rounding" };
373
373
  if (isShallowFullHangingListContinuation(block, isFirstLine)) return { type: "rounding" };
374
374
  if (block.attrs?.listMarker !== void 0) {
375
375
  const hanging = block.attrs.indent?.hanging ?? 0;
@@ -461,7 +461,6 @@ function compressibleSpaceWidth(text, style) {
461
461
  return count === 0 ? 0 : count * measureTextWidth(" ", style);
462
462
  }
463
463
  function justifyFitTolerance(line, strategy, candidateSpaceWidth) {
464
- if (strategy.type === "strict") return 0;
465
464
  if (strategy.type === "rounding") return WIDTH_TOLERANCE;
466
465
  if (strategy.type === "width") return Math.max(WIDTH_TOLERANCE, line.availableWidth * strategy.ratio);
467
466
  const spaceTolerance = (line.regularSpaceWidth + candidateSpaceWidth) * strategy.ratio;
@@ -48,8 +48,20 @@ type FontStyle = {
48
48
  */
49
49
  type FontMetrics = {
50
50
  fontSize: number;
51
+ /** Ink extent above the baseline: how far the glyphs actually reach. */
51
52
  ascent: number;
53
+ /** Ink extent below the baseline. */
52
54
  descent: number;
55
+ /**
56
+ * Extent of the font's own box (hhea/OS-2), which is wider than the ink and
57
+ * is what a browser builds an inline content area from. A painter that
58
+ * positions a text box from the ink extents instead puts every baseline out
59
+ * by half the difference between the two, per face, with nothing to
60
+ * attribute it to. Carried beside the ink extents rather than replacing them
61
+ * because line boxes are sized from the ink.
62
+ */
63
+ fontBoxAscent: number;
64
+ fontBoxDescent: number;
53
65
  lineHeight: number;
54
66
  fontFamily: string;
55
67
  /** OS/2 single-line ratio for OOXML line spacing calculation */
@@ -6,10 +6,11 @@ type TableInlinePlacement = {
6
6
  alignment: "left" | "right";
7
7
  offset: number;
8
8
  };
9
+ type LeadingEdgeTable = Pick<TableBlock, "bidi" | "indent" | "indentCompatibility" | "justification" | "rows">;
9
10
  /** Resolve an inline table's horizontal anchor without losing RTL leading-edge semantics. */
10
- declare const resolveTableInlinePlacement: (table: Pick<TableBlock, "bidi" | "indent" | "justification">, rowJustification?: TableBlock["justification"]) => TableInlinePlacement;
11
+ declare const resolveTableInlinePlacement: (table: LeadingEdgeTable, rowJustification?: TableBlock["justification"]) => TableInlinePlacement;
11
12
  type ResolveTableInlineOffsetOptions = {
12
- table: Pick<TableBlock, "bidi" | "indent" | "justification">;
13
+ table: LeadingEdgeTable;
13
14
  rowJustification?: TableBlock["justification"];
14
15
  frameWidth: number;
15
16
  tableWidth: number;
@@ -1,9 +1,22 @@
1
+ import { resolveTableCellPadding } from "../types.js";
1
2
  //#region src/layout-engine/measure/tableInlinePlacement.ts
3
+ /**
4
+ * Distance the leading border is pulled back so the leading cell's text lands
5
+ * on `w:tblInd`. Zero unless the document uses the pre-Word-2013 indent
6
+ * semantics, where the indent measures to that text edge rather than to the
7
+ * border. Only the leading, indent-bearing edge compensates; centered and
8
+ * trailing-edge tables are placed by their border box.
9
+ */
10
+ const leadingEdgeCompensation = (table) => {
11
+ if (table.indentCompatibility?.type !== "legacy") return 0;
12
+ const padding = resolveTableCellPadding(table.rows.at(0)?.cells.at(0));
13
+ return table.bidi === true ? padding.right : padding.left;
14
+ };
2
15
  /** Resolve an inline table's horizontal anchor without losing RTL leading-edge semantics. */
3
16
  const resolveTableInlinePlacement = (table, rowJustification) => {
4
17
  const logicalJustification = rowJustification ?? table.justification ?? "left";
5
18
  if (logicalJustification === "center") return { alignment: "center" };
6
- const offset = logicalJustification === "left" ? table.indent ?? 0 : 0;
19
+ const offset = logicalJustification === "left" ? (table.indent ?? 0) - leadingEdgeCompensation(table) : 0;
7
20
  if (table.bidi !== true) return {
8
21
  alignment: logicalJustification,
9
22
  offset
@@ -0,0 +1,5 @@
1
+ import { TableBlock } from "./types.js";
2
+ //#region src/layout-engine/tableIndentCompatibility.d.ts
3
+ declare const resolveTableIndentCompatibility: (compatibilityMode: number | undefined) => NonNullable<TableBlock["indentCompatibility"]> | undefined;
4
+ //#endregion
5
+ export { resolveTableIndentCompatibility };
@@ -0,0 +1,15 @@
1
+ //#region src/layout-engine/tableIndentCompatibility.ts
2
+ /**
3
+ * First `compatibilityMode` that measures `w:tblInd` to the table border.
4
+ *
5
+ * Word 2013 moved the reference edge: from that mode on, `w:tblInd` offsets
6
+ * the border and the leading cell margin pushes the text further in. Earlier
7
+ * modes measure the indent to the leading cell's text edge and pull the border
8
+ * back by that cell's leading margin, so the first character lands exactly on
9
+ * the indent. A document that declares no `compatSetting` at all is read with
10
+ * the oldest semantics, so it takes the legacy rule too.
11
+ */
12
+ const FIRST_BORDER_EDGE_INDENT_COMPATIBILITY_MODE = 15;
13
+ const resolveTableIndentCompatibility = (compatibilityMode) => compatibilityMode !== void 0 && compatibilityMode >= FIRST_BORDER_EDGE_INDENT_COMPATIBILITY_MODE ? void 0 : { type: "legacy" };
14
+ //#endregion
15
+ export { resolveTableIndentCompatibility };
@@ -644,6 +644,14 @@ type TableBlock = {
644
644
  justification?: "left" | "center" | "right";
645
645
  /** Table indent from the leading margin (in pixels, from w:tblInd). */
646
646
  indent?: number;
647
+ /**
648
+ * Pre-Word-2013 `w:tblInd` semantics: the indent measures to the leading
649
+ * cell's text edge instead of the table border. Derived from the document's
650
+ * `compatibilityMode` (see `resolveTableIndentCompatibility`).
651
+ */
652
+ indentCompatibility?: {
653
+ type: "legacy";
654
+ };
647
655
  /** Right-to-left column order (w:bidiVisual): logical column 0 paints on the right. */
648
656
  bidi?: boolean;
649
657
  /** Floating table properties (pixel values). */
@@ -84,6 +84,12 @@ type FootnoteRenderItem = {
84
84
  /**
85
85
  * Options for rendering a page
86
86
  */
87
+ /** Everything a replacement page renderer is handed. */
88
+ type PaintPageRequest = {
89
+ readonly page: Page;
90
+ readonly context: RenderContext;
91
+ readonly options: RenderPageOptions;
92
+ };
87
93
  type RenderPageOptions = {
88
94
  /** Document to create elements in (default: window.document) */
89
95
  document?: Document;
@@ -163,6 +169,17 @@ type RenderPageOptions = {
163
169
  * silently skipped.
164
170
  */
165
171
  watermarkImageSrc?: string;
172
+ /**
173
+ * Paints the page in place of this module, returning the finished element.
174
+ *
175
+ * The one seam a second page renderer needs: everything around it (page
176
+ * shells, virtualization, the fingerprint comparison, the intersection
177
+ * observer, the painted event) is a page-container concern rather than a
178
+ * painting one, and stays shared. Returning `null` falls back to this
179
+ * module for that page, so a renderer that cannot express a page yields
180
+ * rather than painting it wrong.
181
+ */
182
+ paintPage?: (request: PaintPageRequest) => HTMLElement | null;
166
183
  };
167
184
  type HeaderFooterLayoutInfo = {
168
185
  flowTop: number;
@@ -242,6 +259,17 @@ declare function renderFootnoteArea(footnotes: FootnoteRenderItem[], contentWidt
242
259
  * @returns The page DOM element
243
260
  */
244
261
  declare function renderPage(page: Page, context: RenderContext, options?: RenderPageOptions): HTMLElement;
262
+ /**
263
+ * Which header and footer part a page selects, by `w:titlePg` and
264
+ * `w:evenAndOddHeaders`. Exported because a second consumer needs to tell "this
265
+ * page selects no part" from "this page selects a part nobody supplied", and
266
+ * recomputing the rule to answer that is how the two would come to disagree
267
+ * about which header a page carries. `undefined` means the page selects none.
268
+ */
269
+ declare function selectSectionHeaderFooterRIds(page: Page): {
270
+ headerRId: string | undefined;
271
+ footerRId: string | undefined;
272
+ };
245
273
  declare function applySectionHeaderFooterOptions(page: Page, pageOptions: RenderPageOptions, options: RenderPageOptions): boolean;
246
274
  /**
247
275
  * Compute a fingerprint string for a page that changes when its content changes.
@@ -302,4 +330,4 @@ declare function findPageShellForPmPos(container: HTMLElement, pmPos: number): {
302
330
  isExact: boolean;
303
331
  } | null;
304
332
  //#endregion
305
- export { type AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, type PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft, resolveHeaderFooterIntrinsicFrameHorizontalPosition };
333
+ export { type AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, type PageGeometry, PaintPageRequest, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft, resolveHeaderFooterIntrinsicFrameHorizontalPosition, selectSectionHeaderFooterRIds };
@@ -1,6 +1,7 @@
1
1
  import { resolveFloatingTableX } from "../layout-engine/measure/floatingTablePosition.js";
2
2
  import { rectsToFloatingZones } from "../layout-engine/measure/floatingZones.js";
3
3
  import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
4
+ import { resolveTableInlineOffset } from "../layout-engine/measure/tableInlinePlacement.js";
4
5
  import { floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "../layout-engine/types.js";
5
6
  import { resolveFontFamily } from "../utils/fontResolver.js";
6
7
  import { borderToStyle } from "../utils/formatToStyle.js";
@@ -504,10 +505,11 @@ function renderHeaderFooterContent(content, context, options, layout) {
504
505
  fragEl.style.left = `${left}px`;
505
506
  containerEl.append(fragEl);
506
507
  } else {
507
- let inlineLeft = 0;
508
- if (block.justification === "center") inlineLeft = (contentWidth - measure.totalWidth) / 2;
509
- else if (block.justification === "right") inlineLeft = contentWidth - measure.totalWidth;
510
- else if (block.indent) inlineLeft = block.indent;
508
+ const inlineLeft = resolveTableInlineOffset({
509
+ table: block,
510
+ frameWidth: contentWidth,
511
+ tableWidth: measure.totalWidth
512
+ });
511
513
  fragEl.style.position = "absolute";
512
514
  fragEl.style.top = `${cursorY}px`;
513
515
  fragEl.style.left = `${inlineLeft}px`;
@@ -816,6 +818,12 @@ function getFootnoteMeasureHeight(measure) {
816
818
  */
817
819
  function renderPage(page, context, options = {}) {
818
820
  const doc = options.document ?? document;
821
+ const painted = options.paintPage?.({
822
+ page,
823
+ context,
824
+ options
825
+ });
826
+ if (painted) return painted;
819
827
  const pageEl = doc.createElement("div");
820
828
  pageEl.className = options.pageClassName ?? PAGE_CLASS_NAMES.page;
821
829
  pageEl.dataset["pageNumber"] = String(page.number);
@@ -1171,22 +1179,42 @@ function moveBehindHeaderFooterElementsToPage(slotEl, pageEl, contentTop, conten
1171
1179
  pageEl.prepend(element);
1172
1180
  }
1173
1181
  }
1174
- function applySectionHeaderFooterOptions(page, pageOptions, options) {
1182
+ /**
1183
+ * Which header and footer part a page selects, by `w:titlePg` and
1184
+ * `w:evenAndOddHeaders`. Exported because a second consumer needs to tell "this
1185
+ * page selects no part" from "this page selects a part nobody supplied", and
1186
+ * recomputing the rule to answer that is how the two would come to disagree
1187
+ * about which header a page carries. `undefined` means the page selects none.
1188
+ */
1189
+ function selectSectionHeaderFooterRIds(page) {
1175
1190
  const refs = page.headerFooterRefs;
1176
- if (!refs) return false;
1177
- const isFirstSectionPage = page.sectionPageNumber === 1;
1178
- const useFirst = refs.titlePg === true && isFirstSectionPage;
1191
+ if (!refs) return {
1192
+ headerRId: void 0,
1193
+ footerRId: void 0
1194
+ };
1195
+ const useFirst = refs.titlePg === true && page.sectionPageNumber === 1;
1179
1196
  const useEven = refs.evenAndOddHeaders === true && page.logicalNumber % 2 === 0;
1180
- const headerRId = (() => {
1181
- if (useFirst) return refs.headerFirst;
1182
- if (useEven) return refs.headerEven;
1183
- return refs.headerDefault;
1184
- })();
1185
- const footerRId = (() => {
1186
- if (useFirst) return refs.footerFirst;
1187
- if (useEven) return refs.footerEven;
1188
- return refs.footerDefault;
1189
- })();
1197
+ const pick = (variants) => {
1198
+ if (useFirst) return variants.first;
1199
+ if (useEven) return variants.even;
1200
+ return variants.fallback;
1201
+ };
1202
+ return {
1203
+ headerRId: pick({
1204
+ first: refs.headerFirst,
1205
+ even: refs.headerEven,
1206
+ fallback: refs.headerDefault
1207
+ }),
1208
+ footerRId: pick({
1209
+ first: refs.footerFirst,
1210
+ even: refs.footerEven,
1211
+ fallback: refs.footerDefault
1212
+ })
1213
+ };
1214
+ }
1215
+ function applySectionHeaderFooterOptions(page, pageOptions, options) {
1216
+ if (!page.headerFooterRefs) return false;
1217
+ const { headerRId, footerRId } = selectSectionHeaderFooterRIds(page);
1190
1218
  if (headerRId) {
1191
1219
  const content = options.headerContentByRId?.get(headerRId);
1192
1220
  if (content) pageOptions.headerContent = content;
@@ -1864,4 +1892,4 @@ function depopulatePageShell(shell, pageDataMap) {
1864
1892
  }
1865
1893
  }
1866
1894
  //#endregion
1867
- export { PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft, resolveHeaderFooterIntrinsicFrameHorizontalPosition };
1895
+ export { PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft, resolveHeaderFooterIntrinsicFrameHorizontalPosition, selectSectionHeaderFooterRIds };
@@ -1,4 +1,4 @@
1
- import { HyperlinkInfo, MeasuredLine, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure, Run, TabStop } from "../layout-engine/types.js";
1
+ import { HyperlinkInfo, MeasuredLine, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure, Run, TabRun, TabStop, TextRun } from "../layout-engine/types.js";
2
2
  import { RenderContext } from "./renderUtils.js";
3
3
  import { PhysicalParagraphInlineLayout } from "../utils/paragraphInlineLayout.js";
4
4
  //#region src/layout-painter/renderParagraph.d.ts
@@ -29,6 +29,11 @@ type RenderParagraphOptions = {
29
29
  /** Inline image runs already rendered for this paragraph block */
30
30
  renderedInlineImageKeys?: Set<string>;
31
31
  };
32
+ declare function getRenderableTextColor(run: TextRun | TabRun): string | undefined;
33
+ /**
34
+ * Get leader character for tab
35
+ */
36
+ declare function getLeaderChar(leader: string): string | null;
32
37
  /**
33
38
  * Slice runs for a specific line
34
39
  *
@@ -47,6 +52,20 @@ declare function sliceRunsForLine(block: ParagraphBlock, line: MeasuredLine): Ru
47
52
  * runs without CJK pass through unchanged (the common path).
48
53
  */
49
54
  declare function splitTextRunsByEastAsia(runs: Run[]): Run[];
55
+ type CollapsibleLineEdgeSpacesResult = {
56
+ runs: Run[];
57
+ collapsedLeadingRuns: Set<TextRun>;
58
+ collapsedTrailingRuns: Set<TextRun>;
59
+ };
60
+ /**
61
+ * Word keeps ordinary line-edge spaces addressable in the document model but
62
+ * gives them no painted advance. Collapsible trailing spaces always collapse;
63
+ * leading spaces collapse only after a soft wrap, not at the authored
64
+ * paragraph start or after a manual line break. Split paint runs to preserve
65
+ * exact PM ranges.
66
+ */
67
+ declare const splitCollapsibleLineEdgeSpaces: (sourceRuns: Run[], collapseLeading: boolean) => CollapsibleLineEdgeSpacesResult;
68
+ declare const startsAfterSoftWrap: (block: ParagraphBlock, line: MeasuredLine) => boolean;
50
69
  /**
51
70
  * Options for rendering a line with justify support
52
71
  */
@@ -115,4 +134,4 @@ declare function renderLine(block: ParagraphBlock, line: MeasuredLine, alignment
115
134
  */
116
135
  declare function renderParagraphFragment(fragment: ParagraphFragment, block: ParagraphBlock, measure: ParagraphMeasure, context: RenderContext, options?: RenderParagraphOptions): HTMLElement;
117
136
  //#endregion
118
- export { PARAGRAPH_CLASS_NAMES, RenderParagraphOptions, renderLine, renderParagraphFragment, sliceRunsForLine, splitTextRunsByEastAsia };
137
+ export { PARAGRAPH_CLASS_NAMES, RenderParagraphOptions, getLeaderChar, getRenderableTextColor, renderLine, renderParagraphFragment, sliceRunsForLine, splitCollapsibleLineEdgeSpaces, splitTextRunsByEastAsia, startsAfterSoftWrap };
@@ -1671,4 +1671,4 @@ function renderListMarker({ marker, inlineWidth, visualOffset, doc, formatting,
1671
1671
  return span;
1672
1672
  }
1673
1673
  //#endregion
1674
- export { PARAGRAPH_CLASS_NAMES, renderLine, renderParagraphFragment, sliceRunsForLine, splitTextRunsByEastAsia };
1674
+ export { PARAGRAPH_CLASS_NAMES, getLeaderChar, getRenderableTextColor, renderLine, renderParagraphFragment, sliceRunsForLine, splitCollapsibleLineEdgeSpaces, splitTextRunsByEastAsia, startsAfterSoftWrap };
@@ -38,6 +38,30 @@ const TABLE_CLASS_NAMES = {
38
38
  };
39
39
  const CELL_DIAGONAL_BORDER_CLASS = "layout-table-cell-diagonal-border";
40
40
  const CELL_BOTTOM_BORDER_CLASS = "layout-table-cell-bottom-border";
41
+ /**
42
+ * Quarter turn a `w:textDirection` puts on cell content (§17.18.93, plus the
43
+ * short spellings Word also writes).
44
+ *
45
+ * `btLr` runs bottom-to-top up the left edge, so it turns counter-clockwise;
46
+ * every top-to-bottom direction runs down the right edge and turns clockwise.
47
+ * The horizontal directions are the identity — `rl` is bidi, not rotation.
48
+ * Only `btLr` was handled, so a rotated header cell (the common `tbRl` shape)
49
+ * painted horizontally and overflowed its column.
50
+ *
51
+ * Total over the union on purpose: a new direction has to state its turn here
52
+ * rather than silently falling back to horizontal.
53
+ */
54
+ const CELL_TEXT_ROTATION_DEGREES = {
55
+ lr: 0,
56
+ lrV: 0,
57
+ rl: 0,
58
+ rlV: 0,
59
+ tb: 90,
60
+ tbV: 90,
61
+ tbRl: 90,
62
+ tbRlV: 90,
63
+ btLr: -90
64
+ };
41
65
  function renderCellContent({ cell, cellMeasure, context, doc, contentWidthOverride, pageContentPosition }) {
42
66
  const contentEl = doc.createElement("div");
43
67
  contentEl.className = TABLE_CLASS_NAMES.cellContent;
@@ -122,11 +146,19 @@ function renderCellContent({ cell, cellMeasure, context, doc, contentWidthOverri
122
146
  ...paragraphBlock.pmStart !== void 0 ? { pmStart: paragraphBlock.pmStart } : {},
123
147
  ...paragraphBlock.pmEnd !== void 0 ? { pmEnd: paragraphBlock.pmEnd } : {}
124
148
  };
149
+ const previousBlock = cell.blocks[i - 1];
150
+ const nextBlock = cell.blocks[i + 1];
151
+ const prevBorders = previousBlock?.kind === "paragraph" ? previousBlock.attrs?.borders : void 0;
152
+ const nextBorders = nextBlock?.kind === "paragraph" ? nextBlock.attrs?.borders : void 0;
125
153
  const cellContext = {
126
154
  ...context,
127
155
  insideTableCell: true
128
156
  };
129
- const fragEl = renderParagraphFragment(syntheticFragment, paragraphBlock, paragraphMeasure, cellContext, { document: doc });
157
+ const fragEl = renderParagraphFragment(syntheticFragment, paragraphBlock, paragraphMeasure, cellContext, {
158
+ document: doc,
159
+ ...prevBorders !== void 0 ? { prevBorders } : {},
160
+ ...nextBorders !== void 0 ? { nextBorders } : {}
161
+ });
130
162
  fragEl.style.position = "relative";
131
163
  fragEl.style.boxSizing = "border-box";
132
164
  fragEl.style.height = `${placement.leadingSpacing + placement.contentHeight}px`;
@@ -382,7 +414,8 @@ function renderTableCell({ cell, cellMeasure, x, width, rowHeight, borderFlags,
382
414
  default: break;
383
415
  }
384
416
  }
385
- const contentWidthOverride = cell.textDirection === "btLr" ? Math.max(1, rowHeight - padTop - padBottom) : void 0;
417
+ const cellTextRotation = cell.textDirection ? CELL_TEXT_ROTATION_DEGREES[cell.textDirection] : 0;
418
+ const contentWidthOverride = cellTextRotation === 0 ? void 0 : Math.max(1, rowHeight - padTop - padBottom);
386
419
  const renderedContent = renderCellContent({
387
420
  cell,
388
421
  cellMeasure,
@@ -395,11 +428,11 @@ function renderTableCell({ cell, cellMeasure, x, width, rowHeight, borderFlags,
395
428
  y: pageContentPosition.y + padTop
396
429
  } } : {}
397
430
  });
398
- if (cell.textDirection === "btLr") {
431
+ if (cellTextRotation !== 0) {
399
432
  renderedContent.content.style.position = "absolute";
400
433
  renderedContent.content.style.left = "50%";
401
434
  renderedContent.content.style.top = "50%";
402
- renderedContent.content.style.transform = "translate(-50%, -50%) rotate(-90deg)";
435
+ renderedContent.content.style.transform = `translate(-50%, -50%) rotate(${cellTextRotation}deg)`;
403
436
  }
404
437
  if (renderedContent.floatingLayers.length > 0) {
405
438
  renderedContent.content.style.height = "100%";
@@ -30,6 +30,38 @@ type ClassifyEditorKeydownOptions = {
30
30
  * - Delete/Backspace (no modifiers, focus not in an input) → delete selected table
31
31
  */
32
32
  declare function classifyEditorKeydown(e: KeyboardEvent, { isMac, isInputLike }: ClassifyEditorKeydownOptions): EditorKeydownIntent;
33
+ /**
34
+ * Which key presses the editor's page-level shortcuts answer.
35
+ * - `"document"`: every press on the page.
36
+ * - `"editor"`: only a press whose target is inside the editor.
37
+ * - `"none"`: no page-level listener; the host dispatches shortcuts itself.
38
+ */
39
+ type KeyboardShortcutScope = "document" | "editor" | "none";
40
+ /**
41
+ * The containment surface a scope root has to offer. Structural rather than
42
+ * `Element` so the predicate carries no DOM dependency and answers the same
43
+ * under a server render and a DOM-less test runtime.
44
+ */
45
+ type ShortcutScopeRoot = {
46
+ contains(other: unknown): boolean;
47
+ };
48
+ type IsKeydownInShortcutScopeOptions = {
49
+ scope: KeyboardShortcutScope;
50
+ /**
51
+ * Elements a press must land inside under `"editor"`: the editor root plus
52
+ * any surface it owns outside that subtree (a portaled dialog). A nullish
53
+ * entry — an unmounted ref — never matches.
54
+ */
55
+ roots: readonly (ShortcutScopeRoot | null | undefined)[];
56
+ };
57
+ /**
58
+ * Whether a keydown is one the editor answers under `scope`. The adapters own
59
+ * the listener; this decides, from the event target alone, whether the press
60
+ * belongs to this editor or to whatever else the host has on the page.
61
+ */
62
+ declare function isKeydownInShortcutScope(event: {
63
+ target: unknown;
64
+ }, { scope, roots }: IsKeydownInShortcutScopeOptions): boolean;
33
65
  /**
34
66
  * If the selection is a ProseMirror `CellSelection` covering every cell of its
35
67
  * table, delete the table and return `true`. Returns `false` for any other
@@ -37,4 +69,4 @@ declare function classifyEditorKeydown(e: KeyboardEvent, { isMac, isInputLike }:
37
69
  */
38
70
  declare function deleteSelectedTable(state: EditorState, dispatch: (tr: Transaction) => void): boolean;
39
71
  //#endregion
40
- export { ClassifyEditorKeydownOptions, EditorKeydownIntent, classifyEditorKeydown, deleteSelectedTable, isFocusInInputLike, isMacPlatform };
72
+ export { ClassifyEditorKeydownOptions, EditorKeydownIntent, IsKeydownInShortcutScopeOptions, KeyboardShortcutScope, ShortcutScopeRoot, classifyEditorKeydown, deleteSelectedTable, isFocusInInputLike, isKeydownInShortcutScope, isMacPlatform };