Zum Hauptinhalt springen

Layout Engine

Die Layout Engine berechnet die Positionen und Größen von Elementen innerhalb von Containern. Sie sorgt dafür, dass Elemente automatisch vertikal angeordnet werden.

Vertical Stack Container

Ein Container mit isVerticalStackContainer: true ordnet seine Kinder automatisch vertikal an:

┌─────────────────────────────────┐
│ Container (isVerticalStackContainer: true)
│ ┌─────────────────────────────┐ │
│ │ paddingTop │ │
│ ├─────────────────────────────┤ │
│ │ Child 1 (Text) │ │
│ │ marginBottom: 10 │ │
│ ├─────────────────────────────┤ │
│ │ Child 2 (Text) │ │
│ │ marginBottom: 10 │ │
│ ├─────────────────────────────┤ │
│ │ Child 3 (Text) │ │
│ ├─────────────────────────────┤ │
│ │ paddingBottom │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘

Container Properties

interface Container {
isVerticalStackContainer: boolean;
containerLayout: {
paddingLeft: number; // default: 10
paddingRight: number; // default: 10
paddingTop: number; // default: 10
paddingBottom: number; // default: 10
growDirection: 'top' | 'bottom'; // default: 'bottom'
};
}

growDirection

WertVerhalten
bottomContainer wächst nach unten (Standard)
topContainer wächst nach oben, untere Kante bleibt fix

Child Properties

interface ChildElement {
parentContainerId: string; // ID des Parent-Containers
childLayout: {
marginTop: number; // default: 0
marginBottom: number; // default: 0
marginLeft: number; // default: 0
marginRight: number; // default: 0
};
}

Layout-Berechnung Algorithmus

Schritt 1: Container und Kinder identifizieren

containers = [elem for elem in elements if elem.get('isVerticalStackContainer')]

for container in containers:
children = [elem for elem in elements if elem.get('parentContainerId') == container['id']]

Schritt 2: Kinder nach displayOrder sortieren

sorted_children = sorted(children, key=lambda e: e.get('displayOrder', 0))

Schritt 3: Positionen berechnen

cursor_y = padding_top

for child in sorted_children:
margin_top = child.get('childLayout', {}).get('marginTop', 0)
margin_bottom = child.get('childLayout', {}).get('marginBottom', 0)

# Child-Breite (begrenzt durch Container inner width)
inner_width = container_width - padding_left - padding_right
child_width = min(child['width'], inner_width)

# Child-Höhe messen (bei Text: mit Wrapping!)
child_height = measure_element_height(child, child_width)

# Position berechnen
child_x = container_x + padding_left
child_y = container_y + cursor_y + margin_top

# Cursor vorwärts bewegen
cursor_y += margin_top + child_height + margin_bottom

Schritt 4: Container-Höhe berechnen

container_height = cursor_y + padding_bottom

Text-Höhenmessung

Die kritischste Berechnung ist die Höhe von Text-Elementen:

def measure_text_height(element, max_width):
text = element.get('text', '') or element.get('content', '')
font_size = element['style']['fontSize']
line_height = element['style'].get('lineHeight', 1.2)

# Auto-Resize anwenden (falls aktiviert)
if element['style'].get('autoResize', True):
font_size = calculate_optimal_font_size(...)

# Text wrappen
lines = wrap_text(text, font, max_width)

# Höhe berechnen (CSS line-height Verhalten)
line_height_px = font_size * line_height
total_height = len(lines) * line_height_px

return total_height

CSS line-height Verhalten

Bei font-size: 33px und line-height: 1.0:

┌─────────────────────────────────┐ ─┬─
│ BILDUNG, │ │ 33px (line-height * font-size)
└─────────────────────────────────┘ ─┴─
┌─────────────────────────────────┐ ─┬─
│ DIE DICH │ │ 33px
└─────────────────────────────────┘ ─┴─
┌─────────────────────────────────┐ ─┬─
│ BEGLEITET │ │ 33px
└─────────────────────────────────┘ ─┴─

