@stll/folio-core 0.47.7 → 0.49.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 (213) hide show
  1. package/dist/ai-edits/apply.js +565 -121
  2. package/dist/ai-edits/minimal-replacement.d.ts +64 -0
  3. package/dist/ai-edits/minimal-replacement.js +282 -0
  4. package/dist/controller/fontReadiness.js +3 -3
  5. package/dist/controller/hyphenationReadiness.d.ts +19 -0
  6. package/dist/controller/hyphenationReadiness.js +47 -0
  7. package/dist/controller/layoutPipeline.d.ts +3 -0
  8. package/dist/controller/layoutPipeline.js +70 -18
  9. package/dist/display-list/build/buildDisplayList.js +39 -12
  10. package/dist/display-list/build/pageFurniture.d.ts +7 -1
  11. package/dist/display-list/build/pageFurniture.js +21 -8
  12. package/dist/display-list/build/paragraphPrimitives.js +68 -11
  13. package/dist/display-list/build/tablePrimitives.js +10 -6
  14. package/dist/display-list/build/textBoxPrimitives.js +1 -1
  15. package/dist/display-list/build/unsupported.d.ts +2 -2
  16. package/dist/display-list/build/unsupported.js +0 -0
  17. package/dist/display-list/dom/renderDisplayListToDom.js +0 -1
  18. package/dist/docx/attributeRemainder.js +10 -2
  19. package/dist/docx/blockContentParser.d.ts +2 -0
  20. package/dist/docx/blockContentParser.js +79 -64
  21. package/dist/docx/bulletMarkers.d.ts +18 -2
  22. package/dist/docx/bulletMarkers.js +27 -2
  23. package/dist/docx/commentParser.d.ts +2 -1
  24. package/dist/docx/commentParser.js +57 -45
  25. package/dist/docx/containerChildren.d.ts +21 -8
  26. package/dist/docx/containerChildren.js +39 -9
  27. package/dist/docx/documentParser.d.ts +5 -1
  28. package/dist/docx/documentParser.js +8 -5
  29. package/dist/docx/documentSectionFacts.d.ts +22 -0
  30. package/dist/docx/documentSectionFacts.js +43 -0
  31. package/dist/docx/drawingGroupChildren.d.ts +60 -0
  32. package/dist/docx/drawingGroupChildren.js +176 -0
  33. package/dist/docx/drawingUtils.js +3 -0
  34. package/dist/docx/fieldParser.d.ts +8 -1
  35. package/dist/docx/fieldParser.js +47 -3
  36. package/dist/docx/fontTableParser.js +34 -27
  37. package/dist/docx/footnoteParser.d.ts +3 -2
  38. package/dist/docx/footnoteParser.js +15 -12
  39. package/dist/docx/groupDrawingParser.d.ts +9 -2
  40. package/dist/docx/groupDrawingParser.js +125 -37
  41. package/dist/docx/headerFooterParser.d.ts +7 -2
  42. package/dist/docx/headerFooterParser.js +15 -5
  43. package/dist/docx/hyperlinkParser.d.ts +48 -36
  44. package/dist/docx/hyperlinkParser.js +97 -82
  45. package/dist/docx/numberingParser.js +108 -84
  46. package/dist/docx/packageParts.d.ts +45 -1
  47. package/dist/docx/packageParts.js +93 -2
  48. package/dist/docx/paraIdAttribute.d.ts +3 -2
  49. package/dist/docx/paragraphParser.d.ts +5 -1
  50. package/dist/docx/paragraphParser.js +468 -365
  51. package/dist/docx/paragraphProperties.js +123 -109
  52. package/dist/docx/paragraphPropertySource.d.ts +6 -1
  53. package/dist/docx/paragraphPropertySource.js +9 -1
  54. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  55. package/dist/docx/paragraphTextBoxEnrichment.js +143 -10
  56. package/dist/docx/parser.d.ts +7 -1
  57. package/dist/docx/parser.js +33 -16
  58. package/dist/docx/previewBudget.d.ts +55 -33
  59. package/dist/docx/previewBudget.js +59 -65
  60. package/dist/docx/rezip.js +41 -58
  61. package/dist/docx/runParser.d.ts +4 -1
  62. package/dist/docx/runParser.js +182 -184
  63. package/dist/docx/sdtProperties.js +87 -84
  64. package/dist/docx/sectionParser.js +169 -160
  65. package/dist/docx/selectiveSaveFlags.d.ts +10 -3
  66. package/dist/docx/selectiveSaveFlags.js +1 -1
  67. package/dist/docx/selectiveXmlPatch.d.ts +56 -34
  68. package/dist/docx/selectiveXmlPatch.js +222 -115
  69. package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
  70. package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
  71. package/dist/docx/serializer/paragraphSerializer.js +47 -42
  72. package/dist/docx/serializer/runSerializer.d.ts +5 -1
  73. package/dist/docx/serializer/runSerializer.js +4 -3
  74. package/dist/docx/serializer/settingsSerializer.js +19 -1
  75. package/dist/docx/settingsParser.js +8 -0
  76. package/dist/docx/shapeAlternateContent.d.ts +25 -0
  77. package/dist/docx/shapeAlternateContent.js +52 -0
  78. package/dist/docx/streamingXmlParser.d.ts +6 -2
  79. package/dist/docx/streamingXmlParser.js +12 -7
  80. package/dist/docx/styleParser.js +34 -8
  81. package/dist/docx/tableParser.d.ts +21 -12
  82. package/dist/docx/tableParser.js +518 -441
  83. package/dist/docx/textBoxParser.d.ts +18 -5
  84. package/dist/docx/textBoxParser.js +12 -6
  85. package/dist/docx/verbatimCapture.d.ts +10 -8
  86. package/dist/docx/verbatimCapture.js +45 -2
  87. package/dist/docx/vmlImageParser.d.ts +15 -13
  88. package/dist/docx/vmlImageParser.js +75 -60
  89. package/dist/docx/vmlPreview.d.ts +3 -3
  90. package/dist/docx/vmlPreview.js +3 -4
  91. package/dist/docx/xmlNamespaceContext.d.ts +9 -0
  92. package/dist/docx/xmlNamespaceContext.js +54 -0
  93. package/dist/docx/xmlParser.d.ts +6 -3
  94. package/dist/docx/xmlParser.js +11 -41
  95. package/dist/docx/xmlResourceLimits.d.ts +2 -1
  96. package/dist/docx/xmlResourceLimits.js +4 -1
  97. package/dist/fonts/headlessMeasure.js +0 -0
  98. package/dist/headless-layout.d.ts +11 -2
  99. package/dist/headless-layout.js +153 -107
  100. package/dist/internal/headlessRevisionResolution.js +50 -13
  101. package/dist/internal/paragraphFormattingSerialization.js +3 -3
  102. package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
  103. package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
  104. package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
  105. package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
  106. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
  107. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
  108. package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
  109. package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
  110. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
  111. package/dist/layout-bridge/convert/toFlowBlocks.js +284 -39
  112. package/dist/layout-bridge/engine/hitTest.js +5 -3
  113. package/dist/layout-bridge/engine/selectionRects.js +5 -3
  114. package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
  115. package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
  116. package/dist/layout-engine/index.d.ts +2 -2
  117. package/dist/layout-engine/index.js +102 -14
  118. package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
  119. package/dist/layout-engine/justificationCompatibility.js +9 -5
  120. package/dist/layout-engine/keep-together.d.ts +15 -6
  121. package/dist/layout-engine/keep-together.js +43 -16
  122. package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
  123. package/dist/layout-engine/layoutInstrumentation.js +8 -1
  124. package/dist/layout-engine/measure/advanceComposition.js +50 -7
  125. package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
  126. package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
  127. package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
  128. package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
  129. package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
  130. package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
  131. package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
  132. package/dist/layout-engine/measure/lineBreaks.js +10 -3
  133. package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
  134. package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
  135. package/dist/layout-engine/measure/measureBlocks.js +46 -10
  136. package/dist/layout-engine/measure/measureContainer.js +51 -8
  137. package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
  138. package/dist/layout-engine/measure/measureHelpers.js +34 -8
  139. package/dist/layout-engine/measure/measureParagraph.js +143 -101
  140. package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
  141. package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
  142. package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
  143. package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
  144. package/dist/layout-engine/measure/tabCalculator.js +2 -1
  145. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  146. package/dist/layout-engine/measure/tableCellFloating.js +51 -19
  147. package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
  148. package/dist/layout-engine/measure/tableCellGrid.js +45 -5
  149. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
  150. package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
  151. package/dist/layout-engine/noteAreaFlow.js +83 -0
  152. package/dist/layout-engine/paginator.d.ts +12 -1
  153. package/dist/layout-engine/paginator.js +18 -2
  154. package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
  155. package/dist/layout-engine/paragraphSpacing.js +2 -1
  156. package/dist/layout-engine/tableRowBreak.d.ts +8 -1
  157. package/dist/layout-engine/tableRowBreak.js +25 -2
  158. package/dist/layout-engine/types.d.ts +108 -3
  159. package/dist/layout-engine/types.js +96 -1
  160. package/dist/layout-painter/index.d.ts +3 -0
  161. package/dist/layout-painter/renderPage.js +32 -83
  162. package/dist/layout-painter/renderParagraph.d.ts +6 -1
  163. package/dist/layout-painter/renderParagraph.js +75 -26
  164. package/dist/layout-painter/renderTable.js +26 -13
  165. package/dist/layout-painter/renderTextBox.d.ts +12 -2
  166. package/dist/layout-painter/renderTextBox.js +26 -2
  167. package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
  168. package/dist/prosemirror/alternateContentAttrs.js +35 -0
  169. package/dist/prosemirror/attrs/index.d.ts +9 -9
  170. package/dist/prosemirror/attrs/index.js +133 -11
  171. package/dist/prosemirror/commands/comments.js +51 -41
  172. package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
  173. package/dist/prosemirror/contentControlRevisions.js +84 -0
  174. package/dist/prosemirror/conversion/fromProseDoc.js +54 -23
  175. package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
  176. package/dist/prosemirror/conversion/markInterner.js +90 -0
  177. package/dist/prosemirror/conversion/toProseDoc.js +117 -58
  178. package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  180. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
  181. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
  182. package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
  183. package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
  184. package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
  185. package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
  186. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  187. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
  188. package/dist/prosemirror/listMarker.d.ts +1 -0
  189. package/dist/prosemirror/listMarker.js +4 -2
  190. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  191. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  192. package/dist/prosemirror/paragraphDirection.d.ts +29 -2
  193. package/dist/prosemirror/paragraphDirection.js +21 -2
  194. package/dist/prosemirror/plugins/suggestionMode.js +37 -8
  195. package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
  196. package/dist/prosemirror/rejoinRunCarriers.js +66 -0
  197. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
  198. package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
  199. package/dist/prosemirror/schema/nodes.d.ts +50 -6
  200. package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
  201. package/dist/prosemirror/textBoxHostParagraph.js +21 -0
  202. package/dist/prosemirror/validation.js +91 -80
  203. package/dist/types/content.d.ts +2 -2
  204. package/dist/utils/createDocument.js +10 -1
  205. package/dist/utils/scriptSegments.d.ts +33 -4
  206. package/dist/utils/scriptSegments.js +39 -6
  207. package/dist/utils/textFormattingMerge.d.ts +17 -3
  208. package/dist/utils/textFormattingMerge.js +18 -12
  209. package/dist/utils/trailingText.d.ts +22 -0
  210. package/dist/utils/trailingText.js +32 -0
  211. package/package.json +2 -2
  212. package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
  213. package/dist/layout-engine/justifiedLineFit.js +0 -6
