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>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user