Artículo técnico

Texto vertical en japonés y chino en PDF con Delphi

Ponga 「コード」 a lo largo de una página dibujándolo un carácter a la vez, cada llamada un em más abajo que la anterior, y tres de los cinco glifos salen mal. Los dos corchetes de esquina quedan en la columna como quedan en una línea horizontal, abriendo de lado, y la marca de vocal larga ー es un guion plano que corta la columna donde un lector japonés espera un trazo vertical corto. Solo コ y ド, los dos katakana ordinarios, se ven como los compondría un tipógrafo

Rotar la línea completa noventa grados arregla los corchetes y la marca de vocal larga más o menos por accidente y rompe todo lo demás: cada kanji y kana ahora está de costado. La escritura vertical hace tres cosas a la vez. Los caracteres quedan erguidos, el avance corre hacia abajo por la página en lugar de a través de ella, y un puñado de caracteres cambia a dibujos distintos. Acertar las tres necesita una fuente que cargue formas verticales y métricas verticales, y una fuente PDF configurada para usarlas

Diagrama de PDF Library for Delphi comparando una línea horizontal de texto rotada con el verdadero modo de escritura vertical, donde los glifos quedan erguidos y el avance corre hacia abajo por la página
La escritura vertical compone caracteres erguidos con métricas verticales de cada fuente en lugar de rotar una línea horizontal, y una columna copiada se lee de vuelta carácter por carácter

Activar la escritura vertical en PDF Library for Delphi

PDF Library for Delphi trata la escritura vertical como un modo de dibujo. SetVerticalWritingMode(1) lo activa, cada llamada a DrawText después de esa corre hacia abajo por la página desde el punto que usted le dé, y SetVerticalWritingMode(0) trae de vuelta el texto horizontal. El setter devuelve el modo vigente antes de la llamada y GetVerticalWritingMode lee el actual, así que una rutina de ayuda puede cambiar el modo y después restaurar exactamente lo que encontró

Las dos coordenadas significan algo ligeramente distinto que en el texto horizontal. El valor x nombra la línea central de la columna: cada glifo cuelga con su punto medio horizontal sobre esa línea, así que una marca de puntuación angosta y un kanji de ancho completo quedan alineados sobre el mismo eje. El valor y es la parte superior de la primera celda de carácter, y la columna crece hacia abajo desde ahí. Con SetOrigin(1), que pone y = 0 en el borde superior de la página, la aritmética se lee como usted tendería la página en papel

var
  Lib: TPDFlib;
  H: Double;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.AddTrueTypeFont('MS Mincho', 1);      // 1 = incrustar la fuente
    Lib.SetTextSize(12);
    Lib.SetVerticalWritingMode(1);            // el DrawText ordinario ahora corre hacia abajo
    H := Lib.GetVerticalTextHeight('MS Mincho', 12, '第三章 保守点検');
    Lib.DrawText(480, 72, '第三章 保守点検');     // la columna empieza en y = 72
    Lib.SetVerticalWritingMode(0);            // de vuelta a horizontal
    // Origen 1 pone y = 0 arriba, así que 72 + H es el pie de la columna;
    // la leyenda horizontal queda una línea debajo
    Lib.DrawText(456, 72 + H + 18, 'Chapter 3');
    Lib.SaveToFile('manual-ja.pdf');
  finally
    Lib.Free;
  end;
end;

GetVerticalTextHeight toma el nombre de la fuente, el tamaño y el texto, y devuelve la longitud de la columna en puntos. En el ejemplo, 72 + H es el pie de la columna y la leyenda en inglés va 18 puntos debajo, empezando 24 puntos a la izquierda de la línea central de la columna para que la línea latina corta quede aproximadamente bajo la columna. La función devuelve 0 cuando no puede abrir una fuente con ese nombre, y una maquetación construida sobre una altura cero pone la leyenda encima de la columna, así que verifíquelo antes de usar el valor

