HotPDF dibuja gráficos vectoriales construyendo un trazado en la página actual y luego pidiéndole que se pinte. No hay ningún paso de mapa de bits en medio. Una línea que dibujáis con MoveTo y LineTo acaba como operadores de trazado PDF en el flujo de contenido, por lo que sigue siendo un vector verdadero: nítido al 50% de zoom, nítido al 1600%, y una fracción del tamaño que costaría una versión rasterizada. Para diagramas, líneas de tabla, ejes de gráficos y decoraciones de formularios, eso es exactamente lo que queréis, y la API que hay detrás es lo suficientemente pequeña como para aprenderla en una sesión.
Toda la superficie de dibujo vive en THotPDF.CurrentPage. Entre BeginDoc y EndDoc establecéis el color y el grosor de línea en ese objeto de página, colocáis geometría y llamáis a un operador de pintado para confirmarla. Las cuatro primitivas que más usaréis son MoveTo y LineTo para trazados arbitrarios, Rectangle para cajas, Circle para discos, y los dos operadores de pintado Stroke y Fill.
El sistema de coordenadas tiene el origen en la esquina inferior izquierda
Esto es lo único que confunde a todos los que llegan desde VCL. El TCanvas con el que pintáis controles coloca el origen en la esquina superior izquierda con Y creciendo hacia abajo. PDF hace lo contrario. HotPDF mide desde la esquina inferior izquierda de la página en puntos (1/72 de pulgada), con Y aumentando al moverse hacia arriba. Un punto en Y := 720 se sitúa cerca de la parte superior de una página de tamaño US Letter, que tiene 792 puntos de altura, y Y := 50 se sitúa cerca de la parte inferior. Si vuestro primer dibujo sale reflejado verticalmente, es por esto: el código portado desde gráficos de pantalla asume la dirección equivocada y sale por el borde inferior.
La misma convención rige TextOut, de modo que el texto y las formas comparten un mismo modelo mental una vez que lo internalizáis. Planificad un diseño decidiendo dónde se encuentra la parte inferior de cada elemento, no la superior, y el resto se deduce.
Trazados: MoveTo, LineTo, Stroke
Un trazado trazado es un bolígrafo levantado, colocado y arrastrado. MoveTo levanta el bolígrafo y establece el punto de inicio sin marcar nada. Cada LineTo extiende el trazado actual hasta un nuevo punto. Nada aparece en la página hasta que llamáis a Stroke, que dibuja el trazado acumulado usando el color de trazo y el grosor de línea actuales, y luego borra el trazado para que el siguiente MoveTo empiece de nuevo.
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'DrawPaths.pdf';
Pdf.BeginDoc;
// Line width is in points and applies until you change it.
Pdf.CurrentPage.SetLineWidth(1.5);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
// A horizontal rule near the top of the page (Y measured from bottom).
Pdf.CurrentPage.MoveTo(72, 720);
Pdf.CurrentPage.LineTo(523, 720);
Pdf.CurrentPage.Stroke; // commit the path; nothing drew before this
// A thicker connected polyline: three segments in one path.
Pdf.CurrentPage.SetLineWidth(3);
Pdf.CurrentPage.SetRGBStrokeColor(RGB(30, 90, 200));
Pdf.CurrentPage.MoveTo(72, 640);
Pdf.CurrentPage.LineTo(172, 690);
Pdf.CurrentPage.LineTo(272, 620);
Pdf.CurrentPage.LineTo(372, 680);
Pdf.CurrentPage.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Dos detalles ahorran mucho tiempo de depuración. El grosor de línea es estado, no un argumento: SetLineWidth lo establece una vez y cada Stroke posterior usa ese valor hasta que se cambia de nuevo, que es la razón por la que la polilínea anterior es más gruesa que la regla. Y el trazado se reinicia tras cada Stroke, de modo que un Stroke olvidado significa que la geometría que con tanto cuidado habéis trazado nunca se renderiza. Si falta una forma en la salida, la llamada de pintado es el primer lugar donde buscar.
Las coordenadas son puntos, y los puntos son fraccionarios. MoveTo y LineTo aceptan valores Single, por lo que una línea de cabello en 0.5 puntos o una posición en 72.25 es legal y significativa, no se redondea a la unidad entera más cercana. Esa precisión importa en dos direcciones opuestas. Un grosor de línea por debajo de unos 0.5 puede renderizarse como la línea más fina posible que depende del dispositivo, que desaparece en pantalla y reaparece al imprimir, así que una regla visible requiere un grosor establecido intencionadamente en lugar del valor por defecto. En el otro extremo, ajustar las reglas de tabla y las líneas de cuadrícula a coordenadas de punto entero evita que una cuadrícula densa parezca ligeramente irregular donde las líneas adyacentes redondean de forma distinta. Decidid el espaciado de la cuadrícula en puntos desde el principio y el resto del diseño lo hereda.
Formas rellenas y color
Las primitivas cerradas pueden rellenarse en lugar de solo delinearse. Rectangle toma una posición y un tamaño, Circle toma un centro y un radio, y cualquiera de los dos se confirma con Fill, que pinta el interior con el color de relleno actual, o con Stroke para solo el contorno. El color de relleno y el color de trazo son piezas de estado separadas, establecidas con SetRGBFillColor y SetRGBStrokeColor, ambas con un único TColor. Eso significa que podéis reutilizar directamente las constantes de color de Delphi y el helper RGB.
// Rectangle(X, Y, Width, Height): X and Y are the lower-left corner.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;
// Circle(X, Y, Radius): X and Y are the center.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;
// Outline only: set a stroke color and a width, then Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;
Prestad atención a la forma de los argumentos de Rectangle. Es posición más tamaño, X, Y, Width, Height, no dos esquinas opuestas. El TCanvas.Rectangle que conocen los desarrolladores de Delphi toma (Left, Top, Right, Bottom), por lo que la memoria muscular pasará a HotPDF una segunda esquina donde espera un ancho y una altura, y el cuadro saldrá con el tamaño equivocado. El par (X, Y) es la esquina inferior izquierda, coherente con el origen de la página. Para un círculo, (X, Y) es el centro y el tercer argumento es el radio en puntos.
Una elección de color que el ejemplo original hacía mal
Una versión anterior de este ejemplo generaba colores con Random($FFFFFF) en cada forma. Tiene un aspecto animado, pero es el instinto equivocado para documentos generados. Un PDF que construís desde código normalmente también queréis probarlo, y los colores de relleno aleatorios hacen imposible comparar la salida de una ejecución a otra: una comparación byte a byte contra un fichero de referencia conocido falla cada vez, sin ninguna razón real. Elegid colores explícitos. Cuando queráis variedad en una serie de formas, guiaos por vuestros datos o por un array de paleta fija, de modo que la misma entrada produzca siempre el mismo fichero. El determinismo vale más que la novedad cuando el artefacto pasa por una cadena de publicación.
Cuándo el dibujo vectorial compensa, y cuándo no
Recurrید a estas llamadas de trazado y forma cuando la geometría se genera por código: líneas de cuadrícula de gráficos y barras, las líneas rayadas de una tabla de factura, cuadros de llamada en un diagrama, un logotipo expresado como un puñado de trazados. Todo ello escala sin desenfoque y añade casi nada al tamaño del fichero, porque un rectángulo son unos pocos números en lugar de miles de píxeles. La otra cara también es honesta. Si lo que realmente tenéis es una fotografía o una captura de pantalla, dibujadla como imagen con AddImage y ShowImage; trazar un mapa de bits con llamadas vectoriales no aporta nada. Las curvas complejas también quedan fuera del alcance aquí. Las primitivas anteriores son segmentos rectos, rectángulos y círculos, que cubren la gran mayoría del trabajo real de informes; cualquier cosa que necesite curvas Bezier de forma libre es una parte separada de la API.
El hábito restante que vale la pena conservar es la verificación. La geometría generada puede pasar en vuestra máquina y fallar en la de un cliente, normalmente por la sustitución de fuentes en cualquier texto que mezcléis o por una suposición sobre el tamaño de página que no se cumple. Abrid el fichero terminado a varios niveles de zoom para confirmar que los bordes se mantienen limpios, y comprobad que cada forma cae dentro del cuadro de márgenes previsto. Con un esquema de color determinista, esa comprobación puede automatizarse contra un PDF de referencia en lugar de hacerse a ojo.
Las llamadas MoveTo, LineTo, Stroke, Fill y de color mostradas aquí forman parte del HotPDF Component para Delphi y C++Builder.