@@ -1,15 +1,18 @@
1
1
  import { hasPageBreakBefore } from "../keep-together.js";
2
2
  import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrumentation.js";
3
3
  import { isParagraphFrameTextBox } from "../paragraphFrame.js";
4
+ import { resolveTableSpacing } from "../paragraphSpacing.js";
4
5
  import { sectionStartAdvanceOf } from "../section-breaks.js";
5
6
  import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
6
7
  import { getTextBoxGroupId } from "../textBoxGroup.js";
7
8
  import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, resolveTableCellPadding, resolveTableWidthPx, tableColumnsArePinned } from "../types.js";
8
9
  import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
9
10
  import { resolveFloatingTablePageX } from "./floatingTablePosition.js";
11
+ import { getHyphenationDictionaryMisses } from "./hyphenationDictionaries.js";
10
12
  import { findClearLineY, measureParagraph } from "./measureParagraph.js";
13
+ import { buildTableCellFloatingZones, getTableCellFloatingImages } from "./tableCellFloating.js";
11
14
  import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "./tableCellFlow.js";
12
- import { buildTableCellGrid, getFirstAvailableColumn, getTableCellVerticalBorderHeight } from "./tableCellGrid.js";
15
+ import { buildTableCellGrid, getFirstAvailableColumn, getTableCellSpacingInsetsX, getTableCellSpacingInsetsY, getTableCellVerticalBorderHeight } from "./tableCellGrid.js";
13
16
  import { layoutTextBoxContent } from "./textBoxParagraphLayout.js";