¿Por qué los corchetes quedan de lado en una columna vertical?

Porque la fuente guarda dos dibujos de cada uno de esos caracteres y el horizontal es el predeterminado. Las fuentes OpenType listan las alternativas en una característica GSUB llamada vert, o vrt2 en las fuentes que traen glifos ya rotados para composición vertical. Componer una línea de texto devuelve los glifos horizontales, y algo tiene que aplicar después la característica vertical, un glifo por un glifo. Los corchetes de esquina, los paréntesis y la marca de vocal larga pasan a correr a lo largo de la columna; los kana pequeños y la puntuación como 、 y 。 se mueven a otra esquina de su cuadrado

PDF Library for Delphi lee esa característica de la fuente con la que está dibujando y cambia cada glifo por la forma vertical que la fuente nombra. Cuando una fuente lleva ambas características, vrt2 gana y vert se ignora. La especificación concibe vrt2 como reemplazo de vert, y leer ambas puede entregarle a un carácter dos glifos verticales distintos según qué lookup responda primero. Una fuente que no tiene ninguna característica conserva sus glifos ordinarios. Preferimos ese desenlace a una tabla de sustitución integrada: una tabla adivinada acierta con los caracteres comunes y pisotea en silencio a las fuentes cuyos diseñadores trataron un carácter de forma distinta

Descargue las tablas GSUB de las copias que vienen con Windows y los conteos están muy lejos entre sí: la característica vertical de MS Gothic mapea 979 glifos, Yu Gothic 2.491, SimSun 72, y Arial ninguna (los números exactos se mueven con las versiones de las fuentes). Una fuente latina que por casualidad contenga algún kana dibujará una columna erguida con cada corchete y marca de vocal larga en su forma horizontal, porque no hay nada en la fuente que sustituir

Diagrama de PDF Library for Delphi mostrando cómo la característica vertical de la fuente suministra formas de paréntesis y de marca de sonido prolongado que se tienden a lo largo de una columna vertical mientras los caracteres ordinarios quedan sin cambios, todo medido por GetVerticalTextHeight
La característica vertical de la propia fuente decide qué glifos cambian de forma en una columna, y GetVerticalTextHeight mide las formas que realmente se dibujarán

La sustitución también cambia la medición. Una forma vertical puede tener su propio avance, distinto del glifo horizontal que reemplaza, así que GetVerticalTextHeight aplica la misma sustitución antes de sumar los avances. Mida las formas horizontales y dibuje las verticales, y la columna se pasa del recuadro que reservó para ella por unos pocos puntos por corchete

Nada de esto toca el texto en sí. Copie la columna desde un visor y recupera 「コード」, porque cada glifo vertical mapea al carácter que usted pasó. La búsqueda y la extracción de texto ven su cadena de origen, nunca las formas de presentación rotadas

Cómo acaban las métricas verticales en el PDF

Cada carácter en una corrida vertical avanza la distancia que le dan las métricas verticales de la propia fuente. Abra la salida en un inspector PDF y encontrará una fuente Type0 con codificación Identity-V cuyo CIDFont lleva una entrada W2 por cada glifo usado: el avance vertical de la tabla vmtx de la fuente, la mitad del ancho del glifo como desplazamiento hacia la línea central de la columna, y el origen vertical. ISO 32000-1 §9.7.4.3 define esos tres números. Para una fuente sin métricas verticales no hay arreglo W2, y el DW2 predeterminado avanza cada carácter exactamente un em

El origen vertical es donde las fuentes de terceros discrepan entre sí. La tabla vhea tiene campos ascender y descender que parecen la fuente obvia, pero la convención de signo del descender no se comparte entre fuentes. Lea el campo de las copias de Windows con cualquier visor de tablas de fuentes y sale +140 en MS Gothic y +120 en Yu Gothic, contra −144 en SimSun y −500 en MingLiU, y los valores pueden cambiar con las versiones de las fuentes. Cualquier posición derivada de ese campo deriva una cantidad distinta para cada fuente. PDF Library for Delphi toma el origen del ascender vertical, que las fuentes CJK nombradas arriba reportan consistentemente, y recurre a 880 unidades — el valor predeterminado que la propia especificación PDF da a DW2 — cuando una fuente no reporta nada plausible

