@stll/folio-core 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +39 -0
  2. package/dist/ai-edits/apply.js +18 -4
  3. package/dist/ai-edits/blockRange.d.ts +13 -2
  4. package/dist/ai-edits/blockRange.js +23 -2
  5. package/dist/ai-edits/headless.d.ts +31 -4
  6. package/dist/ai-edits/headless.js +148 -35
  7. package/dist/ai-edits/index.d.ts +6 -4
  8. package/dist/ai-edits/index.js +4 -1
  9. package/dist/ai-edits/scoped-reading.d.ts +9 -0
  10. package/dist/ai-edits/scoped-reading.js +60 -0
  11. package/dist/ai-edits/snapshot.js +53 -7
  12. package/dist/ai-edits/types.d.ts +46 -3
  13. package/dist/compat/eigenpal.d.ts +8 -3
  14. package/dist/compat/eigenpal.js +7 -2
  15. package/dist/controller/fontReadiness.d.ts +29 -0
  16. package/dist/controller/fontReadiness.js +139 -0
  17. package/dist/controller/latestRequestGate.d.ts +13 -0
  18. package/dist/controller/latestRequestGate.js +16 -0
  19. package/dist/controller/layoutPipeline.js +88 -10
  20. package/dist/document-operations.d.ts +10 -3
  21. package/dist/document-operations.js +29 -14
  22. package/dist/docx/blockContentParser.js +53 -10
  23. package/dist/docx/commentParser.js +1 -1
  24. package/dist/docx/compatibility.d.ts +30 -2
  25. package/dist/docx/compatibility.js +118 -39
  26. package/dist/docx/conformance.d.ts +6 -0
  27. package/dist/docx/conformance.js +18 -0
  28. package/dist/docx/encryption/agileDecryption.js +1 -1
  29. package/dist/docx/encryption/encryptionInfo.js +1 -1
  30. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  31. package/dist/docx/ensureParaIds.d.ts +29 -0
  32. package/dist/docx/ensureParaIds.js +423 -0
  33. package/dist/docx/fontTableParser.d.ts +6 -0
  34. package/dist/docx/fontTableParser.js +72 -0
  35. package/dist/docx/groupDrawingParser.js +7 -2
  36. package/dist/docx/headerFooterParser.js +1 -1
  37. package/dist/docx/normalizeBaseDirection.js +1 -1
  38. package/dist/docx/numberingParser.js +2 -0
  39. package/dist/docx/paragraphParser.js +21 -1
  40. package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
  41. package/dist/docx/parser.d.ts +1 -1
  42. package/dist/docx/parser.js +14 -9
  43. package/dist/docx/rezip.js +70 -5
  44. package/dist/docx/runConsolidator.js +4 -0
  45. package/dist/docx/runParser.js +19 -1
  46. package/dist/docx/selectiveSave.js +3 -3
  47. package/dist/docx/selectiveXmlPatch.d.ts +10 -1
  48. package/dist/docx/selectiveXmlPatch.js +1 -1
  49. package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
  50. package/dist/docx/serializer/fontTableSerializer.js +26 -0
  51. package/dist/docx/serializer/headerFooterSerializer.js +1 -1
  52. package/dist/docx/serializer/paragraphSerializer.js +8 -0
  53. package/dist/docx/serializer/runSerializer.js +14 -2
  54. package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
  55. package/dist/docx/serializer/settingsSerializer.js +16 -0
  56. package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
  57. package/dist/docx/serializer/stylesSerializer.js +56 -0
  58. package/dist/docx/serializer/tableSerializer.js +29 -26
  59. package/dist/docx/serializer/themeSerializer.d.ts +6 -0
  60. package/dist/docx/serializer/themeSerializer.js +36 -0
  61. package/dist/docx/settingsParser.js +46 -0
  62. package/dist/docx/styleParser.js +26 -1
  63. package/dist/docx/tableParser.js +19 -1
  64. package/dist/docx/textBoxParser.js +6 -1
  65. package/dist/docx/vmlImageParser.js +146 -1
  66. package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
  67. package/dist/i18n/messages/catalogs.gen.js +5910 -5910
  68. package/dist/index.d.ts +8 -3
  69. package/dist/index.js +7 -2
  70. package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
  71. package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
  72. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
  73. package/dist/layout-bridge/convert/headerFooterLayout.js +22 -3
  74. package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
  75. package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
  76. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
  77. package/dist/layout-bridge/convert/toFlowBlocks.js +93 -17
  78. package/dist/layout-bridge/engine/hitTest.js +2 -1
  79. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  80. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  81. package/dist/layout-bridge/engine/selectionRects.js +2 -1
  82. package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
  83. package/dist/layout-engine/headerFooterRefs.js +40 -0
  84. package/dist/layout-engine/index.d.ts +3 -2
  85. package/dist/layout-engine/index.js +68 -106
  86. package/dist/layout-engine/measure/cache.js +11 -3
  87. package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
  88. package/dist/layout-engine/measure/index.d.ts +2 -1
  89. package/dist/layout-engine/measure/index.js +2 -1
  90. package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
  91. package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
  92. package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
  93. package/dist/layout-engine/measure/lineBreaks.js +11 -18
  94. package/dist/layout-engine/measure/listMarkerWidth.d.ts +4 -1
  95. package/dist/layout-engine/measure/listMarkerWidth.js +33 -8
  96. package/dist/layout-engine/measure/measureBlocks.d.ts +4 -2
  97. package/dist/layout-engine/measure/measureBlocks.js +138 -86
  98. package/dist/layout-engine/measure/measureContainer.js +32 -9
  99. package/dist/layout-engine/measure/measureHelpers.js +5 -2
  100. package/dist/layout-engine/measure/measureParagraph.js +346 -74
  101. package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
  102. package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
  103. package/dist/layout-engine/measure/measureWorker.js +8 -19
  104. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
  105. package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
  106. package/dist/layout-engine/measure/tableCellFloating.js +37 -29
  107. package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
  108. package/dist/layout-engine/measure/tableCellFlow.js +73 -0
  109. package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
  110. package/dist/layout-engine/measure/tableCellGrid.js +62 -0
  111. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
  112. package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
  113. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
  114. package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
  115. package/dist/layout-engine/paginator.d.ts +2 -1
  116. package/dist/layout-engine/paginator.js +7 -6
  117. package/dist/layout-engine/paragraphFrame.d.ts +22 -0
  118. package/dist/layout-engine/paragraphFrame.js +17 -0
  119. package/dist/layout-engine/paragraphSequence.d.ts +7 -0
  120. package/dist/layout-engine/paragraphSequence.js +25 -0
  121. package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
  122. package/dist/layout-engine/paragraphSpacing.js +30 -0
  123. package/dist/layout-engine/renderedBreakReconciliation.d.ts +63 -0
  124. package/dist/layout-engine/renderedBreakReconciliation.js +86 -0
  125. package/dist/layout-engine/tableRowBreak.d.ts +5 -2
  126. package/dist/layout-engine/tableRowBreak.js +62 -29
  127. package/dist/layout-engine/types.d.ts +54 -12
  128. package/dist/layout-engine/types.js +15 -2
  129. package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
  130. package/dist/layout-painter/anchoredImagePosition.js +95 -0
  131. package/dist/layout-painter/borderStroke.d.ts +23 -0
  132. package/dist/layout-painter/borderStroke.js +39 -0
  133. package/dist/layout-painter/renderImage.d.ts +5 -4
  134. package/dist/layout-painter/renderImage.js +18 -4
  135. package/dist/layout-painter/renderPage.d.ts +2 -27
  136. package/dist/layout-painter/renderPage.js +15 -101
  137. package/dist/layout-painter/renderParagraph.js +45 -26
  138. package/dist/layout-painter/renderTable.d.ts +2 -0
  139. package/dist/layout-painter/renderTable.js +257 -47
  140. package/dist/layout-painter/renderTextBox.js +17 -7
  141. package/dist/managers/DocumentLoaderManager.d.ts +1 -1
  142. package/dist/managers/DocumentLoaderManager.js +2 -2
  143. package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
  144. package/dist/paged-layout/sectionBlockWidths.js +9 -0
  145. package/dist/paged-layout/sectionGeometry.js +1 -1
  146. package/dist/prosemirror/attrs/index.d.ts +4 -2
  147. package/dist/prosemirror/attrs/index.js +32 -2
  148. package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
  149. package/dist/prosemirror/conversion/fromProseDoc.js +35 -18
  150. package/dist/prosemirror/conversion/toProseDoc.js +73 -37
  151. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  152. package/dist/prosemirror/extensions/core/ParagraphExtension.js +8 -2
  153. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
  154. package/dist/prosemirror/extensions/features/ListExtension.js +2 -0
  155. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
  156. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
  157. package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
  158. package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
  159. package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
  160. package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
  161. package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
  162. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
  163. package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
  164. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  165. package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
  166. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
  167. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
  168. package/dist/prosemirror/schema/index.d.ts +2 -2
  169. package/dist/prosemirror/schema/marks.d.ts +6 -1
  170. package/dist/prosemirror/schema/nodes.d.ts +16 -12
  171. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  172. package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
  173. package/dist/prosemirror/utils/tabCalculator.js +12 -10
  174. package/dist/prosemirror/validation.js +4 -1
  175. package/dist/redline.js +13 -3
  176. package/dist/server.d.ts +9 -4
  177. package/dist/server.js +7 -2
  178. package/dist/style-sets/extract.d.ts +35 -0
  179. package/dist/style-sets/extract.js +123 -0
  180. package/dist/style-sets/stellaStyle.d.ts +13 -0
  181. package/dist/style-sets/stellaStyle.js +516 -0
  182. package/dist/style-sets/types.d.ts +31 -0
  183. package/dist/style-sets/types.js +5 -0
  184. package/dist/types/index.d.ts +2 -1
  185. package/dist/utils/createDocument.d.ts +12 -2
  186. package/dist/utils/createDocument.js +55 -36
  187. package/dist/utils/fontResolver.js +6 -6
  188. package/dist/utils/formatToStyle.js +1 -1
  189. package/dist/utils/hexId.d.ts +8 -1
  190. package/dist/utils/hexId.js +11 -3
  191. package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
  192. package/dist/utils/paragraphFormattingMerge.js +14 -4
  193. package/package.json +5 -1
