0819c35bbb
Añade 3 tipos Python (PDFDoc, PDFPage, PDFStyle) y 10 funciones Python para construir PDFs con fpdf2 (builder fluent), fusionar PDFs con pypdf y convertir HTML/Markdown a PDF via weasyprint (stub si no disponible). Añade pdf_simple_report en Go como stub hasta que go-pdf/fpdf se integre. - python/types/infra/: pdf_doc, pdf_page, pdf_style - python/functions/infra/: pdf_create, pdf_add_page, pdf_add_text, pdf_add_table, pdf_add_image, pdf_add_header_footer, pdf_from_html, pdf_from_markdown, pdf_merge, pdf_save - functions/infra/pdf_simple_report.go: stub Go con ReportSection/ReportTable - 17 tests Python pasando (pytest) - fpdf2 y pypdf añadidos via uv al venv Python Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
129 lines
4.0 KiB
Python
129 lines
4.0 KiB
Python
"""pdf_add_table — añade una tabla con headers y filas al documento PDF."""
|
|
|
|
import sys
|
|
import os
|
|
|
|
_types_dir = os.path.join(os.path.dirname(__file__), "..", "..", "..", "python", "types", "infra")
|
|
sys.path.insert(0, _types_dir)
|
|
from pdf_doc import PDFDoc
|
|
from pdf_style import PDFStyle
|
|
|
|
|
|
def _parse_hex_color(hex_color: str) -> tuple[int, int, int]:
|
|
h = hex_color.lstrip("#")
|
|
if len(h) == 3:
|
|
h = "".join(c * 2 for c in h)
|
|
return int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
|
|
|
|
|
|
def pdf_add_table(
|
|
doc: PDFDoc,
|
|
headers: list[str],
|
|
rows: list[list[str]],
|
|
col_widths: list[float] | None = None,
|
|
header_style: PDFStyle | None = None,
|
|
row_style: PDFStyle | None = None,
|
|
border: int = 1,
|
|
row_height: float = 8.0,
|
|
alternate_bg: bool = True,
|
|
) -> PDFDoc:
|
|
"""Añade una tabla con headers y filas de datos al documento PDF.
|
|
|
|
Las columnas se distribuyen uniformemente si no se especifican anchos.
|
|
Soporta colores alternados en filas, bordes configurables y estilos
|
|
diferenciados para headers y filas de datos.
|
|
|
|
Args:
|
|
doc: PDFDoc con pagina activa.
|
|
headers: lista de strings con los nombres de columna.
|
|
rows: lista de listas de strings con los datos de cada fila.
|
|
col_widths: lista de anchos de columna en mm. None = distribucion uniforme.
|
|
header_style: PDFStyle para la fila de headers. None usa bold 10pt.
|
|
row_style: PDFStyle para filas de datos. None usa 10pt normal.
|
|
border: grosor/tipo de borde (0=sin borde, 1=borde completo).
|
|
row_height: altura de cada celda en mm. Por defecto 8.
|
|
alternate_bg: si True, alterna fondo gris claro en filas pares.
|
|
|
|
Returns:
|
|
PDFDoc con la tabla añadida.
|
|
"""
|
|
fpdf = doc.fpdf
|
|
|
|
if not headers:
|
|
return doc
|
|
|
|
# Anchos de columna
|
|
available_width = fpdf.epw
|
|
n_cols = len(headers)
|
|
if col_widths is None:
|
|
col_widths = [available_width / n_cols] * n_cols
|
|
else:
|
|
# Rellenar si faltan columnas
|
|
while len(col_widths) < n_cols:
|
|
col_widths.append(available_width / n_cols)
|
|
|
|
# Estilos
|
|
if header_style is None:
|
|
header_style = PDFStyle(
|
|
font_family="Helvetica-Bold",
|
|
font_size=10.0,
|
|
color="#000000",
|
|
alignment="left",
|
|
)
|
|
if row_style is None:
|
|
row_style = PDFStyle(font_size=10.0, color="#000000", alignment="left")
|
|
|
|
def set_font_from_style(s: PDFStyle):
|
|
family = s.font_family
|
|
bold = "-Bold" in family or "-BoldOblique" in family
|
|
italic = "-Oblique" in family or "-BoldOblique" in family
|
|
base = family.split("-")[0]
|
|
st = ("B" if bold else "") + ("I" if italic else "")
|
|
fpdf.set_font(base, style=st, size=s.font_size)
|
|
r, g, b = _parse_hex_color(s.color)
|
|
fpdf.set_text_color(r, g, b)
|
|
|
|
def align_char(a: str) -> str:
|
|
return {"left": "L", "center": "C", "right": "R", "justify": "J"}.get(a, "L")
|
|
|
|
# Header row
|
|
set_font_from_style(header_style)
|
|
fpdf.set_fill_color(230, 230, 230)
|
|
fpdf.set_draw_color(180, 180, 180)
|
|
|
|
for i, (header, width) in enumerate(zip(headers, col_widths)):
|
|
fpdf.cell(
|
|
width,
|
|
row_height,
|
|
str(header),
|
|
border=border,
|
|
align=align_char(header_style.alignment),
|
|
fill=True,
|
|
)
|
|
fpdf.ln()
|
|
|
|
# Data rows
|
|
set_font_from_style(row_style)
|
|
fpdf.set_text_color(*_parse_hex_color(row_style.color))
|
|
|
|
for row_idx, row in enumerate(rows):
|
|
fill = alternate_bg and (row_idx % 2 == 1)
|
|
if fill:
|
|
fpdf.set_fill_color(245, 245, 245)
|
|
else:
|
|
fpdf.set_fill_color(255, 255, 255)
|
|
|
|
for col_idx, (cell, width) in enumerate(zip(row, col_widths)):
|
|
fpdf.cell(
|
|
width,
|
|
row_height,
|
|
str(cell) if cell is not None else "",
|
|
border=border,
|
|
align=align_char(row_style.alignment),
|
|
fill=True,
|
|
)
|
|
fpdf.ln()
|
|
|
|
fpdf.ln(2)
|
|
return doc
|