From f59286d15f8549a4fb4b97bde7e0e8cbb2feb1d7 Mon Sep 17 00:00:00 2001 From: Dotty Dotter Date: Thu, 7 May 2026 14:37:55 +0200 Subject: [PATCH] fix: PNG-Export-Canvas + Doppel-Borders bei field-chip/party-pill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User: 'immer noch doppelte Borders ... Inhalte zu klein skaliert nach oben links gerutscht (800 px breit statt 1080)' Ursachen: 1. Canvas-Content-Mismatch (Inhalt 75% der PNG-Breite): WeasyPrint rechnet 1 CSS-px = 0.75 PDF-pt (96dpi → 72dpi). @page war auf {width}pt × {height}pt (1080×1350) gesetzt, body aber auf 1080×1350 CSS-px. Folge: Body fuellte nur 1080*0.75=810pt der 1080pt-Page → Content top-left, 25% rechts/unten leer; PyMuPDF rasterisiert mit zoom=1 → 1080×1350 PNG, Content nur in den linken 810×1012 px → 'Inhalte zu klein nach oben links gerutscht'. Fix: @page-Groesse auf (width * 0.75)pt × (height * 0.75)pt setzen. Body fuellt jetzt die volle Canvas-Breite. PyMuPDF kompensiert mit zoom = scale * 4/3, damit die PNG wieder die gewuenschten Pixel- Dimensionen hat (1080×1350 für scale=1). 2. Doppel-Borders auf field-chip + party-pill: WeasyPrint hat einen bekannten Render-Bug bei 'border + border-radius' auf inline-flex-Elementen — der Border wird zweimal gezeichnet (innen + aussen). 1.5px → 2px hat das nicht behoben, weil's nicht am Subpixel-Wert lag. Fix: border ersetzt durch box-shadow: inset 0 0 0 2px var(--rule). Inset-Shadow rendert sauber, kein Doppel-Effekt. border-radius bleibt erhalten. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/main.py | 19 +++++++++++++------ .../v2/screens/scorecard_portrait.html | 8 ++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/main.py b/app/main.py index fcfb7b8..8e2c3ca 100644 --- a/app/main.py +++ b/app/main.py @@ -3811,11 +3811,15 @@ async def _render_scorecard_pdf( width=width, height=height, ) - # `size: NNNpt` → PDF-Page hat exakt N×M Punkte. PyMuPDF rendert - # bei zoom=1 dann 1 PDF-Punkt = 1 PNG-Pixel. CSS-Pixel werden - # aber auch in pt umgerechnet (96dpi → 72dpi → ×0.75) — daher - # konvertiere pt→css-px so, dass die Inhalts-Layouts passen. - page_size_css = f"@page {{ size: {width}pt {height}pt; margin: 0; }}" + # CSS rechnet 1px = 0.75pt (96dpi vs. 72dpi). Wenn body width:1080px + # gerendert wird und @page size:1080pt ist, fuellt der Body nur 75% + # der Page-Breite (810pt) → Inhalt rutscht oben links und ist zu klein. + # Fix: @page-Groesse in pt = (CSS-px * 0.75) setzen, Body fuellt + # damit die volle Canvas-Breite. PyMuPDF kompensiert beim Render mit + # zoom=4/3 zurueck auf die gewuenschten Pixel-Dimensionen. + pt_w = width * 0.75 + pt_h = height * 0.75 + page_size_css = f"@page {{ size: {pt_w}pt {pt_h}pt; margin: 0; }}" pdf = HTML(string=html_content).write_pdf(stylesheets=[CSS(string=page_size_css)]) safe = drucksache.replace("/", "-") return pdf, width, height, f"scorecard-{safe}-{format}" @@ -3838,7 +3842,10 @@ async def api_scorecard_png( doc = fitz.open(stream=pdf_bytes, filetype="pdf") page = doc[0] # zoom-Matrix für höhere Auflösung - zoom = max(0.5, min(4.0, float(scale))) + # PDF wird mit @page in pt = CSS-px * 0.75 erzeugt. Um die + # gewuenschte Pixel-Dimension (width × height) wieder zu + # erreichen, kompensiert PyMuPDF mit zoom = 4/3 * scale. + zoom = max(0.5, min(4.0, float(scale))) * 4.0 / 3.0 mat = fitz.Matrix(zoom, zoom) pix = page.get_pixmap(matrix=mat, alpha=False) png = pix.tobytes("png") diff --git a/app/templates/v2/screens/scorecard_portrait.html b/app/templates/v2/screens/scorecard_portrait.html index 0650f7d..974d367 100644 --- a/app/templates/v2/screens/scorecard_portrait.html +++ b/app/templates/v2/screens/scorecard_portrait.html @@ -53,7 +53,10 @@ .ids strong{color:var(--ink);font-weight:600} .party-pill{ display:inline-flex;align-items:center;gap:10px; - border:2px solid var(--rule);border-radius:999px; + /* box-shadow inset statt border: WeasyPrint rendert border+border-radius + auf inline-flex-Elementen doppelt (ein Render-Bug). inset ist sauber. */ + box-shadow: inset 0 0 0 2px var(--rule); + border-radius:999px; font-weight:700;letter-spacing:.04em; } .party-pill::before{content:"";border-radius:50%;background:#d44} @@ -77,7 +80,8 @@ .verdict .fields{display:flex;flex-wrap:wrap} .field-chip{ font-family:'JetBrains Mono',monospace;font-weight:600; - border:2px solid var(--rule);border-radius:4px;background:transparent; + box-shadow: inset 0 0 0 2px var(--rule); + border-radius:4px;background:transparent; } .fractions{display:grid;grid-template-columns:repeat({{ fraktionen_count }},1fr);border:2px solid var(--rule)} .frac{