14
17
  //#region src/layout-engine/measure/measureBlocks.ts
15
18
  /**
@@ -59,6 +62,27 @@ function emuToPixels(emu) {
59
62
  if (emu === void 0) return 0;
60
63
  return Math.round(emu * 96 / 914400);
61
64
  }
65
+ /**
66
+ * Re-measure a cell's paragraphs against the wrap exclusions of the pictures
67
+ * anchored in it, so lines a picture displaces (beside it, or below it when it
68
+ * leaves no room) count toward the cell height. Mirrors the painter's
69
+ * per-paragraph re-measure so both agree on where each line sits.
70
+ */
71
+ function wrapTableCellParagraphsAroundFloats(cell, cellMeasure, contentWidth, fieldValues) {
72
+ const zones = buildTableCellFloatingZones(getTableCellFloatingImages(cell, cellMeasure, contentWidth), contentWidth);
73
+ if (zones.length === 0) return;
74
+ const flowState = createTableCellFlowState();
75
+ for (let blockIdx = 0; blockIdx < cell.blocks.length; blockIdx++) {
76
+ const block = cell.blocks[blockIdx];
77
+ let blockMeasure = cellMeasure.blocks[blockIdx];
78
+ if (!block || !blockMeasure) continue;
79
+ if (block.kind === "paragraph") {
80
+ blockMeasure = measureBlock(block, contentWidth, zones, placeTableCellBlock({ ...flowState }, block, blockMeasure).contentTop, fieldValues);
81
+ cellMeasure.blocks[blockIdx] = blockMeasure;
82
+ }
83
+ placeTableCellBlock(flowState, block, blockMeasure);
84
+ }
85
+ }
62
86
  function measureTableBlock(tableBlock, contentWidth, fieldValues) {
63
87
  let columnWidths = tableBlock.columnWidths ?? [];
64
88
  const explicitWidthPx = resolveTableWidthPx(tableBlock.width, tableBlock.widthType, contentWidth);
@@ -74,24 +98,32 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
74
98
  }