@@ -1,9 +1,13 @@
1
- import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, isFloatingTextBoxBlock, tableColumnsArePinned } from "../types.js";
1
+ import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, tableColumnsArePinned } from "../types.js";
2
2
  import { findClearLineY, measureParagraph } from "./measureParagraph.js";
3
3
  import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
4
+ import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "./tableCellFlow.js";
5
+ import { layoutTextBoxParagraphs } from "./textBoxParagraphLayout.js";
4
6
  import { getTextBoxGroupId } from "../textBoxGroup.js";
7
+ import { isParagraphFrameTextBox } from "../paragraphFrame.js";
5
8
  import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrumentation.js";
6
- import { bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
9
+ import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
10
+ import { buildTableCellGrid, getFirstAvailableColumn, getTableCellVerticalBorderHeight } from "./tableCellGrid.js";
7
11
  //#region src/layout-engine/measure/measureBlocks.ts
8
12
  /**
9
13
  * Pseudo-infinite measurement width (px) used for `w:noWrap` table cells so
@@ -25,7 +29,7 @@ const NO_WRAP_MEASURE_WIDTH = 1e6;
25
29
  * around a background letterhead or a foreground overlay (Codex
26
30
  * PR #258 review).
27
31
  */
28
- function isFloatingImageRun(run) {
32
+ function isSideWrappingImageRun(run) {
29
33
  const wrapType = run.wrapType;
30
34
  const displayMode = run.displayMode;
31
35
  if (wrapType === "behind" || wrapType === "inFront") return false;
@@ -49,42 +53,6 @@ function resolveTableWidthPx(width, widthType, contentWidth) {
49
53
  if (widthType === "pct") return contentWidth * width / 5e3;
50
54
  if (widthType === "dxa" || !widthType || widthType === "auto") return Math.round(width / 20 * 1.333);
51
55
  }
52
- function isInlineFlowImageRun(run) {
53
- if (run.displayMode === "float") return false;
54
- if (run.wrapType === "square" || run.wrapType === "tight" || run.wrapType === "through" || run.wrapType === "behind" || run.wrapType === "inFront") return false;
55
- if (run.displayMode === "block" || run.wrapType === "topAndBottom") return false;
56
- return true;
57
- }
58
- function measureTableCellBlockVisualHeight(block, blockMeasure) {
59
- if (block.kind === "textBox" && isFloatingTextBoxBlock(block)) return 0;
60
- if (block.kind !== "paragraph" || blockMeasure.kind !== "paragraph") {
61
- if ("totalHeight" in blockMeasure) return blockMeasure.totalHeight;
62
- if ("height" in blockMeasure) return blockMeasure.height;
63
- return 0;
64
- }
65
- const paragraphBlock = block;
66
- const paragraphMeasure = blockMeasure;
67
- const nonEmptyRuns = paragraphBlock.runs.filter((run) => run.kind !== "text" || run.text.replace(/\u00a0/gu, " ").trim().length > 0);
68
- if (paragraphMeasure.lines.length !== 1 || nonEmptyRuns.length === 0) return paragraphMeasure.totalHeight;
69
- const inlineImageRuns = [];
70
- for (const run of nonEmptyRuns) {
71
- if (run.kind !== "image" || !isInlineFlowImageRun(run)) return paragraphMeasure.totalHeight;
72
- inlineImageRuns.push(run);
73
- }
74
- let maxImageHeight = 0;
75
- for (const run of inlineImageRuns) maxImageHeight = Math.max(maxImageHeight, run.height);
76
- if (inlineImageRuns.length === 1) {
77
- const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
78
- const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
79
- return spacingBefore + maxImageHeight + spacingAfter;
80
- }
81
- const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
82
- const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
83
- return spacingBefore + maxImageHeight + spacingAfter;
84
- }
85
- function getTableCellVerticalBorderHeight(cell, isFirstRow) {
86
- return (isFirstRow ? cell?.borders?.top?.width ?? 0 : 0) + (cell?.borders?.bottom?.width ?? 0);
87
- }
88
56
  function measureTableBlock(tableBlock, contentWidth, fieldValues) {
89
57
  const DEFAULT_CELL_PADDING_X = 7;
90
58
  const DEFAULT_CELL_PADDING_Y = 0;
@@ -101,38 +69,17 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
101
69
  columnWidths = columnWidths.map((w) => w * scale);
102
70
  }
103
71
  }
104
- const occupiedColumnsPerRow = /* @__PURE__ */ new Map();
105
- for (let rowIdx = 0; rowIdx < tableBlock.rows.length; rowIdx++) {
106
- const row = tableBlock.rows[rowIdx];
107
- if (!row) continue;
108
- let colIdx = 0;
109
- const occupied = occupiedColumnsPerRow.get(rowIdx) ?? /* @__PURE__ */ new Set();
110
- while (occupied.has(colIdx)) colIdx++;
111
- for (const cell of row.cells) {
112
- const colSpan = cell.colSpan ?? 1;
113
- const rowSpan = cell.rowSpan ?? 1;
114
- if (rowSpan > 1) for (let r = rowIdx + 1; r < rowIdx + rowSpan; r++) {
115
- if (!occupiedColumnsPerRow.has(r)) occupiedColumnsPerRow.set(r, /* @__PURE__ */ new Set());
116
- const occSet = occupiedColumnsPerRow.get(r);
117
- for (let c = 0; c < colSpan; c++) occSet.add(colIdx + c);
118
- }
119
- colIdx += colSpan;
120
- while (occupied.has(colIdx)) colIdx++;
121
- }
122
- }
72
+ const cellGrid = buildTableCellGrid(tableBlock.rows, columnWidths.length);
123
73
  const columnsPinned = tableColumnsArePinned(tableBlock);
124
74
  const rows = tableBlock.rows.map((row, rowIdx) => {
125
- let columnIndex = 0;
126
- const occupied = occupiedColumnsPerRow.get(rowIdx) ?? /* @__PURE__ */ new Set();
127
- while (occupied.has(columnIndex)) columnIndex++;
75
+ let columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, row.gridBefore ?? 0);
128
76
  return {
129
77
  cells: row.cells.map((cell) => {
130
78
  const colSpan = cell.colSpan ?? 1;
131
79
  let cellWidth = 0;
132
80
  for (let c = 0; c < colSpan && columnIndex + c < columnWidths.length; c++) cellWidth += columnWidths[columnIndex + c] ?? 0;
133
81
  if (cellWidth === 0) cellWidth = cell.width ?? 100;
134
- columnIndex += colSpan;
135
- while (occupied.has(columnIndex)) columnIndex++;
82
+ columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, columnIndex + colSpan);
136
83
  const padLeft = cell.padding?.left ?? DEFAULT_CELL_PADDING_X;
137
84
  const padRight = cell.padding?.right ?? DEFAULT_CELL_PADDING_X;
138
85
  const cellContentWidth = Math.max(1, cellWidth - padLeft - padRight);
@@ -163,29 +110,31 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
163
110
  }
164
111
  const sourceRowCells = tableBlock.rows[rowIdx]?.cells;
165
112
  let maxCellHeightWithBorders = 0;
166
- let maxBorderHeight = 0;
113
+ let maxCellInsets = 0;
167
114
  for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
168
115
  const cell = row.cells[cellIdx];
169
116
  const sourceCell = sourceRowCells?.[cellIdx];
170
117
  cell.height = 0;
118
+ const flowState = createTableCellFlowState();
171
119
  for (let blockIdx = 0; blockIdx < cell.blocks.length; blockIdx++) {
172
120
  const sourceBlock = sourceCell?.blocks[blockIdx];
173
121
  const blockMeasure = cell.blocks[blockIdx];
174
122
  if (!sourceBlock || !blockMeasure) continue;
175
- cell.height += measureTableCellBlockVisualHeight(sourceBlock, blockMeasure);
123
+ placeTableCellBlock(flowState, sourceBlock, blockMeasure);
176
124
  }
125
+ cell.height = finishTableCellFlow(flowState);
177
126
  const padTop = sourceCell?.padding?.top ?? DEFAULT_CELL_PADDING_Y;
178
127
  const padBottom = sourceCell?.padding?.bottom ?? DEFAULT_CELL_PADDING_Y;
179
128
  cell.height += padTop + padBottom;
180
129
  if ((sourceCell?.rowSpan ?? 1) > 1) continue;
181
- const borderHeight = getTableCellVerticalBorderHeight(sourceCell, rowIdx === 0);
130
+ const borderHeight = getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx);
182
131
  maxCellHeightWithBorders = Math.max(maxCellHeightWithBorders, cell.height + borderHeight);
183
- maxBorderHeight = Math.max(maxBorderHeight, borderHeight);
132
+ maxCellInsets = Math.max(maxCellInsets, padTop + padBottom + borderHeight);
184
133
  }
185
134
  const explicitHeight = sourceRow?.height;
186
135
  const heightRule = sourceRow?.heightRule;
187
136
  if (explicitHeight && heightRule === "exact") row.height = explicitHeight;
188
- else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight + maxBorderHeight);
137
+ else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight + maxCellInsets);
189
138
  else row.height = maxCellHeightWithBorders;
