Inviate la frase araba يوضح ملف PDF هذا al semplice TextOut e la pagina che ne esce è sbagliata in due modi contemporaneamente. Le parole scorrono da sinistra a destra invece che da destra a sinistra, e le lettere restano separate nelle loro forme isolate invece di unirsi in parole legate. Nulla segnala un errore. Il codice Delphi compila, il file si apre, e un revisore che legge arabo vi dice che il risultato è inutilizzabile. La correzione è una sola chiamata, non un cambio di libreria: HotPDF instrada il testo da destra a sinistra attraverso un metodo separato, RtLTextOut, che si occupa del riordino che il semplice TextOut non esegue. Questa pagina è il riferimento operativo di quel metodo: la firma e i suoi parametri, il parametro charset che seleziona la scrittura, l'effetto collaterale a livello di documento, la preparazione del font che deve venire prima e i malfunzionamenti che arrivano davvero al supporto, ciascuno con la sua soluzione
Firma e parametri
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: WideString); overload;
procedure RtLTextOut(X, Y: Single; angle: Extended;
Text: PWORD; TextLength: Integer); overload;
X e Y ancorano il tratto nel sistema di coordinate proprio della pagina, misurato dall'angolo in basso a sinistra con la Y che cresce verso l'alto, la stessa origine che usa ogni chiamata TextOut; RtLTextOut cambia l'ordine dei glifi, non il punto da cui la pagina misura. angle ruota la linea di base esattamente come in TextOut, quindi 0 disegna una riga orizzontale. Text è la stringa in ordine logico, l'ordine in cui la scrivereste, e il secondo overload accetta gli stessi dati UTF-16 come buffer PWORD grezzo con un conteggio esplicito di unità di codice, che è la forma da usare quando il testo arriva da una API anziché da una stringa Delphi. Sulle versioni di Delphi più vecchie, che precedono la risoluzione degli overload per questi tipi, la forma con stringa è esposta con il nome RtLTextOutStr e una lista di parametri identica
La divisione dei compiti fra le due chiamate di output è netta. TextOut disegna i codepoint nell'ordine in cui li passate, che è corretto per latino, cirillico e CJK e sbagliato per arabo ed ebraico. RtLTextOut riordina prima ogni riga in ordine visivo da destra a sinistra, poi disegna, mantenendo le parole latine e le cifre incorporate leggibili da sinistra a destra dentro la riga. HotPDF tiene i due metodi deliberatamente separati invece di indovinare la direzione dai caratteri, quindi la scelta di quale chiamare è la scelta del comportamento di scrittura che ottenete; usate RtLTextOut per i tratti da destra a sinistra, TextOut per tutto il resto, e non fate mai passare uno attraverso l'altro. Perché il riordino esista, che cosa facciano davvero l'algoritmo bidirezionale Unicode e la giunzione contestuale araba, e dove si ferma lo shaping di HotPDF sono il tema dell'articolo di accompagnamento sul rendering di arabo e testo RTL con HotPDF; tutto quello che segue è la preparazione pratica