75
99
  }
76
100
  const cellGrid = buildTableCellGrid(tableBlock.rows, columnWidths.length);
101
+ const spacingRows = resolveTableSpacing(tableBlock).rows;
102
+ const cellSpacing = tableBlock.cellSpacing;
103
+ const bordersSeparated = (cellSpacing ?? 0) > 0;
104
+ const rowCount = tableBlock.rows.length;
77
105
  const columnsPinned = tableColumnsArePinned(tableBlock);
78
106
  const rows = tableBlock.rows.map((row, rowIdx) => {
79
107
  let columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, row.gridBefore ?? 0);
80
108
  return {
81
- cells: row.cells.map((cell) => {
109
+ cells: row.cells.map((cell, cellIdx) => {
82
110
  const colSpan = cell.colSpan ?? 1;
83
111
  let cellWidth = 0;
84
112
  for (let c = 0; c < colSpan && columnIndex + c < columnWidths.length; c++) cellWidth += columnWidths[columnIndex + c] ?? 0;
85
113
  if (cellWidth === 0) cellWidth = cell.width ?? 100;
114
+ const spacingInsets = getTableCellSpacingInsetsX(cellSpacing, columnIndex, colSpan, columnWidths.length);
115
+ cellWidth = Math.max(0, cellWidth - spacingInsets.start - spacingInsets.end);
86
116
  columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, columnIndex + colSpan);
87
117
  const { left: padLeft, right: padRight } = resolveTableCellPadding(cell);
88
118
  const cellContentWidth = Math.max(1, cellWidth - padLeft - padRight);
89
- const measureWidth = cell.noWrap === true && !columnsPinned ? NO_WRAP_MEASURE_WIDTH : cellContentWidth;
119
+ const keepSingleLine = cell.noWrap === true && !columnsPinned;
120
+ const measureWidth = keepSingleLine ? NO_WRAP_MEASURE_WIDTH : cellContentWidth;
90
121
  const cellMeasure = {
91
122
  blocks: cell.blocks.map((b) => measureBlock(b, measureWidth, void 0, void 0, fieldValues)),
92
123
  width: cellWidth,
93
124
  height: 0
94
125
  };
126
+ if (!keepSingleLine) wrapTableCellParagraphsAroundFloats(spacingRows[rowIdx]?.cells[cellIdx] ?? cell, cellMeasure, cellContentWidth, fieldValues);
95
127
  if (cell.colSpan !== void 0) cellMeasure.colSpan = cell.colSpan;
96
128
  if (cell.rowSpan !== void 0) cellMeasure.rowSpan = cell.rowSpan;
97
129
  return cellMeasure;
@@ -111,7 +143,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
111
143
  }));
112
144
  continue;
113
145
  }
114
- const sourceRowCells = tableBlock.rows[rowIdx]?.cells;
146
+ const sourceRowCells = spacingRows[rowIdx]?.cells;
115
147
  let maxCellHeightWithBorders = 0;
116
148
  let maxCellInsets = 0;
117
149
  for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
@@ -129,15 +161,17 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
129
161
  const { top: padTop, bottom: padBottom } = resolveTableCellPadding(sourceCell);
130
162
  cell.height += padTop + padBottom;
131
163
  if ((sourceCell?.rowSpan ?? 1) > 1) continue;
132
- const borderHeight = getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx);
164
+ const borderHeight = getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx, bordersSeparated);
133
165
  maxCellHeightWithBorders = Math.max(maxCellHeightWithBorders, cell.height + borderHeight);
134
166
  maxCellInsets = Math.max(maxCellInsets, padTop + padBottom + borderHeight);
135
167
  }
136
168
  const explicitHeight = sourceRow?.height;
