@stll/folio-core 0.25.2 → 0.25.3
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/layout-bridge/convert/toFlowBlocks.js +3 -4
- package/dist/layout-bridge/engine/clickToPosition.js +13 -28
- package/dist/layout-bridge/engine/hitTest.d.ts +2 -0
- package/dist/layout-bridge/engine/hitTest.js +28 -13
- package/dist/layout-bridge/engine/selectionRects.js +26 -15
- package/dist/layout-engine/index.js +6 -9
- package/dist/layout-engine/measure/hyperlinkInstance.d.ts +6 -0
- package/dist/layout-engine/measure/hyperlinkInstance.js +8 -0
- package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -1
- package/dist/layout-engine/measure/tableCellGrid.js +29 -6
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.js +22 -0
- package/dist/layout-engine/types.d.ts +1 -1
- package/dist/layout-painter/renderParagraph.d.ts +3 -0
- package/dist/layout-painter/renderParagraph.js +13 -19
- package/dist/layout-painter/renderTable.js +36 -74
- package/dist/prosemirror/conversion/toProseDoc.js +35 -39
- package/dist/utils/paragraphInlineLayout.d.ts +14 -0
- package/dist/utils/paragraphInlineLayout.js +25 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { convertBulletToUnicode } from "../../docx/bulletMarkers.js";
|
|
2
2
|
import { resolveDocumentGridLinePitch } from "../../docx/documentGrid.js";
|
|
3
3
|
import { formatOoxmlCounter } from "../../docx/ooxmlCounterFormatter.js";
|
|
4
|
+
import { setHyperlinkInstanceIndex } from "../../layout-engine/measure/hyperlinkInstance.js";
|
|
4
5
|
import { setParagraphFrame } from "../../layout-engine/paragraphFrame.js";
|
|
5
6
|
import { setTextBoxGroupId } from "../../layout-engine/textBoxGroup.js";
|
|
6
7
|
import { DEFAULT_TEXTBOX_MARGINS } from "../../layout-engine/types.js";
|
|
@@ -267,6 +268,7 @@ function extractRunFormatting(marks, theme) {
|
|
|
267
268
|
const attrs = expectHyperlinkMarkAttrs(mark);
|
|
268
269
|
const link = { href: attrs.href };
|
|
269
270
|
if (attrs.tooltip !== void 0) link.tooltip = attrs.tooltip;
|
|
271
|
+
if (attrs._docxHyperlinkIndex !== void 0) setHyperlinkInstanceIndex(link, attrs._docxHyperlinkIndex);
|
|
270
272
|
formatting.hyperlink = link;
|
|
271
273
|
break;
|
|
272
274
|
}
|
|
@@ -468,10 +470,7 @@ const TOC_STYLE_ID = /^TOC\d*$/iu;
|
|
|
468
470
|
*/
|
|
469
471
|
function stripTocHyperlinkStyle(formatting) {
|
|
470
472
|
if (!formatting.hyperlink) return;
|
|
471
|
-
formatting.hyperlink =
|
|
472
|
-
...formatting.hyperlink,
|
|
473
|
-
noDefaultStyle: true
|
|
474
|
-
};
|
|
473
|
+
formatting.hyperlink.noDefaultStyle = true;
|
|
475
474
|
delete formatting.color;
|
|
476
475
|
delete formatting.underline;
|
|
477
476
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { measuredLineAdvance, measuredLineContentOffset } from "../../layout-engine/lineFlow.js";
|
|
2
2
|
import { buildRunFontStyle, findCharacterAtX } from "../../layout-engine/measure/measureHelpers.js";
|
|
3
3
|
import { measureRun } from "../../layout-engine/measure/measureProvider.js";
|
|
4
|
+
import { resolvePhysicalParagraphInlineLayout } from "../../utils/paragraphInlineLayout.js";
|
|
4
5
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
5
6
|
//#region src/layout-bridge/engine/clickToPosition.ts
|
|
6
7
|
/**
|
|
@@ -120,24 +121,12 @@ function findLineAtY(measure, localY, fromLine, toLine) {
|
|
|
120
121
|
if (toLine > fromLine) return Math.min(toLine - 1, measure.lines.length - 1);
|
|
121
122
|
return null;
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
-
* Find the character position at an X coordinate within a line.
|
|
125
|
-
*
|
|
126
|
-
* Uses canvas text measurement for pixel-perfect accuracy.
|
|
127
|
-
*
|
|
128
|
-
* @param block - The paragraph block.
|
|
129
|
-
* @param line - The measured line.
|
|
130
|
-
* @param x - X coordinate relative to the line's start position.
|
|
131
|
-
* @param availableWidth - Available width for alignment calculations.
|
|
132
|
-
* @returns Character offset and PM position.
|
|
133
|
-
*/
|
|
134
|
-
function findCharacterInLine(block, line, x, availableWidth) {
|
|
124
|
+
function findCharacterInLine({ block, line, x, availableWidth, alignment }) {
|
|
135
125
|
const { pmStart, pmEnd } = computeLinePmRange(block, line);
|
|
136
126
|
if (pmStart === void 0 || pmEnd === void 0) return {
|
|
137
127
|
charOffset: 0,
|
|
138
128
|
pmPosition: block.pmStart ?? 0
|
|
139
129
|
};
|
|
140
|
-
const alignment = block.attrs?.alignment ?? "left";
|
|
141
130
|
let alignmentOffset = 0;
|
|
142
131
|
if (alignment === "center") alignmentOffset = Math.max(0, (availableWidth - line.width) / 2);
|
|
143
132
|
else if (alignment === "right") alignmentOffset = Math.max(0, availableWidth - line.width);
|
|
@@ -236,11 +225,15 @@ function clickToPositionInParagraph(fragmentHit) {
|
|
|
236
225
|
if (lineIndex === null) return null;
|
|
237
226
|
const line = paragraphMeasure.lines[lineIndex];
|
|
238
227
|
if (!line) return null;
|
|
239
|
-
const
|
|
240
|
-
const indentLeft = indent?.left ?? 0;
|
|
241
|
-
const indentRight = indent?.right ?? 0;
|
|
228
|
+
const { alignment, indentLeft, indentRight } = resolvePhysicalParagraphInlineLayout(paragraphBlock);
|
|
242
229
|
const availableWidth = Math.max(0, fragment.width - indentLeft - indentRight);
|
|
243
|
-
const { charOffset, pmPosition } = findCharacterInLine(
|
|
230
|
+
const { charOffset, pmPosition } = findCharacterInLine({
|
|
231
|
+
block: paragraphBlock,
|
|
232
|
+
line,
|
|
233
|
+
x: localX - indentLeft,
|
|
234
|
+
availableWidth,
|
|
235
|
+
alignment
|
|
236
|
+
});
|
|
244
237
|
return {
|
|
245
238
|
pmPosition,
|
|
246
239
|
charOffset,
|
|
@@ -254,7 +247,7 @@ function clickToPositionInParagraph(fragmentHit) {
|
|
|
254
247
|
* @returns PM position, or null if mapping fails.
|
|
255
248
|
*/
|
|
256
249
|
function clickToPositionInTableCell(tableCellHit) {
|
|
257
|
-
const { cellBlock, cellMeasure, cellLocalX, cellLocalY } = tableCellHit;
|
|
250
|
+
const { cellBlock, cellMeasure, cellLocalX, cellLocalY, cellContentWidth } = tableCellHit;
|
|
258
251
|
if (!cellBlock || !cellMeasure) return null;
|
|
259
252
|
return clickToPositionInParagraph({
|
|
260
253
|
fragment: {
|
|
@@ -262,7 +255,7 @@ function clickToPositionInTableCell(tableCellHit) {
|
|
|
262
255
|
blockId: cellBlock.id,
|
|
263
256
|
x: 0,
|
|
264
257
|
y: 0,
|
|
265
|
-
width:
|
|
258
|
+
width: cellContentWidth,
|
|
266
259
|
fromLine: 0,
|
|
267
260
|
toLine: cellMeasure.lines.length,
|
|
268
261
|
height: cellMeasure.totalHeight
|
|
@@ -371,21 +364,13 @@ function positionToX(block, measure, pmPosition, _fragmentWidth) {
|
|
|
371
364
|
}
|
|
372
365
|
return null;
|
|
373
366
|
}
|
|
374
|
-
const getMaxLineWidth = (lines, fallback) => {
|
|
375
|
-
let maxWidth = fallback;
|
|
376
|
-
for (const line of lines) maxWidth = Math.max(maxWidth, line.width);
|
|
377
|
-
return maxWidth;
|
|
378
|
-
};
|
|
379
367
|
/**
|
|
380
368
|
* Get the bounding rect for a PM position (for caret rendering).
|
|
381
369
|
*/
|
|
382
370
|
function getPositionRect(block, measure, pmPosition, fragmentX, fragmentY, fragmentWidth, fromLine) {
|
|
383
371
|
const result = positionToX(block, measure, pmPosition, fragmentWidth);
|
|
384
372
|
if (!result) return null;
|
|
385
|
-
const alignment = block
|
|
386
|
-
const indent = block.attrs?.indent;
|
|
387
|
-
const indentLeft = indent?.left ?? 0;
|
|
388
|
-
const indentRight = indent?.right ?? 0;
|
|
373
|
+
const { alignment, indentLeft, indentRight } = resolvePhysicalParagraphInlineLayout(block);
|
|
389
374
|
const availableWidth = Math.max(0, fragmentWidth - indentLeft - indentRight);
|
|
390
375
|
const line = measure.lines[result.lineIndex];
|
|
391
376
|
if (!line) return null;
|
|
@@ -57,6 +57,8 @@ type TableCellHit = {
|
|
|
57
57
|
cellMeasure?: ParagraphMeasure;
|
|
58
58
|
/** X position relative to cell content area. */
|
|
59
59
|
cellLocalX: number;
|
|
60
|
+
/** Width of the cell content area after physical padding. */
|
|
61
|
+
cellContentWidth: number;
|
|
60
62
|
/** Y position relative to cell content area. */
|
|
61
63
|
cellLocalY: number;
|
|
62
64
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getHeaderRowsHeight } from "../../layout-engine/index.js";
|
|
2
2
|
import { measuredLineRangeHeight } from "../../layout-engine/lineFlow.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getTableCellContentWidth } from "../../layout-engine/measure/tableCellFloating.js";
|
|
4
|
+
import { buildTableCellGrid, buildTableCellPlacements } from "../../layout-engine/measure/tableCellGrid.js";
|
|
5
|
+
import { resolveTableCellPadding } from "../../layout-engine/types.js";
|
|
4
6
|
//#region src/layout-bridge/engine/hitTest.ts
|
|
5
7
|
/**
|
|
6
8
|
* Hit Testing Utilities
|
|
@@ -214,21 +216,33 @@ function hitTestTableCell(pageHit, blocks, measures, pagePoint) {
|
|
|
214
216
|
const rowMeasure = tableMeasure.rows[rowIndex];
|
|
215
217
|
const row = tableBlock.rows[rowIndex];
|
|
216
218
|
if (!rowMeasure || !row) continue;
|
|
217
|
-
let colX = getTableRowLeadingWidth(row, tableMeasure.columnWidths);
|
|
218
219
|
let colIndex = -1;
|
|
220
|
+
let cellLeft = 0;
|
|
219
221
|
if (rowMeasure.cells.length === 0 || row.cells.length === 0) continue;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
const cellPlacements = buildTableCellPlacements({
|
|
223
|
+
grid: buildTableCellGrid(tableBlock.rows, tableMeasure.columnWidths.length),
|
|
224
|
+
columnWidths: tableMeasure.columnWidths,
|
|
225
|
+
bidi: tableBlock.bidi === true
|
|
226
|
+
});
|
|
227
|
+
let nearestDistance = Infinity;
|
|
228
|
+
for (let c = 0; c < row.cells.length; c++) {
|
|
229
|
+
const cell = row.cells[c];
|
|
230
|
+
if (!cell) continue;
|
|
231
|
+
const placement = cellPlacements.get(cell);
|
|
232
|
+
if (!placement) continue;
|
|
233
|
+
if (localX >= placement.left && localX < placement.left + placement.width) {
|
|
223
234
|
colIndex = c;
|
|
235
|
+
cellLeft = placement.left;
|
|
224
236
|
break;
|
|
225
237
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
const distance = Math.min(Math.abs(localX - placement.left), Math.abs(localX - placement.left - placement.width));
|
|
239
|
+
if (distance < nearestDistance) {
|
|
240
|
+
nearestDistance = distance;
|
|
241
|
+
colIndex = c;
|
|
242
|
+
cellLeft = placement.left;
|
|
243
|
+
}
|
|
231
244
|
}
|
|
245
|
+
if (colIndex === -1) continue;
|
|
232
246
|
const cellMeasure = rowMeasure.cells[colIndex];
|
|
233
247
|
const cell = row.cells[colIndex];
|
|
234
248
|
if (!cellMeasure || !cell) continue;
|
|
@@ -239,8 +253,6 @@ function hitTestTableCell(pageHit, blocks, measures, pagePoint) {
|
|
|
239
253
|
rowTop = headerHeight;
|
|
240
254
|
for (let r = tableFragment.fromRow; r < rowIndex; r++) rowTop += tableMeasure.rows[r]?.height ?? 0;
|
|
241
255
|
}
|
|
242
|
-
let colLeft = 0;
|
|
243
|
-
for (let c = 0; c < colIndex; c++) colLeft += rowMeasure.cells[c]?.width ?? 0;
|
|
244
256
|
let cellBlock;
|
|
245
257
|
let cellBlockMeasure;
|
|
246
258
|
if (cell.blocks.length > 0) {
|
|
@@ -251,7 +263,9 @@ function hitTestTableCell(pageHit, blocks, measures, pagePoint) {
|
|
|
251
263
|
cellBlockMeasure = firstMeasure;
|
|
252
264
|
}
|
|
253
265
|
}
|
|
254
|
-
const
|
|
266
|
+
const { left: padLeft } = resolveTableCellPadding(cell);
|
|
267
|
+
const cellLocalX = localX - cellLeft - padLeft;
|
|
268
|
+
const cellContentWidth = getTableCellContentWidth(cell, cellMeasure);
|
|
255
269
|
const clipOffset = isClickOnHeader ? 0 : tableFragment.topClip ?? 0;
|
|
256
270
|
const cellLocalY = localY - rowTop + clipOffset;
|
|
257
271
|
return {
|
|
@@ -264,6 +278,7 @@ function hitTestTableCell(pageHit, blocks, measures, pagePoint) {
|
|
|
264
278
|
...cellBlock !== void 0 ? { cellBlock } : {},
|
|
265
279
|
...cellBlockMeasure !== void 0 ? { cellMeasure: cellBlockMeasure } : {},
|
|
266
280
|
cellLocalX: Math.max(0, cellLocalX),
|
|
281
|
+
cellContentWidth,
|
|
267
282
|
cellLocalY: Math.max(0, cellLocalY)
|
|
268
283
|
};
|
|
269
284
|
}
|
|
@@ -4,7 +4,9 @@ import { buildRunFontStyle } from "../../layout-engine/measure/measureHelpers.js
|
|
|
4
4
|
import { measureParagraph } from "../../layout-engine/measure/measureParagraph.js";
|
|
5
5
|
import { measureRun } from "../../layout-engine/measure/measureProvider.js";
|
|
6
6
|
import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "../../layout-engine/measure/tableCellFloating.js";
|
|
7
|
-
import {
|
|
7
|
+
import { buildTableCellGrid, buildTableCellPlacements } from "../../layout-engine/measure/tableCellGrid.js";
|
|
8
|
+
import { resolveTableCellPadding } from "../../layout-engine/types.js";
|
|
9
|
+
import { resolvePhysicalParagraphInlineLayout } from "../../utils/paragraphInlineLayout.js";
|
|
8
10
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
9
11
|
import { getPageTop } from "./hitTest.js";
|
|
10
12
|
//#region src/layout-bridge/engine/selectionRects.ts
|
|
@@ -246,6 +248,7 @@ function selectionToRects(layout, blocks, measures, from, to) {
|
|
|
246
248
|
const selFrom = Math.min(from, to);
|
|
247
249
|
const selTo = Math.max(from, to);
|
|
248
250
|
const rects = [];
|
|
251
|
+
const tableCellPlacements = /* @__PURE__ */ new WeakMap();
|
|
249
252
|
for (let pageIndex = 0; pageIndex < layout.pages.length; pageIndex++) {
|
|
250
253
|
const page = layout.pages[pageIndex];
|
|
251
254
|
const pageTopY = getPageTop(layout, pageIndex);
|
|
@@ -260,6 +263,8 @@ function selectionToRects(layout, blocks, measures, from, to) {
|
|
|
260
263
|
const paragraphBlock = block;
|
|
261
264
|
const paragraphMeasure = measure;
|
|
262
265
|
const paragraphFragment = fragment;
|
|
266
|
+
const { alignment, indentLeft, indentRight } = resolvePhysicalParagraphInlineLayout(paragraphBlock);
|
|
267
|
+
const availableWidth = Math.max(0, fragment.width - indentLeft - indentRight);
|
|
263
268
|
const intersectingLines = findLinesInRange(paragraphBlock, paragraphMeasure, selFrom, selTo);
|
|
264
269
|
for (const { line, index } of intersectingLines) {
|
|
265
270
|
if (index < paragraphFragment.fromLine || index >= paragraphFragment.toLine) continue;
|
|
@@ -272,13 +277,8 @@ function selectionToRects(layout, blocks, measures, from, to) {
|
|
|
272
277
|
if (!isEmptyLine && sliceFrom >= sliceTo) continue;
|
|
273
278
|
const charOffsetFrom = pmPosToCharOffset(paragraphBlock, line, sliceFrom);
|
|
274
279
|
const charOffsetTo = pmPosToCharOffset(paragraphBlock, line, sliceTo);
|
|
275
|
-
const indent = paragraphBlock.attrs?.indent;
|
|
276
|
-
const indentLeft = indent?.left ?? 0;
|
|
277
|
-
const indentRight = indent?.right ?? 0;
|
|
278
|
-
const availableWidth = Math.max(0, fragment.width - indentLeft - indentRight);
|
|
279
280
|
const startX = charOffsetToX(paragraphBlock, line, charOffsetFrom, availableWidth);
|
|
280
281
|
const endX = charOffsetToX(paragraphBlock, line, charOffsetTo, availableWidth);
|
|
281
|
-
const alignment = paragraphBlock.attrs?.alignment ?? "left";
|
|
282
282
|
let alignmentOffset = 0;
|
|
283
283
|
if (alignment === "center") alignmentOffset = Math.max(0, (availableWidth - line.width) / 2);
|
|
284
284
|
else if (alignment === "right") alignmentOffset = Math.max(0, availableWidth - line.width);
|
|
@@ -305,6 +305,15 @@ function selectionToRects(layout, blocks, measures, from, to) {
|
|
|
305
305
|
const tableBlock = block;
|
|
306
306
|
const tableMeasure = measure;
|
|
307
307
|
const tableFragment = fragment;
|
|
308
|
+
let cellPlacements = tableCellPlacements.get(tableBlock);
|
|
309
|
+
if (!cellPlacements) {
|
|
310
|
+
cellPlacements = buildTableCellPlacements({
|
|
311
|
+
grid: buildTableCellGrid(tableBlock.rows, tableMeasure.columnWidths.length),
|
|
312
|
+
columnWidths: tableMeasure.columnWidths,
|
|
313
|
+
bidi: tableBlock.bidi === true
|
|
314
|
+
});
|
|
315
|
+
tableCellPlacements.set(tableBlock, cellPlacements);
|
|
316
|
+
}
|
|
308
317
|
const hdrCount = tableFragment.headerRowCount ?? 0;
|
|
309
318
|
let rowY = hdrCount > 0 && tableFragment.continuesFromPrev ? getHeaderRowsHeight(tableMeasure, hdrCount) : 0;
|
|
310
319
|
for (let rowIndex = tableFragment.fromRow; rowIndex < tableFragment.toRow && rowIndex < tableBlock.rows.length; rowIndex++) {
|
|
@@ -313,11 +322,12 @@ function selectionToRects(layout, blocks, measures, from, to) {
|
|
|
313
322
|
if (!row || !rowMeasure) continue;
|
|
314
323
|
const clipTop = tableFragment.topClip ?? 0;
|
|
315
324
|
const clipBottom = tableFragment.bottomClip ?? rowMeasure.height;
|
|
316
|
-
let cellX = getTableRowLeadingWidth(row, tableMeasure.columnWidths);
|
|
317
325
|
for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) {
|
|
318
326
|
const cell = row.cells[cellIndex];
|
|
319
327
|
const cellMeasure = rowMeasure.cells[cellIndex];
|
|
320
328
|
if (!cell || !cellMeasure) continue;
|
|
329
|
+
const placement = cellPlacements.get(cell);
|
|
330
|
+
if (!placement) continue;
|
|
321
331
|
const contentWidth = getTableCellContentWidth(cell, cellMeasure);
|
|
322
332
|
const floatingZones = buildTableCellFloatingZones(getTableCellFloatingImages(cell, cellMeasure, contentWidth), contentWidth);
|
|
323
333
|
const contentOffsetX = getCellContentOffsetX(cell);
|
|
@@ -340,6 +350,8 @@ function selectionToRects(layout, blocks, measures, from, to) {
|
|
|
340
350
|
floatingZones,
|
|
341
351
|
paragraphYOffset: blockY
|
|
342
352
|
});
|
|
353
|
+
const { alignment, indentLeft, indentRight } = resolvePhysicalParagraphInlineLayout(paragraphBlock);
|
|
354
|
+
const availableWidth = Math.max(0, contentWidth - indentLeft - indentRight);
|
|
343
355
|
const intersectingLines = findLinesInRange(paragraphBlock, paragraphMeasure, selFrom, selTo);
|
|
344
356
|
for (const { line, index } of intersectingLines) {
|
|
345
357
|
const range = computeLinePmRange(paragraphBlock, line);
|
|
@@ -351,13 +363,16 @@ function selectionToRects(layout, blocks, measures, from, to) {
|
|
|
351
363
|
if (!isEmptyLine && sliceFrom >= sliceTo) continue;
|
|
352
364
|
const charOffsetFrom = pmPosToCharOffset(paragraphBlock, line, sliceFrom);
|
|
353
365
|
const charOffsetTo = pmPosToCharOffset(paragraphBlock, line, sliceTo);
|
|
354
|
-
const startX = charOffsetToX(paragraphBlock, line, charOffsetFrom,
|
|
355
|
-
const endX = charOffsetToX(paragraphBlock, line, charOffsetTo,
|
|
366
|
+
const startX = charOffsetToX(paragraphBlock, line, charOffsetFrom, availableWidth);
|
|
367
|
+
const endX = charOffsetToX(paragraphBlock, line, charOffsetTo, availableWidth);
|
|
368
|
+
let alignmentOffset = 0;
|
|
369
|
+
if (alignment === "center") alignmentOffset = Math.max(0, (availableWidth - line.width) / 2);
|
|
370
|
+
else if (alignment === "right") alignmentOffset = Math.max(0, availableWidth - line.width);
|
|
356
371
|
const lineY = measuredLineContentOffset(paragraphMeasure.lines, 0, index);
|
|
357
372
|
const clippedLineY = contentOffsetY + blockY + lineY;
|
|
358
373
|
if (clippedLineY + line.lineHeight <= clipTop || clippedLineY >= clipBottom) continue;
|
|
359
374
|
rects.push({
|
|
360
|
-
x: tableFragment.x +
|
|
375
|
+
x: tableFragment.x + placement.left + contentOffsetX + indentLeft + alignmentOffset + Math.min(startX, endX),
|
|
361
376
|
y: tableFragment.y + rowY + clippedLineY - clipTop + pageTopY,
|
|
362
377
|
width: isEmptyLine ? EMPTY_PARAGRAPH_SLIVER_WIDTH : Math.max(1, Math.abs(endX - startX)),
|
|
363
378
|
height: line.lineHeight,
|
|
@@ -366,7 +381,6 @@ function selectionToRects(layout, blocks, measures, from, to) {
|
|
|
366
381
|
}
|
|
367
382
|
blockY += paragraphMeasure.totalHeight;
|
|
368
383
|
}
|
|
369
|
-
cellX += cellMeasure.width;
|
|
370
384
|
}
|
|
371
385
|
rowY += rowMeasure.height;
|
|
372
386
|
}
|
|
@@ -419,12 +433,9 @@ function getCaretPosition(layout, blocks, measures, pmPosition) {
|
|
|
419
433
|
if (range.pmStart === void 0 || range.pmEnd === void 0) continue;
|
|
420
434
|
if (pmPosition >= range.pmStart && pmPosition <= range.pmEnd) {
|
|
421
435
|
const charOffset = pmPosToCharOffset(paragraphBlock, line, pmPosition);
|
|
422
|
-
const
|
|
423
|
-
const indentLeft = indent?.left ?? 0;
|
|
424
|
-
const indentRight = indent?.right ?? 0;
|
|
436
|
+
const { alignment, indentLeft, indentRight } = resolvePhysicalParagraphInlineLayout(paragraphBlock);
|
|
425
437
|
const availableWidth = Math.max(0, fragment.width - indentLeft - indentRight);
|
|
426
438
|
const x = charOffsetToX(paragraphBlock, line, charOffset, availableWidth);
|
|
427
|
-
const alignment = paragraphBlock.attrs?.alignment ?? "left";
|
|
428
439
|
let alignmentOffset = 0;
|
|
429
440
|
if (alignment === "center") alignmentOffset = Math.max(0, (availableWidth - line.width) / 2);
|
|
430
441
|
else if (alignment === "right") alignmentOffset = Math.max(0, availableWidth - line.width);
|
|
@@ -3,6 +3,7 @@ import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
|
|
|
3
3
|
import { calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
|
|
4
4
|
import { measuredLineAdvance } from "./lineFlow.js";
|
|
5
5
|
import { resolveFloatingTableX } from "./measure/floatingTablePosition.js";
|
|
6
|
+
import { resolveTableInlinePlacement } from "./measure/tableInlinePlacement.js";
|
|
6
7
|
import { createPaginator } from "./paginator.js";
|
|
7
8
|
import { getParagraphFragmentPmRange } from "./paragraphFragmentRange.js";
|
|
8
9
|
import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacingTree } from "./paragraphSpacing.js";
|
|
@@ -521,15 +522,11 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
521
522
|
const breakInfo = buildTableRowBreakInfo(block, measure);
|
|
522
523
|
const verticallyMergedRows = getVerticallyMergedRows(block);
|
|
523
524
|
const computeTableX = (columnIndex) => {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const leadingCellMargin = block.rows.at(0)?.cells.at(0)?.padding?.left ?? 0;
|
|
530
|
-
x -= leadingCellMargin;
|
|
531
|
-
}
|
|
532
|
-
return x;
|
|
525
|
+
const x = paginator.getColumnX(columnIndex);
|
|
526
|
+
const placement = resolveTableInlinePlacement(block);
|
|
527
|
+
if (placement.alignment === "center") return x + (paginator.columnWidth - measure.totalWidth) / 2;
|
|
528
|
+
if (placement.alignment === "right") return x + paginator.columnWidth - measure.totalWidth - placement.offset;
|
|
529
|
+
return x + placement.offset;
|
|
533
530
|
};
|
|
534
531
|
const getCurrentRowCapacity = (state = paginator.getCurrentState()) => state.rawContentBottom - state.topMargin;
|
|
535
532
|
const hasAdjacentPriorTableRows = (rowIndex, state = paginator.getCurrentState()) => {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { HyperlinkInfo } from "../types.js";
|
|
2
|
+
//#region src/layout-engine/measure/hyperlinkInstance.d.ts
|
|
3
|
+
declare const setHyperlinkInstanceIndex: (hyperlink: HyperlinkInfo, instanceIndex: number) => void;
|
|
4
|
+
declare const getHyperlinkInstanceIndex: (hyperlink: HyperlinkInfo) => number | undefined;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { getHyperlinkInstanceIndex, setHyperlinkInstanceIndex };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/hyperlinkInstance.ts
|
|
2
|
+
const hyperlinkInstanceIndexes = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
const setHyperlinkInstanceIndex = (hyperlink, instanceIndex) => {
|
|
4
|
+
hyperlinkInstanceIndexes.set(hyperlink, instanceIndex);
|
|
5
|
+
};
|
|
6
|
+
const getHyperlinkInstanceIndex = (hyperlink) => hyperlinkInstanceIndexes.get(hyperlink);
|
|
7
|
+
//#endregion
|
|
8
|
+
export { getHyperlinkInstanceIndex, setHyperlinkInstanceIndex };
|
|
@@ -5,10 +5,24 @@ type TableCellGrid = {
|
|
|
5
5
|
sourceCellsByRow: ReadonlyMap<number, ReadonlyMap<number, TableCell>>;
|
|
6
6
|
sourceColumnsByCell: ReadonlyMap<TableCell, number>;
|
|
7
7
|
};
|
|
8
|
+
type TableCellPlacement = {
|
|
9
|
+
sourceColumn: number;
|
|
10
|
+
columnSpan: number;
|
|
11
|
+
left: number;
|
|
12
|
+
width: number;
|
|
13
|
+
};
|
|
14
|
+
type TableCellPlacements = ReadonlyMap<TableCell, TableCellPlacement>;
|
|
15
|
+
type BuildTableCellPlacementsOptions = {
|
|
16
|
+
grid: TableCellGrid;
|
|
17
|
+
columnWidths: readonly number[];
|
|
18
|
+
bidi: boolean;
|
|
19
|
+
};
|
|
8
20
|
declare const buildTableCellGrid: (rows: readonly TableRow[], columnCount: number) => TableCellGrid;
|
|
9
21
|
declare const getFirstAvailableColumn: (grid: TableCellGrid, rowIndex: number, startingColumn: number) => number;
|
|
10
22
|
declare const getSourceCellAt: (grid: TableCellGrid, rowIndex: number, columnIndex: number) => TableCell | undefined;
|
|
11
23
|
declare const getSourceCellColumn: (grid: TableCellGrid, cell: TableCell) => number | undefined;
|
|
24
|
+
/** Resolve all source cells to canonical logical columns and physical boxes. */
|
|
25
|
+
declare const buildTableCellPlacements: ({ grid, columnWidths, bidi }: BuildTableCellPlacementsOptions) => TableCellPlacements;
|
|
12
26
|
declare const getTableCellVerticalBorderHeight: (grid: TableCellGrid, cell: TableCell | undefined, rowIndex: number) => number;
|
|
13
27
|
//#endregion
|
|
14
|
-
export { TableCellGrid, buildTableCellGrid, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
28
|
+
export { TableCellGrid, TableCellPlacement, TableCellPlacements, buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
@@ -6,10 +6,11 @@ const buildTableCellGrid = (rows, columnCount) => {
|
|
|
6
6
|
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
7
7
|
const row = rows[rowIndex];
|
|
8
8
|
if (!row) continue;
|
|
9
|
-
|
|
9
|
+
const gridBefore = Math.max(0, Math.min(columnCount, Math.trunc(row.gridBefore ?? 0)));
|
|
10
|
+
let columnIndex = firstAvailableColumn(occupiedColumnsByRow.get(rowIndex), gridBefore);
|
|
10
11
|
for (const cell of row.cells) {
|
|
11
|
-
const columnSpan = cell.colSpan ?? 1;
|
|
12
|
-
const rowSpan = cell.rowSpan ?? 1;
|
|
12
|
+
const columnSpan = Math.max(1, Math.trunc(cell.colSpan ?? 1));
|
|
13
|
+
const rowSpan = Math.max(1, Math.trunc(cell.rowSpan ?? 1));
|
|
13
14
|
sourceColumnsByCell.set(cell, columnIndex);
|
|
14
15
|
const rowEnd = Math.min(rows.length, rowIndex + rowSpan);
|
|
15
16
|
const columnEnd = Math.min(columnCount, columnIndex + columnSpan);
|
|
@@ -17,9 +18,9 @@ const buildTableCellGrid = (rows, columnCount) => {
|
|
|
17
18
|
const cellsByColumn = getOrCreateCellRow(sourceCellsByRow, gridRowIndex);
|
|
18
19
|
for (let gridColumnIndex = columnIndex; gridColumnIndex < columnEnd; gridColumnIndex++) cellsByColumn.set(gridColumnIndex, cell);
|
|
19
20
|
}
|
|
20
|
-
if (rowSpan > 1) for (let spannedRowIndex = rowIndex + 1; spannedRowIndex <
|
|
21
|
+
if (rowSpan > 1) for (let spannedRowIndex = rowIndex + 1; spannedRowIndex < rowEnd; spannedRowIndex++) {
|
|
21
22
|
const occupied = getOrCreateOccupiedRow(occupiedColumnsByRow, spannedRowIndex);
|
|
22
|
-
for (let
|
|
23
|
+
for (let gridColumnIndex = columnIndex; gridColumnIndex < columnEnd; gridColumnIndex++) occupied.add(gridColumnIndex);
|
|
23
24
|
}
|
|
24
25
|
columnIndex = firstAvailableColumn(occupiedColumnsByRow.get(rowIndex), columnIndex + columnSpan);
|
|
25
26
|
}
|
|
@@ -33,6 +34,28 @@ const buildTableCellGrid = (rows, columnCount) => {
|
|
|
33
34
|
const getFirstAvailableColumn = (grid, rowIndex, startingColumn) => firstAvailableColumn(grid.occupiedColumnsByRow.get(rowIndex), startingColumn);
|
|
34
35
|
const getSourceCellAt = (grid, rowIndex, columnIndex) => grid.sourceCellsByRow.get(rowIndex)?.get(columnIndex);
|
|
35
36
|
const getSourceCellColumn = (grid, cell) => grid.sourceColumnsByCell.get(cell);
|
|
37
|
+
/** Resolve all source cells to canonical logical columns and physical boxes. */
|
|
38
|
+
const buildTableCellPlacements = ({ grid, columnWidths, bidi }) => {
|
|
39
|
+
const columnOffsets = [0];
|
|
40
|
+
for (const columnWidth of columnWidths) columnOffsets.push((columnOffsets.at(-1) ?? 0) + columnWidth);
|
|
41
|
+
const tableWidth = columnOffsets.at(-1) ?? 0;
|
|
42
|
+
const placements = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const [cell, sourceColumn] of grid.sourceColumnsByCell) {
|
|
44
|
+
if (sourceColumn < 0 || sourceColumn >= columnWidths.length) continue;
|
|
45
|
+
const declaredColumnSpan = Math.max(1, Math.trunc(cell.colSpan ?? 1));
|
|
46
|
+
const columnSpan = Math.min(declaredColumnSpan, columnWidths.length - sourceColumn);
|
|
47
|
+
const logicalLeft = columnOffsets.at(sourceColumn) ?? 0;
|
|
48
|
+
const logicalRight = columnOffsets.at(sourceColumn + columnSpan) ?? logicalLeft;
|
|
49
|
+
const width = logicalRight - logicalLeft;
|
|
50
|
+
placements.set(cell, {
|
|
51
|
+
sourceColumn,
|
|
52
|
+
columnSpan,
|
|
53
|
+
left: bidi ? tableWidth - logicalRight : logicalLeft,
|
|
54
|
+
width
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return placements;
|
|
58
|
+
};
|
|
36
59
|
const getTableCellVerticalBorderHeight = (grid, cell, rowIndex) => {
|
|
37
60
|
const sourceColumn = cell ? getSourceCellColumn(grid, cell) : void 0;
|
|
38
61
|
const aboveBottom = (sourceColumn === void 0 ? void 0 : getSourceCellAt(grid, rowIndex - 1, sourceColumn))?.borders?.bottom;
|
|
@@ -59,4 +82,4 @@ const getOrCreateOccupiedRow = (occupiedColumnsByRow, rowIndex) => {
|
|
|
59
82
|
return occupiedColumns;
|
|
60
83
|
};
|
|
61
84
|
//#endregion
|
|
62
|
-
export { buildTableCellGrid, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
85
|
+
export { buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TableBlock } from "../types.js";
|
|
2
|
+
//#region src/layout-engine/measure/tableInlinePlacement.d.ts
|
|
3
|
+
type TableInlinePlacement = {
|
|
4
|
+
alignment: "center";
|
|
5
|
+
} | {
|
|
6
|
+
alignment: "left" | "right";
|
|
7
|
+
offset: number;
|
|
8
|
+
};
|
|
9
|
+
/** Resolve an inline table's horizontal anchor without losing RTL leading-edge semantics. */
|
|
10
|
+
declare const resolveTableInlinePlacement: (table: Pick<TableBlock, "bidi" | "indent" | "justification" | "rows">) => TableInlinePlacement;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { resolveTableInlinePlacement };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { resolveTableCellPadding } from "../types.js";
|
|
2
|
+
//#region src/layout-engine/measure/tableInlinePlacement.ts
|
|
3
|
+
/** Resolve an inline table's horizontal anchor without losing RTL leading-edge semantics. */
|
|
4
|
+
const resolveTableInlinePlacement = (table) => {
|
|
5
|
+
if (table.justification === "center") return { alignment: "center" };
|
|
6
|
+
if (table.justification === "right") return {
|
|
7
|
+
alignment: "right",
|
|
8
|
+
offset: 0
|
|
9
|
+
};
|
|
10
|
+
const firstCell = table.rows.at(0)?.cells.at(0);
|
|
11
|
+
const firstCellPadding = firstCell ? resolveTableCellPadding(firstCell) : void 0;
|
|
12
|
+
if (table.justification === "left" || table.bidi !== true) return {
|
|
13
|
+
alignment: "left",
|
|
14
|
+
offset: table.indent ?? -(firstCellPadding?.left ?? 0)
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
alignment: "right",
|
|
18
|
+
offset: table.indent ?? -(firstCellPadding?.right ?? 0)
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { resolveTableInlinePlacement };
|
|
@@ -632,7 +632,7 @@ type TableBlock = {
|
|
|
632
632
|
layout?: "fixed" | "autofit";
|
|
633
633
|
/** Table horizontal alignment */
|
|
634
634
|
justification?: "left" | "center" | "right";
|
|
635
|
-
/** Table indent from
|
|
635
|
+
/** Table indent from the leading margin (in pixels, from w:tblInd). */
|
|
636
636
|
indent?: number;
|
|
637
637
|
/** Right-to-left column order (w:bidiVisual): logical column 0 paints on the right. */
|
|
638
638
|
bidi?: boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HyperlinkInfo, MeasuredLine, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure, Run, TabStop } from "../layout-engine/types.js";
|
|
2
2
|
import { RenderContext } from "./renderUtils.js";
|
|
3
|
+
import { PhysicalParagraphInlineLayout } from "../utils/paragraphInlineLayout.js";
|
|
3
4
|
//#region src/layout-painter/renderParagraph.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* CSS class names for paragraph rendering
|
|
@@ -70,6 +71,8 @@ type RenderLineOptions = {
|
|
|
70
71
|
firstLineIndentPx?: number;
|
|
71
72
|
/** Paragraph base direction for logical first-line indentation. */
|
|
72
73
|
isRtl?: boolean;
|
|
74
|
+
/** Explicit alignment after resolving authored bidi physical sides. */
|
|
75
|
+
explicitAlignment?: PhysicalParagraphInlineLayout["explicitAlignment"];
|
|
73
76
|
/** Full paragraph content-box width before physical indents. */
|
|
74
77
|
contentWidthPx?: number;
|
|
75
78
|
/** Line-specific floating image margins (calculated per-line based on Y overlap) */
|
|
@@ -2,6 +2,7 @@ import { ommlToMathml } from "../docx/mathToMathml.js";
|
|
|
2
2
|
import { parseXmlDocument } from "../docx/xmlParser.js";
|
|
3
3
|
import { evaluateFieldInstruction } from "../fields/evaluateField.js";
|
|
4
4
|
import { hasComplexScriptFormatting, resolveComplexScriptFormatting } from "../layout-engine/measure/complexScriptFormatting.js";
|
|
5
|
+
import { getHyperlinkInstanceIndex } from "../layout-engine/measure/hyperlinkInstance.js";
|
|
5
6
|
import { getListMarkerInlineWidth, getListMarkerVisualOffset, resolveListMarkerFont } from "../layout-engine/measure/listMarkerWidth.js";
|
|
6
7
|
import { DOCX_SCRIPT_FONT_SCALE } from "../layout-engine/measure/measureHelpers.js";
|
|
7
8
|
import { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode, toPaintedText } from "../layout-engine/measure/textMeasurementPolicy.js";
|
|
@@ -10,7 +11,7 @@ import { calculateTabWidth } from "../prosemirror/utils/tabCalculator.js";
|
|
|
10
11
|
import { AUTHOR_COLORS, getAuthorColorIdx } from "../utils/authorColors.js";
|
|
11
12
|
import { resolveFontFamily } from "../utils/fontResolver.js";
|
|
12
13
|
import "../utils/fontWeights.js";
|
|
13
|
-
import {
|
|
14
|
+
import { resolvePhysicalParagraphInlineLayout } from "../utils/paragraphInlineLayout.js";
|
|
14
15
|
import { inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox } from "../utils/rotationBoundingBox.js";
|
|
15
16
|
import { applySanitizedImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
16
17
|
import { SCRIPT_CLASS, hasCjk, hasComplexScript, segmentByScript } from "../utils/scriptSegments.js";
|
|
@@ -49,6 +50,9 @@ function isTextRun(run) {
|
|
|
49
50
|
return run.kind === "text";
|
|
50
51
|
}
|
|
51
52
|
function hyperlinksMatch(left, right) {
|
|
53
|
+
const leftInstanceIndex = getHyperlinkInstanceIndex(left);
|
|
54
|
+
const rightInstanceIndex = getHyperlinkInstanceIndex(right);
|
|
55
|
+
if (leftInstanceIndex !== void 0 || rightInstanceIndex !== void 0) return leftInstanceIndex === rightInstanceIndex;
|
|
52
56
|
return left.href === right.href && left.tooltip === right.tooltip && left.noDefaultStyle === right.noDefaultStyle;
|
|
53
57
|
}
|
|
54
58
|
function collectLeftToRightDisplayedUrlHyperlinks(runs) {
|
|
@@ -1097,7 +1101,7 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
1097
1101
|
return runEl;
|
|
1098
1102
|
};
|
|
1099
1103
|
const onlyRun = runsForLine.length === 1 ? runsForLine[0] : void 0;
|
|
1100
|
-
if (onlyRun && isMathRun(onlyRun) && onlyRun.display === "block" && block.
|
|
1104
|
+
if (onlyRun && isMathRun(onlyRun) && onlyRun.display === "block" && (options?.explicitAlignment ?? resolvePhysicalParagraphInlineLayout(block).explicitAlignment) !== "right") lineEl.style.textAlign = "center";
|
|
1101
1105
|
if (runsForLine.length === 1 && isImageRun(runsForLine[0])) {
|
|
1102
1106
|
lineEl.style.display = "flex";
|
|
1103
1107
|
lineEl.style.alignItems = "center";
|
|
@@ -1342,26 +1346,15 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1342
1346
|
if (fragment.continuesFromPrev) fragmentEl.dataset["continuesFromPrev"] = "true";
|
|
1343
1347
|
if (fragment.continuesOnNext) fragmentEl.dataset["continuesOnNext"] = "true";
|
|
1344
1348
|
const lines = measure.lines.slice(fragment.fromLine, fragment.toLine);
|
|
1345
|
-
const
|
|
1349
|
+
const inlineLayout = resolvePhysicalParagraphInlineLayout(block);
|
|
1350
|
+
const { alignment, indentLeft, indentRight, isRtl } = inlineLayout;
|
|
1346
1351
|
if (block.attrs?.styleId) fragmentEl.dataset["styleId"] = block.attrs.styleId;
|
|
1347
|
-
const isRtl = isRtlParagraph(block);
|
|
1348
1352
|
if (isRtl) fragmentEl.dir = "rtl";
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
else if (isRtl) fragmentEl.style.textAlign = "right";
|
|
1354
|
-
const effectiveAlignment = alignment ?? (isRtl ? "right" : void 0);
|
|
1353
|
+
let fragmentAlignment = alignment;
|
|
1354
|
+
if (alignment === "justify") fragmentAlignment = isRtl ? "right" : "left";
|
|
1355
|
+
fragmentEl.style.textAlign = fragmentAlignment;
|
|
1356
|
+
const effectiveAlignment = alignment;
|
|
1355
1357
|
const indent = block.attrs?.indent;
|
|
1356
|
-
let indentLeft = 0;
|
|
1357
|
-
let indentRight = 0;
|
|
1358
|
-
if (indent) if (isRtl) {
|
|
1359
|
-
if (indent.left !== void 0) indentRight = indent.left;
|
|
1360
|
-
if (indent.right !== void 0) indentLeft = indent.right;
|
|
1361
|
-
} else {
|
|
1362
|
-
if (indent.left !== void 0) indentLeft = indent.left;
|
|
1363
|
-
if (indent.right !== void 0) indentRight = indent.right;
|
|
1364
|
-
}
|
|
1365
1358
|
const borders = block.attrs?.borders;
|
|
1366
1359
|
if (borders) {
|
|
1367
1360
|
const borderStyleToCss = (style) => {
|
|
@@ -1448,6 +1441,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1448
1441
|
tabLeftIndentPx: indent?.left ?? 0,
|
|
1449
1442
|
firstLineIndentPx: isFirstLine ? firstLineIndentPx : 0,
|
|
1450
1443
|
isRtl,
|
|
1444
|
+
...inlineLayout.explicitAlignment === void 0 ? {} : { explicitAlignment: inlineLayout.explicitAlignment },
|
|
1451
1445
|
contentWidthPx: fragment.width,
|
|
1452
1446
|
context,
|
|
1453
1447
|
floatingMargins: {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
|
|
2
2
|
import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "../layout-engine/measure/tableCellFloating.js";
|
|
3
3
|
import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "../layout-engine/measure/tableCellFlow.js";
|
|
4
|
-
import {
|
|
4
|
+
import { buildTableCellGrid, buildTableCellPlacements, getSourceCellAt } from "../layout-engine/measure/tableCellGrid.js";
|
|
5
|
+
import { resolveTableInlinePlacement } from "../layout-engine/measure/tableInlinePlacement.js";
|
|
6
|
+
import { isFloatingImageRun, isFloatingTextBoxBlock, resolveTableCellPadding, tableColumnsArePinned } from "../layout-engine/types.js";
|
|
5
7
|
import { applySanitizedImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
6
8
|
import { emuToPixels } from "../utils/units.js";
|
|
7
9
|
import { resolveAnchoredImagePosition } from "./anchoredImagePosition.js";
|
|
@@ -228,11 +230,14 @@ function renderNestedTable(block, measure, context, doc) {
|
|
|
228
230
|
tableEl.style.position = "relative";
|
|
229
231
|
tableEl.style.width = `${measure.totalWidth}px`;
|
|
230
232
|
tableEl.style.display = "block";
|
|
231
|
-
|
|
233
|
+
const placement = resolveTableInlinePlacement(block);
|
|
234
|
+
if (placement.alignment === "center") {
|
|
232
235
|
tableEl.style.marginLeft = "auto";
|
|
233
236
|
tableEl.style.marginRight = "auto";
|
|
234
|
-
} else if (
|
|
235
|
-
|
|
237
|
+
} else if (placement.alignment === "right") {
|
|
238
|
+
tableEl.style.marginLeft = "auto";
|
|
239
|
+
tableEl.style.marginRight = `${placement.offset}px`;
|
|
240
|
+
} else tableEl.style.marginLeft = `${placement.offset}px`;
|
|
236
241
|
tableEl.dataset["blockId"] = String(block.id);
|
|
237
242
|
if (block.pmStart !== void 0) tableEl.dataset["pmStart"] = String(block.pmStart);
|
|
238
243
|
if (block.pmEnd !== void 0) tableEl.dataset["pmEnd"] = String(block.pmEnd);
|
|
@@ -243,9 +248,13 @@ function renderNestedTable(block, measure, context, doc) {
|
|
|
243
248
|
yPos += i_item.height;
|
|
244
249
|
}
|
|
245
250
|
rowYPositions.push(yPos);
|
|
246
|
-
const spanningCells = /* @__PURE__ */ new Map();
|
|
247
251
|
const columnsPinned = tableColumnsArePinned(block);
|
|
248
|
-
const cellGrid = buildTableCellGrid(block, measure.columnWidths.length);
|
|
252
|
+
const cellGrid = buildTableCellGrid(block.rows, measure.columnWidths.length);
|
|
253
|
+
const cellPlacements = buildTableCellPlacements({
|
|
254
|
+
grid: cellGrid,
|
|
255
|
+
columnWidths: measure.columnWidths,
|
|
256
|
+
bidi: block.bidi === true
|
|
257
|
+
});
|
|
249
258
|
let y = 0;
|
|
250
259
|
for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex++) {
|
|
251
260
|
const row = block.rows[rowIndex];
|
|
@@ -264,11 +273,11 @@ function renderNestedTable(block, measure, context, doc) {
|
|
|
264
273
|
totalRows: block.rows.length,
|
|
265
274
|
context,
|
|
266
275
|
doc,
|
|
267
|
-
spanningCells,
|
|
268
276
|
rowYPositions,
|
|
269
277
|
bidi: block.bidi === true,
|
|
270
278
|
columnsPinned,
|
|
271
|
-
cellGrid
|
|
279
|
+
cellGrid,
|
|
280
|
+
cellPlacements
|
|
272
281
|
});
|
|
273
282
|
tableEl.append(rowEl);
|
|
274
283
|
y += rowMeasure.height;
|
|
@@ -320,13 +329,13 @@ function renderCellDiagonalBorder({ border, direction, cellWidth, cellHeight, do
|
|
|
320
329
|
}
|
|
321
330
|
return line;
|
|
322
331
|
}
|
|
323
|
-
function renderTableCell({ cell, cellMeasure, x, rowHeight, borderFlags, columnsPinned, context, doc, contentClip, pageContentPosition }) {
|
|
332
|
+
function renderTableCell({ cell, cellMeasure, x, width, rowHeight, borderFlags, columnsPinned, context, doc, contentClip, pageContentPosition }) {
|
|
324
333
|
const cellEl = doc.createElement("div");
|
|
325
334
|
cellEl.className = TABLE_CLASS_NAMES.cell;
|
|
326
335
|
cellEl.style.position = "absolute";
|
|
327
336
|
cellEl.style.left = `${x}px`;
|
|
328
337
|
cellEl.style.top = "0";
|
|
329
|
-
cellEl.style.width = `${
|
|
338
|
+
cellEl.style.width = `${width}px`;
|
|
330
339
|
cellEl.style.height = `${rowHeight}px`;
|
|
331
340
|
cellEl.style.overflow = "hidden";
|
|
332
341
|
cellEl.style.boxSizing = "border-box";
|
|
@@ -396,7 +405,7 @@ function renderTableCell({ cell, cellMeasure, x, rowHeight, borderFlags, columns
|
|
|
396
405
|
const topLeftToBottomRight = renderCellDiagonalBorder({
|
|
397
406
|
border: cell.borders?.topLeftToBottomRight,
|
|
398
407
|
direction: "top-left-to-bottom-right",
|
|
399
|
-
cellWidth:
|
|
408
|
+
cellWidth: width,
|
|
400
409
|
cellHeight: rowHeight,
|
|
401
410
|
doc
|
|
402
411
|
});
|
|
@@ -404,7 +413,7 @@ function renderTableCell({ cell, cellMeasure, x, rowHeight, borderFlags, columns
|
|
|
404
413
|
const topRightToBottomLeft = renderCellDiagonalBorder({
|
|
405
414
|
border: cell.borders?.topRightToBottomLeft,
|
|
406
415
|
direction: "top-right-to-bottom-left",
|
|
407
|
-
cellWidth:
|
|
416
|
+
cellWidth: width,
|
|
408
417
|
cellHeight: rowHeight,
|
|
409
418
|
doc
|
|
410
419
|
});
|
|
@@ -423,52 +432,23 @@ function renderTableCell({ cell, cellMeasure, x, rowHeight, borderFlags, columns
|
|
|
423
432
|
}
|
|
424
433
|
return cellEl;
|
|
425
434
|
}
|
|
426
|
-
const tableCellGridKey = (rowIndex, columnIndex) => `${rowIndex}:${columnIndex}`;
|
|
427
|
-
function buildTableCellGrid(block, columnCount) {
|
|
428
|
-
const grid = /* @__PURE__ */ new Map();
|
|
429
|
-
for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex++) {
|
|
430
|
-
const row = block.rows[rowIndex];
|
|
431
|
-
if (!row) continue;
|
|
432
|
-
let columnIndex = 0;
|
|
433
|
-
for (const cell of row.cells) {
|
|
434
|
-
while (grid.has(tableCellGridKey(rowIndex, columnIndex))) columnIndex += 1;
|
|
435
|
-
const colSpan = cell.colSpan ?? 1;
|
|
436
|
-
const rowSpan = cell.rowSpan ?? 1;
|
|
437
|
-
const rowEnd = Math.min(block.rows.length, rowIndex + rowSpan);
|
|
438
|
-
const columnEnd = Math.min(columnCount, columnIndex + colSpan);
|
|
439
|
-
for (let gridRow = rowIndex; gridRow < rowEnd; gridRow++) for (let gridColumn = columnIndex; gridColumn < columnEnd; gridColumn++) grid.set(tableCellGridKey(gridRow, gridColumn), cell);
|
|
440
|
-
columnIndex += colSpan;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
return grid;
|
|
444
|
-
}
|
|
445
435
|
const hasVisibleBorder = (border) => border !== void 0 && border.style !== "none" && border.style !== "nil";
|
|
446
|
-
function renderTableRow({ row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc,
|
|
436
|
+
function renderTableRow({ row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc, rowYPositions, isFirstRowInFragment, bidi = false, columnsPinned = false, cellGrid, cellPlacements, contentClip, pageContentPosition }) {
|
|
447
437
|
const rowEl = doc.createElement("div");
|
|
448
438
|
rowEl.className = TABLE_CLASS_NAMES.row;
|
|
449
|
-
const tableWidth = bidi ? columnWidths.reduce((sum, columnWidth) => sum + columnWidth, 0) : 0;
|
|
450
439
|
rowEl.style.position = "absolute";
|
|
451
440
|
rowEl.style.left = "0";
|
|
452
441
|
rowEl.style.top = `${y}px`;
|
|
453
442
|
rowEl.style.width = "100%";
|
|
454
443
|
rowEl.style.height = `${rowMeasure.height}px`;
|
|
455
444
|
rowEl.dataset["rowIndex"] = String(rowIndex);
|
|
456
|
-
const occupiedColumns = /* @__PURE__ */ new Set();
|
|
457
|
-
if (spanningCells) {
|
|
458
|
-
for (const [, spanCell] of spanningCells) if (spanCell.startRow < rowIndex && spanCell.startRow + spanCell.rowSpan > rowIndex) for (let c = 0; c < spanCell.colSpan; c++) occupiedColumns.add(spanCell.columnIndex + c);
|
|
459
|
-
}
|
|
460
|
-
const gridBefore = row.gridBefore ?? 0;
|
|
461
|
-
let x = getTableRowLeadingWidth(row, columnWidths);
|
|
462
|
-
let columnIndex = gridBefore;
|
|
463
|
-
while (occupiedColumns.has(columnIndex)) {
|
|
464
|
-
x += columnWidths[columnIndex] ?? 0;
|
|
465
|
-
columnIndex++;
|
|
466
|
-
}
|
|
467
445
|
for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) {
|
|
468
446
|
const cell = row.cells[cellIndex];
|
|
469
447
|
const cellMeasure = rowMeasure.cells[cellIndex];
|
|
470
448
|
if (!cell || !cellMeasure) continue;
|
|
471
|
-
const
|
|
449
|
+
const placement = cellPlacements.get(cell);
|
|
450
|
+
if (!placement) continue;
|
|
451
|
+
const { sourceColumn: columnIndex, columnSpan: colSpan, left: cellLeft, width } = placement;
|
|
472
452
|
const rowSpan = cell.rowSpan ?? 1;
|
|
473
453
|
let cellHeight = rowMeasure.height;
|
|
474
454
|
if (rowSpan > 1 && rowYPositions) {
|
|
@@ -476,24 +456,21 @@ function renderTableRow({ row, rowMeasure, rowIndex, y, columnWidths, totalRows,
|
|
|
476
456
|
for (let r = rowIndex; r < rowIndex + rowSpan && r < rowYPositions.length - 1; r++) cellHeight += (rowYPositions[r + 1] ?? 0) - (rowYPositions[r] ?? 0);
|
|
477
457
|
if (cellHeight === 0) cellHeight = rowMeasure.height * rowSpan;
|
|
478
458
|
}
|
|
479
|
-
let cellWidth = 0;
|
|
480
|
-
for (let c = 0; c < colSpan && columnIndex + c < columnWidths.length; c++) cellWidth += columnWidths[columnIndex + c] ?? 0;
|
|
481
|
-
const cellLeft = bidi ? tableWidth - x - cellWidth : x;
|
|
482
459
|
const isFirstRow = rowIndex === 0 || isFirstRowInFragment === true;
|
|
483
460
|
const isLastRow = rowIndex + rowSpan >= totalRows;
|
|
484
461
|
const atLogicalStart = columnIndex === 0;
|
|
485
462
|
const atLogicalEnd = columnIndex + colSpan >= columnWidths.length;
|
|
486
463
|
const isFirstCol = bidi ? atLogicalEnd : atLogicalStart;
|
|
487
464
|
const isLastCol = bidi ? atLogicalStart : atLogicalEnd;
|
|
488
|
-
const aboveCell = cellGrid
|
|
489
|
-
const
|
|
490
|
-
const leftCell = cellGrid?.get(tableCellGridKey(rowIndex, leftNeighborColumn));
|
|
465
|
+
const aboveCell = getSourceCellAt(cellGrid, rowIndex - 1, columnIndex);
|
|
466
|
+
const leftCell = getSourceCellAt(cellGrid, rowIndex, bidi ? columnIndex + colSpan : columnIndex - 1);
|
|
491
467
|
const drawTop = isFirstRow || !hasVisibleBorder(aboveCell?.borders?.bottom);
|
|
492
468
|
const drawLeft = isFirstCol || !hasVisibleBorder(leftCell?.borders?.right);
|
|
493
469
|
const cellEl = renderTableCell({
|
|
494
470
|
cell,
|
|
495
471
|
cellMeasure,
|
|
496
472
|
x: cellLeft,
|
|
473
|
+
width,
|
|
497
474
|
rowHeight: cellHeight,
|
|
498
475
|
borderFlags: {
|
|
499
476
|
drawTop,
|
|
@@ -515,25 +492,6 @@ function renderTableRow({ row, rowMeasure, rowIndex, y, columnWidths, totalRows,
|
|
|
515
492
|
cellEl.dataset["columnIndex"] = String(columnIndex);
|
|
516
493
|
if (rowSpan > 1) cellEl.dataset["rowSpan"] = String(rowSpan);
|
|
517
494
|
rowEl.append(cellEl);
|
|
518
|
-
if (rowSpan > 1 && spanningCells) {
|
|
519
|
-
const key = `${rowIndex}-${columnIndex}`;
|
|
520
|
-
spanningCells.set(key, {
|
|
521
|
-
cell,
|
|
522
|
-
cellMeasure,
|
|
523
|
-
columnIndex,
|
|
524
|
-
startRow: rowIndex,
|
|
525
|
-
rowSpan,
|
|
526
|
-
colSpan,
|
|
527
|
-
x: cellLeft,
|
|
528
|
-
totalHeight: cellHeight
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
x += cellWidth;
|
|
532
|
-
columnIndex += colSpan;
|
|
533
|
-
while (occupiedColumns.has(columnIndex)) {
|
|
534
|
-
x += columnWidths[columnIndex] ?? 0;
|
|
535
|
-
columnIndex++;
|
|
536
|
-
}
|
|
537
495
|
}
|
|
538
496
|
return rowEl;
|
|
539
497
|
}
|
|
@@ -592,9 +550,13 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
|
|
|
592
550
|
yPos += i_item.height;
|
|
593
551
|
}
|
|
594
552
|
rowYPositions.push(yPos);
|
|
595
|
-
const spanningCells = /* @__PURE__ */ new Map();
|
|
596
553
|
const columnsPinned = tableColumnsArePinned(block);
|
|
597
|
-
const cellGrid = buildTableCellGrid(block, measure.columnWidths.length);
|
|
554
|
+
const cellGrid = buildTableCellGrid(block.rows, measure.columnWidths.length);
|
|
555
|
+
const cellPlacements = buildTableCellPlacements({
|
|
556
|
+
grid: cellGrid,
|
|
557
|
+
columnWidths: measure.columnWidths,
|
|
558
|
+
bidi: block.bidi === true
|
|
559
|
+
});
|
|
598
560
|
const headerRowCount = fragment.headerRowCount ?? 0;
|
|
599
561
|
let y = 0;
|
|
600
562
|
if (headerRowCount > 0 && fragment.continuesFromPrev) for (let hdrIdx = 0; hdrIdx < headerRowCount; hdrIdx++) {
|
|
@@ -614,12 +576,12 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
|
|
|
614
576
|
totalRows: block.rows.length,
|
|
615
577
|
context,
|
|
616
578
|
doc,
|
|
617
|
-
spanningCells,
|
|
618
579
|
rowYPositions,
|
|
619
580
|
isFirstRowInFragment: hdrIdx === 0,
|
|
620
581
|
bidi: block.bidi === true,
|
|
621
582
|
columnsPinned,
|
|
622
583
|
cellGrid,
|
|
584
|
+
cellPlacements,
|
|
623
585
|
...tablePageContentPosition ? { pageContentPosition: {
|
|
624
586
|
...tablePageContentPosition,
|
|
625
587
|
y: tablePageContentPosition.y + y
|
|
@@ -667,12 +629,12 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
|
|
|
667
629
|
totalRows: block.rows.length,
|
|
668
630
|
context,
|
|
669
631
|
doc,
|
|
670
|
-
spanningCells,
|
|
671
632
|
rowYPositions,
|
|
672
633
|
isFirstRowInFragment,
|
|
673
634
|
bidi: block.bidi === true,
|
|
674
635
|
columnsPinned,
|
|
675
636
|
cellGrid,
|
|
637
|
+
cellPlacements,
|
|
676
638
|
...contentClip ? { contentClip } : {},
|
|
677
639
|
...tablePageContentPosition ? { pageContentPosition: {
|
|
678
640
|
...tablePageContentPosition,
|
|
@@ -30,6 +30,11 @@ const createTextBoxGroupIdFactory = () => {
|
|
|
30
30
|
let index = 0;
|
|
31
31
|
return () => `${salt}:${index++}`;
|
|
32
32
|
};
|
|
33
|
+
/** Keep imported hyperlink identity unique across every nested conversion scope. */
|
|
34
|
+
const createHyperlinkInstanceIndexAllocator = () => {
|
|
35
|
+
let index = 0;
|
|
36
|
+
return () => index++;
|
|
37
|
+
};
|
|
33
38
|
/**
|
|
34
39
|
* Convert a Document to a ProseMirror document
|
|
35
40
|
*
|
|
@@ -42,6 +47,11 @@ function toProseDoc(document, options) {
|
|
|
42
47
|
const styleResolver = createStyleEngine(options?.styles ?? document.package.styles);
|
|
43
48
|
const theme = options?.theme ?? document.package.theme ?? null;
|
|
44
49
|
const nextTextBoxGroupId = createTextBoxGroupIdFactory();
|
|
50
|
+
const conversionContext = {
|
|
51
|
+
theme,
|
|
52
|
+
nextTextBoxGroupId,
|
|
53
|
+
nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator()
|
|
54
|
+
};
|
|
45
55
|
const convertBodyBlocks = (blocks) => {
|
|
46
56
|
const out = [];
|
|
47
57
|
for (const block of blocks) if (block.type === "paragraph") {
|
|
@@ -49,10 +59,7 @@ function toProseDoc(document, options) {
|
|
|
49
59
|
if (pbPos === "before") out.push(schema.node("pageBreak"));
|
|
50
60
|
const converted = convertParagraphWithTextBoxes(block, styleResolver, {
|
|
51
61
|
textBoxGroupId: nextTextBoxGroupId(),
|
|
52
|
-
context:
|
|
53
|
-
theme,
|
|
54
|
-
nextTextBoxGroupId
|
|
55
|
-
}
|
|
62
|
+
context: conversionContext
|
|
56
63
|
});
|
|
57
64
|
const firstConverted = converted.at(0);
|
|
58
65
|
if (pbPos === "before" && converted.length === 1 && firstConverted?.type.name === "paragraph" && firstConverted.content.size === 0 && document.package.settings?.splitPageBreakAndParagraphMark !== true) {
|
|
@@ -72,10 +79,7 @@ function toProseDoc(document, options) {
|
|
|
72
79
|
}
|
|
73
80
|
out.push(...converted);
|
|
74
81
|
if (pbPos === "after") out.push(schema.node("pageBreak"));
|
|
75
|
-
} else if (block.type === "table") out.push(convertTable(block, styleResolver,
|
|
76
|
-
theme,
|
|
77
|
-
nextTextBoxGroupId
|
|
78
|
-
}));
|
|
82
|
+
} else if (block.type === "table") out.push(convertTable(block, styleResolver, conversionContext));
|
|
79
83
|
else out.push(convertBlockSdt(block, convertBodyBlocks));
|
|
80
84
|
return out;
|
|
81
85
|
};
|
|
@@ -122,14 +126,13 @@ function convertBlockSdt(blockSdt, convertBlocks) {
|
|
|
122
126
|
* Resolves style-based text formatting and passes it to runs so that
|
|
123
127
|
* paragraph styles (like Heading1) apply their font size, color, etc.
|
|
124
128
|
*/
|
|
125
|
-
function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
|
|
129
|
+
function convertParagraph(paragraph, styleResolver, nextHyperlinkInstanceIndex, activeCommentIds, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
|
|
126
130
|
const attrs = paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay);
|
|
127
131
|
const isTocParagraph = isTocStyleId(paragraph.formatting?.styleId);
|
|
128
132
|
const inlineNodes = [];
|
|
129
133
|
let inlineOffset = 0;
|
|
130
134
|
let bookmarksArr;
|
|
131
135
|
let emptyHyperlinks;
|
|
132
|
-
let hyperlinkIndex = 0;
|
|
133
136
|
const commentIds = activeCommentIds ?? /* @__PURE__ */ new Set();
|
|
134
137
|
const emitInlineNodes = (nodes) => {
|
|
135
138
|
if (nodes.length === 0) return;
|
|
@@ -160,7 +163,7 @@ function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFo
|
|
|
160
163
|
return suppressParagraphMarkFormatting(baseRunFormatting, inheritableParagraphRunFormatting, formatting, paragraphMarkPrecedesStyle);
|
|
161
164
|
};
|
|
162
165
|
const emitTrackedChange = (change, markType, moveKind) => {
|
|
163
|
-
emitInlineNodes(convertTrackedChange(change, markType, getInheritedRunFormatting, styleResolver, moveKind, textBoxAnchors));
|
|
166
|
+
emitInlineNodes(convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, moveKind, textBoxAnchors));
|
|
164
167
|
};
|
|
165
168
|
for (const content of paragraph.content) {
|
|
166
169
|
if (content.type === "commentRangeStart") commentIds.add(content.id);
|
|
@@ -168,12 +171,10 @@ function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFo
|
|
|
168
171
|
else if (content.type === "commentReference") anchorPointComment(inlineNodes, content.id);
|
|
169
172
|
else if (content.type === "run") emitInlineNodes(convertRun(content, getInheritedRunFormatting(content.formatting), styleResolver, textBoxAnchors));
|
|
170
173
|
else if (content.type === "hyperlink") {
|
|
171
|
-
const currentHyperlinkIndex = hyperlinkIndex;
|
|
172
|
-
hyperlinkIndex += 1;
|
|
173
174
|
const linkNodes = convertHyperlink(content, {
|
|
174
175
|
getInheritedRunFormatting,
|
|
175
176
|
styleResolver,
|
|
176
|
-
hyperlinkIndex:
|
|
177
|
+
hyperlinkIndex: nextHyperlinkInstanceIndex(),
|
|
177
178
|
textBoxAnchors
|
|
178
179
|
});
|
|
179
180
|
if (linkNodes.length === 0) {
|
|
@@ -189,7 +190,7 @@ function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFo
|
|
|
189
190
|
}
|
|
190
191
|
emitInlineNodes(linkNodes);
|
|
191
192
|
} else if (content.type === "simpleField" || content.type === "complexField") emitInlineNode(convertField(content, getInheritedRunFormatting, styleResolver));
|
|
192
|
-
else if (content.type === "inlineSdt") emitInlineNode(convertInlineSdt(content, getInheritedRunFormatting, styleResolver, textBoxAnchors));
|
|
193
|
+
else if (content.type === "inlineSdt") emitInlineNode(convertInlineSdt(content, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, textBoxAnchors));
|
|
193
194
|
else if (content.type === "insertion" || content.type === "moveTo") emitTrackedChange(content, "insertion", content.type === "moveTo" ? "moveTo" : null);
|
|
194
195
|
else if (content.type === "deletion" || content.type === "moveFrom") emitTrackedChange(content, "deletion", content.type === "moveFrom" ? "moveFrom" : null);
|
|
195
196
|
else if (content.type === "mathEquation") emitInlineNode(convertMathEquation(content));
|
|
@@ -243,13 +244,11 @@ function anchorPointComment(nodes, commentId) {
|
|
|
243
244
|
* Convert tracked change (insertion or deletion) content to PM nodes with
|
|
244
245
|
* an insertion/deletion mark applied.
|
|
245
246
|
*/
|
|
246
|
-
function convertTrackedChange(change, markType, getInheritedRunFormatting, styleResolver, moveKind = null, textBoxAnchors) {
|
|
247
|
+
function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, moveKind = null, textBoxAnchors) {
|
|
247
248
|
const nodes = [];
|
|
248
|
-
let hyperlinkIndex = 0;
|
|
249
249
|
for (const item of change.content) if (item.type === "run") nodes.push(...convertRun(item, getInheritedRunFormatting(item.formatting), styleResolver, textBoxAnchors));
|
|
250
250
|
else {
|
|
251
|
-
const currentHyperlinkIndex =
|
|
252
|
-
hyperlinkIndex += 1;
|
|
251
|
+
const currentHyperlinkIndex = nextHyperlinkInstanceIndex();
|
|
253
252
|
nodes.push(...convertHyperlink(item, {
|
|
254
253
|
getInheritedRunFormatting,
|
|
255
254
|
styleResolver,
|
|
@@ -921,7 +920,8 @@ function standaloneTableCellToProseMirror(cell, nodeType) {
|
|
|
921
920
|
styleResolver: null,
|
|
922
921
|
context: {
|
|
923
922
|
theme: null,
|
|
924
|
-
nextTextBoxGroupId: createTextBoxGroupIdFactory()
|
|
923
|
+
nextTextBoxGroupId: createTextBoxGroupIdFactory(),
|
|
924
|
+
nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator()
|
|
925
925
|
},
|
|
926
926
|
isHeader: nodeType === "tableHeader",
|
|
927
927
|
gridWidthPercent: void 0,
|
|
@@ -973,20 +973,17 @@ function convertMathEquation(math) {
|
|
|
973
973
|
/**
|
|
974
974
|
* Convert an InlineSdt to a ProseMirror sdt node with inline content.
|
|
975
975
|
*/
|
|
976
|
-
function convertInlineSdt(sdt, getInheritedRunFormatting, styleResolver, textBoxAnchors) {
|
|
976
|
+
function convertInlineSdt(sdt, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, textBoxAnchors) {
|
|
977
977
|
const props = sdt.properties;
|
|
978
978
|
const inlineNodes = [];
|
|
979
|
-
let hyperlinkIndex = 0;
|
|
980
979
|
for (const content of sdt.content) if (content.type === "run") {
|
|
981
980
|
const runNodes = convertRun(content, getInheritedRunFormatting(content.formatting), styleResolver, textBoxAnchors);
|
|
982
981
|
inlineNodes.push(...runNodes);
|
|
983
982
|
} else if (content.type === "hyperlink") {
|
|
984
|
-
const currentHyperlinkIndex = hyperlinkIndex;
|
|
985
|
-
hyperlinkIndex += 1;
|
|
986
983
|
const linkNodes = convertHyperlink(content, {
|
|
987
984
|
getInheritedRunFormatting,
|
|
988
985
|
styleResolver,
|
|
989
|
-
hyperlinkIndex:
|
|
986
|
+
hyperlinkIndex: nextHyperlinkInstanceIndex(),
|
|
990
987
|
textBoxAnchors
|
|
991
988
|
});
|
|
992
989
|
inlineNodes.push(...linkNodes);
|
|
@@ -994,12 +991,12 @@ function convertInlineSdt(sdt, getInheritedRunFormatting, styleResolver, textBox
|
|
|
994
991
|
const fieldNode = convertField(content, getInheritedRunFormatting, styleResolver);
|
|
995
992
|
if (fieldNode) inlineNodes.push(fieldNode);
|
|
996
993
|
} else if (content.type === "inlineSdt") {
|
|
997
|
-
const nestedSdt = convertInlineSdt(content, getInheritedRunFormatting, styleResolver, textBoxAnchors);
|
|
994
|
+
const nestedSdt = convertInlineSdt(content, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, textBoxAnchors);
|
|
998
995
|
if (nestedSdt) inlineNodes.push(nestedSdt);
|
|
999
|
-
} else if (content.type === "insertion") inlineNodes.push(...convertTrackedChange(content, "insertion", getInheritedRunFormatting, styleResolver, null, textBoxAnchors));
|
|
1000
|
-
else if (content.type === "deletion") inlineNodes.push(...convertTrackedChange(content, "deletion", getInheritedRunFormatting, styleResolver, null, textBoxAnchors));
|
|
1001
|
-
else if (content.type === "moveTo") inlineNodes.push(...convertTrackedChange(content, "insertion", getInheritedRunFormatting, styleResolver, "moveTo", textBoxAnchors));
|
|
1002
|
-
else if (content.type === "moveFrom") inlineNodes.push(...convertTrackedChange(content, "deletion", getInheritedRunFormatting, styleResolver, "moveFrom", textBoxAnchors));
|
|
996
|
+
} else if (content.type === "insertion") inlineNodes.push(...convertTrackedChange(content, "insertion", nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, null, textBoxAnchors));
|
|
997
|
+
else if (content.type === "deletion") inlineNodes.push(...convertTrackedChange(content, "deletion", nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, null, textBoxAnchors));
|
|
998
|
+
else if (content.type === "moveTo") inlineNodes.push(...convertTrackedChange(content, "insertion", nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, "moveTo", textBoxAnchors));
|
|
999
|
+
else if (content.type === "moveFrom") inlineNodes.push(...convertTrackedChange(content, "deletion", nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, "moveFrom", textBoxAnchors));
|
|
1003
1000
|
else {
|
|
1004
1001
|
const mathNode = convertMathEquation(content);
|
|
1005
1002
|
if (mathNode) inlineNodes.push(mathNode);
|
|
@@ -1380,7 +1377,7 @@ function convertShape(shape) {
|
|
|
1380
1377
|
}
|
|
1381
1378
|
function convertParagraphWithTextBoxes(block, styleResolver, { textBoxGroupId, context, extraRunFormatting, tableParagraphOverlay }) {
|
|
1382
1379
|
const { textBoxes, textBoxAnchors } = extractTextBoxesFromParagraph(block, textBoxGroupId);
|
|
1383
|
-
const pmParagraph = convertParagraph(block, styleResolver, void 0, extraRunFormatting, tableParagraphOverlay, textBoxAnchors);
|
|
1380
|
+
const pmParagraph = convertParagraph(block, styleResolver, context.nextHyperlinkInstanceIndex, void 0, extraRunFormatting, tableParagraphOverlay, textBoxAnchors);
|
|
1384
1381
|
const nodes = [];
|
|
1385
1382
|
const isEmptyAfterExtraction = textBoxes.length > 0 && !hasContentBesidesTextBoxAnchors(pmParagraph);
|
|
1386
1383
|
const keepWrapperParagraph = isEmptyAfterExtraction && hasParagraphBoundaryPayload(block, pmParagraph);
|
|
@@ -1639,19 +1636,18 @@ function headerFooterToProseDoc(content, options) {
|
|
|
1639
1636
|
const styleResolver = options?.styles ? createStyleEngine(options.styles) : null;
|
|
1640
1637
|
const theme = options?.theme ?? null;
|
|
1641
1638
|
const nextTextBoxGroupId = createTextBoxGroupIdFactory();
|
|
1639
|
+
const conversionContext = {
|
|
1640
|
+
theme,
|
|
1641
|
+
nextTextBoxGroupId,
|
|
1642
|
+
nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator()
|
|
1643
|
+
};
|
|
1642
1644
|
const convertBlocks = (blocks) => {
|
|
1643
1645
|
const out = [];
|
|
1644
1646
|
for (const block of blocks) if (block.type === "paragraph") out.push(...convertParagraphWithTextBoxes(block, styleResolver, {
|
|
1645
1647
|
textBoxGroupId: nextTextBoxGroupId(),
|
|
1646
|
-
context:
|
|
1647
|
-
theme,
|
|
1648
|
-
nextTextBoxGroupId
|
|
1649
|
-
}
|
|
1650
|
-
}));
|
|
1651
|
-
else if (block.type === "table") out.push(convertTable(block, styleResolver, {
|
|
1652
|
-
theme,
|
|
1653
|
-
nextTextBoxGroupId
|
|
1648
|
+
context: conversionContext
|
|
1654
1649
|
}));
|
|
1650
|
+
else if (block.type === "table") out.push(convertTable(block, styleResolver, conversionContext));
|
|
1655
1651
|
else out.push(convertBlockSdt(block, convertBlocks));
|
|
1656
1652
|
return out;
|
|
1657
1653
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ParagraphAttrs, ParagraphBlock } from "../layout-engine/types.js";
|
|
2
|
+
//#region src/utils/paragraphInlineLayout.d.ts
|
|
3
|
+
type ParagraphAlignment = NonNullable<ParagraphAttrs["alignment"]>;
|
|
4
|
+
type PhysicalParagraphInlineLayout = {
|
|
5
|
+
alignment: ParagraphAlignment;
|
|
6
|
+
explicitAlignment?: ParagraphAlignment;
|
|
7
|
+
indentLeft: number;
|
|
8
|
+
indentRight: number;
|
|
9
|
+
isRtl: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** Resolve logical OOXML paragraph sides into physical inline geometry. */
|
|
12
|
+
declare const resolvePhysicalParagraphInlineLayout: (block: ParagraphBlock) => PhysicalParagraphInlineLayout;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { PhysicalParagraphInlineLayout, resolvePhysicalParagraphInlineLayout };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isRtlParagraph } from "./paragraphBaseDirection.js";
|
|
2
|
+
//#region src/utils/paragraphInlineLayout.ts
|
|
3
|
+
const mirrorHorizontalAlignment = (alignment) => {
|
|
4
|
+
if (alignment === "left") return "right";
|
|
5
|
+
if (alignment === "right") return "left";
|
|
6
|
+
return alignment;
|
|
7
|
+
};
|
|
8
|
+
/** Resolve logical OOXML paragraph sides into physical inline geometry. */
|
|
9
|
+
const resolvePhysicalParagraphInlineLayout = (block) => {
|
|
10
|
+
const attrs = block.attrs;
|
|
11
|
+
const isRtl = isRtlParagraph(block);
|
|
12
|
+
const mirrorsHorizontalSides = attrs?.bidi === true;
|
|
13
|
+
const explicitAlignment = mirrorsHorizontalSides ? mirrorHorizontalAlignment(attrs?.alignment) : attrs?.alignment;
|
|
14
|
+
const indentLeft = attrs?.indent?.left ?? 0;
|
|
15
|
+
const indentRight = attrs?.indent?.right ?? 0;
|
|
16
|
+
return {
|
|
17
|
+
alignment: explicitAlignment ?? (isRtl ? "right" : "left"),
|
|
18
|
+
...explicitAlignment === void 0 ? {} : { explicitAlignment },
|
|
19
|
+
indentLeft: mirrorsHorizontalSides ? indentRight : indentLeft,
|
|
20
|
+
indentRight: mirrorsHorizontalSides ? indentLeft : indentRight,
|
|
21
|
+
isRtl
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { resolvePhysicalParagraphInlineLayout };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.3",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|