Generar un informe se reduce a colocar tres cosas en una página y lograr que se pongan de acuerdo sobre dónde va cada una: texto en coordenadas conocidas, fuentes que se representen igual en el servidor que en su escritorio, e imágenes dimensionadas para encajar. Todo lo demás que hace una biblioteca de informes se ordena alrededor de esas tres. HotPDF, la biblioteca de generación de PDF de losLab para Delphi y C++Builder, le entrega cada una como una llamada directa sobre el objeto de página, y la única fricción real es el sistema de coordenadas de abajo, que corre al revés que el lienzo de la VCL al que usted está acostumbrado. Resuelva primero esa orientación y el resto del trabajo de maquetado dejará de pelear con usted
Colocación del texto y el origen abajo a la izquierda
Casi a todo el mundo su primer informe le sale al revés. El título aterriza cerca del borde inferior y cada línea siguiente trepa hacia arriba. No hay nada averiado. El espacio de usuario de PDF, definido en ISO 32000-1 §8.3, pone el origen en la esquina inferior izquierda con la Y creciendo hacia arriba, que es la imagen espejo del lienzo GDI donde la Y crece hacia abajo desde la esquina superior izquierda. Cinco minutos dedicados a hacer las paces con eso le ahorran un maquetado que de otro modo reescribiría en cuanto los números dejen de tener sentido
La llamada central del objeto de página es TextOut(X, Y, Angle, Text). X e Y ubican el texto en puntos desde la esquina inferior izquierda, y Angle lo rota en grados, que es como se dibuja un sello diagonal de DRAFT o COPY sin ningún soporte especial. El truco que permite que la intuición entrenada en la VCL siga sirviendo es expresar la Y como la altura de la página menos la distancia que usted quiere desde arriba:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-0001.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 792 - 50, 0, 'INVOICE'); // 50 pt desde el borde superior de Letter
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 792 - 70, 0, 'Date: 2026-06-11');
Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY'); // sello rotado
Pdf.AddPage; // CurrentPage ahora apunta aquí
Pdf.CurrentPage.SetFont('Arial', [], 10); // el estado de fuente no se arrastra
Pdf.CurrentPage.TextOut(50, 742, 0, 'Page 2 detail rows');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Los dos comportamientos con estado de ese listado son responsables de la mayoría de los errores que solo aparecen en la página dos. AddPage reapunta CurrentPage a la página que acaba de crear, así que una referencia de página que usted guardó antes ya no dibuja donde espera. La selección de fuente también es por página y no por documento. Si se salta el SetFont después de un AddPage, el primer TextOut de la página nueva recae en el valor predeterminado con el que arrancó esa página, no en la fuente de título en negrita que usted estableció tres páginas atrás. El hábito seguro es tratar iniciar una página nueva y restablecer el estado de texto como un único paso inseparable dentro del bucle del informe
Fuentes que existen en el servidor, no solo en su escritorio
La mayoría de los problemas de fuentes son en realidad problemas de despliegue disfrazados. Su máquina de desarrollo tiene instalada la fuente corporativa, así que el informe se ve bien en su pantalla y se publica. El host de producción corre el trabajo bajo una cuenta de servicio que nunca tuvo esa fuente instalada, el renderizador sustituye en silencio por algo que sí encuentra, y lo primero que alguien sabe del asunto es un cliente preguntando por qué cambió el membrete. La salida consiste en dejar de confiar en el directorio de fuentes del sistema operativo y cargar la fuente desde un archivo que su instalador deja en disco. La llamada de registro Unicode de HotPDF recibe una ruta y hace exactamente eso:
Pdf.RegisterUnicodeTTF('C:\ProgramData\MyApp\Fonts\NotoSans.ttf');
Pdf.CurrentPage.SetFont('NotoSans', [], 12);
Pdf.CurrentPage.TextOut(50, 700, 0, WideString('Łódź - Ünïcode test ✓'));
TextOut acepta un WideString directamente, algo que importa más de lo que parece a primera vista. Un nombre de cliente con acento, una calle alemana, una ciudad polaca: no son casos extremos, son el contenido normal de una tabla de clientes, y pasan por la misma llamada que las etiquetas ASCII que usted escribe a mano, siempre que la fuente registrada contenga de verdad los glifos. Con las fuentes incrustadas viaja una restricción de versión: el documento tiene que ser PDF 1.5 o posterior, así que si un requisito ajeno lo tiene clavado en una versión más vieja, eso es lo que se romperá en silencio. Las escrituras de derecha a izquierda como el árabe y el hebreo necesitan un modelado real y no una búsqueda directa de glifos, y eso tiene su propio pipeline; vea nuestro artículo sobre el modelado de texto de escrituras complejas con HotPDF
Cuando ninguna fuente instalada puede expresar lo que usted necesita, piense en los caracteres MICR de un cheque o en un juego de símbolos propietario, las fuentes de tipo 3 llenan el hueco. Usted define cada glifo como un pequeño flujo de contenido mediante RegisterType3Font y AddType3Glyph. Es un rincón especializado de la API y rara vez recurrirá a él, pero resulta mucho más limpio que desparramar cientos de diminutos mapas de bits de símbolos por la página
Imágenes: los argumentos del medio son un ancho y un alto, no una esquina
El manejo de imágenes se divide en dos pasos, y mantenerlos separados es justamente lo importante. AddImage recibe un TBitmap o un TJPEGImage, lo incrusta una sola vez y devuelve un índice. El arte en PNG hay que decodificarlo a un mapa de bits antes de llegar ahí. Después ShowImage dibuja ese índice donde y cuantas veces usted quiera. El orden de los argumentos de ShowImage es el punto donde conviene bajar la velocidad y leer:
var
Png: TPngImage;
Logo: TBitmap;
LogoIdx: Integer;
begin
Png := TPngImage.Create;
Logo := TBitmap.Create;
try
Png.LoadFromFile('brand-logo.png');
Logo.Assign(Png); // decodifica el PNG a un mapa de bits
LogoIdx := Pdf.AddImage(Logo, icFlate); // sin pérdida para arte de color plano
finally
Logo.Free;
Png.Free;
end;
// (Index, X, Y, Width, Height, Angle): no (X1, Y1, X2, Y2)
Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;
Los dos números que siguen a la posición son un ancho y un alto. No son las coordenadas de la esquina opuesta, y el último argumento es un ángulo de rotación en grados. Lea la firma como una caja X1/Y1/X2/Y2 y un logotipo de 120 por 40 colocado en (50, 700) se estirará desde ahí hasta (120, 40), desparramándose por casi toda la página. La salida vuelve obvio el error mientras el código fuente se ve del todo razonable, que es lo que hace que le cueste una tarde. KeepImageAspectRatio vale True de forma predeterminada, así que una caja con proporciones equivocadas encaja la imagen con bandas en lugar de deformarla; póngala en False solo cuando de verdad quiera estirar
La separación entre registrar y colocar rinde en las corridas largas. Como AddImage incrusta los píxeles una sola vez y cada ShowImage con ese índice apunta al mismo objeto incrustado, dónde llama usted a AddImage decide el tamaño del archivo. Llámelo dentro del bucle de páginas para un estado de cuenta de 500 páginas y el mismo logotipo se incrusta 500 veces. Llámelo una vez antes del bucle, conserve el índice, y el logotipo se guarda una sola vez. Un pequeño diccionario indexado por la ruta del recurso alcanza para asegurar que cada imagen distinta se registre exactamente una vez
La elección del códec es la otra palanca de tamaño. El contenido fotográfico, los adjuntos escaneados y similares van en JPEG: pásele icJpeg a AddImage y baje JpegQuality a unos 85, ya que la propiedad arranca en 100 y la diferencia en 85 es invisible en una página impresa. El arte de color plano como logotipos, gráficos y dibujos lineales va en icFlate, donde la compresión sin pérdida ya es compacta y JPEG embarraría un halo visible alrededor de los bordes duros. Una corrida de estados de cuenta que empuja una foto a calidad plena en cada página puede inflarse hasta los gigabytes; el mismo contenido en JPEG 85 queda en torno a una décima parte del tamaño, y ningún lector nota la diferencia
Líneas, cajas y sombreados con primitivas de rutas
La línea horizontal bajo el encabezado de una tabla y la caja gris detrás de una cifra de totales no necesitan ser imágenes. Dibújelas como vectores y quedarán nítidas a cualquier zoom, se imprimirán con precisión y casi no sumarán peso al archivo. HotPDF sigue el mismo modelo que usan los flujos de contenido PDF en crudo: construya una ruta y luego llame a un operador que la pinte
// Línea horizontal bajo el encabezado de la tabla
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 660);
Pdf.CurrentPage.LineTo(545, 660);
Pdf.CurrentPage.Stroke;
// Caja sombreada de totales: X, Y, ancho, alto
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 120, 150, 40);
Pdf.CurrentPage.Fill;
El orden no es opcional: establezca el estado de pintado, construya la ruta y luego llame a Stroke o a Fill. Una ruta que usted construye pero nunca pinta no aporta nada a la página, que es casi siempre la respuesta cuando una línea no aparece. SetRGBFillColor recibe un solo TColor, así que las constantes conocidas de la VCL como clNavy y clBlack entran directo, y Rectangle usa los mismos argumentos de ancho y alto que la colocación de imágenes en vez de dos esquinas. Una advertencia sobre las líneas finas: cualquier cosa por debajo de aproximadamente medio punto puede verse elegante en un monitor y luego desaparecer en una impresora de oficina de 600 ppp, así que 0,75 pt es un piso razonable para cualquier línea que tenga que sobrevivir a la impresión
Paginación contra datos reales, no contra datos de muestra
Un detalle que conviene acertar antes de que el maquetado se fije: las columnas numéricas deberían alinearse por su borde derecho, y la manera de lograrlo es medir el ancho representado de cada valor y ubicarlo hacia atrás desde el límite de la columna, no rellenar la cadena con espacios a la izquierda. El relleno con espacios solo alinea en una fuente monoespaciada, y nadie compone un informe financiero en una fuente monoespaciada. Pase los valores primero por las rutinas de Delphi sensibles a la configuración regional, como FormatFloat, para que el separador de miles cuyo ancho usted mide sea el mismo que mostrará de verdad la configuración regional del cliente
El peligro de la paginación es que usted la escribe contra el conjunto de datos de demostración, donde diez filas cortas caben en una página y el bucle nunca tiene que cortar. Producción le entrega un cliente cuyo nombre de empresa mide 140 caracteres y un estado de cuenta con 4.000 renglones, y ahora el bucle tiene que cortar bien cada vez. El patrón que aguanta es un único cursor Y que baja a medida que usted resta la altura de cada fila, y una comprobación que abre una página nueva en cuanto el cursor cruzaría el margen inferior. Bajar aquí significa que la Y disminuye, que es el único lugar donde el origen abajo a la izquierda sigue siendo contraintuitivo. Mantenga todo eso en una sola rutina que además reemita SetFont y redibuje el encabezado corriente en la página nueva, y los errores de una página de más nunca se afianzan. Cuando esos mismos informes además tienen que cumplir reglas de archivado o de accesibilidad, las decisiones que usted toma justo aquí, qué fuentes incrusta, si la salida va etiquetada, qué espacios de color usa, son las que esos estándares vigilan; vale la pena leer la guía de PDF/A, PDF/X y PDF/UA de HotPDF antes de que la plantilla se endurezca
Cada llamada mostrada aquí, el posicionamiento de texto, el registro de fuentes, la incrustación de imágenes y el dibujo de rutas, viene en el HotPDF Delphi Component para Delphi y C++Builder, cuya referencia documenta la API de salida completa junto con las funciones de formularios, cifrado y firma que la acompañan