Skip to content

Commit 43bd145

Browse files
committed
fix: visual testing regression
1 parent 9b7d1af commit 43bd145

1 file changed

Lines changed: 21 additions & 8 deletions

File tree

  • packages/layout-engine/layout-engine/src

packages/layout-engine/layout-engine/src/index.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ import type {
2929
FlowMode,
3030
NormalizedColumnLayout,
3131
} from '@superdoc/contracts';
32-
import { buildLayoutSourceIdentityForFragment, normalizeColumnLayout, getFragmentZIndex, resolveAnchoredGraphicY } from '@superdoc/contracts';
32+
import {
33+
buildLayoutSourceIdentityForFragment,
34+
normalizeColumnLayout,
35+
getFragmentZIndex,
36+
resolveAnchoredGraphicY,
37+
} from '@superdoc/contracts';
3338
import { createFloatingObjectManager, computeAnchorX } from './floating-objects.js';
3439
import { computeNextSectionPropsAtBreak } from './section-props';
3540
import {
@@ -2544,10 +2549,6 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
25442549
}
25452550
}
25462551

2547-
// Paragraph start Y (OOXML: anchor for vertAnchor="text"). Captured before layout so
2548-
// paragraph-anchored tables use it as base; offsetV (tblpY) positions below start to avoid overlap.
2549-
const paragraphStartY = paginator.ensurePage().cursorY;
2550-
25512552
layoutParagraphBlock(
25522553
{
25532554
block,
@@ -2587,16 +2588,26 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
25872588
if (tablesForPara) {
25882589
const state = paginator.ensurePage();
25892590
const columnWidthForTable = getCurrentColumnWidth();
2591+
2592+
// Paragraph top after layout (first fragment on this page). Pre-layout cursorY can still
2593+
// sit on the previous page when the anchor paragraph breaks across pages.
2594+
let anchorParagraphTopY = state.cursorY;
2595+
for (const fragment of state.page.fragments) {
2596+
if (fragment.kind === 'para' && fragment.blockId === block.id) {
2597+
anchorParagraphTopY = Math.min(anchorParagraphTopY, fragment.y);
2598+
}
2599+
}
2600+
25902601
let tableBottomY = state.cursorY;
2602+
let nextStackY = state.cursorY;
25912603
for (const { block: tableBlock, measure: tableMeasure } of tablesForPara) {
25922604
if (placedAnchoredTableIds.has(tableBlock.id)) continue;
25932605
const totalWidth = tableMeasure.totalWidth ?? 0;
25942606
if (columnWidthForTable > 0 && totalWidth >= columnWidthForTable * ANCHORED_TABLE_FULL_WIDTH_RATIO) continue;
25952607

2596-
// OOXML anchor base is paragraph-relative. Clamp to paragraph bottom so the table never overlaps
2597-
// paragraph text, then apply offsetV from that resolved anchor position.
2608+
// OOXML anchor base is paragraph-relative. Clamp below laid-out paragraph text, then offsetV.
25982609
const offsetV = tableBlock.anchor?.offsetV ?? 0;
2599-
const anchorBaseY = Math.max(paragraphStartY, state.cursorY);
2610+
const anchorBaseY = Math.max(anchorParagraphTopY, nextStackY);
26002611
const anchorY = anchorBaseY + offsetV;
26012612
floatManager.registerTable(tableBlock, tableMeasure, anchorY, state.columnIndex, state.page.number);
26022613

@@ -2611,7 +2622,9 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
26112622
const wrapType = tableBlock.wrap?.type ?? 'None';
26122623
if (wrapType !== 'None') {
26132624
const bottom = anchorY + (tableMeasure.totalHeight ?? 0);
2625+
const distBottom = tableBlock.wrap?.distBottom ?? 0;
26142626
if (bottom > tableBottomY) tableBottomY = bottom;
2627+
nextStackY = bottom + distBottom;
26152628
}
26162629
}
26172630
state.cursorY = tableBottomY;

0 commit comments

Comments
 (0)