Files
Imhotep/packages/imhotep-topology/src/formatting.ts
T

127 lines
3.9 KiB
TypeScript
Raw Normal View History

// 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 */);
}
}