fix(pdf): sanitize text to Latin-1 before fpdf2, absorb per-line errors
The fpdf2 built-in Helvetica font is Latin-1 only; characters outside that range caused "Not enough horizontal space" errors which silently aborted PDF generation. Fix: - _l1() helper encodes/decodes via latin-1 with replacement before any multi_cell call - _cell() wrapper catches per-line render errors so one bad character doesn't drop the whole document - PDF generation errors are now logged as warnings instead of swallowed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
52
app.py
52
app.py
@@ -262,11 +262,18 @@ def _split_thinking(text: str) -> tuple[str, str]:
|
||||
|
||||
def _md_to_pdf(title: str, content_md: str) -> bytes:
|
||||
"""Render Markdown content to a PDF byte string."""
|
||||
|
||||
def _l1(text: str) -> str:
|
||||
"""Sanitize text to Latin-1; fpdf2 built-in fonts require it."""
|
||||
return text.encode("latin-1", errors="replace").decode("latin-1")
|
||||
|
||||
_title = _l1(title[:100])
|
||||
|
||||
class PDF(fpdflib.FPDF):
|
||||
def header(self):
|
||||
self.set_font("Helvetica", "B", 9)
|
||||
self.set_text_color(100, 100, 100)
|
||||
self.cell(0, 6, title, align="L")
|
||||
self.cell(0, 6, _title, align="L")
|
||||
self.ln(2)
|
||||
self.set_draw_color(200, 200, 200)
|
||||
self.line(self.l_margin, self.get_y(), self.w - self.r_margin, self.get_y())
|
||||
@@ -282,41 +289,37 @@ def _md_to_pdf(title: str, content_md: str) -> bytes:
|
||||
pdf.set_auto_page_break(auto=True, margin=15)
|
||||
pdf.add_page()
|
||||
|
||||
# Render markdown line by line with minimal formatting
|
||||
def _cell(text: str, size: int = 10, bold: bool = False, indent: float = 0, h: float = 5):
|
||||
try:
|
||||
pdf.set_font("Helvetica", "B" if bold else "", size)
|
||||
pdf.set_text_color(20 if bold else 30, 20 if bold else 30, 20 if bold else 30)
|
||||
if indent:
|
||||
pdf.set_x(pdf.l_margin + indent)
|
||||
pdf.multi_cell(pdf.w - pdf.l_margin - pdf.r_margin - indent, h, _l1(text))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for raw_line in content_md.split("\n"):
|
||||
line = raw_line.rstrip()
|
||||
if line.startswith("# "):
|
||||
pdf.set_font("Helvetica", "B", 14)
|
||||
pdf.set_text_color(20, 20, 20)
|
||||
pdf.multi_cell(0, 7, line[2:])
|
||||
_cell(line[2:], size=14, bold=True, h=7)
|
||||
pdf.ln(1)
|
||||
elif line.startswith("## "):
|
||||
pdf.set_font("Helvetica", "B", 12)
|
||||
pdf.set_text_color(40, 40, 40)
|
||||
pdf.multi_cell(0, 6, line[3:])
|
||||
_cell(line[3:], size=12, bold=True, h=6)
|
||||
pdf.ln(1)
|
||||
elif line.startswith("### "):
|
||||
pdf.set_font("Helvetica", "B", 11)
|
||||
pdf.set_text_color(60, 60, 60)
|
||||
pdf.multi_cell(0, 6, line[4:])
|
||||
_cell(line[4:], size=11, bold=True, h=6)
|
||||
elif line.startswith(("- ", "* ", "+ ")):
|
||||
pdf.set_font("Helvetica", "", 10)
|
||||
pdf.set_text_color(30, 30, 30)
|
||||
pdf.multi_cell(0, 5, " • " + line[2:])
|
||||
_cell("• " + line[2:], indent=5)
|
||||
elif re.match(r"^\d+\. ", line):
|
||||
pdf.set_font("Helvetica", "", 10)
|
||||
pdf.set_text_color(30, 30, 30)
|
||||
pdf.multi_cell(0, 5, " " + line)
|
||||
elif line == "" or line == "---":
|
||||
_cell(line, indent=5)
|
||||
elif line in ("", "---"):
|
||||
pdf.ln(3)
|
||||
else:
|
||||
# Strip inline markdown markers for bold/italic
|
||||
clean = re.sub(r"\*\*(.+?)\*\*", r"\1", line)
|
||||
clean = re.sub(r"\*(.+?)\*", r"\1", clean)
|
||||
clean = re.sub(r"`(.+?)`", r"\1", clean)
|
||||
pdf.set_font("Helvetica", "", 10)
|
||||
pdf.set_text_color(30, 30, 30)
|
||||
pdf.multi_cell(0, 5, clean)
|
||||
_cell(clean)
|
||||
|
||||
return bytes(pdf.output())
|
||||
|
||||
@@ -775,8 +778,9 @@ def _process_analysis_job(job_id: int):
|
||||
try:
|
||||
pdf_bytes = _md_to_pdf(step_title, content)
|
||||
attachments.append((f"{fname_base}.pdf", pdf_bytes, "application", "pdf"))
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as pdf_err:
|
||||
import logging
|
||||
logging.warning(f"PDF-Generierung fehlgeschlagen für '{step_title}': {pdf_err}")
|
||||
_send_email(email_to, subject, answer, attachments)
|
||||
except Exception as mail_err:
|
||||
_job_set(job_id, error=f"[E-Mail-Fehler] {mail_err}")
|
||||
|
||||
Reference in New Issue
Block a user