Articol tehnic

RtLTextOut în HotPDF: text PDF RTL în Delphi

Trimiteți propoziția arabă يوضح ملف PDF هذا către un simplu TextOut și pagina care rezultă este greșită în două feluri deodată. Cuvintele curg de la stânga la dreapta în loc de la dreapta la stânga, iar literele stau separate, în formele lor izolate, în loc să se lege în cuvinte unite. Nimic nu semnalează eroare. Codul Delphi compilează, fișierul se deschide, iar un recenzent care citește arabă vă spune că rezultatul este inutilizabil. Remedierea este un singur apel, nu o schimbare de bibliotecă: HotPDF trimite textul de la dreapta la stânga printr-o metodă separată, RtLTextOut, care se ocupă de reordonarea pe care un simplu TextOut nu o face. Această pagină este referința de lucru pentru acea metodă: semnătura și parametrii ei, argumentul de charset care selectează scrierea, efectul secundar la nivel de document, configurarea fontului care trebuie făcută prima și defectele care chiar ajung la suport, fiecare cu remedierea lui

Semnătură și parametri

procedure RtLTextOut(X, Y: Single; angle: Extended;
  Text: WideString); overload;
procedure RtLTextOut(X, Y: Single; angle: Extended;
  Text: PWORD; TextLength: Integer); overload;

X și Y ancorează secvența în sistemul de coordonate propriu al paginii, măsurat din colțul din stânga jos, cu Y crescând în sus, aceeași origine pe care o folosește orice apel TextOut; RtLTextOut schimbă ordinea glifelor, nu punctul din care măsoară pagina. angle rotește linia de bază exact ca în TextOut, deci 0 desenează o linie orizontală. Text este șirul în ordine logică, ordinea în care l-ați tasta, iar a doua supraîncărcare primește aceleași date UTF-16 ca buffer brut PWORD cu un număr explicit de unități de cod, forma potrivită atunci când textul vine dintr-un API, nu dintr-un șir Delphi. Pe versiuni mai vechi de Delphi, anterioare rezolvării supraîncărcărilor pentru aceste tipuri, forma cu șir este expusă sub numele RtLTextOutStr, cu listă de parametri identică

Împărțirea sarcinilor între cele două apeluri de ieșire este strictă. TextOut desenează punctele de cod în ordinea în care le transmiteți, ceea ce este corect pentru latină, chirilică și CJK și greșit pentru arabă și ebraică. RtLTextOut reordonează mai întâi fiecare linie în ordine vizuală de la dreapta la stânga, apoi desenează, păstrând cuvintele latine încorporate și cifrele citindu-se de la stânga la dreapta în interiorul liniei. HotPDF ține cele două metode separate în mod deliberat, în loc să ghicească direcția din caractere, așa că alegerea metodei apelate este alegerea comportamentului de scriere pe care îl obțineți; folosiți RtLTextOut pentru secvențe de la dreapta la stânga, TextOut pentru restul, și nu treceți niciodată una prin cealaltă. De ce există reordonarea, ce fac de fapt algoritmul bidirecțional Unicode și legarea contextuală arabă și unde se oprește modelarea din HotPDF sunt subiectul articolului complementar despre modelarea textului arab și RTL cu HotPDF; tot ce urmează este configurarea practică

Diagramă a modului în care RtLTextOut reordonează o linie mixtă, arabă și latină, în ordine vizuală de la dreapta la stânga înainte de a o desena într-un PDF
RtLTextOut reordonează fiecare linie în ordine vizuală înainte de desenare: secvențele de la dreapta la stânga își păstrează succesiunea, în timp ce cuvintele latine încorporate și cifrele se citesc de la stânga la dreapta în interiorul liniei

Argumentul de charset decide scrierea

Ceea ce îi spune lui RtLTextOut dacă așază arabă sau ebraică nu este metoda, ci fontul. SetFont primește un charset Windows ca al patrulea argument, iar acea valoare duce regulile de scriere în apelul de la dreapta la stânga: 178 selectează araba, 177 selectează ebraica. Setați charsetul, apoi desenați, și cele două linii de mai jos ies în ordinea corectă de citire, fără nicio configurare suplimentară

// Arabă: charsetul 178 îi spune lui RtLTextOut să aplice regulile arabe
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');