Total: 3 lines × 33px = 99px

Implementierungen

Frontend

Datei: frontend/src/lib/utils/containerLayoutEngine.ts

Die Frontend-Layout-Engine wird für die Live-Vorschau im Editor verwendet.

Python Renderer

Datei: renderer/src/layout/engine.py

class ContainerLayoutEngine:
def __init__(self, font_manager):
self.font_manager = font_manager
self.text_utils = TextUtils(self._load_font)

def calculate_container_layouts(self, elements):
# Berechnet Layouts für alle Container
pass

def _measure_text_height(self, element, max_width):
# Misst Text-Höhe mit Wrapping und Auto-Resize
pass

Beispiel: Vollständige Berechnung

Input:

{
"container": {
"id": "container-1",
"x": 20, "y": 20,
"width": 190, "height": 150,
"isVerticalStackContainer": true,
"containerLayout": {
"paddingTop": 0, "paddingBottom": 0,
"paddingLeft": 0, "paddingRight": 0
}
},
"children": [
{
"id": "text-1",
"parentContainerId": "container-1",
"content": "BILDUNG, DIE DICH BEGLEITET",
"style": { "fontSize": 33, "lineHeight": 1 },
"childLayout": { "marginBottom": 10 }
},
{
"id": "text-2",
"parentContainerId": "container-1",
"content": "MIT PLAN UND ERFOLG",
"style": { "fontSize": 10, "lineHeight": 1 },
"childLayout": { "marginBottom": 10 }
}
]
}

Berechnung:

Container: x=20, y=20, width=190

1. Text-1:
- Auto-Resize: 33px → 30px (wegen "BEGLEITET")
- Wrap: 3 Zeilen
- Höhe: 3 × 30 = 90px
- Position: x=20, y=20
- cursor_y = 0 + 90 + 10 = 100

2. Text-2:
- Wrap: 1 Zeile
- Höhe: 1 × 10 = 10px
- Position: x=20, y=120
- cursor_y = 100 + 10 + 10 = 120

Container-Höhe: 120 + 0 = 120px

Output:

{
"container": { "height": 120 },
"children": [
{ "id": "text-1", "x": 20, "y": 20, "height": 90 },
{ "id": "text-2", "x": 20, "y": 120, "height": 10 }
]
}

Grow Direction: Top

Bei growDirection: 'top' bleibt die untere Kante des Containers fix:

growDirection: 'bottom'          growDirection: 'top'
(Standard)

┌───────────────┐ ┌───────────────┐
│ Container │ ← top fixed │ Container │
│ ┌───────────┐ │ │ ┌───────────┐ │
│ │ Child 1 │ │ │ │ Child 1 │ │
│ └───────────┘ │ │ └───────────┘ │
│ ┌───────────┐ │ │ ┌───────────┐ │
│ │ Child 2 │ │ │ │ Child 2 │ │
│ └───────────┘ │ │ └───────────┘ │
│ │ └───────────────┘ ← bottom fixed
│ (wächst │ ↑
│ nach │ Container Y
│ unten) │ verschiebt sich
│ │ nach oben
└───────────────┘

Debugging

Problem: Falsche Element-Positionen

  1. Prüfe parentContainerId - ist es korrekt gesetzt?
  2. Prüfe displayOrder - richtige Reihenfolge?
  3. Prüfe childLayout - Margins korrekt?

Problem: Text-Höhe stimmt nicht

  1. Prüfe Font-Loading - wird die Schrift gefunden?
  2. Prüfe Auto-Resize - wird die Schriftgröße reduziert?
  3. Vergleiche lineHeight - Frontend vs Renderer

Debug-Logging aktivieren

# In layout/engine.py
print(f"Element {element['id']}: height={height}, lines={len(lines)}")

Siehe auch