Skip to main content
Glama
1yhy
by 1yhy
grid-layout-research.md58.2 kB
# Grid Layout Detection Algorithm Research ## Research Summary This document synthesizes research findings from multiple sources to design a Grid layout detection algorithm for converting Figma designs to CSS Grid. ## Sources Analyzed ### 1. Academic Research **"A Layout Inference Algorithm for GUIs" (ScienceDirect)** - Uses **Allen's Interval Algebra** for spatial relationship modeling - Two-phase algorithm: 1. Convert absolute coordinates to relative positioning using directed graphs 2. Apply pattern matching and graph rewriting for layout inference - Achieves 97% accuracy in maintaining original layout faithfulness - 84% of views maintain proportions when resized **Allen's 13 Interval Relations** (applicable to 1D spatial analysis): - `before/after`: Element A completely before/after B - `meets/met-by`: Element A ends exactly where B starts - `overlaps/overlapped-by`: Element A partially overlaps B - `starts/started-by`: Element A starts at same position as B - `finishes/finished-by`: Element A ends at same position as B - `during/contains`: Element A completely inside/outside B - `equals`: Element A same position as B ### 2. Open Source Implementations **FigmaToCode (bernaferrari/FigmaToCode)** - Uses intermediate "AltNodes" representation - Analyzes auto-layouts, responsive constraints, color variables - Handles mixed positioning (absolute + auto-layout) - Makes intelligent decisions about code structure **imgcook (Alibaba)** - Layout restoration is "core of entire D2C process" - Uses flat JSON with absolute position, size, style - Expert rule system + machine learning hybrid - Components: Page splitting, Grouping, Loop detection, Multi-status handling - Rule system preferred for controllable, near 100% availability **GRIDS (Aalto University)** - MILP (Mixed Integer Linear Programming) based approach - Mathematical optimization for grid generation - Python implementation with Gurobi optimizer ### 3. Industry Implementations **Figma's Native Grid Auto-Layout (May 2025)** - New Grid flow option alongside horizontal/vertical - Supports span, row/column count - Limited compared to full CSS Grid specification - Missing: `fr` units, named grid areas, subgrid **Screen Parsing (CMU - UIST 2021)** - ML-based UI structure inference - Detects 7 container types including grids - Trained on 130K iOS + 80K Android screens ## Current Implementation Analysis ### Existing Flex Layout Detection The current codebase has robust Flex detection in `src/algorithms/layout/detector.ts`: ``` Flow: Elements → Bounding Boxes → Row/Column Grouping → Gap Analysis → Alignment Detection → CSS Generation ``` **Key Algorithms:** 1. **Y-axis overlap** → Row grouping (elements in same row) 2. **X-axis overlap** → Column grouping (elements in same column) 3. **Gap analysis** → Consistent spacing detection (20% std dev tolerance) 4. **Alignment detection** → left/center/right/stretch 5. **Layout scoring** → Confidence-based direction selection ### Gap in Current Implementation The `LayoutInfo` interface already defines `type: "flex" | "absolute" | "grid"` but Grid detection is **NOT IMPLEMENTED**. ## Grid Layout Detection Algorithm Design ### Core Concept: When to Use Grid vs Flex | Criterion | Flexbox | CSS Grid | | --------------- | --------------------------- | ---------------------------------- | | Dimension | 1D (row OR column) | 2D (rows AND columns) | | Rows consistent | Single row spans full width | Multiple rows with aligned columns | | Column count | Variable per row | Consistent across rows | | Cell alignment | Only within row | Both row AND column aligned | | Gaps | Single gap value | row-gap and column-gap | ### Algorithm: Grid Detection ```typescript interface GridAnalysisResult { isGrid: boolean; confidence: number; rows: number; columns: number; rowGap: number; columnGap: number; trackWidths: number[]; // For grid-template-columns trackHeights: number[]; // For grid-template-rows cellMap: (number | null)[][]; // Element indices in grid positions } function detectGridLayout(rects: ElementRect[]): GridAnalysisResult { // Step 1: Group into rows (Y-axis overlap) const rows = groupIntoRows(rects); // Step 2: Check if column count is consistent across rows const columnCounts = rows.map((row) => row.length); const isConsistentColumns = areValuesAligned(columnCounts, 0); // Step 3: Check column alignment across rows const columnPositions = extractColumnPositions(rows); const areColumnsAligned = checkColumnAlignment(columnPositions); // Step 4: Calculate confidence // Grid confidence higher when: // - Multiple rows exist (> 1) // - Columns are aligned across rows // - Both row and column gaps are consistent // Step 5: Extract track sizes const trackWidths = calculateTrackWidths(rows, columnPositions); const trackHeights = calculateTrackHeights(rows); return result; } ``` ### Step-by-Step Algorithm #### Step 1: Row Detection (existing) ```typescript // Already implemented: groupIntoRows() const rows = groupIntoRows(rects, tolerance); ``` #### Step 2: Column Alignment Detection (NEW) ```typescript function extractColumnPositions(rows: ElementRect[][]): number[][] { // For each row, extract the X positions of elements return rows.map((row) => row.map((el) => el.x).sort((a, b) => a - b)); } function checkColumnAlignment(columnPositions: number[][]): { isAligned: boolean; alignedPositions: number[]; tolerance: number; } { if (columnPositions.length < 2) { return { isAligned: false, alignedPositions: [], tolerance: 0 }; } // Merge all X positions and cluster them const allPositions = columnPositions.flat(); const clusters = clusterValues(allPositions, tolerance); // Check if each row has elements at cluster positions const alignedPositions = clusters.map((c) => c.center); // Verify alignment across rows let alignedRows = 0; for (const row of columnPositions) { const rowAligned = row.every((x) => alignedPositions.some((ap) => Math.abs(x - ap) <= tolerance), ); if (rowAligned) alignedRows++; } return { isAligned: alignedRows / columnPositions.length >= 0.8, alignedPositions, tolerance, }; } ``` #### Step 3: Grid Confidence Scoring (NEW) ```typescript function calculateGridConfidence( rows: ElementRect[][], columnAlignment: ColumnAlignmentResult, rowGapAnalysis: GapAnalysis, columnGapAnalysis: GapAnalysis, ): number { let score = 0; // 1. Multiple rows (required for grid) if (rows.length >= 2) score += 0.2; if (rows.length >= 3) score += 0.1; // 2. Consistent column count const columnCounts = rows.map((r) => r.length); if (areValuesEqual(columnCounts)) score += 0.2; // 3. Column alignment across rows if (columnAlignment.isAligned) score += 0.25; // 4. Consistent row gap if (rowGapAnalysis.isConsistent) score += 0.1; // 5. Consistent column gap if (columnGapAnalysis.isConsistent) score += 0.1; // 6. 2D regularity (elements form a regular matrix) const expectedCells = rows.length * Math.max(...columnCounts); const actualCells = rows.reduce((sum, r) => sum + r.length, 0); const fillRatio = actualCells / expectedCells; if (fillRatio >= 0.75) score += 0.05; return Math.min(1, score); } ``` #### Step 4: Track Size Extraction (NEW) ```typescript function calculateTrackWidths( rows: ElementRect[][], alignedPositions: number[], ): (number | "auto" | "fr")[] { // Group elements by column position const columns: ElementRect[][] = []; for (let i = 0; i < alignedPositions.length; i++) { columns[i] = []; } for (const row of rows) { for (const el of row) { const colIndex = alignedPositions.findIndex((pos) => Math.abs(el.x - pos) <= tolerance); if (colIndex >= 0) { columns[colIndex].push(el); } } } // Calculate width for each column return columns.map((col) => { const widths = col.map((el) => el.width); const avgWidth = widths.reduce((a, b) => a + b, 0) / widths.length; return roundToCommonValue(avgWidth); }); } function calculateTrackHeights(rows: ElementRect[][]): number[] { return rows.map((row) => { const heights = row.map((el) => el.height); const maxHeight = Math.max(...heights); return roundToCommonValue(maxHeight); }); } ``` #### Step 5: CSS Grid Generation (NEW) ```typescript function generateGridCSS(analysis: GridAnalysisResult): CSSStyle { const css: CSSStyle = { display: "grid", }; // grid-template-columns const columns = analysis.trackWidths.map((w) => (typeof w === "number" ? `${w}px` : w)).join(" "); css["gridTemplateColumns"] = columns; // grid-template-rows (optional, often auto) const rows = analysis.trackHeights .map((h) => (typeof h === "number" ? `${h}px` : "auto")) .join(" "); if (!rows.split(" ").every((r) => r === "auto")) { css["gridTemplateRows"] = rows; } // Gap if (analysis.rowGap > 0 || analysis.columnGap > 0) { if (analysis.rowGap === analysis.columnGap) { css.gap = `${analysis.rowGap}px`; } else { css.gap = `${analysis.rowGap}px ${analysis.columnGap}px`; } } return css; } ``` ### Decision Tree: Grid vs Flex vs Absolute ``` Start │ ▼ ┌─────────────────┐ │ Elements ≥ 2? │──No──▶ position: absolute └────────┬────────┘ │Yes ▼ ┌─────────────────┐ │ Overlapping │──Yes─▶ position: absolute (for overlaps) │ elements? │ └────────┬────────┘ │No ▼ ┌─────────────────┐ │ Multiple rows │──No──▶ display: flex (row) │ detected? │ └────────┬────────┘ │Yes ▼ ┌─────────────────┐ │ Columns aligned │──No──▶ display: flex (column) │ across rows? │ with nested flex rows └────────┬────────┘ │Yes ▼ ┌─────────────────┐ │ Grid confidence │──No──▶ display: flex (column) │ ≥ 0.6? │ └────────┬────────┘ │Yes ▼ display: grid ``` ## Additional CSS Properties to Support ### Grid-Specific Properties | Property | Priority | Description | | -------------------------------- | -------- | --------------------------------- | | `display: grid` | P0 | Enable grid layout | | `grid-template-columns` | P0 | Define column tracks | | `grid-template-rows` | P1 | Define row tracks | | `gap` / `row-gap` / `column-gap` | P0 | Spacing between tracks | | `grid-auto-flow` | P2 | Auto-placement algorithm | | `justify-items` | P1 | Align items in cells horizontally | | `align-items` | P1 | Align items in cells vertically | | `place-items` | P2 | Shorthand for align + justify | ### Child Element Properties | Property | Priority | Description | | -------------- | -------- | ------------------------- | | `grid-column` | P1 | Column span/position | | `grid-row` | P1 | Row span/position | | `grid-area` | P2 | Named grid area | | `justify-self` | P2 | Self horizontal alignment | | `align-self` | P2 | Self vertical alignment | ### Enhanced Flex Properties (Missing) | Property | Priority | Description | | ------------- | -------- | ------------------------------- | | `flex-grow` | P1 | Element growth factor | | `flex-shrink` | P2 | Element shrink factor | | `flex-basis` | P2 | Initial size before grow/shrink | | `flex` | P1 | Shorthand (grow shrink basis) | | `order` | P2 | Element ordering | ## Implementation Plan ### Phase 1: Fix Existing TODO (layout.ts) Split `convertAlign` into two functions: - `convertJustifyContent()` - for main axis alignment - `convertAlignItems()` - for cross axis alignment ### Phase 2: Add Grid Detection (detector.ts) 1. Add `GridAnalysisResult` interface 2. Implement `detectGridLayout()` function 3. Add column alignment detection 4. Implement grid confidence scoring 5. Add track size extraction ### Phase 3: CSS Generation (optimizer.ts) 1. Update `LayoutInfo` usage to include grid type 2. Add `generateGridCSS()` function 3. Integrate into `optimizeDesign()` pipeline 4. Add decision tree for grid vs flex ### Phase 4: Type Updates (simplified.ts) 1. Add Grid CSS properties to `CSSStyle` 2. Extend `LayoutInfo` for grid-specific data ### Phase 5: Testing 1. Add unit tests for grid detection 2. Add integration tests with real Figma data 3. Test edge cases (irregular grids, mixed layouts) ## Current Implementation Issues (2024-12 Analysis) ### Problem: Mixed Layout False Positives Testing with real Figma data revealed that the Grid detection algorithm incorrectly identifies mixed layouts as grids. **Example Case: Keywords Management Panel (node-402-34955)** ``` Container: 1580px × 340px Children: 1. Tabs (320×41) left: 630px top: 0px ← centered tabs 2. Divider (1580×1) left: 0px top: 41px ← full-width line 3. Info Bar (1528×88) left: 26px top: 62px ← nearly full-width 4. Card 1 (500×78) left: 26px top: 170px ┐ 5. Card 2 (500×78) left: 540px top: 170px ├─ actual grid candidates 6. Card 3 (500×78) left: 1054px top: 170px ┘ 7. Card 4 (500×78) left: 26px top: 262px ← second row ``` **Detected Result (WRONG):** ```css display: grid; grid-template-columns: 1580px 1528px 500px 320px 500px; /* ❌ Sum = 4428px > 1580px */ ``` **Expected Result:** - Overall container: `flex-direction: column` or `position: absolute` - Cards only (items 4-7): `display: grid; grid-template-columns: repeat(3, 500px);` ### Root Cause Analysis | Issue | Code Location | Description | | ------------------------------- | --------------------- | ------------------------------------------------------ | | **No element type filtering** | `detector.ts:1073` | All children analyzed together regardless of size/type | | **Y-overlap only row grouping** | `detector.ts:154-185` | 2px tolerance ignores visual/functional differences | | **No homogeneity check** | N/A | Missing validation that elements "look similar" | | **Strict column alignment** | `detector.ts:908-911` | 80% threshold fails on intentionally mixed layouts | ### Research: Industry Approaches #### 1. UI Semantic Group Detection (2024) > Source: [arxiv.org/html/2403.04984v1](https://arxiv.org/html/2403.04984v1) - **Key Insight**: "Group adjacent elements with similar semantics before layout detection" - **Method**: Transformer-based detector using Gestalt principles - **Relevance**: Pre-clustering homogeneous elements #### 2. UIHASH - Grid-Based UI Similarity > Source: [jun-zeng.github.io](https://jun-zeng.github.io/file/uihash_paper.pdf) - **Key Insight**: "Proximity principle - users group adjacent elements as unified entity" - **Method**: Partition screen into regions, encode by constituent controls - **Relevance**: Size-based clustering before grid analysis #### 3. GUI Layout Inference Algorithm > Source: [ScienceDirect](https://www.sciencedirect.com/science/article/abs/pii/S0950584915001718) - **Key Insight**: "Two-phase: relative positioning → pattern matching" - **Method**: Allen relations + exploratory algorithm for layout composition - **Relevance**: Hierarchical layout detection #### 4. Multilevel Homogeneity Structure > Source: [ScienceDirect](https://www.sciencedirect.com/science/article/abs/pii/S0957417417303469) - **Key Insight**: "Bottom-up aggregation into homogeneous regions" - **Method**: Connected components → words → text lines → regions - **Relevance**: Progressive homogeneous grouping ## Optimization Plan ### Solution: Homogeneous Element Pre-filtering Add a pre-filtering step before Grid detection to identify elements that "look similar" and should be considered together. #### Homogeneity Criteria ```typescript interface HomogeneityCheck { sizeVariance: number; // Width/height variance (threshold: 20%) typeConsistency: boolean; // Same node types stylesSimilar: boolean; // Similar CSS properties } function isHomogeneousGroup(nodes: SimplifiedNode[]): boolean { if (nodes.length < 4) return false; // 1. Size clustering - width/height within 20% variance const widths = nodes.map((n) => parseFloat(n.cssStyles?.width || "0")); const heights = nodes.map((n) => parseFloat(n.cssStyles?.height || "0")); const widthCV = coefficientOfVariation(widths); const heightCV = coefficientOfVariation(heights); if (widthCV > 0.2 || heightCV > 0.2) return false; // 2. Type consistency - allow FRAME + INSTANCE + COMPONENT const types = new Set(nodes.map((n) => n.type)); const allowedTypes = new Set(["FRAME", "INSTANCE", "COMPONENT", "GROUP"]); const hasDisallowedType = [...types].some((t) => !allowedTypes.has(t)); if (hasDisallowedType || types.size > 3) return false; // 3. Style similarity (optional) - background, border, etc. // ... return true; } ``` #### Updated Detection Flow ``` Before: detectGridLayout(allChildren) → wrong grid After: 1. clusterBySimilarSize(allChildren) → size groups 2. For each group with 4+ elements: a. isHomogeneousGroup(group) → true/false b. If true: detectGridLayout(group) 3. Remaining elements: use flex/absolute ``` #### Implementation Steps 1. **Add `isHomogeneousGroup()` function** - `detector.ts` 2. **Add `clusterBySimilarSize()` function** - `detector.ts` 3. **Update `LayoutOptimizer.optimizeContainer()`** - `optimizer.ts` - Call homogeneity check before grid detection - Only pass homogeneous elements to `detectGridLayout()` 4. **Add tests** - `layout.test.ts` ### Expected Results | Scenario | Before | After | | ----------------------------- | ------------------ | -------------------- | | Mixed layout (tabs + cards) | Wrong grid for all | Cards only → grid | | Pure card grid (4+ same size) | Correct grid | Correct grid | | Single row items | Flex row | Flex row (no change) | | Irregular sizes | Wrong grid | Flex/absolute | ## References - [Allen's Interval Algebra - Wikipedia](https://en.wikipedia.org/wiki/Allen's_interval_algebra) - [FigmaToCode - GitHub](https://github.com/bernaferrari/FigmaToCode) - [GRIDS Layout Engine - GitHub](https://github.com/aalto-ui/GRIDS) - [CSS Grid Layout Module Level 1 - W3C](https://www.w3.org/TR/css-grid-1/) - [Figma Grid Auto-Layout Help](https://help.figma.com/hc/en-us/articles/31289469907863-Use-the-grid-auto-layout-flow) - [Screen Parsing - CMU ML Blog](https://blog.ml.cmu.edu/2021/12/10/understanding-user-interfaces-with-screen-parsing/) --- ## Complete Implementation Analysis This section provides an in-depth analysis of the Grid layout detection algorithm implementation, including the complete call chain and core functions. ### System Architecture: Grid vs Flex Decision ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ Layout Detection Decision Tree │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────┐ │ │ │ optimizeContainer() │ │ │ │ [optimizer.ts:89] │ │ │ └───────────┬─────────────┘ │ │ │ │ │ ┌───────────────────┼───────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ STEP 1: Overlap │ │ STEP 1.5: │ │ STEP 2: Grid │ │ │ │ Detection │ │ Background │ │ Detection │ │ │ │ IoU > 0.1 ? │ │ Detection │ │ (Priority) │ │ │ └────────┬────────┘ └────────────────┘ └────────┬────────┘ │ │ │ │ │ │ │ ┌────────────────────┤ │ │ │ │ │ │ │ │ Grid detection OK? Grid detection failed │ │ │ confidence >= 0.6 │ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ display: grid │ │ STEP 3: Flex │ │ │ │ │ grid-template- │ │ Detection │ │ │ │ │ columns/rows │ │ (Fallback) │ │ │ │ └─────────────────┘ └────────┬────────┘ │ │ │ │ │ │ │ │ │ Flex detection OK? │ │ │ │ score > 0.4 │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ Overlapping keeps │ Generate Grid │ │ display: flex │ │ │ position: absolute │ CSS + padding │ │ + gap + align │ │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ └───────────┴─────────────────────────────────────────────────────────────┘ ``` ### Grid Detection Entry: detectGridIfApplicable ``` detectGridIfApplicable(nodes) - [optimizer.ts:918-961] ═══════════════════════════════════════════════════════════════════════════ ┌─────────────────────────────────┐ │ detectGridIfApplicable(nodes) │ │ [optimizer.ts:918] │ └───────────────┬─────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ Pre-checks │ │ • nodes.length < 4 → return null│ │ • Need at least 2×2 grid │ └───────────────┬─────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ nodesToElementRects(nodes) │ │ Convert to ElementRect[] │ │ [optimizer.ts:856-870] │ └───────────────┬─────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ filterHomogeneousForGrid(elementRects, nodeTypes) │ │ [detector.ts:1216-1235] │ │ │ │ ★ Key step: Homogeneity filtering │ │ • Filter elements with similar sizes │ │ • Prevent mixed layouts from being detected as Grid │ └───────────────────────────┬─────────────────────────┘ │ ┌─────────────────┴─────────────────┐ │ │ homogeneousElements.length < 4 homogeneousElements >= 4 │ │ ▼ ▼ return null ┌─────────────────────────┐ │ detectGridLayout() │ │ [detector.ts:1490] │ │ Run Grid detection on │ │ homogeneous elements │ └───────────────┬─────────┘ │ ┌───────────────────────────────────────┤ │ │ isGrid && confidence >= 0.6 ? confidence < 0.6 rowCount >= 2 && columnCount >= 2 │ │ │ ▼ ▼ ┌─────────────────────────┐ return null │ return { │ │ gridResult, │ │ gridIndices │ │ } │ └─────────────────────────┘ ``` ### Homogeneity Analysis: analyzeHomogeneity ``` analyzeHomogeneity(rects, nodeTypes) - [detector.ts:1127-1196] ═══════════════════════════════════════════════════════════════════════════ Purpose: Ensure only "similar" elements participate in Grid detection, avoiding misdetection of mixed layouts Mixed Layout Example (should be filtered): ┌──────────────────────────────────────────────────────────────────────────┐ │ Container 1580px × 340px │ │ │ │ ┌─────────────────────┐ ← Tabs 320×41 (heterogeneous) │ │ │ Tabs │ │ │ └─────────────────────┘ │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ← Divider 1580×1 (heterogeneous) │ │ ┌──────────────────────────────────────────────────────────────────────┐│ │ │ Info Bar 1528×88 ││ │ └──────────────────────────────────────────────────────────────────────┘│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ← Homogeneous (Grid)│ │ │ Card 500×78│ │ Card 500×78│ │ Card 500×78│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ ┌─────────────┐ │ │ │ Card 500×78│ │ │ └─────────────┘ │ └──────────────────────────────────────────────────────────────────────────┘ Algorithm Flow: ┌────────────────────────────────────────────────────────────────┐ │ 1. Size Clustering (clusterBySimilarSize) │ │ │ │ Input: All child ElementRect[] │ │ Tolerance: 20% (widthDiff <= 0.2 && heightDiff <= 0.2) │ │ │ │ Process: │ │ for each rect: │ │ Find existing cluster with similar size │ │ If found → add to that cluster │ │ If not found → create new cluster │ │ │ │ Output: SizeCluster[] (sorted by element count descending) │ │ │ │ Example: │ │ Cluster 1: [Card1, Card2, Card3, Card4] ← 500×78 │ │ Cluster 2: [InfoBar] ← 1528×88 │ │ Cluster 3: [Tabs] ← 320×41 │ │ Cluster 4: [Divider] ← 1580×1 │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ 2. Get Largest Cluster (largestCluster) │ │ │ │ If largestCluster.length < 4 → not homogeneous │ │ │ │ Example: [Card1, Card2, Card3, Card4] = 4 elements ✓ │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ 3. Calculate Coefficient of Variation │ │ │ │ widthCV = stddev(widths) / mean(widths) │ │ heightCV = stddev(heights) / mean(heights) │ │ │ │ If widthCV > 0.2 || heightCV > 0.2 → not homogeneous │ │ │ │ Example: │ │ widths = [500, 500, 500, 500] → CV = 0 ✓ │ │ heights = [78, 78, 78, 78] → CV = 0 ✓ │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ 4. Type Consistency Check (optional) │ │ │ │ Allowed types: FRAME, INSTANCE, COMPONENT, GROUP, RECTANGLE│ │ If other types exist → not homogeneous │ │ │ │ Example: All Cards are FRAME → pass ✓ │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ Output: HomogeneityResult │ │ { │ │ isHomogeneous: true, │ │ widthCV: 0, │ │ heightCV: 0, │ │ types: ['FRAME'], │ │ homogeneousElements: [Card1, Card2, Card3, Card4], │ │ outlierElements: [Tabs, Divider, InfoBar] │ │ } │ └────────────────────────────────────────────────────────────────┘ ``` ### Grid Detection Core: detectGridLayout ``` detectGridLayout(rects) - [detector.ts:1490-1573] ═══════════════════════════════════════════════════════════════════════════ ┌─────────────────────────────────┐ │ detectGridLayout(rects) │ │ Input: Filtered homogeneous │ │ elements │ └───────────────┬─────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 1: Row Grouping (groupIntoRows) │ │ [detector.ts:1513] │ │ │ │ Use Y-axis overlap detection to group elements into "rows" │ │ │ │ Input (4 cards): │ │ ┌────┐ ┌────┐ ┌────┐ │ │ │ 1 │ │ 2 │ │ 3 │ y: 170, height: 78 │ │ └────┘ └────┘ └────┘ │ │ ┌────┐ │ │ │ 4 │ y: 262, height: 78 │ │ └────┘ │ │ │ │ Output: rows = [[1, 2, 3], [4]] │ │ rowCount = 2 │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 2: Column Alignment Detection (checkColumnAlignment) │ │ [detector.ts:1305-1339] │ │ │ │ Check if X positions across rows are aligned │ │ │ │ 1. Extract all X positions: │ │ Row 1: [26, 540, 1054] │ │ Row 2: [26] │ │ │ │ 2. Cluster all X positions (tolerance=3px): │ │ Cluster 1: center=26 │ │ Cluster 2: center=540 │ │ Cluster 3: center=1054 │ │ │ │ 3. Verify row elements are at cluster positions: │ │ Row 1: [26 ≈ 26 ✓, 540 ≈ 540 ✓, 1054 ≈ 1054 ✓] │ │ Row 2: [26 ≈ 26 ✓] │ │ │ │ Output: { isAligned: true, alignedPositions: [26, 540, 1054] } │ │ columnCount = 3 │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 3: Gap Analysis │ │ [detector.ts:1524-1529] │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Row Gap: │ │ │ │ │ │ │ │ Row 1 bottom = max(170+78) = 248 │ │ │ │ Row 2 top = min(262) = 262 │ │ │ │ rowGap = 262 - 248 = 14px │ │ │ │ │ │ │ │ analyzeGaps([14]) → { isConsistent: true, rounded: 16 }│ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Column Gap: │ │ │ │ │ │ │ │ Row 1: gap1 = 540 - (26+500) = 14px │ │ │ │ gap2 = 1054 - (540+500) = 14px │ │ │ │ │ │ │ │ analyzeGaps([14,14]) → { isConsistent: true, rounded: 16 }│ │ │ └─────────────────────────────────────────────────────────┘ │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 4: Grid Confidence Calculation (calculateGridConfidence) │ │ [detector.ts:1446-1479] │ │ │ │ 6 Scoring Factors: │ │ │ │ ┌────────────────────────────────────────────────────────────┐│ │ │ Factor │ Condition │ Score ││ │ ├────────────────────────────────────────────────────────────┤│ │ │ 1. Multiple rows (>= 2) │ 2 rows │ +0.2 ││ │ │ 2. Multiple rows (>= 3) │ 2 rows < 3 │ +0.0 ││ │ │ 3. Consistent column count │ [3, 1] inconsistent│ +0.0 ││ │ │ 4. Column alignment │ isAligned = true │ +0.25 ││ │ │ 5. Row gap consistency │ isConsistent = true│ +0.1 ││ │ │ 6. Column gap consistency │ isConsistent = true│ +0.1 ││ │ │ 7. Fill rate >= 75% │ 4/6 = 67% < 75% │ +0.0 ││ │ └────────────────────────────────────────────────────────────┘│ │ │ │ Total: 0.2 + 0.25 + 0.1 + 0.1 = 0.65 │ │ Threshold: 0.6 │ │ Result: 0.65 >= 0.6 → isGrid = true ✓ │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 5-6: Track Size Calculation │ │ [detector.ts:1552-1557] │ │ │ │ trackWidths = calculateTrackWidths(rows, alignedPositions) │ │ = [500, 500, 500] │ │ │ │ trackHeights = calculateTrackHeights(rows) │ │ = [78, 78] │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ STEP 7: Cell Map Building (buildCellMap) │ │ [detector.ts:1556] │ │ │ │ cellMap: │ │ ┌─────────────────────────────────────┐ │ │ │ Col 0 │ Col 1 │ Col 2 │ │ │ ├───────────┼───────────┼─────────────┤ │ │ │ Card 0 │ Card 1 │ Card 2 │ Row 0 │ │ │ Card 3 │ null │ null │ Row 1 │ │ └───────────┴───────────┴─────────────┘ │ └────────────────────────────────────────────────────────────────┘ ``` ### CSS Grid Generation ``` generateGridCSS(gridResult) - [optimizer.ts:875-913] ═══════════════════════════════════════════════════════════════════════════ Input: GridAnalysisResult Output: CSS Grid style object ┌─────────────────────────────────┐ │ GridAnalysisResult │ │ { │ │ isGrid: true, │ │ rowCount: 2, │ │ columnCount: 3, │ │ rowGap: 16, │ │ columnGap: 16, │ │ trackWidths: [500, 500, 500],│ │ trackHeights: [78, 78] │ │ } │ └───────────────┬─────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ Generate grid-template-columns │ │ │ │ trackWidths = [500, 500, 500] │ │ → "500px 500px 500px" │ │ │ │ (Can be optimized to repeat(3, 500px) if all same width) │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ Generate grid-template-rows (only if heights differ) │ │ │ │ trackHeights = [78, 78] │ │ All row heights same → don't set (use auto) │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ Generate gap │ │ │ │ rowGap = 16, columnGap = 16 │ │ rowGap === columnGap → gap: "16px" │ │ │ │ If different → gap: "${rowGap}px ${columnGap}px" │ └────────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────────┐ │ Output CSS: │ │ { │ │ display: "grid", │ │ gridTemplateColumns: "500px 500px 500px", │ │ gap: "16px" │ │ } │ └────────────────────────────────────────────────────────────────┘ ``` ### Core Data Structures ```typescript // Grid-related data structures in detector.ts ═══════════════════════════════════════════════════════════════════════════ // Grid analysis result interface GridAnalysisResult { isGrid: boolean; // whether valid Grid detected confidence: number; // confidence (0-1) rowCount: number; // row count columnCount: number; // column count rowGap: number; // row gap (px) columnGap: number; // column gap (px) isRowGapConsistent: boolean; // is row gap consistent isColumnGapConsistent: boolean; // is column gap consistent trackWidths: number[]; // column widths [col0Width, col1Width, ...] trackHeights: number[]; // row heights [row0Height, row1Height, ...] alignedColumnPositions: number[]; // aligned column X coordinates rows: ElementRect[][]; // grouped row data cellMap: (number | null)[][]; // cell to element index mapping } // Homogeneity analysis result interface HomogeneityResult { isHomogeneous: boolean; // is homogeneous widthCV: number; // width coefficient of variation heightCV: number; // height coefficient of variation types: string[]; // element type list homogeneousElements: ElementRect[]; // homogeneous elements outlierElements: ElementRect[]; // outlier elements (not in Grid) } // Size cluster interface SizeCluster { width: number; // cluster representative width height: number; // cluster representative height elements: ElementRect[]; // elements in this cluster types?: string[]; // element types } ``` ### File Path Mapping | Module | File Path | Line Range | | -------------------------- | ------------------------------------ | ---------- | | Grid entry | `src/algorithms/layout/optimizer.ts` | 918-961 | | Grid CSS generation | `src/algorithms/layout/optimizer.ts` | 875-913 | | Homogeneity filtering | `src/algorithms/layout/detector.ts` | 1216-1235 | | Homogeneity analysis | `src/algorithms/layout/detector.ts` | 1127-1196 | | Size clustering | `src/algorithms/layout/detector.ts` | 1077-1116 | | CV calculation | `src/algorithms/layout/detector.ts` | 1057-1067 | | Grid detection core | `src/algorithms/layout/detector.ts` | 1490-1573 | | Column alignment detection | `src/algorithms/layout/detector.ts` | 1305-1339 | | Row gap calculation | `src/algorithms/layout/detector.ts` | 1344-1356 | | Column gap calculation | `src/algorithms/layout/detector.ts` | 1361-1376 | | Confidence calculation | `src/algorithms/layout/detector.ts` | 1446-1479 | | Track width calculation | `src/algorithms/layout/detector.ts` | 1381-1404 | | Track height calculation | `src/algorithms/layout/detector.ts` | 1409-1415 | | Cell map building | `src/algorithms/layout/detector.ts` | 1420-1441 | --- _Last updated: 2025-12-06_

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/1yhy/Figma-Context-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server