Articolo tecnico

Shaping del testo arabo e RTL nei PDF Delphi con HotPDF

Prendi la frase araba يوضح ملف PDF, passala a TextOut e apri l'output: le lettere scorrono nella direzione sbagliata e ciascuna resta nella forma isolata, scollegata dalle vicine. Un lettore arabo vede qualcosa di simile a inglese digitato al contrario con uno spazio dopo ogni lettera. Non è fallito nulla — nessuna eccezione, nessun avviso — perché due trasformazioni testuali distinte semplicemente non sono state eseguite. Capire quali siano queste trasformazioni, e quale API le applichi, è l'intera partita nell'output PDF per script complessi.

Questo articolo attraversa testo right-to-left e script complessi con HotPDF, componente PDF VCL nativo per Delphi e C++Builder, incluso il punto in cui il suo supporto allo shaping finisce davvero; un dettaglio altrettanto importante quando devi decidere se copre le tue locale.

Due trasformazioni separano una stringa da una riga stampata

Unicode memorizza il testo in ordine logico: l'ordine in cui viene digitato, conservato e letto ad alta voce. Un renderer disegna in ordine visuale. Per gli script da destra a sinistra i due ordini differiscono, e per contenuto misto — una frase araba che contiene il token latino "PDF" o un prezzo in cifre — l'Unicode Bidirectional Algorithm (UAX #9) definisce come i run da sinistra a destra si incorporano in una riga da destra a sinistra. Questa è la prima trasformazione: il riordinamento.

La seconda trasformazione è lo shaping contestuale. Una lettera araba usa un glifo diverso a seconda che si trovi all'inizio, al centro o alla fine di una parola, oppure isolata: il codepoint non cambia, cambia solo la forma renderizzata. Una pipeline testuale che mappa i codepoint direttamente ai glifi di default produce l'output disconnesso descritto sopra. L'ebraico non richiede giunzione, ma richiede comunque riordinamento; l'arabo richiede entrambi, ed è per questo il caso di prova canonico.

Lo sviluppo desktop nasconde questa meccanica. Quando un'applicazione VCL disegna arabo a schermo, lo stack testuale del sistema operativo lo riordina e lo modella in modo invisibile; per questo la stessa stringa che renderizza perfettamente in un TEdit esce sbagliata in un PDF ingenuo. Un content stream PDF memorizza glifi posizionati, non run di testo modificabili: chi scrive lo stream possiede lo shaping, ed è il vuoto che RtLTextOut serve a colmare.

RtLTextOut: riordinamento e giunzione in una sola chiamata

HotPDF separa il percorso latino dal percorso per script complessi a livello di API. TextOut disegna ciò che riceve, nell'ordine in cui lo riceve; RtLTextOut esegue prima riordinamento e analisi contestuale. Il parametro charset di SetFont indica al motore quali regole di script applicare: 178 seleziona l'elaborazione araba, 177 quella ebraica.

// Arabic: pass logical order; RtLTextOut reorders and joins
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF');

// Hebrew: reordering only, no contextual joining
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

La trappola che costa più tempo di debug: RtLTextOut esegue l'inversione da sé. Fornirgli testo già invertito — di solito un vecchio "fix" rimasto da un tentativo precedente con semplice TextOut — inverte la riga due volte. Può persino sembrare corretto con una singola stringa di test solo araba, per poi rompersi alla prima riga contenente lettere latine o cifre, perché i run misti non seguono più l'ordinamento UAX #9. Passa sempre l'ordine logico e lascia fare all'API.

Il contenuto a direzioni miste è anche il punto in cui le aspettative manuali falliscono nelle revisioni: dentro una riga da destra a sinistra, numeri e parole latine incorporate continuano a leggersi da sinistra a destra. I revisori non abituati al layout bidirezionale lo segnalano spesso come bug; è il comportamento corretto secondo la specifica, e merita una nota nella documentazione di accettazione prima della prima revisione con un madrelingua.

La copertura dei glifi decide il risultato prima dello shaping

Lo shaping seleziona i glifi; il font deve contenerli davvero. Il classico fallimento di deployment è un report che renderizza perfettamente sulla workstation dello sviluppatore — dove Arial Unicode MS è installato — e produce quadratini vuoti sul server del cliente, dove Windows ha sostituito in silenzio un font senza copertura araba. La correzione è smettere di dipendere dai font di sistema installati e registrare un file font che distribuisci tu:

// Ship a known font instead of relying on installed system fonts
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12);

// Audit coverage for the codepoints your data actually uses
GID := Pdf.GetUnicodeGlyphForCodepoint($0628);  // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);

