Cada cadena visible en un documento HotPDF llega a través de una única llamada: TextOut(X, Y, angle, Text). El ejemplo Hello World la usa en su forma más básica, con la fuente establecida una vez y los cuatro argumentos en valores predeterminados razonables. A partir de esa primera página, los mismos cuatro argumentos soportan todo el peso de la maquetación. El tercer argumento rota el texto. La fuente establecida justo antes determina el tamaño y el estilo. Y el par X, Y, medido desde la esquina de la página en puntos, es lo único que separa un informe limpio de texto que se solapa, se recorta o cae una línea más abajo en la impresora de otra persona. Aquí es donde TextOut demuestra su valor, y donde los valores predeterminados dejan de ser suficientes.
Vale la pena memorizar la firma antes de nada: X e Y son Single en puntos, angle es un Extended en grados, y Text es un WideString, de modo que el Unicode pasa directamente sin necesidad de una llamada adicional. Una segunda sobrecarga acepta un PWORD más una longitud para cuando ya tienes códigos de glifo, pero para cadenas ordinarias la forma WideString es la que se utiliza habitualmente.
El tamaño y el estilo provienen de SetFont, no de TextOut
TextOut no tiene parámetro de tamaño. El tamaño, el grosor y la inclinación residen todos en la llamada SetFont que precede al texto, y permanecen en vigor hasta que el siguiente SetFont los reemplaza. Ese es el único hecho que explica la mayoría de la confusión del primer día: una línea sale en negrita porque tres llamadas antes algo estableció [fsBold] y nada lo borró.
Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report'); // 24pt regular
Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue'); // 12pt bold
Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands'); // 11pt italic
Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, ' +18.4% YoY'); // styles combine
El segundo argumento es un conjunto TFontStyles, de modo que [fsBold, fsItalic] es negrita cursiva y [] es normal. El tamaño se expresa en puntos, la misma unidad que las coordenadas, lo que facilita razonar sobre el espaciado vertical: una línea de 12 puntos necesita aproximadamente 14 o 16 puntos de avance vertical para respirar, de modo que decrementar Y en 14 por línea es un interlineado inicial razonable. No hay avance de línea automático. Calculas tú mismo cada línea base, lo cual resulta tedioso para un párrafo pero exacto para un formulario donde cada campo ocupa una coordenada fija.
Dos notas prácticas sobre el nombre de fuente. Se resuelve contra las fuentes instaladas en la máquina de compilación, y lo que el sistema operativo devuelva es lo que se incrusta, de modo que un nombre que se resuelve en tu escritorio y otro que se resuelve en un servidor de compilación no están garantizados a ser la misma tipografía. Además, la fuente debe cubrir los alfabetos de la cadena. Un fragmento de texto cirílico o CJK bajo una tipografía exclusivamente latina se renderiza como cuadros de glifo ausente sin ningún error, razón por la que el ejemplo Hello World utiliza una tipografía Unicode amplia cuando mezcla idiomas.
El argumento de ángulo rota en torno al punto de anclaje
El tercer argumento es el que la mayoría del código deja en cero indefinidamente. Si se pasa un valor distinto de cero, el texto rota en sentido antihorario alrededor de su propio punto de anclaje (X, Y), la esquina inferior izquierda del texto, ese número de grados. El propio anclaje no se mueve, de modo que la misma coordenada que colocó una etiqueta horizontal coloca su gemela rotada; solo cambia la dirección en que avanzan los glifos.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);
// A vertical axis label down the left margin: 90 degrees reads bottom-to-top.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');
// A diagonal DRAFT watermark across the page body.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');
// Column headers tilted 60 degrees so long labels fit a narrow table.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');
Noventa grados es el caso más habitual: una etiqueta que sube por el lateral de un gráfico o un título de lomo. Cuarenta y cinco grados gestiona cabeceras de columna inclinadas, el truco que permite que una etiqueta ancha quede sobre una columna estrecha sin invadir las columnas adyacentes. La rotación no cambia cómo se interpreta el punto de anclaje, lo cual desconcierta a la gente: un texto a 90 grados sigue comenzando en (X, Y) y crece hacia arriba desde ahí, de modo que para centrar una etiqueta rotada se ajusta el anclaje, no el ángulo. Cuando varios textos rotados comparten una línea base, dales el mismo Y e incrementa X, exactamente igual que incrementarías Y para líneas horizontales apiladas.
Colocar coordenadas sin adivinar
Las coordenadas son la parte que supera la revisión o la falla en silencio. HotPDF mide desde la esquina inferior izquierda de la página, con Y creciendo hacia arriba, en puntos a 72 por pulgada. Una página US Letter tiene 612 por 792 puntos; A4, 595 por 842. Un margen superior de una pulgada en Letter sitúa tu primera línea base cerca de Y = 792 menos 72 menos el tamaño de fuente, no en un número pequeño cerca de la parte superior. Cualquiera que venga de coordenadas de pantalla, donde Y crece hacia abajo desde cero, escribe la primera línea fuera del borde inferior y pasa diez minutos preguntándose adónde fue a parar.
Trata la maquetación como aritmética sobre anclas con nombre en lugar de una columna de números mágicos. Un margen izquierdo, una línea base en curso que decrementas por línea y un interlineado fijo convierten un bloque de etiquetas en un bucle corto en lugar de una pared de literales:
const
LeftMargin = 72; // 1 inch in
TopBaseline = 720; // first line, ~1 inch down on Letter
Leading = 16; // vertical step between lines
var
Y: Single;
Line: string;
begin
Pdf.CurrentPage.SetFont('Arial', [], 11);
Y := TopBaseline;
for Line in ReportLines do
begin
Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
Y := Y - Leading;
if Y < 72 then // bottom margin reached
begin
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 11); // font resets on a new page
Y := TopBaseline;
end;
end;
end;
El control de salto de página es la línea que todos olvidan primero y que el campo nota con más dureza. No hay maquetación de flujo por debajo de TextOut. Si decrementas más allá del margen inferior, el texto sigue dibujándose en el canal, fuera de la página, hacia la nada, sin ningún aviso. Así que vigilas Y tú mismo, llamas a AddPage cuando cruza el límite inferior y restableces la línea base. El SetFont tras AddPage no es relleno opcional: la fuente actual no sobrevive a un salto de página, y el primer texto de la nueva página sale con la tipografía predeterminada del visor si te lo saltas.
Espaciado de caracteres y palabras para ajuste y alineación
A veces una cadena es correcta pero tiene el ancho equivocado: una cabecera que debe abarcar una regla fija, un código que debería leerse con dígitos más aireados, una columna cuyos valores necesitan un pequeño ajuste para alinearse. PDF dispone de dos operadores de estado de texto para esto: espaciado de caracteres (Tc, espacio extra añadido tras cada glifo) y espaciado de palabras (Tw, espacio extra añadido en cada carácter de espacio), y ambos se expresan en unidades de espacio de texto sin escalar, efectivamente puntos al tamaño de fuente actual. Son estado, no argumentos de TextOut, así que los estableces, dibujas y los restableces.
// Letter-space a short heading so it stretches across a rule.
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 740, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0); // reset before normal body text
// Open up the gaps between words on a single wide line.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name Department Extension');
Pdf.CurrentPage.SetWordSpacing(0);
El espaciado de palabras solo actúa sobre el carácter de espacio (código 32), lo cual tiene una consecuencia que merece conocerse: no hace nada dentro de un fragmento CJK que no tenga espacios ASCII, y su interacción es extraña con texto codificado como índices de glifo en lugar de bytes. Para salida tabular en latín es la manera barata de ampliar los huecos sin reescribir la cadena. El espaciado de caracteres es la herramienta más adecuada para una cabecera que debe alcanzar un ancho objetivo, ya que distribuye el ajuste uniformemente entre todos los glifos en lugar de concentrarlo en los espacios.
El restablecimiento es toda la disciplina. El espaciado, como la fuente, forma parte del estado de dibujo de la página, y el estado persiste hasta que lo cambias. Si espacias una cabecera y olvidas ponerlo a cero, cada párrafo posterior hereda el estiramiento, lo que produce una rareza sutil y difícil de localizar que supera una revisión superficial y falla ante una cuidadosa. El hábito fiable es establecer un valor de espaciado, dibujar el texto que lo necesita y restablecerlo a cero en la línea siguiente, de modo que ningún código posterior tenga que saber qué hizo una sección anterior.
Verificar la salida donde realmente falla
La maquetación de texto falla en la segunda máquina, no en la primera, de modo que las comprobaciones que importan se hacen lejos de tu escritorio. Abre el fichero generado en un sistema sin tu conjunto de fuentes de desarrollador instalado y confirma que las tipografías incrustadas se siguen renderizando, incluido el latín acentuado, cualquier alfabeto no latino y la puntuación, en una sola pasada en lugar de verificar solo los caracteres fáciles. Selecciona y copia unas pocas líneas para confirmar que el texto es texto real y no contornos, lo que importa en cuanto la búsqueda o la extracción estén en juego. Alimenta la maquetación con datos representativos: la etiqueta alemana más larga y el número más ancho, no un marcador de posición ordenado, porque el texto que desborda un campo siempre es el que no escribiste a mano. Y si la página debe encajar en un formulario preimpreso, imprime o rasteriza una muestra y compárala con el original; una desviación de línea base de un cuarto de milímetro es invisible en pantalla y evidente en papel.
Si todavía no has escrito ni una sola página, empieza con el ejemplo Hello World de HotPDF, que configura el documento, la fuente y el sistema de coordenadas de origen inferior izquierdo del que depende todo lo anterior. Las llamadas TextOut, SetFont y de espaciado mostradas aquí forman parte del HotPDF Component para Delphi y C++Builder.