137
169
  const heightRule = sourceRow?.heightRule;
138
- if (explicitHeight && heightRule === "exact") row.height = explicitHeight;
139
- else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight + maxCellInsets);
140
- else row.height = maxCellHeightWithBorders;
170
+ const spacing = getTableCellSpacingInsetsY(cellSpacing, rowIdx, 1, rowCount);
171
+ const spacingHeight = spacing.top + spacing.bottom;
172
+ if (explicitHeight && heightRule === "exact") row.height = (bordersSeparated ? Math.max(0, explicitHeight - (cellSpacing ?? 0)) : explicitHeight) + spacingHeight;
173
+ else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight + maxCellInsets) + spacingHeight;
174
+ else row.height = maxCellHeightWithBorders + spacingHeight;
141
175
  }
142
176
  for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
143
177
  const sourceRow = tableBlock.rows[rowIdx];
@@ -151,7 +185,8 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
151
185
  const spanEnd = Math.min(rows.length, rowIdx + rowSpan);
152
186
  let combinedHeight = 0;
153
187
  for (let spannedRowIdx = rowIdx; spannedRowIdx < spanEnd; spannedRowIdx++) combinedHeight += rows[spannedRowIdx]?.height ?? 0;
154
- const deficit = measuredCell.height + getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx) - combinedHeight;
188
+ const spanSpacing = getTableCellSpacingInsetsY(cellSpacing, rowIdx, rowSpan, rowCount);
189
+ const deficit = measuredCell.height + getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx, bordersSeparated) + spanSpacing.top + spanSpacing.bottom - combinedHeight;
155
190
  if (deficit <= 0) continue;
