@@ -0,0 +1,92 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user