""" Cairo-basiertes Rendering der Vektorpfade. Erzeugt aus den manipulierten Pfaden ein neues Bild (für Preview und Export). """ from __future__ import annotations import cairo import numpy as np from PySide6.QtGui import QImage, QPixmap from svgpathtools import Path as SvgPath, Line, CubicBezier, QuadraticBezier from core.logo_model import LogoProject, GlyphGroup def _walk_svg_path(ctx: cairo.Context, svg_path: SvgPath): """Zeichnet einen svgpathtools-Pfad auf einen Cairo-Context.""" for seg in svg_path: start = seg.start if isinstance(seg, Line): ctx.line_to(seg.end.real, seg.end.imag) elif isinstance(seg, CubicBezier): ctx.curve_to( seg.control1.real, seg.control1.imag, seg.control2.real, seg.control2.imag, seg.end.real, seg.end.imag, ) elif isinstance(seg, QuadraticBezier): # Quadratische Bezier → kubische Approximation p0 = seg.start p1 = seg.control p2 = seg.end cp1 = p0 + (2 / 3) * (p1 - p0) cp2 = p2 + (2 / 3) * (p1 - p2) ctx.curve_to( cp1.real, cp1.imag, cp2.real, cp2.imag, p2.real, p2.imag, ) def render_project(project: LogoProject) -> np.ndarray: """ Rendert das gesamte Projekt mit Cairo und gibt RGBA-Pixels zurück. Berücksichtigt globale Strichdicke und Buchstabenabstand. """ w = max(project.width, 1) h = max(project.height, 1) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(surface) ctx.set_source_rgba(0, 0, 0, 0) ctx.paint() ctx.set_source_rgba(0, 0, 0, 1) cumulative_offset = 0.0 for glyph in project.glyphs: ctx.save() spacing = project.global_letter_spacing + glyph.offset_x ctx.translate(cumulative_offset + spacing, glyph.offset_y) cumulative_offset += spacing stroke_w = project.global_stroke_width * glyph.stroke_width_factor ctx.set_line_width(stroke_w) for svg_path in glyph.paths: if not svg_path: continue first_pt = svg_path[0].start ctx.move_to(first_pt.real, first_pt.imag) _walk_svg_path(ctx, svg_path) ctx.close_path() ctx.fill_preserve() if stroke_w > 0: ctx.stroke() else: ctx.new_path() ctx.restore() buf = surface.get_data() arr = np.frombuffer(buf, dtype=np.uint8).reshape((h, w, 4)).copy() # Cairo liefert BGRA, wir brauchen RGBA arr[:, :, [0, 2]] = arr[:, :, [2, 0]] return arr def render_to_qpixmap(project: LogoProject) -> QPixmap: """Rendert das Projekt und gibt ein QPixmap zurück.""" pixels = render_project(project) h, w, _ = pixels.shape qimg = QImage( pixels.data.tobytes(), w, h, 4 * w, QImage.Format.Format_RGBA8888, ) return QPixmap.fromImage(qimg)