Envie a frase em árabe يوضح ملف PDF هذا para o TextOut normal e a página gerada estará incorreta de duas formas ao mesmo tempo. As palavras correm da esquerda para a direita em vez da direita para a esquerda, e as letras aparecem separadas nas suas formas isoladas em vez de se unirem em palavras ligadas. Nenhum erro é gerado. O Delphi compila, o ficheiro abre, e um revisor que leia árabe diz-lhe que o resultado é inutilizável. A solução é uma única chamada, não a substituição de uma biblioteca: o HotPDF encaminha o texto da direita para a esquerda através de um método separado, RtLTextOut, que lida com a reordenação que o TextOut normal não faz. Quatro aspetos sobre esse método decidem se o resultado é utilizável: o que ele faz à cadeia de caracteres, como o seu argumento charset seleciona o script, a alteração ao nível do documento que faz como efeito secundário, e o trabalho com fontes que deve ser feito primeiro.
Porque é que a direita para a esquerda precisa da sua própria chamada
Um fluxo de conteúdo PDF (content stream) não armazena texto editável. Armazena glifos em posições fixas, o que significa que o que quer que emita o fluxo tem a tarefa de decidir em que ordem esses glifos ficam. No ecrã, o sistema operativo fez isso por si: coloque árabe num TEdit e a pilha de texto do SO reordena-o e liga-o antes de ver um único píxel. É exatamente por isso que a string parece perfeita no seu formulário e falha no PDF. O ambiente de trabalho fez o trabalho silenciosamente e, no momento em que escreve o seu próprio fluxo de conteúdo, o trabalho volta para o seu lado.
O TextOut confia na sua palavra. Desenha os pontos de código (codepoints) na ordem em que os passa, da esquerda para a direita, o que é correto para Latim, Cirílico e CJK, mas incorreto para Árabe e Hebraico. O RtLTextOut é a chamada que reordena a linha primeiro para a ordem visual da direita para a esquerda e depois desenha. O HotPDF mantém os dois métodos deliberadamente separados em vez de adivinhar a direção a partir dos caracteres, pelo que a escolha de qual chamar é a escolha de qual comportamento de script obtém. A mecânica mais profunda da reordenação bidirecional e da ligação contextual do árabe são tópicos próprios, abordados no artigo sobre formatação de texto em árabe e RTL com o HotPDF; aqui o ponto prático é mais restrito. Use RtLTextOut para sequências da direita para a esquerda, use TextOut para tudo o resto e nunca encaminhe um através do outro.

