@stll/folio-core 0.25.1 → 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.
Files changed (31) hide show
  1. package/dist/ai-edits/headless.d.ts +6 -0
  2. package/dist/ai-edits/headless.js +60 -7
  3. package/dist/ai-edits/read.d.ts +26 -1
  4. package/dist/ai-edits/read.js +26 -1
  5. package/dist/docx/rezip.d.ts +2 -0
  6. package/dist/docx/rezip.js +61 -58
  7. package/dist/docx/selectiveXmlPatch.d.ts +24 -1
  8. package/dist/docx/selectiveXmlPatch.js +186 -1
  9. package/dist/docx/xmlParser.d.ts +7 -1
  10. package/dist/docx/xmlParser.js +11 -4
  11. package/dist/layout-bridge/convert/toFlowBlocks.js +3 -4
  12. package/dist/layout-bridge/engine/clickToPosition.js +13 -28
  13. package/dist/layout-bridge/engine/hitTest.d.ts +2 -0
  14. package/dist/layout-bridge/engine/hitTest.js +28 -13
  15. package/dist/layout-bridge/engine/selectionRects.js +26 -15
  16. package/dist/layout-engine/index.js +6 -9
  17. package/dist/layout-engine/measure/hyperlinkInstance.d.ts +6 -0
  18. package/dist/layout-engine/measure/hyperlinkInstance.js +8 -0
  19. package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -1
  20. package/dist/layout-engine/measure/tableCellGrid.js +29 -6
  21. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +12 -0
  22. package/dist/layout-engine/measure/tableInlinePlacement.js +22 -0
  23. package/dist/layout-engine/types.d.ts +1 -1
  24. package/dist/layout-painter/renderParagraph.d.ts +3 -0
  25. package/dist/layout-painter/renderParagraph.js +13 -19
  26. package/dist/layout-painter/renderTable.js +36 -74
  27. package/dist/prosemirror/conversion/toProseDoc.js +42 -44
  28. package/dist/prosemirror/extensions/marks/CommentExtension.js +1 -0
  29. package/dist/utils/paragraphInlineLayout.d.ts +14 -0
  30. package/dist/utils/paragraphInlineLayout.js +25 -0
  31. package/package.json +1 -1
@@ -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
- let columnIndex = firstAvailableColumn(occupiedColumnsByRow.get(rowIndex), row.gridBefore ?? 0);
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 < rowIndex + rowSpan; spannedRowIndex++) {
21
+ if (rowSpan > 1) for (let spannedRowIndex = rowIndex + 1; spannedRowIndex < rowEnd; spannedRowIndex++) {
21
22
  const occupied = getOrCreateOccupiedRow(occupiedColumnsByRow, spannedRowIndex);
22
- for (let columnOffset = 0; columnOffset < columnSpan; columnOffset++) occupied.add(columnIndex + columnOffset);
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 left margin (in pixels, from w:tblInd) */
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 { isRtlParagraph } from "../utils/paragraphBaseDirection.js";
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.attrs?.alignment !== "right") lineEl.style.textAlign = "center";
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 alignment = block.attrs?.alignment;
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
- if (alignment) if (alignment === "center") fragmentEl.style.textAlign = "center";
1350
- else if (alignment === "right") fragmentEl.style.textAlign = "right";
1351
- else if (alignment === "left") fragmentEl.style.textAlign = "left";
1352
- else fragmentEl.style.textAlign = isRtl ? "right" : "left";
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 { getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, resolveTableCellPadding, tableColumnsArePinned } from "../layout-engine/types.js";
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
- if (block.justification === "center") {
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 (block.justification === "right") tableEl.style.marginLeft = "auto";
235
- else if (block.indent) tableEl.style.marginLeft = `${block.indent}px`;
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 = `${cellMeasure.width}px`;
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: cellMeasure.width,
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: cellMeasure.width,
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, spanningCells, rowYPositions, isFirstRowInFragment, bidi = false, columnsPinned = false, cellGrid, contentClip, pageContentPosition }) {
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 colSpan = cell.colSpan ?? 1;
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?.get(tableCellGridKey(rowIndex - 1, columnIndex));
489
- const leftNeighborColumn = bidi ? columnIndex + colSpan : columnIndex - 1;
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,