Il parametro charset decide la scrittura
Ciò che dice a RtLTextOut se sta impaginando arabo o ebraico non è il metodo, è il font. SetFont accetta un charset Windows come quarto argomento, e quel valore porta le regole della scrittura dentro la chiamata da destra a sinistra: 178 seleziona l'arabo, 177 seleziona l'ebraico. Impostate il charset, poi disegnate, e le due righe qui sotto escono nel corretto ordine di lettura senza altra configurazione
// Arabo: il charset 178 dice a RtLTextOut di applicare le regole arabe
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
// Ebraico: il charset 177 passa alle regole ebraiche
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
Un dettaglio di sequenza è facile da mancare: il SetFont deve venire prima e va ripetuto dopo ogni AddPage, perché il font corrente, charset incluso, non sopravvive a un cambio di pagina. Dimenticate la ripetizione e la seconda pagina ripiega su qualunque font fosse attivo, il che per l'arabo di solito significa riquadri vuoti
Non inverte il testo che avete già invertito
L'errore singolo che qui divora più tempo di debug è passare a RtLTextOut una stringa che avete già capovolto a mano. Si arriva a questo metodo dopo che un primo tentativo con il semplice TextOut è uscito al contrario, e un ripiego comune è invertire i caratteri nel codice prima di disegnare. RtLTextOut inverte internamente per conto suo, quindi una stringa pre-invertita viene invertita una seconda volta e torna esattamente al punto di partenza. Passate il testo in ordine logico, l'ordine in cui lo scrivereste e lo leggereste ad alta voce, e lasciate che sia la chiamata a fare il riordino
La trappola è più insidiosa di un semplice capovolgimento, perché una stringa invertita due volte può sembrare corretta su una frase di prova tutta in arabo e poi rompersi nell'istante in cui una riga porta una parola latina o un numero. Dentro una riga da destra a sinistra quei tratti incorporati devono leggersi da sinistra a destra, e l'inversione manuale distrugge quell'annidamento mentre il caso di solo arabo per puro caso sopravvive. Così il difetto supera il primo smoke test e riemerge più tardi su una fattura reale che contiene un numero di conto. Eliminate ogni inversione manuale nel momento in cui passate a RtLTextOut
L'effetto collaterale su Direction che vale la pena conoscere
Chiamare RtLTextOut cambia più della riga che state disegnando. Ribalta anche la preferenza di direzione di lettura del documento verso destra-sinistra, la stessa cosa che altrimenti impostereste voi tramite la proprietà Direction. Quel setter aggiunge vpDirection alle ViewerPreferences del documento, che dicono a un visualizzatore come disporre le doppie pagine e da quale lato parte un layout a pagine affiancate. Quando l'intero documento è in arabo o in ebraico questo è esattamente ciò che volete, e lo ottenete gratis
Vale la pena saperlo proprio perché su una pagina sola è invisibile. Se il documento è per lo più da sinistra a destra con un unico blocco da destra a sinistra, la prima chiamata a RtLTextOut ribalterà comunque la preferenza dell'intero file, e nulla nella vostra prova a una pagina lo mostrerà. Il sintomo compare settimane dopo, quando qualcuno stampa un opuscolo fronte-retro e le doppie pagine escono speculari. Se non è quello che volete, riportate Direction in modo esplicito dopo il tratto da destra a sinistra:
// RtLTextOut ha già impostato la direzione del documento su RightToLeft;
// ripristinate da sinistra a destra se il documento è prevalentemente LTR
Pdf.Direction := LeftToRight;
Per un documento che davvero si legge da destra a sinistra, lasciatelo stare. Il punto è sapere che la chiamata ha un effetto su tutto il documento, così la sorpresa dell'opuscolo non capita mai
Registrate il font che distribuite, non quello che sperate sia installato
Nessun riordino conta se il font non ha glifi da disegnare. Il classico fallimento è un report che si rende in modo impeccabile sulla macchina dello sviluppatore, dove Arial Unicode MS è per caso presente, ed esce come file di riquadri vuoti sul server di un cliente dove Windows ha silenziosamente sostituito un font privo di qualsiasi copertura araba. La cura è smettere di fidarsi dei font di sistema installati e registrarne uno che distribuite insieme all'applicazione
// Distribuite un font arabo noto e registratelo prima di disegnare
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
Con la registrazione viaggiano due limiti. Un font introdotto tramite RegisterUnicodeTTF viene incorporato, e la gestione Unicode incorporata di HotPDF richiede il documento in PDF 1.5 o successivo; questo morde solo se qualcosa a valle insiste su PDF 1.4, ma quando succede il fallimento è silenzioso. L'altro è di natura legale più che tecnica: i file TrueType portano bit di permesso per l'incorporamento, e un carattere che a schermo sembra a posto può essere licenziato in modo da vietarne la distribuzione dentro i documenti dei clienti. Verificate la licenza prima di incorporare, non dopo un reclamo
Un esempio console completo
Mettendo insieme i pezzi, ecco un programma autonomo che scrive una pagina con una riga in arabo, una in ebraico e una riga mista che porta un nome di prodotto latino. Ogni blocco imposta il proprio charset, poi disegna in ordine logico
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // unità principale di HotPDF
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// Un titolo latino passa per il normale percorso TextOut
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');
// Arabo: charset 178, ordine logico, RtLTextOut fa il riordino
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 720, 0,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// Ebraico: charset 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 680, 0,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// Riga mista: la parola latina incorporata si legge ancora da sinistra a destra
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.
Eseguitelo e aprite il risultato. Le righe in arabo e in ebraico si leggono da destra a sinistra, le lettere si uniscono dove la scrittura le unisce, e nell'ultima riga il token HotPDF sta da sinistra a destra dentro il tratto arabo. Quell'annidamento è il risultato bidirezionale corretto, non un difetto, anche se i revisori alle prime armi lo segnalano regolarmente come tale; l'articolo sullo shaping collegato sopra spiega perché le regole Unicode lo impongono e come formulare i criteri di accettazione in modo che la segnalazione non venga mai aperta
Errori comuni e relative soluzioni
Ogni malfunzionamento qui sotto è comparso in un vero thread di supporto, e ciascuno risale a una delle sezioni precedenti
- Il risultato si legge al contrario o si scompiglia sulle righe miste — la stringa è stata invertita a mano prima della chiamata, di solito un rimasuglio di un tentativo con
TextOut. Cancellate ogni inversione manuale e passate l'ordine logico;RtLTextOutinverte internamente - Le lettere si stampano staccate in forme isolate — il testo è passato per il semplice
TextOut, oppureSetFontè stato chiamato senza un charset da destra a sinistra. Disegnate conRtLTextOute passate 178 per l'arabo o 177 per l'ebraico come quarto argomento diSetFont - Riquadri vuoti sulla macchina del cliente — Windows ha sostituito un font privo di copertura araba o ebraica. Smettete di nominare font installati; registrate un carattere che distribuite tramite
RegisterUnicodeTTFe richiamatelo conSetFontusando quel nome - La seconda pagina si rende con il font sbagliato — il font corrente non sopravvive ad
AddPage. Ripetete la chiamata aSetFont, charset incluso, dopo ogni cambio di pagina - Le doppie pagine fronte-retro si stampano speculari su un documento prevalentemente LTR — la prima chiamata a
RtLTextOutha ribaltatoDirectiondel documento come effetto collaterale. ImpostatePdf.Direction := LeftToRightdopo il tratto da destra a sinistra - Il testo Unicode incorporato degrada silenziosamente a valle — qualcosa nella pipeline forza PDF 1.4, e la gestione Unicode incorporata di HotPDF richiede 1.5 o successivo. Alzate la versione del documento o rimuovete il vincolo a valle
Prima che il formato entri in produzione, verificate oltre il colpo d'occhio: ricopiate il testo fuori dal visualizzatore, eseguite la ricerca interna al documento, aprite il file su una macchina priva dei vostri font di sviluppo e mettete un documento autentico davanti a un lettore madrelingua. La lista di verifica completa, la mappa di copertura per scrittura e il corpus di stringhe di prova che vale la pena costruire stanno tutti nell'articolo di accompagnamento sul rendering di arabo e testo RTL con HotPDF
Le chiamate RtLTextOut, SetFont e RegisterUnicodeTTF mostrate qui fanno parte del HotPDF Delphi Component per Delphi e C++Builder