Technical Article

RtLTextOut v HotPDF: PDF besedilo od desne proti levi v Delphi

Pošljite arabski stavek يوضح ملف PDF هذا v navaden TextOut in stran, ki jo dobite nazaj, bo napačna na dva načina hkrati. Besede tečejo od leve proti desni namesto od desne proti levi, črke pa stojijo ločeno v svojih izoliranih oblikah, namesto da bi se povezale v besede. Napak ni. Delphi se prevede, datoteka se odpre, ocenjevalec, ki zna arabsko, pa vam pove, da je izhod neuporaben. Rešitev je en klic, ne menjava knjižnice: HotPDF preusmeri besedilo od desne proti levi skozi ločeno metodo RtLTextOut, ki poskrbi za prerazvrščanje, česar navaden TextOut ne stori. Štiri stvari pri tej metodi določajo, ali je izhod uporaben: kaj naredi z nizom, kako njen argument charset izbere pisavo, sprememba na ravni dokumenta, ki jo povzroči kot stranski učinek, in delo s pisavo, ki mora biti opravljeno najprej.

Zakaj besedilo od desne proti levi potrebuje lasten klic

Vsebinska struga PDF (content stream) ne shranjuje uredljivega besedila. Shranjuje glife na določenih položajih, kar pomeni, da mora tisto, kar oddaja strugo, določiti vrstni red teh glifov. Na zaslonu je operacijski sistem to naredil namesto vas: če vstavite arabščino v TEdit, sklad za besedilo OS spremeni vrstni red in jo poveže, še preden vidite katero slikovno piko. Prav zato je niz v vašem obrazcu videti popoln, v PDF-ju pa se pokvari. Namizje je delo opravilo tiho, v trenutku, ko zapišete svojo vsebinsko strugo, pa je delo znova na vaši strani.

TextOut vas vzame za besedo. Glife izriše v vrstnem redu, kot jih podate, od leve proti desni, kar je pravilno za latinico, cirilico in CJK ter napačno za arabščino in hebrejščino. RtLTextOut je klic, ki vrstico najprej prerazporedi v vizualni vrstni red od desne proti levi, nato pa jo izriše. HotPDF namerno ohranja metodi ločeni, namesto da bi ugibal smer iz znakov, zato je izbira klica odvisna od želenega vedenja pisave. Podrobnejši mehanizmi dvosmernega prerazvrščanja in arabskega kontekstualnega povezovanja so lastna tema, ki jo obravnava članek o oblikovanju arabskega in RTL besedila s HotPDF; tukaj je praktična točka ožja. Uporabite RtLTextOut za izpise od desne proti levi, TextOut za vse ostalo in nikoli ne preusmerjajte enega skozi drugega.

Diagram, kako RtLTextOut prerazporedi mešano vrstico arabskega in latiničnega besedila v vizualni vrstni red od desne proti levi, preden jo izriše v PDF
RtLTextOut prerazporedi vsako vrstico v vizualni vrstni red pred izrisom: nizi od desne proti levi ohranijo svoje zaporedje, medtem ko se vgrajene latinične besede in številke v vrstici berejo od leve proti desni.

Argument charset določa pisavo

Tisto, kar pove RtLTextOut, ali postavlja arabščino ali hebrejščino, ni metoda, temveč pisava. SetFont sprejme Windows nabor znakov (charset) kot svoj četrti argument, ta vrednost pa prenese pravila pisave v klic od desne proti levi: 178 izbere arabščino, 177 izbere hebrejščino. Nastavite charset, nato izrišite in spodnji dve vrstici bosta izpisani v pravilnem vrstnem redu branja brez dodatnih nastavitev.

// 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 זה');

Dve podrobnosti o teh koordinatah lahko hitro spregledate. Položaj, ki ga posredujete, je še vedno začetek niza v lastnem koordinatnem sistemu strani, merjen od spodnjega levega kota z Y, ki raste navzgor, kar je isto izhodišče, kot ga uporablja vsak TextOut; RtLTextOut spremeni vrstni red glifov, ne pa mesta, od koder stran meri. Kot pri vsakem klicu za risanje mora biti SetFont na prvem mestu in ga je treba ponoviti po vsakem AddPage, saj trenutna pisava ne preživi preloma strani. Če pozabite na ponovitev, se druga stran vrne na tisto pisavo, ki je bila aktivna, kar za arabščino običajno pomeni prazne kvadratke.

Ne obračajte besedila, ki ste ga že sami obrnili

Najpogostejša napaka, ki vzame največ časa za odpravljanje napak, je pošiljanje niza v RtLTextOut, ki ste ga že ročno obrnili. Programerji pridejo do te metode, ko se njihov prvi poskus z navadnim TextOut izpiše vzvratno, zato je običajna rešitev, da znake pred izrisom obrnejo v kodi. RtLTextOut se samodejno obrne znotraj metode, zato se že obrnjen niz obrne še drugič in pristane natanko tam, kjer se je začel. Besedilo podajte v logičnem zaporedju, torej v vrstnem redu, kot bi ga tipkali in brali na glas, ter pustite klicu, da opravi prerazvrščanje.

Past je nevarnejša od preprostega obračanja, saj je dvojno obrnjen niz lahko videti pravilen pri enem testnem stavku, ki vsebuje samo arabščino, nato pa se zlomi v trenutku, ko vrstica vsebuje latinično besedo ali številko. Znotraj vrstice od desne proti levi se morajo ti vgrajeni nizi brati od leve proti desni, ročno obračanje pa uniči to ugnezdenost, medtem ko jo čisti arabski primer po naključju preživi. Tako se napaka izmuzne vašemu prvemu preizkusu in se pojavi pozneje na dejanskem računu s številko računa. Odstranite vsako ročno obračanje takoj, ko preklopite na RtLTextOut.

