Files
fn_registry/python/functions/infra/pdf_add_table.py
T
egutierrez df424f2de0 feat: issue/0020 — generacion de PDFs en Python y Go
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>
2026-04-13 02:02:51 +02:00

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