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​

Shared render core​

Ort: packages/render-engine/ (von Studio, Browser- und Remotion-Renderer genutzt)

Die Layout-Berechnung läuft im gemeinsamen Renderkern — dieselbe Logik für Live-Vorschau und Export.

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​