Posicionar glifos verticales a mano tiene su propia trampa. El texto horizontal a menudo fija el espaciado con números dentro de un arreglo TJ. Bajo un modo de escritura vertical, ISO 32000-1 §9.4.4 aplica esos números a lo largo de la columna, así que una corrección calculada para espaciado horizontal empuja cada carácter más abajo en la página. Por eso una corrida vertical en el content stream es una cadena plana de códigos de glifo sin ningún ajuste, y cada bit de espaciado viene de W2

Cuando revise la salida, mire la tinta en lugar de los diccionarios. Encontrar /Identity-V en la fuente prueba que la codificación fue declarada; no dice nada sobre hacia dónde corre realmente el texto. Renderizar la página y medir la caja delimitadora de los píxeles dibujados sí lo dice: una corrida de siete caracteres debería salir de alrededor de un carácter de ancho y siete de alto

Una corrida vertical en una página horizontal

DrawVerticalText dibuja una sola corrida en vertical y deja el modo de escritura en paz. Toma la posición, el nombre de la fuente, el tamaño y el texto, así que la selección de fuente actual también queda intacta. Etiquetas de lomo, sellos, una sola columna de nombres junto a una tabla: esas son las excepciones verticales dentro de un documento horizontal, y alternar un modo global dentro y fuera alrededor de cada una es más estado del que el trabajo merece

Diagrama de PDF Library for Delphi de DrawVerticalText colocando una corrida vertical erguida junto a líneas horizontales ordinarias de DrawText en una página compartida sin tocar el modo de escritura global
DrawVerticalText lleva su propia fuente, tamaño y posición, así que una sola columna vertical coexiste con las llamadas horizontales a su alrededor
// Una corrida vertical dentro de una página por lo demás horizontal
if Lib.DrawVerticalText(520, 96, 'MS Mincho', 14, '保守点検記録') = 0 then
begin
  // La fuente no se pudo abrir o no pudo componer este texto.
  // LogLayoutProblem es su propia rutina, no parte de la biblioteca
  LogLayoutProblem('vertical label on page ' + IntToStr(Lib.SelectedPage));
end;
// El texto horizontal alrededor no se ve afectado
Lib.DrawText(72, 96, 'Maintenance inspection record');

Una página que mezcla ambas direcciones lleva la fuente dos veces en sus recursos, una con codificación horizontal y otra con Identity-V, construidas desde el mismo programa de fuente. Cada dirección conserva sus propias métricas, así que una página de libro japonés con cuerpo vertical y cabeceras corrientes horizontales, o un certificado chino con un título vertical sobre líneas de detalle horizontales, sale con ambas direcciones espaciadas correctamente

El valor de retorno es la razón por la que recurrimos a DrawVerticalText incluso en documentos mayormente verticales. En el dibujo por modo, una corrida que no puede componerse en vertical se dibuja de la manera horizontal ordinaria en lugar de descartarse, y DrawText reporta éxito en cualquier caso. Para el cuerpo de texto ese es el mejor predeterminado, porque una línea horizontal perdida es fácil de pillar en la prueba de imprenta y un párrafo ausente no. Para una etiqueta cuya dirección es todo el punto, use DrawVerticalText y trate un 0 como fallo de maquetación

Maquetar columnas de derecha a izquierda

El orden de las columnas lo decide usted. El texto vertical japonés y chino tradicional se lee columna por columna de derecha a izquierda, así que una página empieza en su margen derecho y cada columna nueva se sienta a la izquierda de la anterior. Ninguna configuración del modo de escritura puede inferir eso del texto, y lo mismo vale para el orden de páginas en un libro encuadernado por la derecha y para dónde pertenecen los furigana, las notas al pie y las leyendas de figuras