// Ebraică: charsetul 177 comută regulile pe ebraică
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

Un detaliu de ordine este ușor de ratat: SetFont trebuie să fie primul și trebuie repetat după fiecare AddPage, pentru că fontul curent, charsetul inclus, nu supraviețuiește unei treceri la pagină nouă. Uitați repetarea și a doua pagină revine la orice font era activ, ceea ce pentru arabă înseamnă de obicei casete goale

Nu inversează un text pe care l-ați inversat deja

Singura greșeală care înghite cel mai mult timp de depanare aici este să îi dați lui RtLTextOut un șir pe care l-ați răsturnat deja manual. Oamenii ajung la această metodă după ce o primă încercare cu un simplu TextOut a ieșit inversată, iar un paliativ frecvent este inversarea caracterelor în cod înainte de desenare. RtLTextOut inversează intern, pe cont propriu, așa că un șir preinversat este inversat a doua oară și ajunge exact de unde a plecat. Transmiteți textul în ordine logică, ordinea în care l-ați tasta și l-ați citi cu voce tare, și lăsați apelul să facă reordonarea

Capcana este mai urâtă decât o simplă răsturnare, pentru că un șir inversat de două ori poate părea corect pentru o frază de test integral arabă și apoi se strică în clipa în care o linie poartă un cuvânt latin sau un număr. În interiorul unei linii de la dreapta la stânga, acele secvențe încorporate trebuie să se citească de la stânga la dreapta, iar inversarea manuală distruge acea imbricare, în timp ce cazul pur arab se întâmplă să îi supraviețuiască. Așa că defectul trece nevăzut prin primul test de fum și apare mai târziu pe o factură reală cu un număr de cont în ea. Eliminați orice inversare manuală în momentul în care treceți la RtLTextOut

Efectul secundar Direction care merită știut

Apelarea lui RtLTextOut schimbă mai mult decât linia pe care o desenați. Comută și preferința de direcție de citire a documentului la dreapta-la-stânga, exact lucrul pe care altfel l-ați seta chiar dumneavoastră prin proprietatea Direction. Acel setter adaugă vpDirection la ViewerPreferences ale documentului, ceea ce îi spune unui vizualizator cum să aranjeze paginile duble și din ce parte începe un aspect cu pagini față în față. Când întregul document este în arabă sau ebraică, exact asta doriți și îl obțineți gratuit

Merită știut tocmai pentru că este invizibil pe o singură pagină. Dacă documentul este preponderent de la stânga la dreapta, cu un singur bloc de la dreapta la stânga, primul apel RtLTextOut tot va răsturna preferința întregului fișier, iar nimic din proba dumneavoastră de o pagină nu o va arăta. Simptomul apare săptămâni mai târziu, când cineva tipărește o broșură duplex și paginile duble ies în oglindă. Dacă nu asta doriți, readuceți explicit Direction după secvența de la dreapta la stânga:

// RtLTextOut a setat deja direcția documentului la RightToLeft;
// restaurați stânga-la-dreapta dacă documentul este preponderent LTR
Pdf.Direction := LeftToRight;

Pentru un document care se citește cu adevărat de la dreapta la stânga, lăsați-l așa. Ideea este să știți că apelul are un efect la nivel de document, astfel încât surpriza cu broșura să nu se producă niciodată

Înregistrați fontul pe care îl livrați, nu pe cel pe care sperați că e instalat

Nicio reordonare nu contează dacă fontul nu are glife de desenat. Defectul clasic este un raport care se randează impecabil pe mașina dezvoltatorului, unde Arial Unicode MS se întâmplă să fie prezent, și iese ca rânduri de casete goale pe serverul unui client, unde Windows a substituit discret un font fără nicio acoperire pentru arabă. Remediul este să nu mai aveți încredere în fonturile de sistem instalate și să înregistrați unul pe care îl livrați cu aplicația

// Livrați un font arab cunoscut și înregistrați-l înainte de desenare
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');

Două limite vin la pachet cu înregistrarea. Un font adus prin RegisterUnicodeTTF este încorporat, iar tratarea Unicode încorporat din HotPDF cere documentul la PDF 1.5 sau mai nou; asta mușcă doar dacă ceva din aval insistă pe PDF 1.4, dar când o face, defectul este silențios. Cealaltă este juridică, nu tehnică: fișierele TrueType poartă biți de permisiune pentru încorporare, iar un chip care arată bine pe ecran poate fi licențiat într-un fel care interzice livrarea lui în documentele clienților. Confirmați licența înainte de a încorpora, nu după o reclamație

