Rendering Architecture
heycreo verwendet zwei Rendering-Systeme: ein Frontend-Renderer für die Live-Vorschau und ein Python-Renderer für den Export.
Übersicht
┌─────────────────────────────────────────────────────────────┐
│ User Input │
│ (Template + Control Values) │
└─────────────────────────┬───────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Frontend Renderer │ │ Python Renderer │
│ (React + Canvas) │ │ (PIL + FFmpeg) │
├─────────────────────┤ ├─────────────────────┤
│ • Live-Vorschau │ │ • PNG/JPEG Export │
│ • Interaktiv │ │ • MP4 Video │
│ • Browser-basiert │ │ • Batch-Processing │
└─────────────────────┘ └─────────────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Browser Display │ │ File Download │
└─────────────────────┘ └─────────────────────┘
Frontend Renderer
Zweck: Live-Vorschau im Editor während der Bearbeitung
Technologien:
- React Components (
TemplatePreview.tsx,DraggableElement.tsx) - CSS für Styling und Layout
- Canvas API für Text-Messung
Dateien:
frontend/src/
├── components/templates/
│ ├── TemplatePreview.tsx # Hauptkomponente
│ └── DraggableElement.tsx # Element-Rendering
├── lib/utils/
│ ├── textAutoResize.ts # Text-Größenberechnung
│ └── containerLayoutEngine.ts # Layout-Berechnung
Vorteile:
- ✅ Echtzeit-Updates
- ✅ Native Browser-Rendering (CSS, Fonts)
- ✅ Interaktiv (Drag & Drop, Editing)
Nachteile:
- ❌ Nicht für Export geeignet (Screenshot-Qualität)
- ❌ Browser-abhängig
Python Renderer
Zweck: Hochqualitätiver Export von Bildern und Videos
Technologien:
- PIL/Pillow für Raster-Grafiken
- FFmpeg für Video-Encoding
- Multiprocessing für paralleles Frame-Rendering
Dateien:
renderer/src/
├── core/
│ ├── renderer.py # Hauptrenderer
│ └── video_encoder.py # FFmpeg Wrapper
├── elements/
│ └── renderer.py # Element-Rendering
├── layout/
│ └── engine.py # Container-Layout
├── utils/
│ └── text_utils.py # Shared Text-Utilities
├── animations/
│ ├── easing.py # Easing-Funktionen
│ └── timeline.py # Animation-Timeline
├── effects/
│ ├── gradient.py # Gradient-Rendering
│ └── shadow.py # Schatten-Effekte
└── media/
├── fonts.py # Font-Management
├── font_cache.py # Font-Caching
└── image_cache.py # Image-Caching
Vorteile:
- ✅ Konsistente Ausgabe (unabhängig vom Browser)
- ✅ Hohe Qualität (300+ DPI möglich)
- ✅ Video-Support mit Animationen
Nachteile:
- ❌ Muss Browser-Verhalten nachbilden
- ❌ Text-Messung unterscheidet sich leicht
Shared Components
Um konsistentes Verhalten zu gewährleisten, teilen beide Renderer die gleiche Logik:
┌─────────────────────┐
│ TextUtils │
│ (Python + TS) │
├─────────────────────┤
│ • wrap_text() │
│ • check_words_fit() │
│ • calculate_ │
│ optimal_font_size │
└─────────────────────┘
│
┌───────────────┴───────────── ──┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Frontend │ │ Python Renderer │
│ textAutoResize.ts │ │ text_utils.py │
└─────────────────────┘ └─────────────────────┘
Rendering Pipeline
1. Template Processing (API)
// api/src/modules/export/template-processor.service.ts
// Control Values anwenden
element.text = controlValue;
element.content = controlValue;
// Visibility Controls
if (visibilityControlValue === false) {
element.hidden = true;
}
2. Layout Calculation
# renderer/src/layout/engine.py
layout_engine = ContainerLayoutEngine(font_manager)
elements = layout_engine.calculate_container_layouts(elements)
3. Element Rendering
# renderer/src/elements/renderer.py
for element in sorted_elements:
if element['type'] == 'text':
self._render_text(draw, canvas, element)
elif element['type'] == 'image':
self._render_image(draw, canvas, element)
elif element['type'] == 'shape':
self._render_shape(draw, canvas, element)
4. Video Encoding
# renderer/src/core/video_encoder.py
# Frames parallel rendern
with Pool(workers) as pool:
frames = pool.map(render_frame, frame_numbers)
# FFmpeg für Encoding
ffmpeg -framerate 30 -i frame_%04d.png -c:v libx264 output.mp4
Browser vs PIL Unterschiede
Text-Messung
| Aspekt | Browser | PIL |
|---|---|---|
| Engine | Varies (Blink, Gecko, WebKit) | FreeType |
| Precision | Sub-pixel | Integer |
| Width | ~2-3% schmaler | ~2-3% breiter |
Lösung: 2% Toleranz in Word-Fit Checks
tolerance = max_width * 0.02
if word_width > max_width + tolerance:
return False
Descenders (Unterlängen)
Bei line-height: 1 können Buchstaben wie g, p, y abgeschnitten werden:
Browser (CSS): PIL (ohne Buffer):
┌─────────────────┐ ┌─────────────────┐
│ Typography │ │ Typograph │ ← 'y' abgeschnitten!
└─────────────────┘ └─────────────────┘
↑
CSS erlaubt
visuelles Overflow
Lösung: Descender-Buffer für Container-Kinder
if is_container_child:
actual_line_box = line_height_px
actual_font_height = ascent + descent
descender_overflow = max(0, actual_font_height - actual_line_box)
padded_height = int(final_height + descender_overflow)
Line-Height
CSS line-height vs PIL:
CSS line-height: 1.2
font-size: 20px
→ Zeilenhöhe: 24px (20 × 1.2)
PIL:
line_height_px = font_size * line_height
total_height = num_lines * line_height_px
Performance-Optimierungen
Font Caching
# renderer/src/media/font_cache.py
class FontCache:
def get_font(self, family, size, weight, style):
cache_key = f"{family}-{size}-{weight}-{style}"
if cache_key not in self.cache:
self.cache[cache_key] = load_font(...)
return self.cache[cache_key]
Image Caching
# renderer/src/media/image_cache.py
class ImageCache:
def get_image(self, url):
if url not in self.cache:
self.cache[url] = download_and_load(url)
return self.cache[url]
Parallel Frame Rendering
# Für Videos: Frames parallel rendern
from multiprocessing import Pool
with Pool(processes=4) as pool:
frames = pool.map(render_frame, range(total_frames))
Debugging
Frontend → Renderer Vergleich
- Screenshot im Frontend machen
- Export im Renderer machen
- Visuell vergleichen:
- Text-Größe identisch?
- Positionen identisch?
- Farben identisch?
Debug-Logging
# In renderer/src/elements/renderer.py
print(f"Text '{text[:20]}': font_size={font_size}, lines={len(lines)}")
print(f"Position: x={x}, y={y}, width={width}, height={height}")
Common Issues
| Symptom | Ursache | Lösung |
|---|---|---|
| Text zu klein | PIL misst breiter | 2% Toleranz prüfen |
| Text abgeschnitten | Descenders | Buffer hinzufügen |
| Falsche Position | Layout nicht berechnet | calculate_container_layouts() aufrufen |
| Schrift falsch | Font nicht gefunden | Font-Pfade prüfen |
Siehe auch
- Text Auto-Resize - Schriftgrößen-Berechnung
- Layout Engine - Container-Layout