// Formatting context boundary computation for Imhotep topology engine. import { type DomTree, type StyleFacts, type TopologyGraph, Display, Overflow, Position, ConfidenceLevel, INVALID_ID, setConfidence, } from './graph.js'; // --------------------------------------------------------------------------- // Formatting context establishment predicates // --------------------------------------------------------------------------- /** * Returns true if the element establishes an independent formatting context. * * Rules (simplified, covering the common cases): * 1. Root element. * 2. display: inline-block, flex, grid, table, table-cell. * 3. overflow != visible on a block container (block formatting context). * 4. position: absolute or fixed (they establish a new containing block, * which implies an independent formatting context for their contents). * 5. contain: layout or paint (CSS Containment). * * Floats also establish BFCs, but we do not model float here. */ export function establishesFormattingContext( subjectId: number, isRoot: boolean, styles: StyleFacts, ): { yes: boolean; confidence: number } { if (isRoot) { return { yes: true, confidence: ConfidenceLevel.EXACT }; } const disp = styles.display[subjectId]; // Inline-block, flex, grid, table all establish independent formatting contexts. if ( disp === Display.INLINE_BLOCK || disp === Display.FLEX || disp === Display.GRID || disp === Display.TABLE ) { return { yes: true, confidence: ConfidenceLevel.DERIVED }; } // Block container with overflow != visible establishes a BFC. if (disp === Display.BLOCK) { const overflowHidden = styles.overflowX[subjectId] !== Overflow.VISIBLE || styles.overflowY[subjectId] !== Overflow.VISIBLE; if (overflowHidden) { return { yes: true, confidence: ConfidenceLevel.DERIVED }; } } // Absolutely or fixed positioned elements establish an independent formatting // context for their contents because they form a containing block. const pos = styles.position[subjectId]; if (pos === Position.ABSOLUTE || pos === Position.FIXED) { return { yes: true, confidence: ConfidenceLevel.DERIVED }; } // CSS contain: layout or paint. const contain = styles.containFlags[subjectId]; if (contain & (0x01 | 0x02)) { return { yes: true, confidence: ConfidenceLevel.DERIVED }; } return { yes: false, confidence: ConfidenceLevel.EXACT }; } // --------------------------------------------------------------------------- // Formatting context assignment // --------------------------------------------------------------------------- /** * For each node, find the nearest ancestor (including itself) that establishes * a formatting context. This is the formatting context the node participates in. */ export function assignFormattingContexts( dom: DomTree, styles: StyleFacts, graph: TopologyGraph, ): void { // Pre-compute formatting context roots. const isFormattingRoot = new Uint8Array(dom.nodeCount).fill(0); const formattingConfidence = new Float32Array(dom.nodeCount).fill(ConfidenceLevel.EXACT); for (let i = 0; i < dom.nodeCount; i++) { const isRoot = dom.parentNodeId[i] === INVALID_ID; const { yes, confidence } = establishesFormattingContext(i, isRoot, styles); if (yes) { isFormattingRoot[i] = 1; } formattingConfidence[i] = confidence; } for (let i = 0; i < dom.nodeCount; i++) { let cursor = i; let found = INVALID_ID; while (cursor !== INVALID_ID) { if (isFormattingRoot[cursor]) { found = cursor; break; } cursor = dom.parentNodeId[cursor]; } graph.formattingContextOf[i] = found; const minConfidence: number = found === INVALID_ID ? ConfidenceLevel.INDETERMINATE : Math.min(formattingConfidence[found], ConfidenceLevel.DERIVED); setConfidence(graph, i, minConfidence as any, 40 /* formatting context */); } }