Skip to content

Commit 7937fa9

Browse files
committed
fix: visual testing regression
1 parent 648609f commit 7937fa9

1 file changed

Lines changed: 16 additions & 8 deletions

File tree

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

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ import {
3535
buildLayoutSourceIdentityForFragment,
3636
normalizeColumnLayout,
3737
getFragmentZIndex,
38+
resolveAnchoredGraphicY,
3839
resolveEffectiveHeaderFooterRef,
3940
selectHeaderFooterVariantForPage,
4041
} from '@superdoc/contracts';
41-
import { buildLayoutSourceIdentityForFragment, normalizeColumnLayout, getFragmentZIndex, resolveAnchoredGraphicY } from '@superdoc/contracts';
4242
import { createFloatingObjectManager, computeAnchorX } from './floating-objects.js';
4343
import { computeNextSectionPropsAtBreak } from './section-props';
4444
import {
@@ -2526,10 +2526,6 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
25262526
}
25272527
}
25282528

2529-
// Paragraph start Y (OOXML: anchor for vertAnchor="text"). Captured before layout so
2530-
// paragraph-anchored tables use it as base; offsetV (tblpY) positions below start to avoid overlap.
2531-
const paragraphStartY = paginator.ensurePage().cursorY;
2532-
25332529
layoutParagraphBlock(
25342530
{
25352531
block,
@@ -2569,16 +2565,26 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
25692565
if (tablesForPara) {
25702566
const state = paginator.ensurePage();
25712567
const columnWidthForTable = getCurrentColumnWidth();
2568+
2569+
// Paragraph top after layout (first fragment on this page). Pre-layout cursorY can still
2570+
// sit on the previous page when the anchor paragraph breaks across pages.
2571+
let anchorParagraphTopY = state.cursorY;
2572+
for (const fragment of state.page.fragments) {
2573+
if (fragment.kind === 'para' && fragment.blockId === block.id) {
2574+
anchorParagraphTopY = Math.min(anchorParagraphTopY, fragment.y);
2575+
}
2576+
}
2577+
25722578
let tableBottomY = state.cursorY;
2579+
let nextStackY = state.cursorY;
25732580
for (const { block: tableBlock, measure: tableMeasure } of tablesForPara) {
25742581
if (placedAnchoredTableIds.has(tableBlock.id)) continue;
25752582
const totalWidth = tableMeasure.totalWidth ?? 0;
25762583
if (columnWidthForTable > 0 && totalWidth >= columnWidthForTable * ANCHORED_TABLE_FULL_WIDTH_RATIO) continue;
25772584

2578-
// OOXML anchor base is paragraph-relative. Clamp to paragraph bottom so the table never overlaps
2579-
// paragraph text, then apply offsetV from that resolved anchor position.
2585+
// OOXML anchor base is paragraph-relative. Clamp below laid-out paragraph text, then offsetV.
25802586
const offsetV = tableBlock.anchor?.offsetV ?? 0;
2581-
const anchorBaseY = Math.max(paragraphStartY, state.cursorY);
2587+
const anchorBaseY = Math.max(anchorParagraphTopY, nextStackY);
25822588
const anchorY = anchorBaseY + offsetV;
25832589
floatManager.registerTable(tableBlock, tableMeasure, anchorY, state.columnIndex, state.page.number);
25842590

@@ -2593,7 +2599,9 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
25932599
const wrapType = tableBlock.wrap?.type ?? 'None';
25942600
if (wrapType !== 'None') {
25952601
const bottom = anchorY + (tableMeasure.totalHeight ?? 0);
2602+
const distBottom = tableBlock.wrap?.distBottom ?? 0;
25962603
if (bottom > tableBottomY) tableBottomY = bottom;
2604+
nextStackY = bottom + distBottom;
25972605
}
25982606
}
25992607
state.cursorY = tableBottomY;

0 commit comments

Comments
 (0)