Un exemplu complet de consolă

Punând piesele laolaltă, iată un program de sine stătător care scrie o pagină cu o linie arabă, o linie ebraică și o linie mixtă care poartă un nume de produs latin. Fiecare bloc își setează charsetul, apoi desenează în ordine logică

program RtLTextOutDemo;

{$APPTYPE CONSOLE}

uses
  HPDFDoc;   // unitatea principală HotPDF

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'RtLTextOut.pdf';
    Pdf.BeginDoc;

    // Un titlu latin trece prin traseul obișnuit TextOut
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');

    // Arabă: charset 178, ordine logică, RtLTextOut face reordonarea
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
    Pdf.CurrentPage.RtLTextOut(400, 720, 0,
      'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');

    // Ebraică: charset 177
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
    Pdf.CurrentPage.RtLTextOut(400, 680, 0,
      'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');

    // Linie mixtă: cuvântul latin încorporat se citește tot de la stânga la dreapta
    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.

Rulați-l și deschideți rezultatul. Liniile arabă și ebraică se citesc de la dreapta la stânga, literele se leagă acolo unde scrierea le leagă, iar în ultima linie tokenul HotPDF stă de la stânga la dreapta în interiorul secvenței arabe. Acea imbricare este rezultatul bidirecțional corect, nu un defect, chiar dacă recenzenții aflați la prima experiență îl raportează în mod curent ca atare; articolul despre modelare legat mai sus explică de ce regulile Unicode o cer și cum să vă formulați criteriile de acceptanță astfel încât raportul să nu mai fie deschis niciodată

Erori frecvente și remedierile lor

Fiecare defect de mai jos a apărut într-un fir real de suport, iar fiecare se întoarce la una dintre secțiunile de mai sus

  • Ieșirea se citește invers sau se amestecă pe liniile mixte — șirul a fost inversat manual înainte de apel, de obicei un artificiu rămas de la o încercare cu TextOut. Ștergeți orice inversare manuală și transmiteți ordinea logică; RtLTextOut inversează intern
  • Literele se tipăresc dezlegate, în forme izolate — textul a trecut printr-un simplu TextOut sau SetFont a fost apelat fără un charset de la dreapta la stânga. Desenați cu RtLTextOut și transmiteți 178 pentru arabă sau 177 pentru ebraică drept al patrulea argument al lui SetFont
  • Casete goale pe mașina clientului — Windows a substituit un font fără acoperire pentru arabă sau ebraică. Nu mai numiți fonturi instalate; înregistrați un chip pe care îl livrați, prin RegisterUnicodeTTF, și aplicați-l cu SetFont după acel nume
  • A doua pagină se randează cu fontul greșit — fontul curent nu supraviețuiește lui AddPage. Repetați apelul SetFont, cu tot cu charset, după fiecare trecere la pagină nouă
  • Paginile duplex se tipăresc în oglindă pe un document preponderent LTR — primul apel RtLTextOut a răsturnat Direction al documentului ca efect secundar. Setați Pdf.Direction := LeftToRight după secvența de la dreapta la stânga
  • Textul Unicode încorporat se degradează silențios în aval — ceva din lanț forțează PDF 1.4, iar tratarea Unicode încorporat din HotPDF are nevoie de 1.5 sau mai nou. Ridicați versiunea documentului sau eliminați constrângerea din aval

Înainte ca formatul să ajungă în producție, verificați dincolo de inspecția vizuală: copiați textul înapoi din vizualizator, rulați căutarea în document, deschideți fișierul pe o mașină fără fonturile dumneavoastră de dezvoltare și puneți un document autentic în fața unui cititor nativ. Lista completă de verificare, harta de acoperire pe scrieri și corpusul de șiruri de test care merită construit se află toate în articolul complementar despre modelarea textului arab și RTL cu HotPDF

Apelurile RtLTextOut, SetFont și RegisterUnicodeTTF arătate aici fac parte din HotPDF Delphi Component pentru Delphi și C++Builder