@stll/folio-core 0.6.1 → 0.8.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/dist/ai-edits/blockRange.d.ts +13 -2
- package/dist/ai-edits/blockRange.js +23 -2
- package/dist/ai-edits/index.d.ts +4 -2
- package/dist/ai-edits/index.js +3 -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 +13 -4
- package/dist/ai-edits/types.d.ts +44 -2
- package/dist/compat/eigenpal.d.ts +5 -2
- package/dist/compat/eigenpal.js +4 -1
- package/dist/controller/fontReadiness.d.ts +29 -0
- package/dist/controller/fontReadiness.js +139 -0
- package/dist/controller/layoutPipeline.js +86 -11
- package/dist/docx/blockContentParser.js +51 -8
- package/dist/docx/capabilities.d.ts +41 -0
- package/dist/docx/capabilities.js +322 -0
- package/dist/docx/compatibility.d.ts +34 -2
- package/dist/docx/compatibility.js +128 -38
- package/dist/docx/conformance.d.ts +6 -0
- package/dist/docx/conformance.js +18 -0
- package/dist/docx/ensureParaIds.d.ts +29 -0
- package/dist/docx/ensureParaIds.js +423 -0
- package/dist/docx/groupDrawingParser.d.ts +1 -1
- package/dist/docx/groupDrawingParser.js +49 -8
- package/dist/docx/numberingParser.js +1 -0
- package/dist/docx/paragraphParser.js +9 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
- package/dist/docx/parser.js +2 -0
- package/dist/docx/runParser.js +11 -2
- package/dist/docx/selectiveXmlPatch.d.ts +10 -1
- package/dist/docx/selectiveXmlPatch.js +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +4 -0
- package/dist/docx/serializer/runSerializer.js +7 -2
- package/dist/docx/serializer/tableSerializer.js +4 -0
- package/dist/docx/server/boundedArchive.d.ts +24 -0
- package/dist/docx/server/boundedArchive.js +106 -0
- package/dist/docx/server/extractDocxText.d.ts +23 -0
- package/dist/docx/server/extractDocxText.js +154 -0
- package/dist/docx/tableParser.js +8 -0
- package/dist/docx/textBoxParser.js +6 -1
- package/dist/docx/vmlImageParser.js +145 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/layout-bridge/convert/headerFooterLayout.js +16 -2
- 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.js +106 -13
- package/dist/layout-bridge/engine/hitTest.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 +125 -72
- package/dist/layout-engine/keep-together.d.ts +7 -5
- package/dist/layout-engine/keep-together.js +20 -4
- package/dist/layout-engine/measure/cache.js +2 -0
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
- package/dist/layout-engine/measure/listMarkerWidth.js +23 -4
- package/dist/layout-engine/measure/measureBlocks.d.ts +3 -0
- package/dist/layout-engine/measure/measureBlocks.js +133 -51
- package/dist/layout-engine/measure/measureParagraph.js +42 -15
- 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/paginator.d.ts +2 -1
- package/dist/layout-engine/paginator.js +17 -8
- 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 +58 -0
- package/dist/layout-engine/renderedBreakReconciliation.js +63 -0
- package/dist/layout-engine/types.d.ts +31 -10
- package/dist/layout-engine/types.js +15 -2
- package/dist/layout-painter/renderPage.js +2 -2
- package/dist/layout-painter/renderParagraph.js +94 -10
- package/dist/layout-painter/renderTable.js +42 -12
- package/dist/layout-painter/renderTextBox.js +17 -7
- package/dist/managers/DocumentLoaderManager.js +1 -1
- 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.js +11 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +2 -0
- package/dist/prosemirror/conversion/toProseDoc.js +29 -23
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
- package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/nodes.d.ts +6 -3
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/utils/tabCalculator.js +1 -1
- package/dist/server.d.ts +6 -2
- package/dist/server.js +5 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/utils/createDocument.js +26 -18
- package/dist/utils/formatToStyle.js +3 -3
- package/dist/utils/hexId.d.ts +8 -1
- package/dist/utils/hexId.js +11 -3
- package/dist/utils/units.d.ts +6 -6
- package/dist/utils/units.js +8 -8
- package/package.json +1 -1
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, isFloatingTextBoxBlock, tableColumnsArePinned } from "../types.js";
|
|
1
|
+
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingTextBoxBlock, tableColumnsArePinned } from "../types.js";
|
|
2
2
|
import { findClearLineY, measureParagraph } from "./measureParagraph.js";
|
|
3
3
|
import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
|
|
4
|
+
import { layoutTextBoxParagraphs } from "./textBoxParagraphLayout.js";
|
|
4
5
|
import { getTextBoxGroupId } from "../textBoxGroup.js";
|
|
6
|
+
import { isParagraphFrameTextBox } from "../paragraphFrame.js";
|
|
5
7
|
import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrumentation.js";
|
|
6
|
-
import { bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
|
|
8
|
+
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
|
|
9
|
+
import { buildTableCellGrid, getFirstAvailableColumn, getTableCellVerticalBorderHeight } from "./tableCellGrid.js";
|
|
7
10
|
//#region src/layout-engine/measure/measureBlocks.ts
|
|
8
11
|
/**
|
|
9
12
|
* Pseudo-infinite measurement width (px) used for `w:noWrap` table cells so
|
|
@@ -25,7 +28,7 @@ const NO_WRAP_MEASURE_WIDTH = 1e6;
|
|
|
25
28
|
* around a background letterhead or a foreground overlay (Codex
|
|
26
29
|
* PR #258 review).
|
|
27
30
|
*/
|
|
28
|
-
function
|
|
31
|
+
function isSideWrappingImageRun(run) {
|
|
29
32
|
const wrapType = run.wrapType;
|
|
30
33
|
const displayMode = run.displayMode;
|
|
31
34
|
if (wrapType === "behind" || wrapType === "inFront") return false;
|
|
@@ -82,9 +85,6 @@ function measureTableCellBlockVisualHeight(block, blockMeasure) {
|
|
|
82
85
|
const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
|
|
83
86
|
return spacingBefore + maxImageHeight + spacingAfter;
|
|
84
87
|
}
|
|
85
|
-
function getTableCellVerticalBorderHeight(cell, isFirstRow) {
|
|
86
|
-
return (isFirstRow ? cell?.borders?.top?.width ?? 0 : 0) + (cell?.borders?.bottom?.width ?? 0);
|
|
87
|
-
}
|
|
88
88
|
function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
89
89
|
const DEFAULT_CELL_PADDING_X = 7;
|
|
90
90
|
const DEFAULT_CELL_PADDING_Y = 0;
|
|
@@ -101,38 +101,17 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
101
101
|
columnWidths = columnWidths.map((w) => w * scale);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
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
|
-
}
|
|
104
|
+
const cellGrid = buildTableCellGrid(tableBlock.rows, columnWidths.length);
|
|
123
105
|
const columnsPinned = tableColumnsArePinned(tableBlock);
|
|
124
106
|
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++;
|
|
107
|
+
let columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, row.gridBefore ?? 0);
|
|
128
108
|
return {
|
|
129
109
|
cells: row.cells.map((cell) => {
|
|
130
110
|
const colSpan = cell.colSpan ?? 1;
|
|
131
111
|
let cellWidth = 0;
|
|
132
112
|
for (let c = 0; c < colSpan && columnIndex + c < columnWidths.length; c++) cellWidth += columnWidths[columnIndex + c] ?? 0;
|
|
133
113
|
if (cellWidth === 0) cellWidth = cell.width ?? 100;
|
|
134
|
-
columnIndex
|
|
135
|
-
while (occupied.has(columnIndex)) columnIndex++;
|
|
114
|
+
columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, columnIndex + colSpan);
|
|
136
115
|
const padLeft = cell.padding?.left ?? DEFAULT_CELL_PADDING_X;
|
|
137
116
|
const padRight = cell.padding?.right ?? DEFAULT_CELL_PADDING_X;
|
|
138
117
|
const cellContentWidth = Math.max(1, cellWidth - padLeft - padRight);
|
|
@@ -163,7 +142,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
163
142
|
}
|
|
164
143
|
const sourceRowCells = tableBlock.rows[rowIdx]?.cells;
|
|
165
144
|
let maxCellHeightWithBorders = 0;
|
|
166
|
-
let
|
|
145
|
+
let maxCellInsets = 0;
|
|
167
146
|
for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
|
|
168
147
|
const cell = row.cells[cellIdx];
|
|
169
148
|
const sourceCell = sourceRowCells?.[cellIdx];
|
|
@@ -178,14 +157,14 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
178
157
|
const padBottom = sourceCell?.padding?.bottom ?? DEFAULT_CELL_PADDING_Y;
|
|
179
158
|
cell.height += padTop + padBottom;
|
|
180
159
|
if ((sourceCell?.rowSpan ?? 1) > 1) continue;
|
|
181
|
-
const borderHeight = getTableCellVerticalBorderHeight(sourceCell, rowIdx
|
|
160
|
+
const borderHeight = getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx);
|
|
182
161
|
maxCellHeightWithBorders = Math.max(maxCellHeightWithBorders, cell.height + borderHeight);
|
|
183
|
-
|
|
162
|
+
maxCellInsets = Math.max(maxCellInsets, padTop + padBottom + borderHeight);
|
|
184
163
|
}
|
|
185
164
|
const explicitHeight = sourceRow?.height;
|
|
186
165
|
const heightRule = sourceRow?.heightRule;
|
|
187
166
|
if (explicitHeight && heightRule === "exact") row.height = explicitHeight;
|
|
188
|
-
else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight +
|
|
167
|
+
else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight + maxCellInsets);
|
|
189
168
|
else row.height = maxCellHeightWithBorders;
|
|
190
169
|
}
|
|
191
170
|
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
|
|
@@ -200,7 +179,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
200
179
|
const spanEnd = Math.min(rows.length, rowIdx + rowSpan);
|
|
201
180
|
let combinedHeight = 0;
|
|
202
181
|
for (let spannedRowIdx = rowIdx; spannedRowIdx < spanEnd; spannedRowIdx++) combinedHeight += rows[spannedRowIdx]?.height ?? 0;
|
|
203
|
-
const deficit = measuredCell.height + getTableCellVerticalBorderHeight(sourceCell, rowIdx
|
|
182
|
+
const deficit = measuredCell.height + getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx) - combinedHeight;
|
|
204
183
|
if (deficit <= 0) continue;
|
|
205
184
|
for (let spannedRowIdx = spanEnd - 1; spannedRowIdx >= rowIdx; spannedRowIdx--) {
|
|
206
185
|
if (tableBlock.rows[spannedRowIdx]?.heightRule === "exact") continue;
|
|
@@ -223,13 +202,34 @@ function perBlockNumberValue(value, blockIndex, fallback) {
|
|
|
223
202
|
if (Array.isArray(value)) return value[blockIndex] ?? fallback;
|
|
224
203
|
return value;
|
|
225
204
|
}
|
|
205
|
+
function textBoxWrapSide({ box, contentX, boxWidth, contentWidth }) {
|
|
206
|
+
if (box.wrapText === "left") return "left";
|
|
207
|
+
if (box.wrapText === "right") return "right";
|
|
208
|
+
const leftSpace = contentX;
|
|
209
|
+
const rightSpace = contentWidth - contentX - boxWidth;
|
|
210
|
+
if (box.wrapText === "largest") return leftSpace >= rightSpace ? "left" : "right";
|
|
211
|
+
return contentX + boxWidth / 2 < contentWidth / 2 ? "right" : "left";
|
|
212
|
+
}
|
|
226
213
|
function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry) {
|
|
227
214
|
const zones = [];
|
|
215
|
+
const defaultContentWidth = Array.isArray(contentWidth) ? contentWidth[0] ?? 0 : contentWidth;
|
|
228
216
|
const textBoxGroupAnchors = /* @__PURE__ */ new Map();
|
|
217
|
+
const paragraphFrameGroupSizes = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const block of blocks) {
|
|
219
|
+
if (block.kind !== "textBox" || !isParagraphFrameTextBox(block)) continue;
|
|
220
|
+
const groupId = getTextBoxGroupId(block);
|
|
221
|
+
if (groupId !== void 0) paragraphFrameGroupSizes.set(groupId, (paragraphFrameGroupSizes.get(groupId) ?? 0) + 1);
|
|
222
|
+
}
|
|
229
223
|
const defaultMarginTop = Array.isArray(marginTop) ? marginTop[0] ?? 0 : marginTop;
|
|
224
|
+
const pageWidthInput = pageGeometry?.pageWidth ?? defaultContentWidth;
|
|
230
225
|
const pageHeightInput = pageGeometry?.pageHeight ?? 0;
|
|
226
|
+
const marginLeftInput = pageGeometry?.marginLeft ?? 0;
|
|
227
|
+
const marginRightInput = pageGeometry?.marginRight ?? 0;
|
|
231
228
|
const marginBottomInput = pageGeometry?.marginBottom ?? 0;
|
|
229
|
+
const defaultPageWidth = Array.isArray(pageWidthInput) ? pageWidthInput[0] ?? defaultContentWidth : pageWidthInput;
|
|
232
230
|
const defaultPageHeight = Array.isArray(pageHeightInput) ? pageHeightInput[0] ?? 0 : pageHeightInput;
|
|
231
|
+
const defaultMarginLeft = Array.isArray(marginLeftInput) ? marginLeftInput[0] ?? 0 : marginLeftInput;
|
|
232
|
+
const defaultMarginRight = Array.isArray(marginRightInput) ? marginRightInput[0] ?? 0 : marginRightInput;
|
|
233
233
|
const defaultMarginBottom = Array.isArray(marginBottomInput) ? marginBottomInput[0] ?? 0 : marginBottomInput;
|
|
234
234
|
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
|
235
235
|
const block = blocks[blockIndex];
|
|
@@ -238,13 +238,36 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
238
238
|
for (const run of paragraphBlock.runs) {
|
|
239
239
|
if (run.kind !== "image") continue;
|
|
240
240
|
const imgRun = run;
|
|
241
|
-
|
|
241
|
+
const positionedBand = imgRun.wrapType === "topAndBottom" && imgRun.position !== void 0;
|
|
242
|
+
if (!isSideWrappingImageRun(imgRun) && !positionedBand) continue;
|
|
242
243
|
let topY = 0;
|
|
243
244
|
const position = imgRun.position;
|
|
244
245
|
const distTop = imgRun.distTop ?? 0;
|
|
245
246
|
const distBottom = imgRun.distBottom ?? 0;
|
|
246
247
|
const distLeft = imgRun.distLeft ?? 12;
|
|
247
248
|
const distRight = imgRun.distRight ?? 12;
|
|
249
|
+
if (positionedBand && position !== void 0) {
|
|
250
|
+
const vertical = position.vertical;
|
|
251
|
+
const blockMarginTop = perBlockNumberValue(marginTop, blockIndex, defaultMarginTop);
|
|
252
|
+
const pageFrameRelative = isPageFrameRelativeAnchor(vertical?.relativeTo);
|
|
253
|
+
const rawTop = pageFrameRelative ? bandTopContentY(vertical, {
|
|
254
|
+
pageHeight: perBlockNumberValue(pageHeightInput, blockIndex, defaultPageHeight),
|
|
255
|
+
marginTop: blockMarginTop,
|
|
256
|
+
marginBottom: perBlockNumberValue(marginBottomInput, blockIndex, defaultMarginBottom),
|
|
257
|
+
boxHeight: imgRun.height
|
|
258
|
+
}) : emuToPixels(vertical?.posOffset ?? 0);
|
|
259
|
+
const bottomY = rawTop + imgRun.height + distBottom;
|
|
260
|
+
if (bottomY > 0) zones.push({
|
|
261
|
+
leftMargin: 0,
|
|
262
|
+
rightMargin: 0,
|
|
263
|
+
topY: Math.max(0, rawTop - distTop),
|
|
264
|
+
bottomY,
|
|
265
|
+
anchorBlockIndex: blockIndex,
|
|
266
|
+
...pageFrameRelative ? { isMarginRelative: true } : {},
|
|
267
|
+
fullWidthBlock: true
|
|
268
|
+
});
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
248
271
|
if (position?.vertical) {
|
|
249
272
|
const v = position.vertical;
|
|
250
273
|
if (v.align === "top" && v.relativeTo === "margin") topY = 0;
|
|
@@ -259,8 +282,8 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
259
282
|
else if (h.align === "right") rightMargin = imgRun.width + distLeft;
|
|
260
283
|
else if (h.posOffset !== void 0) {
|
|
261
284
|
const x = emuToPixels(h.posOffset);
|
|
262
|
-
if (x <
|
|
263
|
-
else rightMargin =
|
|
285
|
+
if (x < defaultContentWidth / 2) leftMargin = x + imgRun.width + distRight;
|
|
286
|
+
else rightMargin = defaultContentWidth - x + distLeft;
|
|
264
287
|
}
|
|
265
288
|
} else if (imgRun.cssFloat === "left") leftMargin = imgRun.width + distRight;
|
|
266
289
|
else if (imgRun.cssFloat === "right") rightMargin = imgRun.width + distLeft;
|
|
@@ -283,7 +306,7 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
283
306
|
const tableBlock = block;
|
|
284
307
|
const floating = tableBlock.floating;
|
|
285
308
|
if (!floating) continue;
|
|
286
|
-
const tableMeasure = measureTableBlock(tableBlock,
|
|
309
|
+
const tableMeasure = measureTableBlock(tableBlock, defaultContentWidth);
|
|
287
310
|
const tableWidth = tableMeasure.totalWidth;
|
|
288
311
|
const tableHeight = tableMeasure.totalHeight;
|
|
289
312
|
const distLeft = floating.leftFromText ?? 12;
|
|
@@ -295,12 +318,12 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
295
318
|
let x = 0;
|
|
296
319
|
if (floating.tblpX !== void 0) x = floating.tblpX;
|
|
297
320
|
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 =
|
|
321
|
+
else if (floating.tblpXSpec === "right" || floating.tblpXSpec === "outside") x = defaultContentWidth - tableWidth;
|
|
322
|
+
else x = (defaultContentWidth - tableWidth) / 2;
|
|
323
|
+
else if (tableBlock.justification === "center") x = (defaultContentWidth - tableWidth) / 2;
|
|
324
|
+
else if (tableBlock.justification === "right") x = defaultContentWidth - tableWidth;
|
|
325
|
+
if (x < defaultContentWidth / 2) leftMargin = x + tableWidth + distRight;
|
|
326
|
+
else rightMargin = defaultContentWidth - x + distLeft;
|
|
304
327
|
const topY = floating.tblpY ?? 0;
|
|
305
328
|
const bottomY = topY + tableHeight;
|
|
306
329
|
zones.push({
|
|
@@ -311,6 +334,64 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
311
334
|
anchorBlockIndex: blockIndex
|
|
312
335
|
});
|
|
313
336
|
}
|
|
337
|
+
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
|
338
|
+
const block = blocks[blockIndex];
|
|
339
|
+
if (block.kind !== "textBox") continue;
|
|
340
|
+
const tb = block;
|
|
341
|
+
if (!floatingTextBoxWrapsText(tb) || tb.position === void 0) continue;
|
|
342
|
+
const measure = measureTextBoxBlock(tb);
|
|
343
|
+
const blockMarginTop = perBlockNumberValue(marginTop, blockIndex, defaultMarginTop);
|
|
344
|
+
const blockMarginLeft = perBlockNumberValue(marginLeftInput, blockIndex, defaultMarginLeft);
|
|
345
|
+
const blockMarginRight = perBlockNumberValue(marginRightInput, blockIndex, defaultMarginRight);
|
|
346
|
+
const blockPageWidth = perBlockNumberValue(pageWidthInput, blockIndex, defaultPageWidth);
|
|
347
|
+
const blockContentWidth = perBlockNumberValue(contentWidth, blockIndex, defaultContentWidth);
|
|
348
|
+
const vertical = tb.position.vertical;
|
|
349
|
+
const pageFrameRelative = isPageFrameRelativeAnchor(vertical?.relativeTo);
|
|
350
|
+
const topY = pageFrameRelative ? bandTopContentY(vertical, {
|
|
351
|
+
pageHeight: perBlockNumberValue(pageHeightInput, blockIndex, defaultPageHeight),
|
|
352
|
+
marginTop: blockMarginTop,
|
|
353
|
+
marginBottom: perBlockNumberValue(marginBottomInput, blockIndex, defaultMarginBottom),
|
|
354
|
+
boxHeight: measure.height
|
|
355
|
+
}) : emuToPixels(vertical?.posOffset ?? 0);
|
|
356
|
+
const groupId = getTextBoxGroupId(tb);
|
|
357
|
+
const anchorBlockIndex = groupId ? textBoxGroupAnchors.get(groupId) ?? blockIndex : blockIndex;
|
|
358
|
+
if (groupId && !textBoxGroupAnchors.has(groupId)) textBoxGroupAnchors.set(groupId, blockIndex);
|
|
359
|
+
if (isParagraphFrameTextBox(tb) && groupId !== void 0 && (paragraphFrameGroupSizes.get(groupId) ?? 0) > 1) {
|
|
360
|
+
zones.push({
|
|
361
|
+
leftMargin: 0,
|
|
362
|
+
rightMargin: 0,
|
|
363
|
+
topY: 0,
|
|
364
|
+
bottomY: topY + measure.height + (tb.distBottom ?? 0),
|
|
365
|
+
anchorBlockIndex,
|
|
366
|
+
...pageFrameRelative ? { isMarginRelative: true } : {},
|
|
367
|
+
fullWidthBlock: true
|
|
368
|
+
});
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const horizontal = tb.position.horizontal;
|
|
372
|
+
const contentX = (horizontal ? bandFragmentX(horizontal, {
|
|
373
|
+
pageWidth: blockPageWidth,
|
|
374
|
+
marginLeft: blockMarginLeft,
|
|
375
|
+
marginRight: blockMarginRight,
|
|
376
|
+
boxWidth: measure.width
|
|
377
|
+
}) : blockMarginLeft) - blockMarginLeft;
|
|
378
|
+
const wrapSide = textBoxWrapSide({
|
|
379
|
+
box: tb,
|
|
380
|
+
contentX,
|
|
381
|
+
boxWidth: measure.width,
|
|
382
|
+
contentWidth: blockContentWidth
|
|
383
|
+
});
|
|
384
|
+
const leftMargin = wrapSide === "right" ? contentX + measure.width + (tb.distRight ?? 12) : 0;
|
|
385
|
+
const rightMargin = wrapSide === "left" ? blockContentWidth - contentX + (tb.distLeft ?? 12) : 0;
|
|
386
|
+
zones.push({
|
|
387
|
+
leftMargin: Math.max(0, leftMargin),
|
|
388
|
+
rightMargin: Math.max(0, rightMargin),
|
|
389
|
+
topY: topY - (tb.distTop ?? 0),
|
|
390
|
+
bottomY: topY + measure.height + (tb.distBottom ?? 0),
|
|
391
|
+
anchorBlockIndex,
|
|
392
|
+
...pageFrameRelative ? { isMarginRelative: true } : {}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
314
395
|
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
|
315
396
|
const block = blocks[blockIndex];
|
|
316
397
|
if (block.kind !== "textBox") continue;
|
|
@@ -389,8 +470,8 @@ function measureTextBoxBlock(tb, fieldValues) {
|
|
|
389
470
|
const margins = tb.margins ?? DEFAULT_TEXTBOX_MARGINS;
|
|
390
471
|
const innerWidth = tb.width - margins.left - margins.right;
|
|
391
472
|
const innerMeasures = tb.content.map((p) => measureParagraph(p, innerWidth, fieldValues ? { fieldValues } : void 0));
|
|
392
|
-
const
|
|
393
|
-
const totalHeight = tb.height ??
|
|
473
|
+
const contentBoxHeight = layoutTextBoxParagraphs(tb.content, innerMeasures).totalHeight + margins.top + margins.bottom;
|
|
474
|
+
const totalHeight = tb.autoFit === "shape" ? Math.max(tb.height ?? 0, contentBoxHeight) : tb.height ?? contentBoxHeight;
|
|
394
475
|
return {
|
|
395
476
|
kind: "textBox",
|
|
396
477
|
width: tb.width,
|
|
@@ -418,7 +499,7 @@ function isNextPageSectionBreak(block) {
|
|
|
418
499
|
*/
|
|
419
500
|
function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldValues) {
|
|
420
501
|
const defaultWidth = Array.isArray(contentWidth) ? contentWidth[0] ?? 0 : contentWidth;
|
|
421
|
-
const floatingZonesWithAnchors = extractFloatingZones(blocks,
|
|
502
|
+
const floatingZonesWithAnchors = extractFloatingZones(blocks, contentWidth, marginTop, pageGeometry);
|
|
422
503
|
const marginRelative = floatingZonesWithAnchors.filter((z) => z.isMarginRelative && !z.fullWidthBlock);
|
|
423
504
|
const ownAnchorZones = floatingZonesWithAnchors.filter((z) => !z.isMarginRelative || z.fullWidthBlock);
|
|
424
505
|
const marginByTopY = /* @__PURE__ */ new Map();
|
|
@@ -461,10 +542,11 @@ function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldV
|
|
|
461
542
|
try {
|
|
462
543
|
const blockWidth = Array.isArray(contentWidth) ? contentWidth[blockIndex] ?? defaultWidth : contentWidth;
|
|
463
544
|
const measure = measureBlock(block, blockWidth, zones, cumulativeY, fieldValues);
|
|
464
|
-
const
|
|
465
|
-
if (
|
|
545
|
+
const clearingZones = measure.kind === "table" ? zones : zones?.filter((zone) => zone.fullWidthBlock);
|
|
546
|
+
if (clearingZones?.length && (measure.kind === "image" || measure.kind === "table" && !block.floating)) {
|
|
466
547
|
const blockHeight = measure.kind === "image" ? measure.height : measure.totalHeight;
|
|
467
|
-
const
|
|
548
|
+
const requiredWidth = measure.kind === "table" ? Math.min(blockWidth, measure.totalWidth) : 24;
|
|
549
|
+
const skip = findClearLineY(cumulativeY, blockHeight, clearingZones, blockWidth, Math.max(24, requiredWidth)) - cumulativeY;
|
|
468
550
|
if (skip > 0) {
|
|
469
551
|
measure.bandSkipBefore = skip;
|
|
470
552
|
cumulativeY += skip;
|
|
@@ -22,8 +22,9 @@ const DEFAULT_FONT_FAMILY = "Calibri";
|
|
|
22
22
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
23
23
|
const WIDTH_TOLERANCE = .5;
|
|
24
24
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
25
|
+
const JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO = .015;
|
|
25
26
|
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
26
|
-
const JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO = .
|
|
27
|
+
const JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO = .02;
|
|
27
28
|
const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
|
|
28
29
|
const DEFAULT_LIST_HANGING_INDENT_PX = 24;
|
|
29
30
|
const ALL_CAPS_RATIO_THRESHOLD = .8;
|
|
@@ -245,6 +246,7 @@ function isEmptyTextRun(run) {
|
|
|
245
246
|
* preceding a floating image.
|
|
246
247
|
*/
|
|
247
248
|
function isBlockLayoutImageRun(run) {
|
|
249
|
+
if (isFloatingImageRun(run)) return false;
|
|
248
250
|
return run.wrapType === "topAndBottom" || run.displayMode === "block";
|
|
249
251
|
}
|
|
250
252
|
function measureInlineWidthAfterTab(runs, tabIndex, fieldValues) {
|
|
@@ -329,20 +331,34 @@ function uppercaseLetterRatio(text) {
|
|
|
329
331
|
}
|
|
330
332
|
return letters === 0 ? 0 : uppercase / letters;
|
|
331
333
|
}
|
|
334
|
+
function fixedSpaceAdjustedShrinkTolerance({ tolerance, regularSpaceCount, nonBreakingSpaceCount }) {
|
|
335
|
+
const totalSpaces = regularSpaceCount + nonBreakingSpaceCount;
|
|
336
|
+
if (totalSpaces === 0) return tolerance;
|
|
337
|
+
return Math.max(JUSTIFY_SHRINK_TOLERANCE_RATIO, tolerance * (regularSpaceCount / totalSpaces));
|
|
338
|
+
}
|
|
332
339
|
function justifyShrinkToleranceRatio(block, isFirstLine, regularSpaceCount, nonBreakingSpaceCount) {
|
|
333
340
|
if (block.attrs?.listMarker !== void 0) {
|
|
334
|
-
|
|
335
|
-
if (isFirstLine) return JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO;
|
|
336
|
-
const
|
|
337
|
-
if (
|
|
338
|
-
return
|
|
341
|
+
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
342
|
+
if (isFirstLine) return hanging <= DEFAULT_LIST_HANGING_INDENT_PX ? JUSTIFY_SHRINK_TOLERANCE_RATIO : JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO;
|
|
343
|
+
const left = block.attrs.indent?.left ?? 0;
|
|
344
|
+
if (hanging > 0 && left > hanging) return JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO;
|
|
345
|
+
return fixedSpaceAdjustedShrinkTolerance({
|
|
346
|
+
tolerance: JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO,
|
|
347
|
+
regularSpaceCount,
|
|
348
|
+
nonBreakingSpaceCount
|
|
349
|
+
});
|
|
339
350
|
}
|
|
340
351
|
const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
|
|
341
352
|
const hasTabRuns = block.runs.some(isTabRun);
|
|
353
|
+
if (isFirstLine && hasTabRuns && (block.attrs?.indent?.hanging ?? 0) > 0) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
342
354
|
if (!isFirstLine && hasTabRuns && !hasTabStops) return JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO;
|
|
343
|
-
if (
|
|
355
|
+
if (hasTabRuns && (isFirstLine || hasTabStops)) return (block.attrs?.indent?.firstLine ?? 0) === 0 ? JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO : JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
344
356
|
if (uppercaseLetterRatio(block.runs.map((run) => isTextRun(run) ? run.text ?? "" : "").join("")) > ALL_CAPS_RATIO_THRESHOLD) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
345
|
-
return
|
|
357
|
+
return fixedSpaceAdjustedShrinkTolerance({
|
|
358
|
+
tolerance: JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO,
|
|
359
|
+
regularSpaceCount,
|
|
360
|
+
nonBreakingSpaceCount
|
|
361
|
+
});
|
|
346
362
|
}
|
|
347
363
|
function trimTrailingSpacesAndTabs(text) {
|
|
348
364
|
let end = text.length;
|
|
@@ -479,15 +495,17 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
479
495
|
};
|
|
480
496
|
}
|
|
481
497
|
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY);
|
|
498
|
+
const outlineLineHeight = attrs?.reserveEmptyOutlineHeight ? emptyMetrics.lineHeight * 2 : emptyMetrics.lineHeight;
|
|
482
499
|
lines.push({
|
|
483
500
|
fromRun: 0,
|
|
484
501
|
fromChar: 0,
|
|
485
502
|
toRun: 0,
|
|
486
503
|
toChar: 0,
|
|
487
504
|
width: 0,
|
|
488
|
-
...emptyMetrics
|
|
505
|
+
...emptyMetrics,
|
|
506
|
+
lineHeight: outlineLineHeight
|
|
489
507
|
});
|
|
490
|
-
let totalHeight =
|
|
508
|
+
let totalHeight = outlineLineHeight;
|
|
491
509
|
if (spacing?.before) totalHeight += spacing.before;
|
|
492
510
|
if (spacing?.after) totalHeight += spacing.after;
|
|
493
511
|
return {
|
|
@@ -536,7 +554,14 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
536
554
|
...firstLineFloatingMargins.segments?.length ? { segmentZones: firstLineFloatingMargins.segments } : {}
|
|
537
555
|
};
|
|
538
556
|
const calculateLineTypography = (line) => {
|
|
539
|
-
const
|
|
557
|
+
const paragraphFontSize = attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE;
|
|
558
|
+
const paragraphFontFamily = attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY;
|
|
559
|
+
const fontSize = line.maxFontMetrics ? line.maxFontSize : paragraphFontSize;
|
|
560
|
+
const metrics = line.maxFontMetrics ?? getFontMetrics({
|
|
561
|
+
fontSize: paragraphFontSize,
|
|
562
|
+
fontFamily: paragraphFontFamily
|
|
563
|
+
});
|
|
564
|
+
const finalTypography = { ...calculateTypographyMetrics(fontSize, spacing, metrics) };
|
|
540
565
|
const inlineObjectHeight = Math.max(line.maxImageHeightPx, line.maxMathHeightPx);
|
|
541
566
|
if (inlineObjectHeight > finalTypography.lineHeight) {
|
|
542
567
|
const objectHeight = inlineObjectHeight;
|
|
@@ -651,8 +676,9 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
651
676
|
decimalPrefixWidth
|
|
652
677
|
});
|
|
653
678
|
let tabWidth = tabResult.width;
|
|
679
|
+
const landsOnLeftIndent = tabResult.alignment === "start" && indentLeft > 0 && Math.abs(contentX + tabWidth - indentLeft) <= WIDTH_TOLERANCE;
|
|
654
680
|
const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
|
|
655
|
-
if (!hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
681
|
+
if (!landsOnLeftIndent && !hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
656
682
|
if (currentLine.width + tabWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
|
|
657
683
|
startNewLine(runIndex, 0);
|
|
658
684
|
updateMaxFont(style);
|
|
@@ -665,7 +691,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
665
691
|
}
|
|
666
692
|
if (isImageRun(run)) {
|
|
667
693
|
const wrapType = run.wrapType;
|
|
668
|
-
if (run
|
|
694
|
+
if (isFloatingImageRun(run)) {
|
|
669
695
|
currentLine.toRun = runIndex;
|
|
670
696
|
currentLine.toChar = 1;
|
|
671
697
|
continue;
|
|
@@ -794,12 +820,13 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
794
820
|
}
|
|
795
821
|
const rawGlueWidth = nextBreak === text.length && word.length > 0 && !isBreakChar(word[word.length - 1]) ? trailingGlueWidths[runIndex] ?? 0 : 0;
|
|
796
822
|
const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + widthTolerance ? rawGlueWidth : 0;
|
|
797
|
-
if (currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth + widthTolerance) {
|
|
823
|
+
if (wordWidth > 0 && currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth + widthTolerance) {
|
|
798
824
|
startNewLine(runIndex, charIndex);
|
|
799
825
|
updateMaxFont(lineHeightStyle);
|
|
800
826
|
}
|
|
801
827
|
currentLine.width += fullWordWidth;
|
|
802
|
-
|
|
828
|
+
const wordTrailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
829
|
+
currentLine.trailingWhitespaceWidth = wordWidth === 0 ? currentLine.trailingWhitespaceWidth + wordTrailingWhitespaceWidth : wordTrailingWhitespaceWidth;
|
|
803
830
|
currentLine.regularSpaceCount += word.split(" ").length - 1;
|
|
804
831
|
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
805
832
|
currentLine.toRun = runIndex;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TableCell, TableRow } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/measure/tableCellGrid.d.ts
|
|
4
|
+
type TableCellGrid = {
|
|
5
|
+
occupiedColumnsByRow: ReadonlyMap<number, ReadonlySet<number>>;
|
|
6
|
+
sourceCellsByRow: ReadonlyMap<number, ReadonlyMap<number, TableCell>>;
|
|
7
|
+
sourceColumnsByCell: ReadonlyMap<TableCell, number>;
|
|
8
|
+
};
|
|
9
|
+
declare const buildTableCellGrid: (rows: readonly TableRow[], columnCount: number) => TableCellGrid;
|
|
10
|
+
declare const getFirstAvailableColumn: (grid: TableCellGrid, rowIndex: number, startingColumn: number) => number;
|
|
11
|
+
declare const getSourceCellAt: (grid: TableCellGrid, rowIndex: number, columnIndex: number) => TableCell | undefined;
|
|
12
|
+
declare const getSourceCellColumn: (grid: TableCellGrid, cell: TableCell) => number | undefined;
|
|
13
|
+
declare const getTableCellVerticalBorderHeight: (grid: TableCellGrid, cell: TableCell | undefined, rowIndex: number) => number;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { TableCellGrid, buildTableCellGrid, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/tableCellGrid.ts
|
|
2
|
+
const buildTableCellGrid = (rows, columnCount) => {
|
|
3
|
+
const occupiedColumnsByRow = /* @__PURE__ */ new Map();
|
|
4
|
+
const sourceCellsByRow = /* @__PURE__ */ new Map();
|
|
5
|
+
const sourceColumnsByCell = /* @__PURE__ */ new Map();
|
|
6
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
7
|
+
const row = rows[rowIndex];
|
|
8
|
+
if (!row) continue;
|
|
9
|
+
let columnIndex = firstAvailableColumn(occupiedColumnsByRow.get(rowIndex), row.gridBefore ?? 0);
|
|
10
|
+
for (const cell of row.cells) {
|
|
11
|
+
const columnSpan = cell.colSpan ?? 1;
|
|
12
|
+
const rowSpan = cell.rowSpan ?? 1;
|
|
13
|
+
sourceColumnsByCell.set(cell, columnIndex);
|
|
14
|
+
const rowEnd = Math.min(rows.length, rowIndex + rowSpan);
|
|
15
|
+
const columnEnd = Math.min(columnCount, columnIndex + columnSpan);
|
|
16
|
+
for (let gridRowIndex = rowIndex; gridRowIndex < rowEnd; gridRowIndex++) {
|
|
17
|
+
const cellsByColumn = getOrCreateCellRow(sourceCellsByRow, gridRowIndex);
|
|
18
|
+
for (let gridColumnIndex = columnIndex; gridColumnIndex < columnEnd; gridColumnIndex++) cellsByColumn.set(gridColumnIndex, cell);
|
|
19
|
+
}
|
|
20
|
+
if (rowSpan > 1) for (let spannedRowIndex = rowIndex + 1; spannedRowIndex < rowIndex + rowSpan; spannedRowIndex++) {
|
|
21
|
+
const occupied = getOrCreateOccupiedRow(occupiedColumnsByRow, spannedRowIndex);
|
|
22
|
+
for (let columnOffset = 0; columnOffset < columnSpan; columnOffset++) occupied.add(columnIndex + columnOffset);
|
|
23
|
+
}
|
|
24
|
+
columnIndex = firstAvailableColumn(occupiedColumnsByRow.get(rowIndex), columnIndex + columnSpan);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
occupiedColumnsByRow,
|
|
29
|
+
sourceCellsByRow,
|
|
30
|
+
sourceColumnsByCell
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const getFirstAvailableColumn = (grid, rowIndex, startingColumn) => firstAvailableColumn(grid.occupiedColumnsByRow.get(rowIndex), startingColumn);
|
|
34
|
+
const getSourceCellAt = (grid, rowIndex, columnIndex) => grid.sourceCellsByRow.get(rowIndex)?.get(columnIndex);
|
|
35
|
+
const getSourceCellColumn = (grid, cell) => grid.sourceColumnsByCell.get(cell);
|
|
36
|
+
const getTableCellVerticalBorderHeight = (grid, cell, rowIndex) => {
|
|
37
|
+
const sourceColumn = cell ? getSourceCellColumn(grid, cell) : void 0;
|
|
38
|
+
const aboveBottom = (sourceColumn === void 0 ? void 0 : getSourceCellAt(grid, rowIndex - 1, sourceColumn))?.borders?.bottom;
|
|
39
|
+
const aboveOwnsEdge = aboveBottom !== void 0 && aboveBottom.width !== 0 && aboveBottom.style !== "none" && aboveBottom.style !== "nil";
|
|
40
|
+
return (rowIndex === 0 || !aboveOwnsEdge ? cell?.borders?.top?.width ?? 0 : 0) + (cell?.borders?.bottom?.width ?? 0);
|
|
41
|
+
};
|
|
42
|
+
const firstAvailableColumn = (occupiedColumns, startingColumn) => {
|
|
43
|
+
let columnIndex = startingColumn;
|
|
44
|
+
while (occupiedColumns?.has(columnIndex)) columnIndex++;
|
|
45
|
+
return columnIndex;
|
|
46
|
+
};
|
|
47
|
+
const getOrCreateCellRow = (sourceCellsByRow, rowIndex) => {
|
|
48
|
+
const existing = sourceCellsByRow.get(rowIndex);
|
|
49
|
+
if (existing !== void 0) return existing;
|
|
50
|
+
const cellsByColumn = /* @__PURE__ */ new Map();
|
|
51
|
+
sourceCellsByRow.set(rowIndex, cellsByColumn);
|
|
52
|
+
return cellsByColumn;
|
|
53
|
+
};
|
|
54
|
+
const getOrCreateOccupiedRow = (occupiedColumnsByRow, rowIndex) => {
|
|
55
|
+
const existing = occupiedColumnsByRow.get(rowIndex);
|
|
56
|
+
if (existing !== void 0) return existing;
|
|
57
|
+
const occupiedColumns = /* @__PURE__ */ new Set();
|
|
58
|
+
occupiedColumnsByRow.set(rowIndex, occupiedColumns);
|
|
59
|
+
return occupiedColumns;
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { buildTableCellGrid, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ParagraphBlock, ParagraphMeasure } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/measure/textBoxParagraphLayout.d.ts
|
|
4
|
+
type TextBoxParagraphPlacement = {
|
|
5
|
+
leadingSpacing: number;
|
|
6
|
+
contentHeight: number;
|
|
7
|
+
};
|
|
8
|
+
type TextBoxParagraphLayout = {
|
|
9
|
+
placements: TextBoxParagraphPlacement[];
|
|
10
|
+
totalHeight: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Lay out a text box's paragraph story as one unpaginated flow. Adjacent
|
|
14
|
+
* before/after spacing collapses to the larger value, matching body flow.
|
|
15
|
+
*/
|
|
16
|
+
declare function layoutTextBoxParagraphs(blocks: readonly ParagraphBlock[], measures: readonly ParagraphMeasure[]): TextBoxParagraphLayout;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { TextBoxParagraphLayout, TextBoxParagraphPlacement, layoutTextBoxParagraphs };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { measuredLineRangeHeight } from "../lineFlow.js";
|
|
2
|
+
import { getParagraphSpacingAfter, getParagraphSpacingBefore } from "../paragraphSpacing.js";
|
|
3
|
+
import { panic } from "better-result";
|
|
4
|
+
//#region src/layout-engine/measure/textBoxParagraphLayout.ts
|
|
5
|
+
/**
|
|
6
|
+
* Lay out a text box's paragraph story as one unpaginated flow. Adjacent
|
|
7
|
+
* before/after spacing collapses to the larger value, matching body flow.
|
|
8
|
+
*/
|
|
9
|
+
function layoutTextBoxParagraphs(blocks, measures) {
|
|
10
|
+
if (blocks.length !== measures.length) panic("layoutTextBoxParagraphs: block and measure counts must match");
|
|
11
|
+
const placements = [];
|
|
12
|
+
let totalHeight = 0;
|
|
13
|
+
let trailingSpacing = 0;
|
|
14
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
15
|
+
const block = blocks[index];
|
|
16
|
+
const measure = measures[index];
|
|
17
|
+
const leadingSpacing = Math.max(getParagraphSpacingBefore(block), trailingSpacing);
|
|
18
|
+
const contentHeight = measuredLineRangeHeight(measure.lines, 0, measure.lines.length);
|
|
19
|
+
placements.push({
|
|
20
|
+
leadingSpacing,
|
|
21
|
+
contentHeight
|
|
22
|
+
});
|
|
23
|
+
totalHeight += leadingSpacing + contentHeight;
|
|
24
|
+
trailingSpacing = getParagraphSpacingAfter(block);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
placements,
|
|
28
|
+
totalHeight: totalHeight + trailingSpacing
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { layoutTextBoxParagraphs };
|
|
@@ -35,7 +35,8 @@ type PaginatorOptions = {
|
|
|
35
35
|
* its title page (typically extended top margin to clear an overflowing
|
|
36
36
|
* first-page header) vs. its body pages. Pages 2+ use `margins`.
|
|
37
37
|
*/
|
|
38
|
-
firstPageMargins?: PageMargins; /**
|
|
38
|
+
firstPageMargins?: PageMargins; /** Per-section body margins used on even section pages. */
|
|
39
|
+
sectionEvenPageMargins?: (PageMargins | undefined)[]; /** Column configuration (optional). */
|
|
39
40
|
columns?: ColumnLayout; /** Per-page footnote reserved heights (pageNumber → height in pixels). */
|
|
40
41
|
footnoteReservedHeights?: Map<number, number>; /** Header/footer refs by section index. */
|
|
41
42
|
sectionHeaderFooterRefs?: PageHeaderFooterRefs[]; /** Callback when a new page is created. */
|