"""Minimal PDF table export with Cyrillic support (fpdf2 + system fonts).""" from __future__ import annotations import os from pathlib import Path from typing import Any, Sequence from fpdf import FPDF _FONT_CANDIDATES = ( Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"), Path("/usr/share/fonts/TTF/DejaVuSans.ttf"), Path("C:/Windows/Fonts/arial.ttf"), Path("C:/Windows/Fonts/Arial.ttf"), Path(os.environ.get("WINDIR", "C:/Windows")) / "Fonts" / "arial.ttf", ) def _resolve_font_path() -> str: for candidate in _FONT_CANDIDATES: if candidate.exists(): return str(candidate) raise RuntimeError("PDF font not found (install fonts-dejavu-core or use Windows Arial)") def _truncate(text: Any, max_len: int) -> str: value = str(text if text is not None else "") if len(value) <= max_len: return value return value[: max(0, max_len - 1)] + "…" def build_table_pdf( *, title: str, subtitle_lines: Sequence[str] | None = None, headers: Sequence[str], rows: Sequence[Sequence[Any]], footer_lines: Sequence[str] | None = None, ) -> bytes: font_path = _resolve_font_path() pdf = FPDF(orientation="P", unit="mm", format="A4") pdf.set_auto_page_break(auto=True, margin=15) pdf.add_page() pdf.add_font("DocFont", "", font_path) pdf.set_font("DocFont", size=14) effective_width = pdf.w - pdf.l_margin - pdf.r_margin pdf.cell(effective_width, 8, _truncate(title, 120), new_x="LMARGIN", new_y="NEXT", align="C") pdf.ln(2) if subtitle_lines: pdf.set_font("DocFont", size=10) for line in subtitle_lines: if line: pdf.multi_cell(effective_width, 5, _truncate(line, 200)) pdf.ln(2) col_count = len(headers) if col_count == 0: col_count = 1 page_width = effective_width col_width = page_width / col_count pdf.set_font("DocFont", size=9) for header in headers: pdf.cell(col_width, 7, _truncate(header, 40), border=1, align="C") pdf.ln() pdf.set_font("DocFont", size=9) if rows: for row in rows: for value in row: pdf.cell(col_width, 6, _truncate(value, 48), border=1) pdf.ln() else: pdf.cell(page_width, 8, "Нет данных за выбранный период", border=1, align="C") pdf.ln() if footer_lines: pdf.ln(4) pdf.set_font("DocFont", size=10) for line in footer_lines: if line: pdf.multi_cell(effective_width, 5, _truncate(line, 200)) out = pdf.output() if isinstance(out, bytearray): return bytes(out) if isinstance(out, bytes): return out return str(out).encode("latin-1")