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