Si applicano due confini di versione. I font registrati in questo modo devono essere incorporati, e la gestione dei font Unicode incorporati di HotPDF richiede che la versione PDF del documento sia 1.5 o successiva: rilevante solo se qualche sistema downstream blocca l'output a PDF 1.4. Inoltre la licenza del font deve consentire l'embedding: i file TrueType portano bit di permesso per l'incorporamento, e un font che renderizza bene a schermo può essere legalmente inadatto alla distribuzione dentro documenti cliente.

GetUnicodeGlyphForCodepoint è l'hook di audit: attraversa all'avvio del servizio gli intervalli di codepoint usati dai tuoi dati e registra gli ID glifo risolti, così un buco di copertura compare in una riga di log durante il deployment invece che come caratteri mancanti in una fattura cliente.

Per testo Unicode che non è right-to-left — stringhe CJK, diacritici vietnamiti, script europei misti — si applica la pipeline semplice: TextOut accetta un WideString e lo disegna tramite il font registrato senza analisi bidirezionale. Tenere distinti i due percorsi di chiamata nel codice report, una routine per i run RTL e una per tutto il resto, rende esplicito il comportamento locale invece di seppellirlo in un flag che nessuno ricorda di impostare.

L'ordine di lettura è anche una proprietà del documento

La correttezza a livello di glifo non chiude il lavoro. ISO 32000-1 §12.2 definisce una preferenza del viewer, /Direction, che dichiara l'ordine di lettura predominante del documento. Non cambia alcun glifo; dice ai viewer come ordinare le aperture a due pagine, dove ancorare la progressione nei layout a pagine affiancate e quale direzione assumere per l'interfaccia: dettagli importanti per libretti e per qualsiasi documento che l'utente sfogli.

// Declare right-to-left reading order at the document level
Pdf.Direction := RightToLeft;  // adds vpDirection to ViewerPreferences

Assegnare Direction è sufficiente da solo: il setter della proprietà aggiunge automaticamente vpDirection a ViewerPreferences, quindi la preferenza arriva nel file con una sola riga. L'omissione da sorvegliare è saltare del tutto la dichiarazione, cosa facile proprio perché su una pagina singola non cambia nulla di visibile; emerge solo quando qualcuno stampa un libretto fronte-retro e le aperture risultano specchiate.

Dove si ferma lo shaping di HotPDF

Una mappa onesta delle capacità risparmia una settimana di valutazione. RtLTextOut gestisce automaticamente riordinamento bidirezionale e giunzione contestuale araba. Le legature tipografiche opzionali e l'applicazione più ampia delle feature OpenType non sono automatiche: GetSingleSubstituteGlyph(GID, 'liga') risolve una sostituzione alla volta — ID glifo in ingresso, tag feature accanto — e funziona per un elenco di legature noto e finito che applichi tu, ma non è un motore GSUB generale. Per script le cui esigenze di shaping vanno oltre, tipicamente gli script indici con segni vocalici riordinati, esegui un pilot con stringhe reali del cliente prima di impegnarti sulla locale, invece di estrapolare dai risultati arabi.

La verifica deve essere end-to-end, perché una pagina può apparire corretta e fallire comunque ogni uso downstream. Tre controlli intercettano la maggior parte dei casi: copia il testo fuori da Acrobat e confronta i codepoint con la stringa sorgente; cerca dentro il documento una parola che appare sulla pagina; rivedi l'output su una macchina che non ha installati i font di sviluppo. Un collega madrelingua che guarda un documento reale vale più di qualsiasi quantità di dati sintetici: programma quella revisione prima che il formato vada in produzione, non dopo il primo reclamo.

Scegli le stringhe di test deliberatamente invece di riusare qualunque testo un traduttore abbia inviato l'anno scorso. Un set minimo utile per locale: una frase solo nello script, una frase con nomi di brand latini incorporati, una riga con cifre e valuta, e nomi con diacritici o segni combinanti. I nomi reali dei clienti rompono assunzioni sullo shaping che il testo riempitivo non tocca mai: il corpus di regressione dovrebbe crescere di una voce ogni volta che un caso di supporto espone un nuovo pattern.

Registrazione dei font, subsetting e API generale di disegno testo sono trattati nell'articolo su output report, font e immagini con HotPDF; se gli stessi documenti devono anche soddisfare profili di accessibilità, i requisiti di tagging lingua e struttura nell'articolo di validazione PDF/A e PDF/UA si sovrappongono al lavoro di shaping descritto qui.

Le API right-to-left e per font Unicode di questo articolo sono incluse in HotPDF Component per Delphi e C++Builder; la pagina prodotto collega il riferimento completo dell'output testuale.