Skip to main content
Glama
1yhy
by 1yhy
layout-detection.md43.7 kB
# Design-to-Code: Flex Layout Detection Algorithm ## Table of Contents 1. [Background](#1-background) 2. [Industry Solutions Analysis](#2-industry-solutions-analysis) 3. [Core Algorithm Principles](#3-core-algorithm-principles) 4. [Implementation Details](#4-implementation-details) 5. [Testing & Verification](#5-testing--verification) 6. [Best Practices](#6-best-practices) --- ## 1. Background ### 1.1 Problem Statement Design files (like Figma) typically use **absolute positioning** (x, y, width, height), while frontend code requires **relative layouts** (Flexbox, Grid) for responsive design. **Core Challenge**: How to accurately infer Flex layout structure from a flat list of absolutely positioned elements? ### 1.2 Key Difficulties | Challenge | Description | | -------------------- | -------------------------------------------------------------------------- | | Row/Column Detection | How to determine if elements are arranged horizontally or vertically? | | Nested Structure | How to convert a flat list into a nested DOM tree? | | Gap Calculation | How to determine if gaps are consistent and should use the `gap` property? | | Alignment Detection | How to detect `justify-content` and `align-items`? | | Overlap Handling | How to handle overlapping elements that need absolute positioning? | | Tolerance Handling | How to handle small offsets in design files? | --- ## 2. Industry Solutions Analysis ### 2.1 Major Tools Comparison | Tool | Developer | Layout Detection | Open Source | | --------------- | ------------ | -------------------------------- | ----------- | | **FigmaToCode** | bernaferrari | Relies on Figma Auto Layout data | ✓ | | **Grida** | gridaco | Rules + ML hybrid | ✓ | | **imgcook** | Alibaba | Rule system + Machine Learning | ✗ | | **Anima** | Anima | Constraint inference | ✗ | ### 2.2 FigmaToCode Analysis **GitHub**: https://github.com/bernaferrari/FigmaToCode **Features**: - No layout inference, directly maps Figma Auto Layout properties - Uses AltNodes as intermediate representation - Uses absolute positioning for non-Auto Layout designs **Limitations**: - Depends on designers correctly using Auto Layout - Cannot handle legacy designs or manually positioned layouts ### 2.3 imgcook Layout Algorithm (Alibaba) **Core Flow**: ``` Flattened JSON → Row/Column Grouping → Layout Inference → Semantics → Code Generation ``` **Key Technologies**: 1. **Page Segmentation**: Split page into different sub-modules 2. **Grouping Algorithm**: Determine element containment relationships 3. **Loop Detection**: Identify lists/repeated elements 4. **Multi-state Recognition**: Identify different states of the same component --- ## 3. Core Algorithm Principles ### 3.1 Y-Axis Overlap Detection (Row Grouping) **Principle**: If two elements overlap on the Y-axis, they belong to the same row. ``` Element A: y=10, height=30 → Y range [10, 40] Element B: y=20, height=30 → Y range [20, 50] [10, 40] and [20, 50] intersect → Same row ``` **Implementation**: ```typescript function isOverlappingY(a: ElementRect, b: ElementRect, tolerance = 0): boolean { return !(a.bottom + tolerance < b.y || b.bottom + tolerance < a.y); } ``` ### 3.2 X-Axis Overlap Detection (Column Grouping) **Principle**: If two elements overlap on the X-axis, they belong to the same column. ```typescript function isOverlappingX(a: ElementRect, b: ElementRect, tolerance = 0): boolean { return !(a.right + tolerance < b.x || b.right + tolerance < a.x); } ``` ### 3.3 Gap Consistency Analysis **Principle**: Calculate the standard deviation of all gaps to determine consistency. ```typescript function analyzeGaps(gaps: number[], tolerancePercent = 20) { const average = gaps.reduce((a, b) => a + b, 0) / gaps.length; const variance = gaps.reduce((sum, g) => sum + Math.pow(g - average, 2), 0) / gaps.length; const stdDev = Math.sqrt(variance); return { isConsistent: stdDev <= average * (tolerancePercent / 100), average, rounded: Math.round(average / 4) * 4, // Round to 4px grid stdDev, }; } ``` ### 3.4 Alignment Detection **Horizontal Alignment**: - Left aligned: All elements have the same `left` value - Center aligned: All elements have the same center X coordinate - Right aligned: All elements have the same `right` value **Vertical Alignment**: - Top aligned: All elements have the same `top` value - Center aligned: All elements have the same center Y coordinate - Bottom aligned: All elements have the same `bottom` value --- ## 4. Implementation Details ### 4.1 Layout Detection Flow ``` 1. Extract bounding boxes from child elements 2. Group elements into rows (Y-axis overlap) 3. If single row → check column grouping (X-axis overlap) 4. Determine layout direction (row vs column) 5. Calculate gaps and check consistency 6. Detect alignment (justify-content, align-items) 7. Handle overlapping elements (mark for absolute positioning) ``` ### 4.2 Confidence Scoring The algorithm calculates a confidence score based on: - Number of elements that fit the detected pattern - Gap consistency - Alignment accuracy --- ## 5. Testing & Verification ### 5.1 Test Results | Test Case | Status | Result | | -------------------------- | ------ | ---------------------------------- | | Bounding box extraction | ✓ | Extracted 2 child elements | | Row grouping algorithm | ✓ | Grouped into 1 row | | Column grouping algorithm | ✓ | Grouped into 1 column | | Consistent gap detection | ✓ | Avg: 16px, StdDev: 0.63 | | Inconsistent gap detection | ✓ | Avg: 25px, StdDev: 14.14 | | Left alignment detection | ✓ | Detected: left | | Center alignment detection | ✓ | Detected: center | | Layout tree building | ✓ | Type: container, Direction: column | **Overall: 8/9 tests passed** --- ## 6. Best Practices ### 6.1 Recommended Parameters | Parameter | Recommended Value | Description | | ------------------- | ----------------- | ---------------------------- | | Y-axis tolerance | 2px | For row grouping | | Gap tolerance | 20% | Standard deviation threshold | | Alignment tolerance | 2px | For alignment detection | | Max recursion depth | 5 | For nested layouts | ### 6.2 When to Use Absolute Positioning - Elements with significant overlap (IoU > 0.1) - Complex icon compositions - Decorative elements with specific positioning --- ## 7. Complete Implementation Analysis This section provides an in-depth analysis of the layout detection algorithm implementation, including the complete call chain and core functions. ### 7.1 System Architecture Overview ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ Layout Optimization System │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ optimizer.ts (Orchestration Layer) │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ optimizeDesign() → optimizeNodeTree() → optimizeContainer() │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────┼───────────────┐ │ │ ▼ ▼ ▼ │ │ ┌─────────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ │ │ detector.ts │ │ detector.ts │ │ optimizer.ts │ │ │ │ (Overlap Detection)│ │ (Layout Detect)│ │ (Style Generation) │ │ │ │ │ │ │ │ │ │ │ │ detectOverlapping() │ │ analyzeLayout() │ │ generateGridCSS() │ │ │ │ detectBackground() │ │ detectGrid() │ │ convertToRelative() │ │ │ └─────────────────────┘ └─────────────────┘ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### 7.2 Core Entry Function Call Chain ``` src/algorithms/layout/optimizer.ts ═══════════════════════════════════════════════════════════════════════════ ┌─────────────────────────┐ │ LayoutOptimizer. │ │ optimizeDesign(design) │ │ [optimizer.ts:36] │ └───────────┬─────────────┘ │ Reset containerIdCounter │ ▼ ┌─────────────────────────────────┐ │ design.nodes.map((node) => │ │ optimizeNodeTree(node) │ │ ) │ │ [optimizer.ts:46] │ └───────────────┬─────────────────┘ │ ▼ (Recursively process each node) ┌─────────────────────────────────┐ │ optimizeNodeTree(node) │ │ [optimizer.ts:61] │ │ │ │ Recursively call itself for │ │ each child, then call │ │ optimizeContainer() │ └───────────────┬─────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ optimizeContainer(node) │ │ [optimizer.ts:89] │ │ │ │ Core optimization logic (7.3) │ └─────────────────────────────────┘ ``` ### 7.3 optimizeContainer Four-Step Algorithm ``` optimizeContainer(node) - [optimizer.ts:89-356] ═══════════════════════════════════════════════════════════════════════════ Input: SimplifiedNode (container node with children) Output: Optimized SimplifiedNode ┌─────────────────────────────────────────────────────────────────────────┐ │ Pre-checks │ │ • children.length <= 1 → return immediately │ │ • Check if FRAME/GROUP container │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ STEP 1: Overlap Detection │ │ [optimizer.ts:98-109] │ │ │ │ elementRects = nodesToElementRects(children) │ │ │ │ │ ▼ │ │ overlapResult = detectOverlappingElements(elementRects, 0.1) │ │ │ │ │ ┌───────────────┴───────────────┐ │ │ │ │ │ │ ▼ ▼ │ │ flowElements[] stackedElements[] │ │ (participate in layout) (keep absolute) │ │ │ │ If flowElements.length < 2 → skip layout optimization │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ STEP 1.5: Background Element Detection │ │ [optimizer.ts:111-151] │ │ │ │ detectBackgroundElement(elementRects, parentWidth, parentHeight) │ │ │ │ │ ┌─────────────┴─────────────┐ │ │ │ hasBackground: true │ │ │ │ backgroundIndex >= 0 │ │ │ └─────────────┬─────────────┘ │ │ │ │ │ isBackgroundElement() ──► extractBackgroundStyles() │ │ │ │ │ │ │ ▼ │ │ │ mergedBackgroundStyles = { │ │ │ backgroundColor, borderRadius, │ │ │ border, boxShadow... │ │ │ } │ │ │ │ │ ▼ │ │ filteredChildren = children.filter(non-background) │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ STEP 2: Grid Detection (Priority over Flex) │ │ [optimizer.ts:165-206] │ │ │ │ if (isContainer) { │ │ gridDetection = detectGridIfApplicable(filteredChildren) │ │ │ │ │ if (gridDetection) { │ │ ┌─────────────┴─────────────┐ │ │ │ gridResult │ │ │ │ gridIndices (Grid elements)│ │ │ └─────────────┬─────────────┘ │ │ │ │ │ gridStyles = generateGridCSS(gridResult) │ │ │ │ │ convertAbsoluteToRelative(..., gridIndices) │ │ │ │ │ return Grid layout node ──────────────────────► [END] │ │ } │ │ } │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ (Grid not detected) ┌─────────────────────────────────────────────────────────────────────────┐ │ STEP 3: Flex Detection (Fallback) │ │ [optimizer.ts:208-356] │ │ │ │ { isRow, isColumn, rowGap, columnGap, │ │ isGapConsistent, justifyContent, alignItems } │ │ = analyzeLayoutDirection(filteredChildren) │ │ │ │ │ ┌───────────────┴───────────────┐ │ │ │ │ │ │ isRow = true isColumn = true │ │ direction: 'row' direction: 'column' │ │ │ │ │ │ └───────────────┬───────────────┘ │ │ │ │ │ flexStyles = { │ │ display: 'flex', │ │ flexDirection: direction, // only set for column │ │ gap: `${gap}px`, // only when consistent │ │ justifyContent, │ │ alignItems │ │ } │ │ │ │ │ convertAbsoluteToRelative() → padding + clean child styles │ │ │ │ │ return Flex layout node │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### 7.4 Flex Layout Detection Flow ``` analyzeLayoutDirection(nodes) - [optimizer.ts:361-438] ═══════════════════════════════════════════════════════════════════════════ Input: SimplifiedNode[] (child nodes array) Output: { isRow, isColumn, rowGap, columnGap, isGapConsistent, justifyContent, alignItems } ┌─────────────────────────────────┐ │ Extract position info │ │ rects = nodes.map(node => { │ │ left, top, width, height │ │ }) │ └───────────────┬─────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ analyzeAlignment(rects) │ │ [optimizer.ts:443-489] │ │ │ │ Analyze horizontal/vertical │ │ alignment: │ │ • leftAligned │ │ • rightAligned │ │ • centerHAligned │ │ • topAligned │ │ • bottomAligned │ │ • centerVAligned │ └───────────────┬─────────────────┘ │ ┌───────────────┴───────────────┐ ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ │ calculateRowScore() │ │ calculateColumnScore() │ │ [optimizer.ts:551-579] │ │ [optimizer.ts:584-612] │ │ │ │ │ │ Row layout scoring: │ │ Column layout scoring: │ │ │ │ │ │ 1. Sort by left │ │ 1. Sort by top │ │ │ │ │ │ 2. Calculate h-gaps │ │ 2. Calculate v-gaps │ │ gap = next.left - │ │ gap = next.top - │ │ current.right │ │ current.bottom │ │ │ │ │ │ 3. Count consecutive │ │ 3. Count consecutive │ │ positive gaps │ │ positive gaps │ │ (0 <= gap <= 50px) │ │ (0 <= gap <= 50px) │ │ │ │ │ │ 4. Score calculation: │ │ 4. Score calculation: │ │ distribution * 0.7 │ │ distribution * 0.7 │ │ + alignment * 0.3 │ │ + alignment * 0.3 │ └───────────────┬─────────┘ └───────────────┬──────────┘ │ │ └───────────────┬───────────────┘ │ ▼ ┌─────────────────────────────────┐ │ Layout direction decision │ │ │ │ if (rowScore > columnScore │ │ && rowScore > 0.4) │ │ → isRow = true │ │ │ │ if (columnScore > rowScore │ │ && columnScore > 0.4) │ │ → isColumn = true │ └───────────────┬─────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ CSS property mapping │ │ │ │ Row layout: │ │ justify = horizontal align │ │ align = vertical align │ │ │ │ Column layout: │ │ justify = vertical align │ │ align = horizontal align │ └─────────────────────────────────┘ ``` ### 7.5 Overlap Detection Algorithm (IoU) ``` detectOverlappingElements(rects, iouThreshold=0.1) - [detector.ts:246-281] ═══════════════════════════════════════════════════════════════════════════ IoU (Intersection over Union) Calculation: ┌──────────────────────────────┐ │ Element A │ │ ┌──────────────────────┐ │ │ │ Intersection │ │ └────┼──────────────────────┼──┘ │ Element B │ └──────────────────────┘ IoU = Intersection Area / Union Area where: Intersection = overlap_width × overlap_height Union = Area_A + Area_B - Intersection Algorithm Flow: ┌────────────────────────────────────────────────────────────────┐ │ for each pair (i, j) where i < j: │ │ │ │ iou = calculateIoU(rects[i], rects[j]) │ │ │ │ if (iou > 0.1): │ │ stackedIndices.add(rects[i].index) │ │ stackedIndices.add(rects[j].index) │ │ │ │ Result: │ │ flowElements = elements NOT in stackedIndices │ │ stackedElements = elements in stackedIndices │ └────────────────────────────────────────────────────────────────┘ IoU Threshold Classification: ┌────────────┬───────────────────────────────────────────────────┐ │ IoU Range │ Classification │ ├────────────┼───────────────────────────────────────────────────┤ │ = 0 │ none/adjacent (no overlap or adjacent) │ │ 0 < x < 0.1│ partial (slight overlap - can participate) │ │ 0.1 ≤ x < 0.5│ significant (needs absolute positioning) │ │ ≥ 0.5 │ contained (severe overlap/containment) │ └────────────┴───────────────────────────────────────────────────┘ ``` ### 7.6 Gap Analysis Algorithm ``` calculateGaps() + analyzeGaps() - [detector.ts:447-509] ═══════════════════════════════════════════════════════════════════════════ Gap Calculation Example (horizontal direction): ┌────┐ gap1 ┌────┐ gap2 ┌────┐ │ A │ ──────── │ B │ ──────── │ C │ └────┘ └────┘ └────┘ right left right left gap1 = B.x - A.right gap2 = C.x - B.right Gap Consistency Analysis: ┌────────────────────────────────────────────────────────────────┐ │ analyzeGaps(gaps, tolerancePercent=20) │ │ │ │ 1. Calculate average: │ │ average = sum(gaps) / gaps.length │ │ │ │ 2. Calculate standard deviation: │ │ variance = Σ(gap - average)² / n │ │ stdDev = √variance │ │ │ │ 3. Consistency check: │ │ tolerance = average × 20% │ │ isConsistent = (stdDev <= tolerance) │ │ │ │ 4. Round to common values: │ │ COMMON_VALUES = [0,2,4,6,8,10,12,16,20,24,32,40,48,64...] │ │ rounded = findClosest(average, COMMON_VALUES) │ └────────────────────────────────────────────────────────────────┘ Example: ┌──────────────────────────────────────────────────────────────┐ │ gaps = [16, 15, 17, 16] │ │ average = 16 │ │ stdDev = 0.71 │ │ tolerance = 16 × 0.2 = 3.2 │ │ isConsistent = (0.71 <= 3.2) = true ✓ │ │ rounded = 16px │ └──────────────────────────────────────────────────────────────┘ ``` ### 7.7 Absolute to Relative Position Conversion ``` convertAbsoluteToRelative() - [optimizer.ts:1591-1685] ═══════════════════════════════════════════════════════════════════════════ Original Layout (Absolute Positioning): ┌────────────────────────────────────────────────┐ │ Parent Container │ │ ┌──────────────────────────────────────────┐ │ │ │ ← paddingLeft │ │ │ │ ┌────────┐ gap ┌────────┐ │ │ │ │ │ Child1 │ ──────── │ Child2 │ │ │ │ │ │left:20 │ │left:140│ │ │ │ │ │top:10 │ │top:10 │ │ │ │ │ └────────┘ └────────┘ │ │ │ │ paddingRight→│ │ └──────────────────────────────────────────┘ │ │ ↓ paddingBottom │ └────────────────────────────────────────────────┘ After Conversion (Flex Layout): ┌────────────────────────────────────────────────┐ │ Parent Container │ │ display: flex; │ │ padding: 10px 30px 20px 20px; │ │ gap: 20px; │ │ ┌──────────────────────────────────────────┐ │ │ │ ┌────────┐ ←gap→ ┌────────┐ │ │ │ │ │ Child1 │ │ Child2 │ │ │ │ │ │(no left)│ │(no left)│ │ │ │ │ │(no top) │ │(no top) │ │ │ │ │ └────────┘ └────────┘ │ │ │ └──────────────────────────────────────────┘ │ └────────────────────────────────────────────────┘ Conversion Steps: ┌────────────────────────────────────────────────────────────────┐ │ 1. collectFlowChildOffsets() │ │ Collect position info for all flow children │ │ │ │ 2. inferContainerPadding() │ │ Infer container padding from child positions: │ │ • paddingLeft = min(children.left) │ │ • paddingTop = min(children.top) │ │ • paddingRight = parentWidth - max(children.right) │ │ • paddingBottom = parentHeight - max(children.bottom) │ │ │ │ 3. calculateChildMargins() │ │ Calculate individual child margin adjustments │ │ (cross-axis offset) │ │ │ │ 4. generatePaddingCSS() │ │ Generate CSS padding shorthand: │ │ • All same: "10px" │ │ • Top/bottom same, left/right same: "10px 20px" │ │ • All different: "10px 20px 30px 40px" │ │ │ │ 5. cleanChildStylesForLayout() │ │ Clean child element styles: │ │ • Remove position: absolute │ │ • Remove left, top │ │ • Keep width, height │ └────────────────────────────────────────────────────────────────┘ ``` ### 7.8 Core Data Structures ```typescript // Core data structures in detector.ts ═══════════════════════════════════════════════════════════════════════════ // Basic bounding box interface BoundingBox { x: number; // left offset y: number; // top offset width: number; // width height: number; // height } // Extended element rect (with computed properties) interface ElementRect extends BoundingBox { index: number; // index in original array right: number; // x + width bottom: number; // y + height centerX: number; // x + width/2 centerY: number; // y + height/2 } // Layout analysis result interface LayoutAnalysisResult { direction: 'row' | 'column' | 'none'; confidence: number; // 0-1 confidence score gap: number; // gap value (px) isGapConsistent: boolean; // is gap consistent justifyContent: string; // CSS justify-content alignItems: string; // CSS align-items rows: ElementRect[][]; // row grouping result columns: ElementRect[][]; // column grouping result overlappingElements: ElementRect[]; // overlapping elements } // Overlap detection result interface OverlapDetectionResult { flowElements: ElementRect[]; // elements participating in layout stackedElements: ElementRect[]; // elements needing absolute stackedIndices: Set<number>; // overlapping element indices } // Background detection result interface BackgroundDetectionResult { backgroundIndex: number; // background element index (-1 if none) contentIndices: number[]; // content element indices hasBackground: boolean; // whether background detected } ``` ### 7.9 File Path Mapping | Module | File Path | Line Range | | -------------------------- | ------------------------------------ | ---------- | | Bounding box extraction | `src/algorithms/layout/detector.ts` | 56-109 | | Overlap detection (IoU) | `src/algorithms/layout/detector.ts` | 111-281 | | Background detection | `src/algorithms/layout/detector.ts` | 283-344 | | Row/column grouping | `src/algorithms/layout/detector.ts` | 346-422 | | Gap analysis | `src/algorithms/layout/detector.ts` | 442-535 | | Alignment detection | `src/algorithms/layout/detector.ts` | 537-642 | | Layout direction detection | `src/algorithms/layout/detector.ts` | 644-755 | | Layout tree building | `src/algorithms/layout/detector.ts` | 847-982 | | Optimization entry | `src/algorithms/layout/optimizer.ts` | 36-53 | | Container optimization | `src/algorithms/layout/optimizer.ts` | 89-356 | | CSS generation | `src/algorithms/layout/optimizer.ts` | 875-913 | | Position conversion | `src/algorithms/layout/optimizer.ts` | 1591-1685 | --- _Document version: 2.0_ _Last updated: 2025-12-06_ _For the Chinese version of this document, see [docs/zh-CN/layout-detection.md](../zh-CN/layout-detection.md)_

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