/** * World builder for Playwright-extracted elements. * * Converts raw extracted element data (from page.evaluate bounding boxes) * into a solver-compatible GeometryWorld. All rect math lives here — * public.ts delegates to this module so that coordinate arithmetic is * isolated from the FOL compilation path. */ import type { GeometryWorld } from 'imhotep-solver' export interface ExtractedElement { tagName: string rect: { x: number; y: number; width: number; height: number } } /** * Build a GeometryWorld from a flat list of extracted elements. * * Each element receives a sequential subject ID starting at 1. * Boxes, rects, and subject tables are populated from the visual rects. */ export function buildGeometryWorld(elements: ExtractedElement[]): GeometryWorld { const ids: number[] = [] const borderLeft: number[] = [] const borderTop: number[] = [] const borderRight: number[] = [] const borderBottom: number[] = [] const paddingLeft: number[] = [] const paddingTop: number[] = [] const paddingRight: number[] = [] const paddingBottom: number[] = [] const contentLeft: number[] = [] const contentTop: number[] = [] const contentRight: number[] = [] const contentBottom: number[] = [] for (let i = 0; i < elements.length; i++) { const id = i + 1 const el = elements[i] const r = el.rect ids.push(id) borderLeft.push(r.x) borderTop.push(r.y) borderRight.push(r.x + r.width) borderBottom.push(r.y + r.height) paddingLeft.push(0) paddingTop.push(0) paddingRight.push(0) paddingBottom.push(0) contentLeft.push(r.x) contentTop.push(r.y) contentRight.push(r.x + r.width) contentBottom.push(r.y + r.height) } return { sceneId: 'scene', snapshotId: 'snapshot', env: { viewportWidth: 800, viewportHeight: 600, deviceScaleFactor: 1, colorScheme: 'light', pointer: 'fine', hover: false, reducedMotion: false, locale: 'en', writingMode: 'horizontal-tb', }, strings: { values: [] }, subjects: { ids, domNodeId: [...ids], subjectKind: ids.map(() => 0), primaryBoxId: [...ids], firstFragmentId: ids.map(() => 0), fragmentCount: ids.map(() => 0), }, dom: { nodeId: [], parentNodeId: [], childCount: [], tagNameStringId: [], }, boxes: { boxId: [...ids], subjectId: [...ids], frameId: ids.map(() => 0), borderLeft, borderTop, borderRight, borderBottom, paddingLeft, paddingTop, paddingRight, paddingBottom, contentLeft, contentTop, contentRight, contentBottom, }, visualBoxes: { boxId: [...ids], subjectId: [...ids], frameId: ids.map(() => 0), borderLeft: [...borderLeft], borderTop: [...borderTop], borderRight: [...borderRight], borderBottom: [...borderBottom], paddingLeft: [...paddingLeft], paddingTop: [...paddingTop], paddingRight: [...paddingRight], paddingBottom: [...paddingBottom], contentLeft: [...contentLeft], contentTop: [...contentTop], contentRight: [...contentRight], contentBottom: [...contentBottom], }, transforms: { transformId: [], subjectId: [], matrixStart: [], matrixLength: [], originX: [], originY: [], }, matrices: { values: [], }, rects: { rectId: [...ids], left: [...borderLeft], top: [...borderTop], right: [...borderRight], bottom: [...borderBottom], }, topology: { containingBlockOf: [], nearestPositionedAncestorOf: [], scrollContainerOf: [], stackingContextOf: [], formattingContextOf: [], clippingRootOf: [], paintOrderBucket: [], paintOrderIndex: [], }, scroll: { containerId: [], scrollLeft: [], scrollTop: [], scrollWidth: [], scrollHeight: [], clientWidth: [], clientHeight: [], }, clipping: { clipNodeId: [], subjectId: [], clipKind: [], clipLeft: [], clipTop: [], clipRight: [], clipBottom: [], parentClipNodeId: [], }, visibility: { subjectId: [], isRendered: [], isVisible: [], visibleArea: [], clippedArea: [], }, } }