156
191
  for (let spannedRowIdx = spanEnd - 1; spannedRowIdx >= rowIdx; spannedRowIdx--) {
157
192
  if (tableBlock.rows[spannedRowIdx]?.heightRule === "exact") continue;
@@ -529,8 +564,9 @@ function measureBlock(block, contentWidth, floatingZones, cumulativeY, fieldValu
529
564
  if (floatingZones) measureOpts.floatingZones = floatingZones;
530
565
  if (fieldValues) measureOpts.fieldValues = fieldValues;
531
566
  if (options?.allowEndTabOverflow === true) measureOpts.allowEndTabOverflow = true;
567
+ const missesBefore = getHyphenationDictionaryMisses();
532
568
  const result = measureParagraph(pBlock, contentWidth, measureOpts);
533
- if (cacheable) setCachedParagraphMeasure(pBlock, contentWidth, result);
569
+ if (cacheable && getHyphenationDictionaryMisses() === missesBefore) setCachedParagraphMeasure(pBlock, contentWidth, result);
534
570
  return result;
535
571
  }
536
572
  case "table": return measureTableBlock(block, contentWidth, fieldValues);
@@ -1,11 +1,12 @@
1
1
  import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
2
- import { SCRIPT_CLASS, hasCjk, hasComplexScript, scriptClassOf, segmentByScript } from "../../utils/scriptSegments.js";
2
+ import { SCRIPT_CLASS, hasComplexScript, hasEastAsiaSlotText, scriptClassOf, segmentByScript } from "../../utils/scriptSegments.js";
3
3
  import { getCachedFontMetrics, getCachedTextWidth, getTextWidthCacheGeneration, setCachedFontMetrics, setCachedTextWidth } from "./cache.js";
4
4
  import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./complexScriptFormatting.js";
5
5
  import { buildFontString, getResolvedData, ptToPx } from "./measureHelpers.js";
6
6
  import { setMeasureProvider } from "./measureProvider.js";
7
7
  import { canPrefetchMeasurement, prefetchMeasurement } from "./measureWorker.js";
8
8
  import { WORKER_FONT_FINGERPRINT_TEXT, countCodePoints } from "./measureWorkerProtocol.js";
9
+ import { SMALL_CAPS_SCALE, smallCapsMask, smallCapsSegments } from "./smallCapsCasing.js";
9
10
  import { getFontKerningMode } from "./textMeasurementPolicy.js";
10
11
  import { panic } from "better-result";
11
12
  //#region src/layout-engine/measure/measureContainer.ts
@@ -70,7 +71,7 @@ function getWorkerFontFingerprintWidth(ctx, font, fontCacheKey, fontKerning) {
70
71
  */
71
72
  function canvasGetFontMetrics(style) {
72
73
  const fontSize = style.fontSize ?? 11;
73
- const fontFamily = style.fontFamily ?? "Calibri";
74
+ const fontFamily = style.fontFamily ?? "Times New Roman";
74
75
  const bold = style.bold ?? false;
75
76
  const italic = style.italic ?? false;
76
77
  const fontVariant = style.fontVariant;
@@ -136,6 +137,7 @@ function canvasMeasureTextWidth(text, sourceStyle) {
136
137
  if (!text) return 0;
137
138
  const style = sourceStyle.forceComplexScript ? applyComplexScriptFormatting(sourceStyle, sourceStyle) : sourceStyle;
138
139
  const measuredText = applyTextTransform(text, style);
140
+ if (!sourceStyle.forceComplexScript && style.fontVariant === "small-caps") return measureSmallCapsWidth(measuredText, style);
139
141
  if (!sourceStyle.forceComplexScript && !style.letterSpacing && needsPerScriptFonts(style, measuredText)) return measureMixedScriptWidth(measuredText, style);
140
142
  const ctx = getCanvasContext();
141
143
  const font = buildFontString(style);
@@ -197,7 +199,7 @@ function glyphAdvanceStyle(style) {
197
199
  * AND text that would select it. Keeps the all-Latin path on a single font.
198
200
  */
199
201
  function needsPerScriptFonts(style, measuredText) {
200
- if (style.eastAsiaFontFamily && hasCjk(measuredText)) return true;
202
+ if (style.eastAsiaFontFamily && hasEastAsiaSlotText(measuredText, style.eastAsiaHint)) return true;
201
203
  return hasComplexScriptFormatting(style) && hasComplexScript(measuredText);
202
204
  }
203
205
  /** Resolve the independent font/style slot selected by a script segment. */
@@ -219,7 +221,39 @@ function measureMixedScriptWidth(measuredText, style) {
219
221
  const letterSpacing = style.letterSpacing ?? 0;
220
222
  const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
221
223
  let glyphWidth = 0;
222
- for (const segment of segmentByScript(measuredText)) glyphWidth += canvasMeasureTextWidth(segment.text, glyphAdvanceStyle(scriptStyle(style, segment.script)));
224
+ for (const segment of segmentByScript(measuredText, style.eastAsiaHint)) glyphWidth += canvasMeasureTextWidth(segment.text, glyphAdvanceStyle(scriptStyle(style, segment.script)));
225
+ let width = glyphWidth;
226
+ if (letterSpacing) {
227
+ const codePoints = countCodePoints(measuredText);
228
+ if (codePoints > 1) width += letterSpacing * (codePoints - 1);
229
+ }
230
+ return width * horizontalScale;
231
+ }
232
+ /**
233
+ * Width of a `w:smallCaps` run: its lowercase letters (and the whitespace
234
+ * that follows them — see smallCapsCasing.ts) draw at `SMALL_CAPS_SCALE` of
235
+ * the run's size, everything else at full size. A small segment is measured
236
+ * at that literal, smaller font string — not the full-size advance scaled by
237
+ * arithmetic — so hinting and sub-pixel snapping agree with the painted DOM
238
+ * span, which sets this same font-size. Each segment still recurses into
239
+ * {@link canvasMeasureTextWidth} (so one that itself needs the East-Asian or
240
+ * complex-script font still gets it); letter spacing and horizontal scale
241
+ * apply once over the whole string, as {@link measureMixedScriptWidth} does
242
+ * for a script split.
243
+ */
244
+ function measureSmallCapsWidth(measuredText, style) {
245
+ const letterSpacing = style.letterSpacing ?? 0;
246
+ const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
247
+ const segmentStyle = { ...style };
248
+ delete segmentStyle.fontVariant;
249
+ delete segmentStyle.letterSpacing;
250
+ delete segmentStyle.horizontalScale;
251
+ const smallSegmentStyle = {
252
+ ...segmentStyle,
253
+ fontSize: (segmentStyle.fontSize ?? 11) * SMALL_CAPS_SCALE
254
+ };
255
+ let glyphWidth = 0;
256
+ for (const segment of smallCapsSegments(measuredText)) glyphWidth += canvasMeasureTextWidth(segment.text, segment.small ? smallSegmentStyle : segmentStyle);
223
257
  let width = glyphWidth;
224
258
  if (letterSpacing) {
225
259
  const codePoints = countCodePoints(measuredText);
@@ -293,18 +327,26 @@ function canvasMeasureRun(text, sourceStyle) {
293
327
  ...style.eastAsiaAlternateFontFamily !== void 0 ? { alternateFontFamily: style.eastAsiaAlternateFontFamily } : {}
294
328
  }) : void 0;
295
329
  const complexScriptFont = hasComplexScriptFormatting(style) && !style.letterSpacing ? buildFontString(scriptStyle(style, SCRIPT_CLASS.complex)) : void 0;
330
+ const smallCapsFont = style.fontVariant === "small-caps" ? buildFontString({
331
+ ...style,
332
+ fontSize: (style.fontSize ?? 11) * SMALL_CAPS_SCALE
333
+ }) : void 0;
296
334
  const letterSpacing = style.letterSpacing ?? 0;
297
335
  const scale = getHorizontalScaleFactor(style.horizontalScale);
298
336
  const charWidths = [];
299
337
  let totalWidth = 0;
338
+ const smallCapsAt = smallCapsFont !== void 0 ? smallCapsMask(text) : void 0;
300
339
  let offset = 0;
340
+ let codePointIndex = 0;
301
341
  for (const char of text) {
302
342
  const cp = char.codePointAt(0);
303
- const measured = applyTextTransform(char, style);
304
- if (eastAsiaFont !== void 0 || complexScriptFont !== void 0) {
305
- const script = scriptClassOf(cp);
343
+ const isSmallCap = smallCapsAt?.[codePointIndex] ?? false;
344
+ const measured = isSmallCap ? char.toLocaleUpperCase() : applyTextTransform(char, style);
345
+ if (isSmallCap && smallCapsFont !== void 0) ctx.font = smallCapsFont;
346
+ else if (eastAsiaFont !== void 0 || complexScriptFont !== void 0) {
347
+ const script = scriptClassOf(cp, style.eastAsiaHint);
306
348
  ctx.font = (script === SCRIPT_CLASS.eastAsia ? eastAsiaFont : void 0) ?? (script === SCRIPT_CLASS.complex ? complexScriptFont : void 0) ?? baseFont;
307
- }
349
+ } else if (smallCapsFont !== void 0) ctx.font = baseFont;
308
350
  let charWidth = ctx.measureText(measured).width;
309
351
  if (letterSpacing && offset + char.length < text.length) charWidth += letterSpacing;
310
352
  charWidth *= scale;
@@ -312,6 +354,7 @@ function canvasMeasureRun(text, sourceStyle) {
312
354
  if (char.length === 2) charWidths.push(0);
313
355
  totalWidth += charWidth;
314
356
  offset += char.length;
357
+ codePointIndex += 1;
315
358
  }
316
359
  return {
317
360
  width: totalWidth,
@@ -2,8 +2,20 @@ import { RunFormatting } from "../types.js";
2
2
  import { FontStyle } from "./measureTypes.js";
3
3
  //#region src/layout-engine/measure/measureHelpers.d.ts
4
4
  declare const DEFAULT_FONT_SIZE = 11;
5
- declare const DEFAULT_FONT_FAMILY = "Calibri";
6
- declare const DOCX_SCRIPT_FONT_SCALE = 0.75;
5
+ /**
6
+ * The face for text whose run properties name no font anywhere in the cascade:
7
+ * no `w:rFonts` family or theme reference in the run, its styles or
8
+ * `w:docDefaults`. ECMA-376 §17.3.2.26 leaves this to the application; its
9
+ * default is Times New Roman. A document that names a font at any level never
10
+ * reaches it.
11
+ */
12
+ declare const DEFAULT_FONT_FAMILY = "Times New Roman";
13
+ /**
14
+ * Point size of `w:vertAlign="superscript"`/`"subscript"` text: two thirds
15
+ * of the run's size, truncated to whole half-points (the `w:sz` unit) and
16
+ * never below one half-point.
17
+ */
18
+ declare function docxScriptFontSize(fontSize: number): number;
7
19
  /**
8
20
  * Build a measurement `FontStyle` from a run's formatting. Single source of
9
21
  * truth for run → FontStyle so every measurement path (layout line-breaking,
@@ -88,4 +100,4 @@ declare function halfPtToPx(halfPt: number): number;
88
100
  */
89
101
  declare function pxToHalfPt(px: number): number;
90
102
  //#endregion
91
- export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DOCX_SCRIPT_FONT_SCALE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
103
+ export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, docxScriptFontSize, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
@@ -1,6 +1,7 @@
1
1
  import { resolveFontFamily } from "../../utils/fontResolver.js";
2
2
  import "../../utils/fontWeights.js";
3
3
  import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
4
+ import { eastAsiaHintApplies } from "../../utils/scriptSegments.js";
4
5
  import { FONT_KERNING_MODE, getRunFontKerningMode } from "./textMeasurementPolicy.js";
5
6
  //#region src/layout-engine/measure/measureHelpers.ts
6
7
  /**
@@ -16,8 +17,34 @@ const TWIPS_PER_INCH = 1440;
16
17
  const PX_PER_INCH = 96;
17
18
  const TWIPS_PER_PX = TWIPS_PER_INCH / PX_PER_INCH;
18
19
  const DEFAULT_FONT_SIZE = 11;
19
- const DEFAULT_FONT_FAMILY = "Calibri";
20
- const DOCX_SCRIPT_FONT_SCALE = .75;
20
+ /**
21
+ * The face for text whose run properties name no font anywhere in the cascade:
22
+ * no `w:rFonts` family or theme reference in the run, its styles or
23
+ * `w:docDefaults`. ECMA-376 §17.3.2.26 leaves this to the application; its
24
+ * default is Times New Roman. A document that names a font at any level never
25
+ * reaches it.
26
+ */
27
+ const DEFAULT_FONT_FAMILY = "Times New Roman";
28
+ /**
29
+ * Point size of `w:vertAlign="superscript"`/`"subscript"` text: two thirds
30
+ * of the run's size, truncated to whole half-points (the `w:sz` unit) and
31
+ * never below one half-point.
32
+ */
33
+ function docxScriptFontSize(fontSize) {
34
+ return Math.max(1, Math.floor(fontSize * 2 * 2 / 3)) / 2;
35
+ }
36
+ /**
37
+ * The `textTransform`/`fontVariant` a run's caps toggles contribute.
38
+ *
39
+ * `w:caps` wins when both are set: full-size capitals, never synthesized
40
+ * small ones (matching the else-if in formatToStyle.ts's live editor
41
+ * styling).
42
+ */
43
+ function capsStyleOf(run) {
44
+ if (run.allCaps) return { textTransform: "uppercase" };
45
+ if (run.smallCaps) return { fontVariant: "small-caps" };
46
+ return {};
47
+ }
21
48
  /**
22
49
  * Build a measurement `FontStyle` from a run's formatting. Single source of
23
50
  * truth for run → FontStyle so every measurement path (layout line-breaking,
@@ -28,13 +55,14 @@ const DOCX_SCRIPT_FONT_SCALE = .75;
28
55
  */
29
56
  function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
30
57
  const baseFontSize = run.fontSize ?? fallbackFontSize;
31
- const fontSize = run.superscript || run.subscript ? baseFontSize * DOCX_SCRIPT_FONT_SCALE : baseFontSize;
58
+ const fontSize = run.superscript || run.subscript ? docxScriptFontSize(baseFontSize) : baseFontSize;
32
59
  const horizontalScale = normalizeHorizontalScalePercent(run.horizontalScale);
33
60
  return {
34
61
  fontFamily: run.fontFamily ?? fallbackFontFamily,
35
62
  ...run.alternateFontFamily !== void 0 ? { alternateFontFamily: run.alternateFontFamily } : {},
36
63
  ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
37
64
  ...run.eastAsiaAlternateFontFamily !== void 0 ? { eastAsiaAlternateFontFamily: run.eastAsiaAlternateFontFamily } : {},
65
+ ...eastAsiaHintApplies(run) ? { eastAsiaHint: true } : {},
38
66
  ...run.complexScriptFontFamily !== void 0 ? { complexScriptFontFamily: run.complexScriptFontFamily } : {},
39
67
  ...run.complexScriptAlternateFontFamily !== void 0 ? { complexScriptAlternateFontFamily: run.complexScriptAlternateFontFamily } : {},
40
68
  ...run.complexScriptFontSize !== void 0 ? { complexScriptFontSize: run.complexScriptFontSize } : {},
@@ -45,8 +73,7 @@ function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
45
73
  ...run.bold !== void 0 ? { bold: run.bold } : {},
46
74
  ...run.italic !== void 0 ? { italic: run.italic } : {},
47
75
  ...run.letterSpacing !== void 0 ? { letterSpacing: run.letterSpacing } : {},
48
- ...run.allCaps ? { textTransform: "uppercase" } : {},
49
- ...run.smallCaps ? { fontVariant: "small-caps" } : {},
76
+ ...capsStyleOf(run),
50
77
  ...horizontalScale !== void 0 ? { horizontalScale } : {},
51
78
  kerning: getRunFontKerningMode(run, fallbackFontSize) === FONT_KERNING_MODE.enabled
52
79
  };
@@ -103,11 +130,10 @@ function clearFontResolvedCache() {
103
130
  function buildFontString(style) {
104
131
  const parts = [];
105
132
  if (style.italic) parts.push("italic");
106
- if (style.fontVariant) parts.push(style.fontVariant);
107
133
  if (style.bold) parts.push("700");
108
134
  const fontSizePx = ptToPx(style.fontSize ?? 11);
109
135
  parts.push(`${fontSizePx}px`);
110
- const fontFamily = style.fontFamily ?? "Calibri";
136
+ const fontFamily = style.fontFamily ?? "Times New Roman";
111
137
  parts.push(getResolvedFallback(fontFamily, style.alternateFontFamily));
112
138
  return parts.join(" ");
113
139
  }
@@ -181,4 +207,4 @@ function pxToHalfPt(px) {
181
207
  return pxToPt(px) * 2;
182
208
  }
183
209
  //#endregion
184
- export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DOCX_SCRIPT_FONT_SCALE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
210
+ export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, docxScriptFontSize, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };