Artigo Técnico

Texto Vertical CJK em PDF com Delphi e HotPDF

Coloque um romance japonês na página e a primeira coisa que você notará é que o texto desce pela coluna, em vez de cruzar a linha, e que as colunas avançam da borda direita da folha em direção à esquerda. Um leitor criado com isso acha o texto horizontal ligeiramente clínico. O problema de engenharia é que o PDF, como quase todo sistema de texto digital, foi construído em torno de uma linha de base horizontal que cresce da esquerda para a direita, e um fluxo de conteúdo não tem noção de "escreva este parágrafo para baixo em vez disso". Portanto, quando um aplicativo Delphi precisa produzir um certificado, um poema, uma placa de sinalização ou um documento legal em formato tradicional para um leitor taiwanês, japonês ou coreano, o layout deve ser montado manualmente: um caractere abaixo do outro, uma coluna à esquerda da última

O HotPDF oferece uma opção que faz a contabilidade por caractere para você. A fonte que você define carrega um sinalizador IsVertical e, assim que estiver ativado, uma única chamada TextOut empilha uma string inteira em uma coluna vertical em vez de executá-la ao longo de uma linha de base. O posicionamento da coluna, a ordem da direita para a esquerda e uma substituição de glifo silenciosamente importante são o que o restante desta página abordará

Página PDF A4 produzida pelo HotPDF mostrando texto em chinês, japonês e coreano empilhados em colunas verticais lidas da direita para a esquerda
Uma única página A4 com texto em chinês, japonês e coreano em colunas verticais, gerada a partir de um procedimento Delphi.

A opção vive em SetFont

O layout vertical não é uma propriedade da página ou do documento. É uma propriedade da fonte com a qual você desenha e você a ativa com o quinto argumento de SetFont:

// SetFont(FontName, FontStyle, Size, FontCharset, IsVertical)
// The 5th argument flips the current font into vertical mode.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, False); // horizontal
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, True);  // vertical

Como o sinalizador fica na fonte, você alterna entre a escrita horizontal e vertical simplesmente chamando SetFont novamente com um último argumento diferente. Uma página pode conter um título horizontal na parte superior e um texto de corpo vertical abaixo dele, e o HotPDF mantém os dois modos separados por objeto de fonte em vez de por página. É isso que torna os layouts mistos possíveis sem qualquer manipulação de modo especial da sua parte: cada TextOut após um SetFont vertical é empilhado, cada TextOut após um horizontal corre ao longo da linha de base, e o último SetFont vence

O quarto argumento é o conjunto de caracteres do Windows, o mesmo que uma chamada horizontal recebe. Passar DEFAULT_CHARSET permite que o sistema resolva glifos por string, o que é importante aqui porque um documento vertical frequentemente mistura scripts. Tudo o mais sobre a fonte ainda se aplica: ela deve estar instalada na máquina de compilação, e você quase sempre desejará FontEmbedding := True para que o arquivo renderize os mesmos glifos CJK em um leitor que nunca teve a Arial Unicode MS

Uma chamada TextOut é uma coluna

Com uma fonte vertical ativa, uma chamada TextOut não espalha mais sua string lateralmente a partir do ponto determinado. Ela planta o primeiro caractere no topo e desce o resto direto para baixo, avançando pela altura da linha da fonte para cada glifo. O X que você passa fixa a coluna; o Y que você passa fixa onde o topo da coluna começa. Para definir o layout de uma passagem real, você deve emitir um TextOut por coluna e avançar X para a esquerda entre as chamadas, porque as colunas CJK são lidas da direita para a esquerda

var
  Pdf: THotPDF;
const
  ColTop = 760;   // y of the first glyph in every column (points)
  ColGap = 28;    // horizontal distance between columns
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'VerticalText.pdf';
    Pdf.FontEmbedding := True;       // embed the CJK face for portable rendering
    Pdf.BeginDoc;
    Pdf.CurrentPage.Size := psA4;

    // A horizontal heading first, in the ordinary writing mode.
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 16, DEFAULT_CHARSET, False);
    Pdf.CurrentPage.TextOut(60, 800, 0, 'Tang poem, vertical layout');

    // Switch the font into vertical mode; every TextOut below now stacks.
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 18, DEFAULT_CHARSET, True);

    // Columns advance right to left, so X decreases with each call.
    Pdf.CurrentPage.TextOut(520, ColTop, 0, '床前明月光');
    Pdf.CurrentPage.TextOut(520 - ColGap,     ColTop, 0, '疑是地上霜');
    Pdf.CurrentPage.TextOut(520 - ColGap * 2, ColTop, 0, '舉頭望明月');
    Pdf.CurrentPage.TextOut(520 - ColGap * 3, ColTop, 0, '低頭思故鄉');

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

O poema é lido como deveria: a coluna mais à direita primeiro, de cima para baixo, depois o olho pula para a esquerda para a próxima coluna. Nada sobre a string em si codifica essa ordem. Você a codifica nas coordenadas X, diminuindo-as chamada por chamada. Inverta a direção e o verso sairá revertido, o que é o erro mais comum ao portar um layout horizontal para o vertical

Coordenadas: descendo a página, da direita para a esquerda