O argumento charset decide o script
O que diz ao RtLTextOut se está a desenhar árabe ou hebraico não é o método, mas sim a fonte. O SetFont recebe um charset do Windows como o seu quarto argumento, e esse valor transporta as regras do script para a chamada da direita para a esquerda: 178 seleciona Árabe, 177 seleciona Hebraico. Defina o charset e depois desenhe, e as duas linhas abaixo sairão na ordem de leitura correta sem qualquer configuração adicional.
// Arabic: charset 178 tells RtLTextOut to apply Arabic rules
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
// Hebrew: charset 177 switches the rules to Hebrew
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
Dois detalhes sobre essas coordenadas são fáceis de esquecer. A posição que passa continua a ser o início da sequência no próprio sistema de coordenadas da página, medida a partir do canto inferior esquerdo com o Y a crescer para cima, a mesma origem que qualquer TextOut usa; o RtLTextOut altera a ordem dos glifos, não o ponto de origem da medição da página. E, como em qualquer chamada de desenho, o SetFont tem de vir primeiro e tem de ser repetido após cada AddPage, porque a fonte atual não sobrevive a uma quebra de página. Esqueça a repetição e a segunda página volta a utilizar a fonte que estava ativa, o que para árabe geralmente significa caixas vazias.
Não inverte texto que já tenha invertido manualmente
O único erro que consome mais tempo de depuração aqui é fornecer ao RtLTextOut uma string que já inverteu manualmente. Os programadores chegam a este método após uma primeira tentativa com o TextOut normal ter saído ao contrário, e uma solução temporária comum é inverter os caracteres no código antes de desenhar. O RtLTextOut inverte internamente por si só, pelo que uma string pré-invertida é invertida uma segunda vez e volta exatamente ao ponto de partida. Passe o texto na ordem lógica, a ordem em que o digitaria e o leria em voz alta, e deixe que a chamada faça a reordenação.
A armadilha é mais complexa do que uma simples inversão porque uma string duplamente invertida pode parecer correta para uma frase de teste totalmente em árabe e depois falhar no instante em que uma linha contém uma palavra em latim ou um número. Dentro de uma linha da direita para a esquerda, pressupõe-se que essas sequências incorporadas sejam lidas da esquerda para a direita, e a inversão manual arruína esse encadeamento, enquanto o caso de árabe puro por acaso sobrevive. Assim, o bug passa no seu primeiro teste básico e surge mais tarde numa fatura real com um número de conta nela. Remova todas as inversões manuais no momento em que mudar para o RtLTextOut.
O efeito secundário Direction que vale a pena conhecer
Chamar o RtLTextOut altera mais do que a linha que está a desenhar. Também altera a preferência de direção de leitura do documento para a direita para a esquerda, o mesmo que de outra forma definiria através da propriedade Direction. Essa definição adiciona vpDirection às ViewerPreferences do documento, o que diz a um visualizador como dispor páginas duplas e por que lado começa um layout de páginas opostas. Quando todo o documento é em árabe ou hebraico, isto é exatamente o que deseja e obtém-no gratuitamente.
Vale a pena saber disto precisamente porque é invisível numa única página. Se o documento for maioritariamente da esquerda para a direita com um bloco da direita para a esquerda, a primeira chamada de RtLTextOut continuará a alterar a preferência de todo o ficheiro, e nada na sua prova de uma página o mostrará. O sintoma aparece semanas mais tarde, quando alguém imprime um folheto frente e verso e as páginas surgem espelhadas. Se não for isso que pretende, defina a propriedade Direction explicitamente de volta após a sequência da direita para a esquerda:
// RtLTextOut already set the document direction to RightToLeft;
// restore left-to-right if the document is predominantly LTR
Pdf.Direction := LeftToRight;
Para um documento que se lê genuinamente da direita para a esquerda, não altere nada. O objetivo é saber que a chamada tem um efeito a nível de todo o documento para que a surpresa do folheto nunca aconteça.
Registe a fonte que distribui, não aquela que espera que esteja instalada
Nenhuma reordenação importa se a fonte não tiver glifos para desenhar. A falha clássica é um relatório que é renderizado perfeitamente na máquina do programador, onde a fonte Arial Unicode MS está presente, e surge como linhas de caixas vazias no servidor de um cliente onde o Windows substituiu silenciosamente a fonte por outra sem qualquer cobertura de árabe. A solução é deixar de confiar nas fontes instaladas no sistema e registar uma que distribua com a aplicação.
// Ship a known Arabic font and register it before drawing
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
Duas limitações acompanham o registo. Uma fonte importada através de RegisterUnicodeTTF é incorporada, e o processamento de Unicode incorporado do HotPDF requer o documento em PDF 1.5 ou posterior; isto só afeta se algo a jusante exigir o PDF 1.4, mas quando isso acontece a falha é silenciosa. A outra limitação é legal e não técnica: os ficheiros TrueType contêm bits de permissão de incorporação, e um tipo de letra que parece bom no ecrã pode ser licenciado de uma forma que proíbe o envio do mesmo dentro dos documentos do cliente. Confirme a licença antes de incorporar, e não após uma reclamação.
Um exemplo completo em consola
Reunindo todas as peças, aqui está um programa autónomo que escreve uma página com uma linha em árabe, uma linha em hebraico e uma linha mista que contém um nome de produto em latim. Cada bloco define o seu charset e depois desenha na ordem lógica.
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // HotPDF main unit
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// A Latin heading goes through the ordinary TextOut path
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');
// Arabic: charset 178, logical order, RtLTextOut does the reordering
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 720, 0,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// Hebrew: charset 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 680, 0,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// Mixed line: the embedded Latin word still reads left to right
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 640, 0,
'مرحبا بالعالم! تم إنشاؤه بواسطة HotPDF');
Pdf.EndDoc;
Writeln('Wrote RtLTextOut.pdf');
finally
Pdf.Free;
end;
end.
Execute-o e abra o resultado. As linhas em árabe e hebraico leem-se da direita para a esquerda, as letras ligam-se onde o script as liga e, na última linha, a palavra chave HotPDF fica posicionada da esquerda para a direita dentro da sequência de árabe, o que é o resultado correto de acordo com a especificação, mesmo que surpreenda quem veja um layout bidirecional pela primeira vez. Vale a pena incluir este último ponto nos seus critérios de aceitação antes que um leitor nativo reveja o resultado, uma vez que a leitura da sequência incorporada no sentido 'errado' em relação ao script circundante é o aspeto que mais vezes é reportado como um erro quando na verdade não o é.
Verificar o resultado
Uma página com bom aspeto não é o mesmo que uma página correta, pelo que deve verificar o resultado da mesma forma que um sistema downstream o faria. Copie o texto de volta a partir do visualizador e compare los codepoints com a sua string de origem; uma ordem visual correta com uma ordem lógica baralhada é um modo de falha real. Execute a pesquisa interna do visualizador para encontrar uma palavra que consiga ver na página. Depois, abra o ficheiro numa máquina que não possua as suas fontes de desenvolvimento, a que tem maior probabilidade de expor uma substituição silenciosa. Nada disso substitui a leitura de um documento real por um falante nativo, o que deteta problemas que nenhuma string de teste sintética detetaria, por isso agende essa revisão antes de distribuir o formato.
O RtLTextOut lida com a reordenação bidirecional e com a ligação contextual do árabe, o que abrange a grande maioria dos relatórios e trabalhos de documentos da direita para a esquerda. Onde este termina (scripts que requerem mais do que reordenação e ligação, tais como as famílias índicas, e as funcionalidades opcionais de OpenType que passam pela substituição de glifos individuais), o comportamento está detalhado juntamente com a cobertura de glifos e a formatação no artigo complementar sobre formatação de texto em árabe e RTL com o HotPDF.
As chamadas RtLTextOut, SetFont e RegisterUnicodeTTF aqui apresentadas fazem parte do Componente HotPDF para Delphi e C++Builder.