127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
// 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 */);
|
|
}
|
|
}
|