df424f2de0
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>
108 lines
3.1 KiB
Python
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
|