Dois eixos estão em jogo e eles puxam em direções opostas, portanto vale a pena ser preciso. O HotPDF mede a partir do canto inferior esquerdo da página, com Y aumentando para cima, em pontos. Uma coluna vertical, portanto, começa em um Y alto (perto do topo da folha) e os caracteres descendem a partir daí à medida que o HotPDF subtrai uma altura de linha para cada um. Você define esse Y inicial uma vez por coluna e o componente cuida da descida

O eixo horizontal é aquele que você controla manualmente. Cada coluna fica em seu próprio X, e colunas sucessivas avançam para valores X menores porque a ordem de leitura é da direita para a esquerda. Um ritmo sensato é escolher o X da coluna mais à direita, depois subtrair um espaçamento de coluna fixo para cada chamada subsequente, como o exemplo faz com ColGap. O espaçamento é seu para escolher; muito apertado e colunas adjacentes se tocam, muito solto e o bloco parece esparso. Para texto de corpo de 12 a 18 pontos, um espaçamento um pouco maior do que o tamanho da fonte proporciona uma leitura confortável

Glifos que precisam mudar de forma

Alguns caracteres não são simplesmente cópias rotacionadas de si mesmos horizontalmente; eles precisam ser substituídos quando o texto se torna vertical. O que o HotPDF cuida para você é a marca de som prolongado em japonês (U+30FC), a barra de vogal longa que aparece em palavras katakana como コーヒー. Desenhada horizontalmente, é um traço curto na linha de base. Se você empilhasse esse mesmo glifo em uma coluna, ele ficaria plano sobre a coluna, o que é errado: no japonês vertical, a marca se torna um traço vertical conectando os dois caracteres entre os quais se encontra. O HotPDF detecta o U+30FC no caminho vertical e o renderiza como uma barra vertical (U+007C) para que a marca de vogal longa aponte na direção certa sem nenhum trabalho de sua parte

Essa única substituição abrange o caso que quebra a maioria das implementações ingênuas, mas vale a pena saber onde o problema geral se aprofunda. A tipografia vertical completa também rotaciona letras latinas e pontuação ocidental em noventa graus, desloca os pequenos kanas e reposiciona colchetes e vírgulas em suas formas verticais, e uma implementação completa disso reside nos recursos verticais OpenType de uma fonte, em vez de em regras fixas. O HotPDF pode aproveitar esses recursos quando a fonte os tiver: alternativas verticais (os recursos vert e vrt2 de GSUB) e kerning vertical (pesquisas vkrn e vpal de GPOS) são opcionais, aplicados ao longo do caminho vertical apenas quando a fonte ativa os definir de fato. Para texto misto CJK em uma única face de ampla cobertura como Arial Unicode MS, o tratamento embutido para o U+30FC mais um espaçamento uniforme da altura da linha é o suficiente para produzir colunas corretas e legíveis; os recursos OpenType importam quando você passa para uma fonte projetada para configuração vertical fina e quer o seu posicionamento nativo de kanas e espaçamento entre glifos

Misturando scripts e orientações em uma página

Documentos reais raramente são puros. Uma página em japonês vertical pode conter uma legenda em inglês horizontal, um número de página na parte inferior ou um bloco de coreano colocado verticalmente ao lado do japonês. Como o sinalizador vertical é uma opção em nível de fonte, você compõe isso alternando as chamadas SetFont em vez de gerenciar qualquer estado em toda a página. Defina uma fonte horizontal, escreva o cabeçalho e número de página corridos, defina uma fonte vertical, coloque as colunas, defina uma fonte horizontal novamente para um rodapé. Cada região pega o modo do SetFont mais recente, então a única disciplina exigida é chamá-lo sempre que você mudar de direção

Um detalhe a se planejar quando os scripts se misturam: os ideogramas chineses, japoneses e coreanos são quase quadrados e empilham-se num passo uniforme, mas corridas latinas embutidas em uma coluna vertical não possuem esse avanço uniforme. Se você precisar de algumas palavras em latim dentro de um texto que seria todo vertical, decida deliberadamente se elas devem ser giradas para descer pela coluna ou ser colocadas de pé como um pequeno encarte horizontal, e posicione esse fragmento com seu próprio TextOut em vez de deixá-lo seguir o passo vertical destinado aos ideogramas. Tratar o texto misto como seu próprio problema de posicionamento mantém o ritmo da coluna intacto

Da amostra à produção

As peças são pequenas e se compõem previsivelmente. Ative o sinalizador vertical no SetFont, emita um TextOut por coluna a partir de um Y superior fixo, e diminua o X ao longo das chamadas para que as colunas sejam lidas da direita para a esquerda. Embuta a fonte para que os glifos CJK sobrevivam à viagem para a máquina de um leitor e permita que o componente trate da marca de vogal longa U+30FC e, quando a fonte os suportar, dos recursos verticais OpenType. A partir daí, colocar o layout em produção é em grande parte aritmética: derivar as posições X das colunas a partir de uma largura de bloco medida, quebrar passagens longas em colunas que se encaixem na altura da página e reservar espaço para cabeçalhos e números de página horizontais

Para a superfície mais ampla de texto e fontes sobre a qual isso se baseia, incluindo as convenções de TextOut horizontais e o registro de fonte Unicode, consulte o exemplo multilíngue do Hello World e o exemplo do TextOut. A opção vertical do SetFont e as chamadas TextOut mostradas aqui fazem parte do Componente HotPDF para Delphi e C++Builder