La rutina de abajo parte una cadena en columnas que caben en una altura fija, midiendo con GetVerticalTextHeight sobre la marcha y avanzando hacia la izquierda un em y medio por columna:

procedure DrawColumns(Lib: TPDFlib; const Face: WideString; Size: Double;
  const Text: WideString; Right, Top, BoxH: Double);
var
  Col, Trial: WideString;
  I: Integer;
  X: Double;
begin
  // Guarda sugerida, no una verificación de la biblioteca: una altura cero significa que la fuente
  // no se pudo abrir, y el bucle de abajo jamás rompería una columna
  if Lib.GetVerticalTextHeight(Face, Size, Text) <= 0 then
    Exit;
  X := Right;                                  // primera columna en el margen derecho
  Col := '';
  for I := 1 to Length(Text) do                // asume que no hay pares sustitutos
  begin
    Trial := Col + Text[I];
    if (Col <> '') and (Lib.GetVerticalTextHeight(Face, Size, Trial) > BoxH) then
    begin
      Lib.DrawVerticalText(X, Top, Face, Size, Col);
      X := X - Size * 1.5;                     // siguiente columna a la izquierda
      Col := Text[I];
    end
    else
      Col := Trial;
  end;
  if Col <> '' then
    Lib.DrawVerticalText(X, Top, Face, Size, Col);
end;

Re-medir la cadena de prueba en cada carácter es más lento que sumar avances, pero mide exactamente lo que se dibujará, sustituciones incluidas. Lo que el bucle no hace es aplicar las reglas de kinsoku. Un corchete de cierre o 。 en la parte alta de una columna, o un corchete de apertura varado en el pie de una, es un error tipográfico para un lector japonés, y arreglarlo significa jalar o empujar un carácter a través del quiebre antes de dibujar. Los pares sustitutos necesitan el mismo cuidado: indexar Text[I] parte a la mitad un carácter fuera del Basic Multilingual Plane, así que avance por punto de código si su texto puede contener alguno

Para documentos ensamblados desde datos, el recorrido de búsqueda de texto y enumeración de elementos de página muestra cómo leer de vuelta lo que aterrizó en cada página, que es una forma rápida de confirmar que cada columna lleva los caracteres que usted quiso

¿Qué fuente debería usar un documento vertical?

Una fuente CJK con sus propias métricas verticales y una característica vert o vrt2. Sin ellas usted obtiene caracteres erguidos avanzando un em a la vez y ningún cambio de forma. Cuando el texto vertical se ve sutilmente mal, examine la fuente antes de examinar el código. Un visor de tablas que liste las características GSUB de una fuente y sus tablas vhea y vmtx le dirá en un minuto si la fuente puede hacer el trabajo

DrawVerticalText y GetVerticalTextHeight abren una fuente instalada por su nombre a través de Windows. En builds de Lazarus para otras plataformas, la escritura vertical va por SetVerticalWritingMode y DrawText, que trabajan desde el programa de la fuente que usted cargó y seleccionó, así que las mismas formas verticales y métricas aplican mientras esa fuente sea la cara CJK que usted pensaba usar

La incrustación sigue las reglas de siempre y los costos de siempre. Una fuente CJK completa corre muchos megabytes, así que el subconjunto es el valor sensato por defecto para cualquier documento que sale de su máquina. Las notas sobre optimización de tamaño de archivo PDF y subconjunto de fuentes cubren qué esperar, y el recorrido de incrustar fuentes faltantes en un PDF existente cubre el caso de reparación donde un documento vertical llega sin sus fuentes

La escritura CJK vertical vive en la misma API de dibujo que el resto de las funciones de texto de PDF Library for Delphi, una biblioteca PDF nativa en Pascal para Delphi, C++Builder y Lazarus. La página del producto PDF Library for Delphi lista el conjunto completo de funciones de fuentes y texto junto a ella