190
139
  }
191
140
  for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
@@ -200,7 +149,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
200
149
  const spanEnd = Math.min(rows.length, rowIdx + rowSpan);
201
150
  let combinedHeight = 0;
202
151
  for (let spannedRowIdx = rowIdx; spannedRowIdx < spanEnd; spannedRowIdx++) combinedHeight += rows[spannedRowIdx]?.height ?? 0;
203
- const deficit = measuredCell.height + getTableCellVerticalBorderHeight(sourceCell, rowIdx === 0) - combinedHeight;
152
+ const deficit = measuredCell.height + getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx) - combinedHeight;
204
153
  if (deficit <= 0) continue;
205
154
  for (let spannedRowIdx = spanEnd - 1; spannedRowIdx >= rowIdx; spannedRowIdx--) {
206
155
  if (tableBlock.rows[spannedRowIdx]?.heightRule === "exact") continue;
@@ -223,13 +172,34 @@ function perBlockNumberValue(value, blockIndex, fallback) {
223
172
  if (Array.isArray(value)) return value[blockIndex] ?? fallback;
224
173
  return value;
225
174
  }
175
+ function textBoxWrapSide({ box, contentX, boxWidth, contentWidth }) {
176
+ if (box.wrapText === "left") return "left";
177
+ if (box.wrapText === "right") return "right";
178
+ const leftSpace = contentX;
179
+ const rightSpace = contentWidth - contentX - boxWidth;
180
+ if (box.wrapText === "largest") return leftSpace >= rightSpace ? "left" : "right";
181
+ return contentX + boxWidth / 2 < contentWidth / 2 ? "right" : "left";
182
+ }
226
183
  function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry) {
227
184
  const zones = [];
185
+ const defaultContentWidth = Array.isArray(contentWidth) ? contentWidth[0] ?? 0 : contentWidth;
228
186
  const textBoxGroupAnchors = /* @__PURE__ */ new Map();
187
+ const paragraphFrameGroupSizes = /* @__PURE__ */ new Map();
188
+ for (const block of blocks) {
189
+ if (block.kind !== "textBox" || !isParagraphFrameTextBox(block)) continue;
190
+ const groupId = getTextBoxGroupId(block);
191
+ if (groupId !== void 0) paragraphFrameGroupSizes.set(groupId, (paragraphFrameGroupSizes.get(groupId) ?? 0) + 1);
192
+ }
229
193
  const defaultMarginTop = Array.isArray(marginTop) ? marginTop[0] ?? 0 : marginTop;
194
+ const pageWidthInput = pageGeometry?.pageWidth ?? defaultContentWidth;
230
195
  const pageHeightInput = pageGeometry?.pageHeight ?? 0;
196
+ const marginLeftInput = pageGeometry?.marginLeft ?? 0;
197
+ const marginRightInput = pageGeometry?.marginRight ?? 0;
231
198
  const marginBottomInput = pageGeometry?.marginBottom ?? 0;
199
+ const defaultPageWidth = Array.isArray(pageWidthInput) ? pageWidthInput[0] ?? defaultContentWidth : pageWidthInput;
232
200
  const defaultPageHeight = Array.isArray(pageHeightInput) ? pageHeightInput[0] ?? 0 : pageHeightInput;
201
+ const defaultMarginLeft = Array.isArray(marginLeftInput) ? marginLeftInput[0] ?? 0 : marginLeftInput;
202
+ const defaultMarginRight = Array.isArray(marginRightInput) ? marginRightInput[0] ?? 0 : marginRightInput;
233
203
  const defaultMarginBottom = Array.isArray(marginBottomInput) ? marginBottomInput[0] ?? 0 : marginBottomInput;
234
204
  for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
235
205
  const block = blocks[blockIndex];
@@ -238,13 +208,36 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
238
208
  for (const run of paragraphBlock.runs) {
239
209
  if (run.kind !== "image") continue;
240
210
  const imgRun = run;
241
- if (!isFloatingImageRun(imgRun)) continue;
211
+ const positionedBand = imgRun.wrapType === "topAndBottom" && imgRun.position !== void 0;
212
+ if (!isSideWrappingImageRun(imgRun) && !positionedBand) continue;
242
213
  let topY = 0;
243
214
  const position = imgRun.position;
244
215
  const distTop = imgRun.distTop ?? 0;
245
216
  const distBottom = imgRun.distBottom ?? 0;
246
217
  const distLeft = imgRun.distLeft ?? 12;
247
218
  const distRight = imgRun.distRight ?? 12;
219
+ if (positionedBand && position !== void 0) {
220
+ const vertical = position.vertical;
221
+ const blockMarginTop = perBlockNumberValue(marginTop, blockIndex, defaultMarginTop);
222
+ const pageFrameRelative = isPageFrameRelativeAnchor(vertical?.relativeTo);
223
+ const rawTop = pageFrameRelative ? bandTopContentY(vertical, {
224
+ pageHeight: perBlockNumberValue(pageHeightInput, blockIndex, defaultPageHeight),
225
+ marginTop: blockMarginTop,
226
+ marginBottom: perBlockNumberValue(marginBottomInput, blockIndex, defaultMarginBottom),
227
+ boxHeight: imgRun.height
228
+ }) : emuToPixels(vertical?.posOffset ?? 0);
229
+ const bottomY = rawTop + imgRun.height + distBottom;
230
+ if (bottomY > 0) zones.push({
231
+ leftMargin: 0,
232
+ rightMargin: 0,
233
+ topY: Math.max(0, rawTop - distTop),
234
+ bottomY,
235
+ anchorBlockIndex: blockIndex,
236
+ ...pageFrameRelative ? { isMarginRelative: true } : {},
237
+ fullWidthBlock: true
238
+ });
239
+ continue;
240
+ }
248
241
  if (position?.vertical) {
249
242
  const v = position.vertical;
250
243
  if (v.align === "top" && v.relativeTo === "margin") topY = 0;
@@ -259,8 +252,8 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
259
252
  else if (h.align === "right") rightMargin = imgRun.width + distLeft;
260
253
  else if (h.posOffset !== void 0) {
261
254
  const x = emuToPixels(h.posOffset);
262
- if (x < contentWidth / 2) leftMargin = x + imgRun.width + distRight;
263
- else rightMargin = contentWidth - x + distLeft;
255
+ if (x < defaultContentWidth / 2) leftMargin = x + imgRun.width + distRight;
256
+ else rightMargin = defaultContentWidth - x + distLeft;
264
257
  }
265
258
  } else if (imgRun.cssFloat === "left") leftMargin = imgRun.width + distRight;
266
259
  else if (imgRun.cssFloat === "right") rightMargin = imgRun.width + distLeft;
@@ -283,7 +276,7 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
283
276
  const tableBlock = block;
284
277
  const floating = tableBlock.floating;
285
278
  if (!floating) continue;
286
- const tableMeasure = measureTableBlock(tableBlock, contentWidth);
279
+ const tableMeasure = measureTableBlock(tableBlock, defaultContentWidth);
287
280
  const tableWidth = tableMeasure.totalWidth;
288
281
  const tableHeight = tableMeasure.totalHeight;
289
282
  const distLeft = floating.leftFromText ?? 12;
@@ -295,12 +288,12 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
295
288
  let x = 0;
296
289
  if (floating.tblpX !== void 0) x = floating.tblpX;
297
290
  else if (floating.tblpXSpec) if (floating.tblpXSpec === "left" || floating.tblpXSpec === "inside") x = 0;
298
- else if (floating.tblpXSpec === "right" || floating.tblpXSpec === "outside") x = contentWidth - tableWidth;
299
- else x = (contentWidth - tableWidth) / 2;
300
- else if (tableBlock.justification === "center") x = (contentWidth - tableWidth) / 2;
301
- else if (tableBlock.justification === "right") x = contentWidth - tableWidth;
302
- if (x < contentWidth / 2) leftMargin = x + tableWidth + distRight;
303
- else rightMargin = contentWidth - x + distLeft;
291
+ else if (floating.tblpXSpec === "right" || floating.tblpXSpec === "outside") x = defaultContentWidth - tableWidth;
292
+ else x = (defaultContentWidth - tableWidth) / 2;
293
+ else if (tableBlock.justification === "center") x = (defaultContentWidth - tableWidth) / 2;
294
+ else if (tableBlock.justification === "right") x = defaultContentWidth - tableWidth;
295
+ if (x < defaultContentWidth / 2) leftMargin = x + tableWidth + distRight;
296
+ else rightMargin = defaultContentWidth - x + distLeft;
304
297
  const topY = floating.tblpY ?? 0;
305
298
  const bottomY = topY + tableHeight;
306
299
  zones.push({
@@ -311,6 +304,64 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
311
304
  anchorBlockIndex: blockIndex
312
305
  });
313
306
  }
307
+ for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
308
+ const block = blocks[blockIndex];
309
+ if (block.kind !== "textBox") continue;
310
+ const tb = block;
311
+ if (!floatingTextBoxWrapsText(tb) || tb.position === void 0) continue;
312
+ const measure = measureTextBoxBlock(tb);
313
+ const blockMarginTop = perBlockNumberValue(marginTop, blockIndex, defaultMarginTop);
314
+ const blockMarginLeft = perBlockNumberValue(marginLeftInput, blockIndex, defaultMarginLeft);
315
+ const blockMarginRight = perBlockNumberValue(marginRightInput, blockIndex, defaultMarginRight);
316
+ const blockPageWidth = perBlockNumberValue(pageWidthInput, blockIndex, defaultPageWidth);
317
+ const blockContentWidth = perBlockNumberValue(contentWidth, blockIndex, defaultContentWidth);
318
+ const vertical = tb.position.vertical;
319
+ const pageFrameRelative = isPageFrameRelativeAnchor(vertical?.relativeTo);
320
+ const topY = pageFrameRelative ? bandTopContentY(vertical, {
321
+ pageHeight: perBlockNumberValue(pageHeightInput, blockIndex, defaultPageHeight),
322
+ marginTop: blockMarginTop,
323
+ marginBottom: perBlockNumberValue(marginBottomInput, blockIndex, defaultMarginBottom),
324
+ boxHeight: measure.height
325
+ }) : emuToPixels(vertical?.posOffset ?? 0);
326
+ const groupId = getTextBoxGroupId(tb);
327
+ const anchorBlockIndex = groupId ? textBoxGroupAnchors.get(groupId) ?? blockIndex : blockIndex;
328
+ if (groupId && !textBoxGroupAnchors.has(groupId)) textBoxGroupAnchors.set(groupId, blockIndex);
329
+ if (isParagraphFrameTextBox(tb) && groupId !== void 0 && (paragraphFrameGroupSizes.get(groupId) ?? 0) > 1) {
330
+ zones.push({
331
+ leftMargin: 0,
332
+ rightMargin: 0,
333
+ topY: 0,
334
+ bottomY: topY + measure.height + (tb.distBottom ?? 0),
335
+ anchorBlockIndex,
336
+ ...pageFrameRelative ? { isMarginRelative: true } : {},
337
+ fullWidthBlock: true
338
+ });
339
+ continue;
340
+ }
341
+ const horizontal = tb.position.horizontal;
342
+ const contentX = (horizontal ? bandFragmentX(horizontal, {
343
+ pageWidth: blockPageWidth,
344
+ marginLeft: blockMarginLeft,
345
+ marginRight: blockMarginRight,
346
+ boxWidth: measure.width
347
+ }) : blockMarginLeft) - blockMarginLeft;
348
+ const wrapSide = textBoxWrapSide({
349
+ box: tb,
350
+ contentX,
351
+ boxWidth: measure.width,
352
+ contentWidth: blockContentWidth
353
+ });
354
+ const leftMargin = wrapSide === "right" ? contentX + measure.width + (tb.distRight ?? 12) : 0;
355
+ const rightMargin = wrapSide === "left" ? blockContentWidth - contentX + (tb.distLeft ?? 12) : 0;
356
+ zones.push({
357
+ leftMargin: Math.max(0, leftMargin),
358
+ rightMargin: Math.max(0, rightMargin),
359
+ topY: topY - (tb.distTop ?? 0),
360
+ bottomY: topY + measure.height + (tb.distBottom ?? 0),
361
+ anchorBlockIndex,
362
+ ...pageFrameRelative ? { isMarginRelative: true } : {}
363
+ });
364
+ }
314
365
  for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
