Files
2026-07-17 12:57:18 +03:00

93 lines
2.7 KiB
Python

"""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")