Artículo técnico

Dibujo en el canvas de HotPDF en Delphi: rutas y color

HotPDF dibuja gráficos vectoriales construyendo una ruta en la página actual y pidiendo luego que se pinte. No hay ningún paso de mapa de bits en medio. Una línea que usted dibuja con MoveTo y LineTo termina como operadores de ruta PDF en el flujo de contenido, así que sigue siendo un vector verdadero: nítida al 50 % de zoom, nítida al 1600 %, y una fracción del tamaño que costaría una versión rasterizada. Para diagramas, líneas de tablas, ejes de gráficos y decoraciones de formularios, eso es exactamente lo que usted quiere, y la API detrás es lo bastante pequeña como para aprenderla de una sentada

Toda la superficie de dibujo vive en THotPDF.CurrentPage. Entre BeginDoc y EndDoc usted establece color y ancho de línea en ese objeto de página, coloca la geometría y llama a un operador de pintado para confirmarla. Las cuatro primitivas que más usará son MoveTo y LineTo para rutas arbitrarias, Rectangle para cajas, Circle para discos, y los dos operadores de pintado Stroke y Fill

El sistema de coordenadas tiene el origen abajo a la izquierda

Esta es la única cosa que hace tropezar a todo el que llega desde la VCL. El TCanvas con el que usted pinta controles pone el origen en la esquina superior izquierda con la 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 la Y aumentando a medida que se sube. Un punto en Y := 720 queda cerca de la parte superior de una página US Letter, que mide 792 puntos de alto, y Y := 50 queda cerca de la parte inferior. Si su primer dibujo sale reflejado verticalmente, esta es la razón: el código portado desde gráficos de pantalla asume la dirección equivocada y se sale por el borde inferior

La misma convención rige TextOut, así que texto y formas comparten un solo modelo mental una vez que lo interioriza. Planifique un diseño decidiendo dónde queda la parte inferior de cada elemento, no la superior, y el resto sigue solo

Comparación del origen superior izquierdo del canvas de pantalla y el origen inferior izquierdo de PDF: el mismo punto cerca de la parte superior de una página US Letter se lee Y = 72 en código TCanvas pero Y = 720 en puntos de HotPDF, así que el código portado sin invertir la Y dibuja reflejado
HotPDF mide desde la esquina inferior izquierda en puntos, así que un lugar cerca de la parte superior de la página de 612 por 792 lleva Y = 720 — la misma ubicación física que el código TCanvas direcciona como una Y pequeña hacia abajo

Rutas: MoveTo, LineTo, Stroke

Una ruta trazada es una pluma levantada, colocada y arrastrada. MoveTo levanta la pluma y fija el punto de inicio sin marcar nada. Cada LineTo extiende la ruta actual hasta un punto nuevo. Nada aparece en la página hasta que usted llama a Stroke, que dibuja la ruta acumulada con el color de trazo y el ancho de línea actuales, y luego limpia la ruta para que el siguiente MoveTo empiece de cero

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'DrawPaths.pdf';
    Pdf.BeginDoc;

    // El ancho de línea está en puntos y se aplica hasta que usted lo cambie.
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);

    // Una línea horizontal cerca de la parte superior de la página (Y medida desde abajo).
    Pdf.CurrentPage.MoveTo(72, 720);
    Pdf.CurrentPage.LineTo(523, 720);
    Pdf.CurrentPage.Stroke;          // confirma la ruta; nada se dibujó antes de esto

    // Una polilínea conectada más gruesa: tres segmentos en una ruta.
    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 tiempo real de depuración. El ancho de línea es estado, no un argumento: SetLineWidth lo establece una vez y cada Stroke posterior usa ese valor hasta que usted lo cambie de nuevo, y por eso la polilínea de arriba es más gruesa que la línea. Y la ruta se reinicia después de cada Stroke, así que un Stroke olvidado significa que la geometría que usted colocó con tanto cuidado nunca se renderiza. Si falta una forma en la salida, la llamada de pintado es el primer lugar donde mirar

