@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.
- package/README.md +39 -0
- package/dist/ai-edits/apply.js +18 -4
- package/dist/ai-edits/blockRange.d.ts +13 -2
- package/dist/ai-edits/blockRange.js +23 -2
- package/dist/ai-edits/headless.d.ts +31 -4
- package/dist/ai-edits/headless.js +148 -35
- package/dist/ai-edits/index.d.ts +6 -4
- package/dist/ai-edits/index.js +4 -1
- package/dist/ai-edits/scoped-reading.d.ts +9 -0
- package/dist/ai-edits/scoped-reading.js +60 -0
- package/dist/ai-edits/snapshot.js +53 -7
- package/dist/ai-edits/types.d.ts +46 -3
- package/dist/compat/eigenpal.d.ts +8 -3
- package/dist/compat/eigenpal.js +7 -2
- package/dist/controller/fontReadiness.d.ts +29 -0
- package/dist/controller/fontReadiness.js +139 -0
- package/dist/controller/latestRequestGate.d.ts +13 -0
- package/dist/controller/latestRequestGate.js +16 -0
- package/dist/controller/layoutPipeline.js +88 -10
- package/dist/document-operations.d.ts +10 -3
- package/dist/document-operations.js +29 -14
- package/dist/docx/blockContentParser.js +53 -10
- package/dist/docx/commentParser.js +1 -1
- package/dist/docx/compatibility.d.ts +30 -2
- package/dist/docx/compatibility.js +118 -39
- package/dist/docx/conformance.d.ts +6 -0
- package/dist/docx/conformance.js +18 -0
- package/dist/docx/encryption/agileDecryption.js +1 -1
- package/dist/docx/encryption/encryptionInfo.js +1 -1
- package/dist/docx/encryption/openEncryptedDocx.js +1 -1
- package/dist/docx/ensureParaIds.d.ts +29 -0
- package/dist/docx/ensureParaIds.js +423 -0
- package/dist/docx/fontTableParser.d.ts +6 -0
- package/dist/docx/fontTableParser.js +72 -0
- package/dist/docx/groupDrawingParser.js +7 -2
- package/dist/docx/headerFooterParser.js +1 -1
- package/dist/docx/normalizeBaseDirection.js +1 -1
- package/dist/docx/numberingParser.js +2 -0
- package/dist/docx/paragraphParser.js +21 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
- package/dist/docx/parser.d.ts +1 -1
- package/dist/docx/parser.js +14 -9
- package/dist/docx/rezip.js +70 -5
- package/dist/docx/runConsolidator.js +4 -0
- package/dist/docx/runParser.js +19 -1
- package/dist/docx/selectiveSave.js +3 -3
- package/dist/docx/selectiveXmlPatch.d.ts +10 -1
- package/dist/docx/selectiveXmlPatch.js +1 -1
- package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
- package/dist/docx/serializer/fontTableSerializer.js +26 -0
- package/dist/docx/serializer/headerFooterSerializer.js +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +8 -0
- package/dist/docx/serializer/runSerializer.js +14 -2
- package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
- package/dist/docx/serializer/settingsSerializer.js +16 -0
- package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
- package/dist/docx/serializer/stylesSerializer.js +56 -0
- package/dist/docx/serializer/tableSerializer.js +29 -26
- package/dist/docx/serializer/themeSerializer.d.ts +6 -0
- package/dist/docx/serializer/themeSerializer.js +36 -0
- package/dist/docx/settingsParser.js +46 -0
- package/dist/docx/styleParser.js +26 -1
- package/dist/docx/tableParser.js +19 -1
- package/dist/docx/textBoxParser.js +6 -1
- package/dist/docx/vmlImageParser.js +146 -1
- package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
- package/dist/i18n/messages/catalogs.gen.js +5910 -5910
- package/dist/index.d.ts +8 -3
- package/dist/index.js +7 -2
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +22 -3
- package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
- package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +93 -17
- package/dist/layout-bridge/engine/hitTest.js +2 -1
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +2 -1
- package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
- package/dist/layout-engine/headerFooterRefs.js +40 -0
- package/dist/layout-engine/index.d.ts +3 -2
- package/dist/layout-engine/index.js +68 -106
- package/dist/layout-engine/measure/cache.js +11 -3
- package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
- package/dist/layout-engine/measure/index.d.ts +2 -1
- package/dist/layout-engine/measure/index.js +2 -1
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
- package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
- package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
- package/dist/layout-engine/measure/lineBreaks.js +11 -18
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +4 -1
- package/dist/layout-engine/measure/listMarkerWidth.js +33 -8
- package/dist/layout-engine/measure/measureBlocks.d.ts +4 -2
- package/dist/layout-engine/measure/measureBlocks.js +138 -86
- package/dist/layout-engine/measure/measureContainer.js +32 -9
- package/dist/layout-engine/measure/measureHelpers.js +5 -2
- package/dist/layout-engine/measure/measureParagraph.js +346 -74
- package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
- package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
- package/dist/layout-engine/measure/measureWorker.js +8 -19
- package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
- package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
- package/dist/layout-engine/measure/tableCellFloating.js +37 -29
- package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
- package/dist/layout-engine/measure/tableCellFlow.js +73 -0
- package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
- package/dist/layout-engine/measure/tableCellGrid.js +62 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
- package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
- package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
- package/dist/layout-engine/paginator.d.ts +2 -1
- package/dist/layout-engine/paginator.js +7 -6
- package/dist/layout-engine/paragraphFrame.d.ts +22 -0
- package/dist/layout-engine/paragraphFrame.js +17 -0
- package/dist/layout-engine/paragraphSequence.d.ts +7 -0
- package/dist/layout-engine/paragraphSequence.js +25 -0
- package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
- package/dist/layout-engine/paragraphSpacing.js +30 -0
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +63 -0
- package/dist/layout-engine/renderedBreakReconciliation.js +86 -0
- package/dist/layout-engine/tableRowBreak.d.ts +5 -2
- package/dist/layout-engine/tableRowBreak.js +62 -29
- package/dist/layout-engine/types.d.ts +54 -12
- package/dist/layout-engine/types.js +15 -2
- package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
- package/dist/layout-painter/anchoredImagePosition.js +95 -0
- package/dist/layout-painter/borderStroke.d.ts +23 -0
- package/dist/layout-painter/borderStroke.js +39 -0
- package/dist/layout-painter/renderImage.d.ts +5 -4
- package/dist/layout-painter/renderImage.js +18 -4
- package/dist/layout-painter/renderPage.d.ts +2 -27
- package/dist/layout-painter/renderPage.js +15 -101
- package/dist/layout-painter/renderParagraph.js +45 -26
- package/dist/layout-painter/renderTable.d.ts +2 -0
- package/dist/layout-painter/renderTable.js +257 -47
- package/dist/layout-painter/renderTextBox.js +17 -7
- package/dist/managers/DocumentLoaderManager.d.ts +1 -1
- package/dist/managers/DocumentLoaderManager.js +2 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
- package/dist/paged-layout/sectionBlockWidths.js +9 -0
- package/dist/paged-layout/sectionGeometry.js +1 -1
- package/dist/prosemirror/attrs/index.d.ts +4 -2
- package/dist/prosemirror/attrs/index.js +32 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +35 -18
- package/dist/prosemirror/conversion/toProseDoc.js +73 -37
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +8 -2
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ListExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
- package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
- package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +6 -1
- package/dist/prosemirror/schema/nodes.d.ts +16 -12
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
- package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
- package/dist/prosemirror/utils/tabCalculator.js +12 -10
- package/dist/prosemirror/validation.js +4 -1
- package/dist/redline.js +13 -3
- package/dist/server.d.ts +9 -4
- package/dist/server.js +7 -2
- package/dist/style-sets/extract.d.ts +35 -0
- package/dist/style-sets/extract.js +123 -0
- package/dist/style-sets/stellaStyle.d.ts +13 -0
- package/dist/style-sets/stellaStyle.js +516 -0
- package/dist/style-sets/types.d.ts +31 -0
- package/dist/style-sets/types.js +5 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/utils/createDocument.d.ts +12 -2
- package/dist/utils/createDocument.js +55 -36
- package/dist/utils/fontResolver.js +6 -6
- package/dist/utils/formatToStyle.js +1 -1
- package/dist/utils/hexId.d.ts +8 -1
- package/dist/utils/hexId.js +11 -3
- package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
- package/dist/utils/paragraphFormattingMerge.js +14 -4
- package/package.json +5 -1
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
130
|
+
const borderHeight = getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx);
|
|
182
131
|
maxCellHeightWithBorders = Math.max(maxCellHeightWithBorders, cell.height + borderHeight);
|
|
183
|
-
|
|
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 +
|
|
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
|
|
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
|
-
|
|
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 <
|
|
263
|
-
else rightMargin =
|
|
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,
|
|
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 =
|
|
299
|
-
else x = (
|
|
300
|
-
else if (tableBlock.justification === "center") x = (
|
|
301
|
-
else if (tableBlock.justification === "right") x =
|
|
302
|
-
if (x <
|
|
303
|
-
else rightMargin =
|
|
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
|
|
393
|
-
const totalHeight = tb.height ??
|
|
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,
|
|
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
|
|
465
|
-
if (
|
|
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
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
137
|
-
|
|
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(
|
|
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
|
|
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 */
|