Stranski učinek Direction, ki ga je dobro poznati

Klic RtLTextOut spremeni več kot le vrstico, ki jo izrisujete. Prav tako obrne nastavitev smeri branja dokumenta na smer od desne proti levi, kar bi sicer sami nastavili z lastnostjo Direction. Ta nastavitev doda vpDirection v ViewerPreferences dokumenta, ki bralniku pove, kako naj razporedi dvostranski prikaz in na kateri strani se začne postavitev nasprotnih strani. Ko je celoten dokument v arabščini ali hebrejščini, je to natanko to, kar želite, in to dobite brezplačno.

To je dobro vedeti ravno zato, ker je na eni sami strani nevidno. Če je dokument večinoma od leve proti desni z enim blokom od desne proti levi, bo prvi klic RtLTextOut še vedno spremenil nastavitev celotne datoteke, pri čemer nič v vašem enostranskem preizkusu tega ne bo pokazalo. Simptom se pojavi tedne pozneje, ko nekdo natisne obojestransko knjižico in so strani obrnjene zrcalno. Če to ni tisto, kar želite, po izpisu od desne proti levi eksplicitno nastavite Direction nazaj:

// RtLTextOut already set the document direction to RightToLeft;
// restore left-to-right if the document is predominantly LTR
Pdf.Direction := LeftToRight;

Za dokument, ki se dejansko bere od desne proti levi, to pustite pri miru. Bistvo je vedeti, da ima klic učinek na celoten dokument, tako da se presenečenje s knjižico nikoli ne zgodi.

Registrirajte pisavo, ki jo priložite, ne tiste, za katero upate, da je nameščena

Nobeno prerazvrščanje ne pomaga, če pisava nima glifov za izris. Klasična napaka je poročilo, ki se brezhibno izriše na razvijalčevem računalniku, kjer je nameščena pisava Arial Unicode MS, na strankinem strežniku pa se prikažejo vrstice praznih kvadratkov, saj je Windows tiho zamenjal pisavo s takšno, ki nima nikakršne arabske podpore. Rešitev je, da ne zaupate nameščenim sistemskim pisavam in registrirate tisto, ki jo priložite aplikaciji.

// 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 هذا');

Z registracijo sta povezani dve omejitvi. Pisava, uvožena prek RegisterUnicodeTTF, se vgradi, vgrajeno upravljanje Unicode v HotPDF zahteva različico dokumenta PDF 1.5 ali novejšo; to je težava le, če naslednji sistemi zahtevajo PDF 1.4, vendar je v tem primeru napaka tiha. Druga omejitev je pravne in ne tehnične narave: datoteke TrueType vsebujejo bite z dovoljenji za vgradnjo, pisava, ki je na zaslonu videti dobro, pa je lahko licencirana na način, ki prepoveduje njeno pošiljanje v dokumentih strank. Pred vgradnjo preverite licenco, ne šele po pritožbi.

Celoten konzolni primer

Če združimo vse dele, je tu samostojen program, ki izpiše eno stran z arabsko vrstico, hebrejsko vrstico in mešano vrstico z latiničnim imenom izdelka. Vsak blok nastavi svoj nabor znakov (charset) in nato izriše besedilo v logičnem vrstnem redu.

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.

Zaženite ga in odprite rezultat. Arabska in hebrejska vrstica se bereta od desne proti levi, črke se povezujejo tam, kjer jih pisava povezuje, v zadnji vrstici pa žeton HotPDF stoji od leve proti desni znotraj arabskega niza, kar je po specifikacijah pravilen rezultat, čeprav preseneča vsakogar, ki se prvič sreča z dvosmerno postavitvijo. To zadnjo točko je vredno zapisati v vaša merila za sprejem, preden izhod pregleda domači bralec, saj je vgrajeni niz, ki se bere v »napačni« smeri glede na okoliško pisavo, najpogosteje prijavljen kot napaka, čeprav to ni.

Preverjanje izhoda

Stran, ki je videti pravilna, ni nujno tudi pravilna, zato jo preverite tako, kot jo bo preveril naslednji sistem. Kopirajte besedilo iz bralnika in primerjajte kodne točke z izvornim nizom; pravilen vizualni vrstni red z zmešanim logičnim vrstnim redom je pogost način napake. V bralniku zaženite iskanje besede, ki jo vidite na strani. Nato odprite datoteko na računalniku, ki nima vaših razvojnih pisav, saj boste tako najlažje izpostavili tiho zamenjavo. Nič od tega ne nadomesti domačega govorca, ki prebere celoten dokument in opazi težave, ki jih sintetični testni nizi ne morejo zaznati, zato ta pregled načrtujte, preden začnete pošiljati format.

RtLTextOut upravlja dvosmerno prerazvrščanje in arabsko kontekstualno povezovanje, kar pokriva večino dela s poročili in dokumenti od desne proti levi. Kje se konča – pisave, ki potrebujejo več kot le prerazvrščanje in povezovanje, kot so indijske družine pisav, ter izbirne funkcije OpenType, ki potekajo prek zamenjave posameznih glifov – pa je opisano skupaj s pokritostjo glifov in podrobnostmi o oblikovanju v spremljajočem članku o oblikovanju arabskega in RTL besedila s HotPDF.

Klici RtLTextOut, SetFont in RegisterUnicodeTTF, prikazani tukaj, so del komponente HotPDF Component za Delphi in C++Builder.