Las coordenadas son puntos, y los puntos son fraccionarios. MoveTo y LineTo aceptan valores Single, así que una línea finísima de 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 ancho de línea por debajo de aproximadamente 0.5 puede renderizarse como la línea más delgada posible dependiente del dispositivo, que desaparece en pantalla y reaparece al imprimir, así que una línea visible quiere un ancho que usted establezca a propósito en lugar del predeterminado. En el otro extremo, ajustar las líneas de tabla y las cuadrículas a coordenadas de punto entero evita que una cuadrícula densa se vea ligeramente irregular donde líneas adyacentes se redondean de forma distinta. Decida 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 delinearse. Rectangle toma una posición y un tamaño, Circle toma un centro y un radio, y cualquiera de las dos se confirma con Fill, que pinta el interior con el color de relleno actual, o con Stroke para solo un contorno. El color de relleno y el color de trazo son piezas de estado separadas, establecidas con SetRGBFillColor y SetRGBStrokeColor, y ambas toman un solo TColor. Eso significa que usted puede reutilizar directamente las constantes de color de Delphi y el asistente RGB

Modelo de rutas de HotPDF: MoveTo, LineTo, Rectangle y Circle construyen una ruta actual invisible en memoria, y solo la confirmación con Stroke, Fill o FillAndStroke la pinta usando el estado gráfico persistente de color de trazo, color de relleno y ancho de línea antes de limpiar el búfer
La geometría se acumula en silencio en la ruta actual hasta que un operador de pintado la confirma con el color de trazo, el color de relleno y el ancho de línea almacenados — una llamada de pintado olvidada deja la forma sin dibujar
// Rectangle(X, Y, Width, Height): X e Y son la esquina inferior izquierda.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;

// Circle(X, Y, Radius): X e Y son el centro.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;

// Solo contorno: establezca un color de trazo y un ancho, luego Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;

Cuidado con la forma de los argumentos en Rectangle. Es posición más tamaño, X, Y, Width, Height, no dos esquinas opuestas. El TCanvas.Rectangle que los desarrolladores Delphi conocen toma (Left, Top, Right, Bottom), así que la memoria muscular le entregará a HotPDF una segunda esquina donde espera un ancho y un alto, y la caja sale del tamaño equivocado. El par (X, Y) es la esquina inferior izquierda, consistente 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 decisión de color que el ejemplo original tenía mal

Una versión anterior de este ejemplo sembraba los colores con Random($FFFFFF) en cada forma. Se ve animado, y es el instinto equivocado para documentos generados. Un PDF que usted construye desde código suele ser algo que también quiere probar, 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 archivo conocido como bueno falla siempre, sin ninguna razón real. Elija colores explícitos. Cuando quiera variedad a lo largo de una serie de formas, tómela de sus datos o de un arreglo de paleta fijo, para que la misma entrada siempre produzca el mismo archivo. El determinismo vale más que la novedad cuando el artefacto pasa por un pipeline de lanzamiento

Combinar las primitivas: una caja de llamada

Cada primitiva es simple por sí sola; la recompensa aparece cuando un puñado de ellas se compone en algo que un reporte realmente necesita. Una llamada (callout), la caja anotada que apunta a una figura y la explica, usa todo lo cubierto hasta ahora: un rectángulo relleno con borde, una línea puntero trazada, un punto para anclar el puntero y texto dispuesto dentro de la caja usando las mismas coordenadas con origen abajo a la izquierda que usan las formas. FillAndStroke se gana su lugar aquí, pintando el interior y el contorno de una ruta en una sola confirmación en lugar de construir el rectángulo dos veces

Anatomía de una caja de llamada armada con primitivas de HotPDF: un rectángulo confirmado con FillAndStroke, una línea puntero trazada con MoveTo y LineTo, un punto de anclaje relleno y barras de etiqueta TextOut que comparten la misma cuadrícula con origen abajo a la izquierda que las formas
Cuatro confirmaciones construyen la llamada — FillAndStroke pinta el panel y el borde juntos, el puntero y el punto reutilizan el estado de trazo y relleno, y cada desplazamiento de etiqueta es aritmética simple contra la esquina de la caja (90, 600)
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'Callout.pdf';
    Pdf.BeginDoc;

    // 1. La caja: relleno pálido más un borde visible, una ruta, una confirmación.
    //    Rectangle es esquina inferior izquierda más tamaño, Y medida desde abajo
    Pdf.CurrentPage.SetRGBFillColor(RGB(255, 244, 214));   // panel ámbar pálido
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(180, 130, 40));  // borde más oscuro
    Pdf.CurrentPage.SetLineWidth(1);
    Pdf.CurrentPage.Rectangle(90, 600, 240, 70);
    Pdf.CurrentPage.FillAndStroke;

    // 2. El puntero: un segmento trazado desde el borde de la caja hacia abajo
    //    en dirección a lo que se está anotando
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.MoveTo(90, 615);        // borde izquierdo de la caja
    Pdf.CurrentPage.LineTo(66, 546);
    Pdf.CurrentPage.Stroke;

    // 3. Un punto relleno ancla el puntero en su objetivo
    Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
    Pdf.CurrentPage.Circle(64, 542, 3);
    Pdf.CurrentPage.Fill;

    // 4. La etiqueta, posicionada relativa a la esquina inferior izquierda de la caja.
    //    Texto y formas comparten un sistema de coordenadas, así que los
    //    desplazamientos son aritmética simple contra (90, 600)
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 10);
    Pdf.CurrentPage.TextOut(102, 645, 0, 'Check this total');
    Pdf.CurrentPage.SetFont('Arial', [], 9);
    Pdf.CurrentPage.TextOut(102, 628, 0, 'The rounding rule changed in the');
    Pdf.CurrentPage.TextOut(102, 616, 0, 'June release; verify against v2.1');

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Observe cuán poca gestión de estado necesita el compuesto. El color de relleno, el color de trazo y el ancho de línea se establecen cada uno inmediatamente antes de la forma que los usa, así que cada bloque del dibujo se lee como una unidad autocontenida y puede reordenarse o extraerse a un asistente sin arrastrar estado oculto. Envuelva esto en un procedimiento que tome el punto de anclaje y las cadenas, y tendrá una anotación de diagrama reutilizable por el costo de cuarenta líneas

