La justificación completa es el diseño que hace que una columna de texto se alinee tanto en el borde izquierdo como en el derecho, el aspecto que espera de un libro impreso o un reporte formal. Es fácil de describir y sorprendentemente fácil de equivocar, porque la respuesta a la pregunta "a dónde va el espacio adicional" no es la misma para el inglés que para el japonés, y porque la forma ingenua de medir cada línea convierte una página rápida en una lenta. HotPDF le brinda justificación sensible a la escritura a través de una sola llamada de diseño de caja, y debajo de esa llamada se encuentra una solución de rendimiento clásica que vale la pena entender por sí sola
Este artículo recorre ambos. Primero, la regla tipográfica que decide cómo se distribuye el espacio sobrante para escrituras con espacios entre palabras en comparación con las que no los tienen. Segundo, el cambio de medición que redujo el costo por página de la justificación aproximadamente ochenta veces sin ninguna diferencia visible en la salida. Ambos importan si genera documentos en volumen y desea que se lean como una composición tipográfica real en lugar de una salida de espacio monoespaciado estirada para ajustarse
Lo que realmente requiere la justificación completa
Una línea de texto dibujada en su ancho natural casi nunca alcanza el borde derecho de su columna. Siempre hay un resto, la holgura, entre donde termina el último glifo y donde se asienta el límite de la columna. La alineación izquierda deja esa holgura a la derecha. La alineación derecha la mueve a la izquierda. El centrado la divide. La justificación completa la elimina ensanchando la propia línea hasta que ambos bordes se encuentren con la caja, y la única forma honesta de hacerlo es separar los glifos desde el interior
La regla que separa la buena justificación de la mala es dónde se pone la holgura. Una escritura que escribe palabras con espacios entre ellas, como el inglés y el resto de la familia latina, tiene uniones naturales en cada espacio entre palabras. Ensanchar esos espacios es invisible a la vista porque los lectores ya aceptan que los espacios entre palabras varían. Una escritura que escribe sin espacios entre palabras, como los caracteres Han chinos, el kana japonés o el Hangul coreano, no tiene esas uniones. Allí, la holgura debe distribuirse de manera uniforme entre los glifos adyacentes, que es el principio que los tipógrafos japoneses llaman kintou-waritsuke, espaciado uniforme. Poner el estiramiento del espacio de palabras al estilo latino en una línea CJK, o meter toda la holgura en el único lugar en el que una línea CJK contiene un espacio, produce los ríos y espacios que marcan la salida de un aficionado
Cómo decide HotPDF adónde va el espacio
HotPDF toma esa decisión por cada espacio, no por línea. Cuando justifica una línea recorre cada par de glifos adyacentes y pregunta si existe un límite extensible entre ellos. Un límite es extensible cuando a cualquier lado hay un espacio o tabulación, en el caso latino, o cuando ambos lados son caracteres divisibles CJK, en el caso de espaciado uniforme. Cuenta esos límites, divide la holgura de la línea por igual entre ellos y agrega esa porción a cada espacio elegible
La consecuencia se presenta de forma natural. Una línea en inglés tiene límites extensibles solo en sus espacios de palabras, por lo que toda la holgura recae allí y las palabras se separan mientras que las letras dentro de cada palabra mantienen su espaciado natural. Una línea Han o kana tiene un límite extensible entre casi cada par de glifos, por lo que la holgura se distribuye de manera uniforme en toda la línea, exactamente el espaciado entre glifos uniforme que esas escrituras requieren. Una línea que es una sola palabra latina larga sin espacio interno no tiene un límite extensible en absoluto, por lo que HotPDF la deja con su ancho natural en lugar de separar la palabra letra por letra. La misma lógica maneja secuencias mixtas latinas y CJK en una línea sin requerir un caso especial, porque la decisión es local a cada límite
Un límite se excluye deliberadamente en todas partes. La posición después del glifo final de una línea nunca se trata como un espacio, porque estirar allí solo reintroduciría un remanente del lado derecho, que es lo opuesto a la justificación
Por qué se deja sola la última línea
La última línea de un párrafo es especial, y hacerla mal es el error de justificación más común. La última línea de un párrafo suele ser corta, a menudo de solo unas pocas palabras, y estirarla a todo el ancho de la columna arrastra esas palabras por la página en una fila dispersa y discontinua. La tipografía correcta deja la última línea en su ancho natural, alineada a la izquierda
HotPDF detecta la línea final por su posición. A medida que envuelve el texto en líneas, sabe cuándo la línea que acaba de dividir llega al final de la cadena provista. Esa línea final se emite con alineación izquierda simple y mantiene su ancho natural. Cada línea anterior se justifica a ambos bordes. Los saltos de línea forzados que escribe en el texto se respetan tal como se escribieron, por lo que una línea corta intencional tampoco se estira nunca. El lector ve un bloque de texto rectangular limpio cuya última línea termina de forma natural, que es lo que el ojo espera
El costo de medición que hizo que la justificación fuera lenta
Para justificar una línea debe saber su ancho exacto y debe conocer el avance de cada glifo para que pueda colocar el espacio adicional de manera precisa. La primera implementación obtenía esos números de la manera obvia. Medía toda la línea con una consulta de ancho Unicode completa, luego medía prefijo tras prefijo para recuperar el avance de cada glifo a través de la diferencia. Para una línea de N glifos eso supone N+1 llamadas al motor de medición, y cada llamada es un viaje completo a GDI, que le pide al sistema operativo que forme y mida el texto y devuelva la respuesta
Por cada línea eso parece barato. Para toda una página no lo es. Tome una página A4 densa de texto de cuerpo, aproximadamente cuarenta y cinco líneas de alrededor de ochenta caracteres cada una. Con N+1 viajes por línea, son alrededor de 81 viajes por cada línea y aproximadamente 3.645 por la página, casi todos empleados en volver a medir el texto que el motor ya había mirado momentos antes. En un trabajo por lotes que produce miles de páginas, esa sobrecarga domina el tiempo de diseño, y cada viaje cruza el límite entre su proceso y el subsistema de gráficos
Una llamada en lugar de N más uno
La solución es el tipo de cambio que parece pequeño y vale la pena en gran medida. GDI ya puede reportar el ancho total de una cadena y la posición de cada glifo en una sola consulta. HotPDF expone eso a través de GetWideCharAdvances, que llena un arreglo con el avance natural de cada glifo, incluido el kerning, y devuelve el ancho total en una llamada en lugar de N+1. La rutina de justificación, _HPDFEmitJustifiedWideLine internamente, solicita todos los avances una vez, calcula la holgura, la distribuye a través de los límites extensibles y emite la línea
Para esa misma página A4, la medición por línea se reduce de unos 81 viajes a uno, por lo que la página cae de aproximadamente 3.645 viajes a unos 45, cerca de una reducción de ochenta veces. La salida es idéntica byte a byte, porque no cambió nada acerca de la medición excepto cuántas veces se solicita. El mismo motor GDI, las mismas métricas de fuente y el mismo kerning alimentan los mismos números. Solo disminuyó el conteo de viajes. Cuando una medición ya es correcta, la optimización adecuada es dejar de solicitarla repetidamente, no aproximarla
Cómo llega la línea a la página
Una vez que se distribuye la holgura, HotPDF emite la línea con ExtTextOut y un arreglo de avance por glifo, el arreglo Dx. Cada entrada es la distancia desde el origen de un glifo hasta el siguiente, que es el avance natural de ese glifo más su parte de la holgura cuando le sigue un límite extensible. Esto se correlaciona directamente con el modelo de imágenes de PDF. El texto posicionado se escribe con el operador TJ, un arreglo que intercala series de glifos con ajustes horizontales explícitos, y los valores de Dx se convierten exactamente en esos ajustes. Es por eso que el espacio adicional aterriza entre los glifos en posiciones precisas de subpuntos en lugar de falsificarse con caracteres de relleno, y es por eso que una línea de HotPDF justificada se mide de manera correcta si una herramienta descendente la vuelve a leer
Usted mismo no llama a ExtTextOut para los párrafos justificados. El punto de entrada es WideTextOutBox, que envuelve una cadena Unicode en una caja y aplica la alineación que solicita. Divide el texto en líneas que se ajustan al ancho de la caja, ubica cada línea hacia abajo a lo alto de la caja, y devuelve la cantidad de caracteres que logró encajar antes de quedarse sin espacio vertical. La alineación la elige la enumeración de justificación
type
THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);
Los primeros tres se explican por sí mismos: alineación izquierda, centrada y derecha. El cuarto, jtJustify, es la justificación completa de ambos bordes descrita aquí, y es el valor que WideTextOutBox lee para activar el espaciado sensible a la escritura
Justificación de un párrafo en la práctica
Un ejemplo completo crea un documento, establece una fuente y vierte un párrafo en una caja con justificación completa. El mismo código justifica texto latino y CJK sin ningún cambio de indicador, porque el conocimiento sobre la escritura vive por debajo de la API
uses
HPDFDoc;
procedure JustifyParagraph;
var
Pdf: THotPDF;
Body: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'Justified.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', 11);
Body :=
'Full justification spreads the slack on each filled line so both ' +
'edges meet the column, while the last line keeps its natural width. ' +
'For scripts with word gaps the space lands between words; for ' +
'scripts without them it spreads evenly between glyphs.';
// X, Y, LineSpacing, BoxWidth, BoxHeight, Text, Align
Pdf.CurrentPage.WideTextOutBox(72, 72, 4, 380, 240, Body, jtJustify);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Para dibujar el mismo bloque alineado a la izquierda, centrado o alineado a la derecha, cambie solo el argumento final a jtLeft, jtCenter o jtRight. La envoltura, la ubicación de la línea y el valor de retorno permanecen iguales. El ancho medido que impulsa las cuatro rutas proviene de GetWideTextWidth, la consulta de ancho compatible con Unicode que mide un WideString correctamente donde la medición anterior a nivel de bytes calcularía mal cualquier cosa posterior a Latin-1, que es lo que hace que la caja envuelva texto CJK y pares subrogados en el lugar correcto en un primer momento
La justificación es una capa de una pila mayor de formación de texto. Cuando una línea contiene escrituras que reordenan o unen sus glifos, las decisiones de espaciado aquí se asientan sobre el trabajo descrito en nuestro artículo sobre formación de texto de escritura compleja, y cuando una fuente tiene variantes tipográficas que usted desea seleccionar, consulte cómo controlar alternativas estilísticas OpenType GSUB. Todo esto se incluye en HotPDF Component para Delphi y C++Builder, junto con las API más amplias de texto, diseño y documentos que se cubren en este blog