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
| Wert | Verhalten |
|---|---|
bottom | Container wächst nach unten (Standard) |
top | Container 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
- Prüfe
parentContainerId- ist es korrekt gesetzt? - Prüfe
displayOrder- richtige Reihenfolge? - Prüfe
childLayout- Margins korrekt?
Problem: Text-Höhe stimmt nicht
- Prüfe Font-Loading - wird die Schrift gefunden?
- Prüfe Auto-Resize - wird die Schriftgröße reduziert?
- Vergleiche
lineHeight- Frontend vs Renderer
Debug-Logging aktivieren
# In layout/engine.py
print(f"Element {element['id']}: height={height}, lines={len(lines)}")
Siehe auch
- Text Auto-Resize - Wie Schriftgrößen berechnet werden
- Rendering Architecture - Frontend vs Python Renderer