Files
egutierrez 0819c35bbb 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

108 lines
3.1 KiB
Python

"""pdf_add_text — escribe un bloque de texto en el 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]:
"""Convierte color hex (#RRGGBB) a tupla (r, g, b)."""
h = hex_color.lstrip("#")
if len(h) == 3:
h = "".join(c * 2 for c in h)
r = int(h[0:2], 16)
g = int(h[2:4], 16)
b = int(h[4:6], 16)
return r, g, b
def _align_char(alignment: str) -> str:
"""Convierte nombre de alineacion al caracter fpdf2."""
mapping = {
"left": "L",
"center": "C",
"right": "R",
"justify": "J",
}
return mapping.get(alignment, "L")
def pdf_add_text(
doc: PDFDoc,
text: str,
style: PDFStyle | None = None,
x: float | None = None,
y: float | None = None,
width: float | None = None,
) -> PDFDoc:
"""Escribe un bloque de texto en el documento PDF con el estilo dado.
Posiciona el cursor en (x, y) si se especifican, o continua desde
la posicion actual. Aplica fuente, tamaño, color y alineacion del estilo.
Respeta saltos de linea en el texto.
Args:
doc: PDFDoc con una pagina activa.
text: texto a escribir. Soporta saltos de linea con '\\n'.
style: PDFStyle con fuente, tamaño, color, alineacion. None usa defaults.
x: posicion X en mm. None mantiene la posicion actual (margen izquierdo).
y: posicion Y en mm. None mantiene la posicion actual (posicion del cursor).
width: ancho del bloque de texto en mm. None usa el ancho disponible.
Returns:
PDFDoc con el texto añadido (mismo objeto modificado).
"""
if style is None:
style = PDFStyle()
fpdf = doc.fpdf
# Aplicar margen superior
if style.margin_top > 0:
fpdf.ln(style.margin_top)
# Posicion
if x is not None or y is not None:
cur_x = x if x is not None else fpdf.get_x()
cur_y = y if y is not None else fpdf.get_y()
fpdf.set_xy(cur_x, cur_y)
# Fuente
family = style.font_family
# Detectar bold/italic del nombre
bold = "-Bold" in family or "-BoldOblique" in family
italic = "-Oblique" in family or "-BoldOblique" in family
base_family = family.split("-")[0]
style_str = ""
if bold:
style_str += "B"
if italic:
style_str += "I"
fpdf.set_font(base_family, style=style_str, size=style.font_size)
# Color
r, g, b = _parse_hex_color(style.color)
fpdf.set_text_color(r, g, b)
# Alineacion
align = _align_char(style.alignment)
# Ancho disponible
cell_width = width if width is not None else (fpdf.epw)
# Altura de linea
line_h = style.font_size * style.line_height * 0.352778 # pt a mm aprox
# Escribir texto con multi_cell para soportar saltos de linea y wrap
fpdf.multi_cell(w=cell_width, h=line_h, text=text, align=align)
# Margen inferior
if style.margin_bottom > 0:
fpdf.ln(style.margin_bottom)
return doc