315
366
  const block = blocks[blockIndex];
316
367
  if (block.kind !== "textBox") continue;
@@ -389,8 +440,8 @@ function measureTextBoxBlock(tb, fieldValues) {
389
440
  const margins = tb.margins ?? DEFAULT_TEXTBOX_MARGINS;
390
441
  const innerWidth = tb.width - margins.left - margins.right;
391
442
  const innerMeasures = tb.content.map((p) => measureParagraph(p, innerWidth, fieldValues ? { fieldValues } : void 0));
392
- const contentHeight = innerMeasures.reduce((sum, m) => sum + m.totalHeight, 0);
393
- const totalHeight = tb.height ?? contentHeight + margins.top + margins.bottom;
443
+ const contentBoxHeight = layoutTextBoxParagraphs(tb.content, innerMeasures).totalHeight + margins.top + margins.bottom;
444
+ const totalHeight = tb.autoFit === "shape" ? Math.max(tb.height ?? 0, contentBoxHeight) : tb.height ?? contentBoxHeight;
394
445
  return {
395
446
  kind: "textBox",
396
447
  width: tb.width,
@@ -418,7 +469,7 @@ function isNextPageSectionBreak(block) {
418
469
  */
419
470
  function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldValues) {
420
471
  const defaultWidth = Array.isArray(contentWidth) ? contentWidth[0] ?? 0 : contentWidth;
421
- const floatingZonesWithAnchors = extractFloatingZones(blocks, defaultWidth, marginTop, pageGeometry);
472
+ const floatingZonesWithAnchors = extractFloatingZones(blocks, contentWidth, marginTop, pageGeometry);
422
473
  const marginRelative = floatingZonesWithAnchors.filter((z) => z.isMarginRelative && !z.fullWidthBlock);
423
474
  const ownAnchorZones = floatingZonesWithAnchors.filter((z) => !z.isMarginRelative || z.fullWidthBlock);
424
475
  const marginByTopY = /* @__PURE__ */ new Map();
@@ -461,10 +512,11 @@ function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldV
461
512
  try {
462
513
  const blockWidth = Array.isArray(contentWidth) ? contentWidth[blockIndex] ?? defaultWidth : contentWidth;
463
514
  const measure = measureBlock(block, blockWidth, zones, cumulativeY, fieldValues);
464
- const bandZones = zones?.filter((zone) => zone.fullWidthBlock);
465
- if (bandZones?.length && (measure.kind === "image" || measure.kind === "table" && !block.floating)) {
515
+ const clearingZones = measure.kind === "table" ? zones : zones?.filter((zone) => zone.fullWidthBlock);
516
+ if (clearingZones?.length && (measure.kind === "image" || measure.kind === "table" && !block.floating)) {
466
517
  const blockHeight = measure.kind === "image" ? measure.height : measure.totalHeight;
467
- const skip = findClearLineY(cumulativeY, blockHeight, bandZones, blockWidth, 24) - cumulativeY;
518
+ const requiredWidth = measure.kind === "table" ? Math.min(blockWidth, measure.totalWidth) : 24;
519
+ const skip = findClearLineY(cumulativeY, blockHeight, clearingZones, blockWidth, Math.max(24, requiredWidth)) - cumulativeY;
468
520
  if (skip > 0) {
469
521
  measure.bandSkipBefore = skip;
470
522
  cumulativeY += skip;
@@ -494,4 +546,4 @@ function measureSingleBlockWithoutFloatingZones(block, blockWidth, blockIndex) {
494
546
  return measureBlock(block, blockWidth);
495
547
  }
496
548
  //#endregion
497
- export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTableCellBlockVisualHeight, measureTextBoxBlock };
549
+ export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTextBoxBlock };
@@ -1,4 +1,5 @@
1
1
  import { setMeasureProvider } from "./measureProvider.js";
2
+ import { getFontKerningMode } from "./textMeasurementPolicy.js";
2
3
  import { buildFontString, getResolvedData, ptToPx } from "./measureHelpers.js";
3
4
  import { hasCjk, isCjkCodePoint, segmentByScript } from "../../utils/scriptSegments.js";
4
5
  import { getCachedFontMetrics, getCachedTextWidth, getTextWidthCacheGeneration, setCachedFontMetrics, setCachedTextWidth } from "./cache.js";
@@ -46,13 +47,14 @@ function getCanvasContext() {
46
47
  function resetCanvasContext() {
47
48
  canvasContext = null;
48
49
  }
49
- function getWorkerFontFingerprintWidth(ctx, font) {
50
+ function getWorkerFontFingerprintWidth(ctx, font, fontCacheKey, fontKerning) {
50
51
  const generation = getTextWidthCacheGeneration();
51
- const cached = workerFontFingerprintCache.get(font);
52
+ const cached = workerFontFingerprintCache.get(fontCacheKey);
52
53
  if (cached?.generation === generation) return cached.width;
53
54
  ctx.font = font;
55
+ ctx.fontKerning = fontKerning;
54
56
  const width = ctx.measureText(WORKER_FONT_FINGERPRINT_TEXT).width;
55
- workerFontFingerprintCache.set(font, {
57
+ workerFontFingerprintCache.set(fontCacheKey, {
56
58
  generation,
57
59
  width
58
60
  });
@@ -86,6 +88,7 @@ function canvasGetFontMetrics(style) {
86
88
  try {
87
89
  const ctx = getCanvasContext();
88
90
  ctx.font = buildFontString(style);
91
+ ctx.fontKerning = getFontKerningMode(style);
89
92
  const metrics = ctx.measureText("Hg");
90
93
  if (typeof metrics.actualBoundingBoxAscent === "number" && typeof metrics.actualBoundingBoxDescent === "number") {
91
94
  ascent = metrics.actualBoundingBoxAscent;
@@ -120,10 +123,12 @@ function canvasMeasureTextWidth(text, style) {
120
123
  const font = buildFontString(style);
121
124
  const letterSpacing = style.letterSpacing ?? 0;
122
125
  const horizontalScale = getHorizontalScaleFactor(style);
123
- const fontCacheKey = `${font}|scale:${horizontalScale}`;
126
+ const fontKerning = getFontKerningMode(style);
127
+ const fontCacheKey = style.kerning ? `${font}|kerning:normal|scale:${horizontalScale}` : `${font}|scale:${horizontalScale}`;
124
128
  const cached = getCachedTextWidth(measuredText, fontCacheKey, letterSpacing);
125
129
  if (cached !== void 0) return cached;
126
130
  ctx.font = font;
131
+ ctx.fontKerning = fontKerning;
127
132
  let width = ctx.measureText(measuredText).width;
128
133
  if (letterSpacing) {
129
134
  const codePoints = countCodePoints(measuredText);
@@ -132,9 +137,17 @@ function canvasMeasureTextWidth(text, style) {
132
137
  const scaledWidth = width * horizontalScale;
133
138
  setCachedTextWidth(measuredText, fontCacheKey, letterSpacing, scaledWidth);
134
139
  if (!canPrefetchMeasurement()) return scaledWidth;
135
- const fontFingerprintWidth = getWorkerFontFingerprintWidth(ctx, font);
136
- prefetchMeasurement(measuredText, font, letterSpacing, horizontalScale, fontCacheKey, fontFingerprintWidth);
137
- prefetchBinarySearchProbes(measuredText, font, fontCacheKey, fontFingerprintWidth, letterSpacing, horizontalScale);
140
+ const fontFingerprintWidth = getWorkerFontFingerprintWidth(ctx, font, fontCacheKey, fontKerning);
141
+ prefetchMeasurement({
142
+ text: measuredText,
143
+ font,
144
+ letterSpacing,
145
+ horizontalScale,
146
+ fontCacheKey,
147
+ fontFingerprintWidth,
148
+ fontKerning
149
+ });
150
+ prefetchBinarySearchProbes(measuredText, font, fontCacheKey, fontFingerprintWidth, letterSpacing, horizontalScale, fontKerning);
138
151
  return scaledWidth;
139
152
  }
140
153
  /**
@@ -150,6 +163,7 @@ function glyphAdvanceStyle(style, fontFamily) {
150
163
  if (style.bold !== void 0) result.bold = style.bold;
151
164
  if (style.italic !== void 0) result.italic = style.italic;
152
165
  if (style.fontVariant !== void 0) result.fontVariant = style.fontVariant;
166
+ if (style.kerning !== void 0) result.kerning = style.kerning;
153
167
  return result;
154
168
  }
155
169
  /**
@@ -186,12 +200,20 @@ function measureMixedScriptWidth(measuredText, style) {
186
200
  * pays the main-thread cost as usual. When the worker wins, that probe
187
201
  * lands on a hit.
188
202
  */
189
- function prefetchBinarySearchProbes(text, font, fontCacheKey, fontFingerprintWidth, letterSpacing, horizontalScale) {
203
+ function prefetchBinarySearchProbes(text, font, fontCacheKey, fontFingerprintWidth, letterSpacing, horizontalScale, fontKerning) {
190
204
  if (text.length < 4) return;
191
205
  for (let denom = 2; denom <= 8; denom *= 2) {
192
206
  const len = Math.floor(text.length / denom);
193
207
  if (len < 2) break;
194
- prefetchMeasurement(text.slice(0, len), font, letterSpacing, horizontalScale, fontCacheKey, fontFingerprintWidth);
208
+ prefetchMeasurement({
209
+ text: text.slice(0, len),
210
+ font,
211
+ letterSpacing,
212
+ horizontalScale,
213
+ fontCacheKey,
214
+ fontFingerprintWidth,
215
+ fontKerning
216
+ });
195
217
  }
196
218
  }
197
219
  /**
@@ -224,6 +246,7 @@ function canvasMeasureRun(text, style) {
224
246
  const ctx = getCanvasContext();
225
247
  const baseFont = buildFontString(style);
226
248
  ctx.font = baseFont;
249
+ ctx.fontKerning = style.kerning ? "normal" : "none";
227
250
  const eastAsiaFont = style.eastAsiaFontFamily !== void 0 && !style.letterSpacing ? buildFontString({
228
251
  ...style,
229
252
  fontFamily: style.eastAsiaFontFamily
@@ -1,5 +1,6 @@
1
1
  import { resolveFontFamily } from "../../utils/fontResolver.js";
2
2
  import "../../utils/fontWeights.js";
3
+ import { FONT_KERNING_MODE, getRunFontKerningMode } from "./textMeasurementPolicy.js";
3
4
  //#region src/layout-engine/measure/measureHelpers.ts
4
5
  /**
5
6
  * Pure measurement helpers — no canvas, no DOM, no worker.
@@ -23,16 +24,18 @@ const DEFAULT_FONT_FAMILY = "Calibri";
23
24
  * and size fallbacks for runs that declare neither.
24
25
  */
25
26
  function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
27
+ const fontSize = run.fontSize ?? fallbackFontSize;
26
28
  return {
27
29
  fontFamily: run.fontFamily ?? fallbackFontFamily,
28
30
  ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
29
- fontSize: run.fontSize ?? fallbackFontSize,
31
+ fontSize,
30
32
  ...run.bold !== void 0 ? { bold: run.bold } : {},
31
33
  ...run.italic !== void 0 ? { italic: run.italic } : {},
32
34
  ...run.letterSpacing !== void 0 ? { letterSpacing: run.letterSpacing } : {},
33
35
  ...run.allCaps ? { textTransform: "uppercase" } : {},
34
36
  ...run.smallCaps ? { fontVariant: "small-caps" } : {},
35
- ...run.horizontalScale !== void 0 ? { horizontalScale: run.horizontalScale } : {}
37
+ ...run.horizontalScale !== void 0 ? { horizontalScale: run.horizontalScale } : {},
38
+ kerning: getRunFontKerningMode(run, fallbackFontSize) === FONT_KERNING_MODE.enabled
36
39
  };
37
40
  }
38
41
  /** Cache for resolved font data */