Dónde rinde el dibujo vectorial, y dónde no

Recurra a estas llamadas de rutas y formas cuando la geometría sea generada: cuadrículas y barras de gráficos, las líneas de una tabla de factura, cajas de llamada en un diagrama, una marca de logotipo expresada como un puñado de rutas. Todo eso escala sin desenfoque y no agrega casi nada al tamaño del archivo, porque un rectángulo es unos pocos números en lugar de miles de píxeles. La otra cara también es honesta. Si lo que realmente tiene es una fotografía o una captura de pantalla, dibújela como imagen con AddImage y ShowImage en su lugar; calcar un mapa de bits con llamadas vectoriales no le aporta nada. Los segmentos rectos, rectángulos y círculos de arriba cargan con la gran mayoría del trabajo real de reportes, y los tres refinamientos por los que los desarrolladores preguntan a continuación, curvas, patrones de guiones y transparencia, están en el mismo objeto de página

Curvas, guiones y transparencia en breve

Las curvas de forma libre extienden la misma maquinaria de rutas que usted ya tiene. CurveToC(X1, Y1, X2, Y2, X3, Y3) agrega un segmento Bézier cúbico desde el punto actual hasta (X3, Y3), curvándose hacia los dos puntos de control, y las variantes abreviadas CurveToV y CurveToY cubren los casos en que un punto de control coincide con un extremo. Una ruta puede mezclar segmentos LineTo y CurveToC libremente antes de que un solo Stroke o Fill la confirme, que es como se construyen las esquinas redondeadas y las líneas suaves de los gráficos

Los trazos con guiones son estado, exactamente como el ancho de línea. SetDash([3, 3], 0) cambia cada trazo posterior a un patrón de tres puntos activos y tres inactivos, con el arreglo indicando las longitudes de los tramos activo/inactivo en puntos y el segundo argumento desfasando dónde comienza el ciclo; NoDash devuelve la pluma a una línea sólida. Establézcalo, trace las cuadrículas que lo quieran y reinícielo antes de la siguiente línea sólida, o el guion infecta silenciosamente todo lo que sigue

La transparencia pasa por un estado gráfico con nombre en lugar de un argumento de color, porque el alfa en PDF es una propiedad del diccionario de estado gráfico. Registre uno en el documento con RegisterExtGState, pasando un alfa de relleno y un alfa de trazo entre 0 y 1, y luego aplique el nombre que devuelve con CurrentPage.SetGraphicsState; los rellenos y trazos a partir de ese punto se pintan con la opacidad registrada. Es una ceremonia más pesada que los establecedores de color, y vale la pena la primera vez que una barra de resaltado tiene que quedar sobre un texto sin ocultarlo

El hábito restante que vale la pena conservar es la verificación. La geometría generada puede pasar en su máquina y fallar en la de un cliente, por lo general por la sustitución de fuentes en cualquier texto que usted mezcle o por una suposición de tamaño de página que no se cumple. Abra el archivo terminado a unos cuantos niveles de zoom para confirmar que los bordes se mantienen limpios, y compruebe que cada forma cae dentro de la caja de márgenes que usted pretendía. Con un esquema de colores 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 componente HotPDF para Delphi para Delphi y C++Builder