Műszaki cikk

RtLTextOut a HotPDF-ben: Jobbról balra írt PDF szöveg Delphiben

Küldje el a يوضح ملف PDF هذا arab mondatot az egyszerű TextOut-nak, és a visszakapott oldal egyszerre kétféleképpen is hibás. A szavak balról jobbra futnak jobbról balra helyett, a betűk pedig elszigetelt (isolated) formájukban állnak, ahelyett, hogy összefüggő szavakká kapcsolódnának. Semmi sem jelez hibát. A Delphi lefordítja a kódot, a fájl megnyílik, és egy arabul olvasó ellenőr (reviewer) közli Önnel, hogy a kimenet használhatatlan. A javítás egyetlen hívás, nem pedig könyvtárcsere: a HotPDF a jobbról balra tartó szöveget egy külön metóduson, a RtLTextOut-on keresztül irányítja, amely kezeli azt az átrendezést, amelyet a sima TextOut nem. Négy dolog dönti el ezzel a metódussal kapcsolatban, hogy a kimenet használható-e: mit tesz a karakterlánccal, hogyan választja ki a karakterkészlet (charset) argumentuma az írást (script), milyen dokumentumszintű változást okoz mellékhatásként, és a betűtípussal kapcsolatos munka, aminek először kell következnie

Miért van szüksége a jobbról balra írásnak saját hívásra

Egy PDF tartalom-folyam (content stream) nem tárol szerkeszthető szöveget. Glifákat (glyphs) tárol rögzített pozíciókban, ami azt jelenti, hogy bármi, ami a folyamot kibocsátja, annak a feladata eldönteni, hogy ezek a glifák milyen sorrendben kövessék egymást. A képernyőn az operációs rendszer ezt megtette Ön helyett: dobjon be arab szöveget egy TEdit-be, és az operációs rendszer szövegverme (text stack) átrendezi és összekapcsolja, még mielőtt Ön egyetlen pixelt is látna. Pontosan ez az oka annak, hogy a karakterlánc tökéletesen néz ki az űrlapon, de megtörik a PDF-ben. Az asztali számítógép csendben elvégezte a munkát, és abban a pillanatban, hogy saját tartalom-folyamot ír, a munka visszakerül az Ön oldalára

A TextOut szaván fogja Önt. A kódpontokat (codepoints) abban a sorrendben rajzolja ki, ahogyan átadja őket, balról jobbra, ami helyes a latin, cirill és CJK (kínai, japán, koreai) esetében, de hibás az arab és a héber esetében. Az RtLTextOut az a hívás, amely a sort először vizuális jobbról balra sorrendbe rendezi át, majd utána rajzol. A HotPDF szándékosan külön tartja a két metódust, ahelyett, hogy a karakterekből próbálná kitalálni az irányt, így annak megválasztása, hogy melyiket hívja meg, egyben annak megválasztása is, hogy melyik írási (script) viselkedést kapja. A kétirányú átrendezés (bidirectional reordering) és az arab kontextuális kapcsolódás mélyebb mechanikája külön téma, amellyel az Arab és RTL szövegformálás a HotPDF-fel című cikk foglalkozik; itt a gyakorlati szempont szűkebb. Használja az RtLTextOut-ot a jobbról balra tartó futtatásokhoz (runs), a TextOut-ot minden máshoz, és soha ne irányítsa az egyiket a másikon keresztül

Diagram of how RtLTextOut reorders a mixed Arabic and Latin line into visual right-to-left order before drawing it into a PDF
Az RtLTextOut megrajzolás előtt minden sort vizuális sorrendbe rendez: a jobbról balra tartó futtatások (runs) megtartják a sorrendjüket, míg a beágyazott latin szavak és számjegyek balról jobbra olvashatók a soron belül.

A karakterkészlet (charset) argumentum dönti el az írást

Nem a metódus, hanem a betűtípus (font) mondja meg az RtLTextOut-nak, hogy arab vagy héber szöveget rendez-e el. A SetFont negyedik argumentumaként egy Windows karakterkészletet vesz fel, és ez az érték viszi be az írás szabályait a jobbról balra hívásba: a 178 az arabot, a 177 a hébert választja ki. Állítsa be a karakterkészletet, majd rajzoljon, és az alábbi két sor minden további konfiguráció nélkül helyes olvasási sorrendben jelenik meg

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

Ezen koordináták két részlete felett könnyű elsiklani. Az átadott pozíció továbbra is a futtatás kezdete az oldal saját koordináta-rendszerében, a bal alsó sarokból mérve, felfelé növekvő Y-nal – ugyanaz az origó, amit minden TextOut használ; az RtLTextOut a glifák sorrendjét változtatja meg, nem pedig azt, hogy az oldal honnan mér. És mint minden rajzolási hívásnál, a SetFont-nak kell először következnie, és minden AddPage után meg kell ismételni, mert az aktuális betűtípus nem éli túl az oldaltörést. Felejtse el az ismétlést, és a második oldal visszatér ahhoz a betűtípushoz, amelyik aktív volt, ami az arab esetében általában üres dobozokat jelent

Nem fordítja meg a már Ön által megfordított szöveget

Az egyetlen hiba, amely a legtöbb hibakeresési (debugging) időt emészti fel itt, az, ha egy olyan karakterláncot adunk az RtLTextOut-nak, amelyet már kézzel megfordítottunk. Az emberek akkor jutnak el ehhez a metódushoz, amikor a sima TextOut-tal tett első kísérletük visszafelé sült el, és egy gyakori áthidaló megoldás (stopgap), hogy rajzolás előtt a kódban megfordítják a karaktereket. Az RtLTextOut belülről, saját maga végzi a megfordítást, így egy előre megfordított karakterlánc másodszor is megfordul, és pontosan oda kerül vissza, ahonnan elindult. Adja át a szöveget logikai sorrendben, abban a sorrendben, ahogyan gépelné és hangosan felolvasná, és hagyja, hogy a hívás végezze el az átrendezést

A csapda csúnyább egy egyszerű megfordításnál, mert egy kétszeresen megfordított karakterlánc helyesnek tűnhet egy tisztán arab tesztkifejezés esetében, de azonnal megtörik, amint egy sor egy latin szót vagy egy számot tartalmaz. Egy jobbról balra tartó soron belül ezeknek a beágyazott futtatásoknak balról jobbra kellene olvashatónak lenniük, és a kézi megfordítás tönkreteszi ezt a beágyazást (nesting), miközben a tiszta arab eset történetesen túléli azt. Így a hiba átcsúszik az első füstteszten (smoke test), és később bukkan fel egy valódi számlán, amelyben számlaszám szerepel. Töröljön ki minden kézi megfordítást abban a pillanatban, ahogy áttér az RtLTextOut-ra

A Direction mellékhatás, amit érdemes ismerni

Az RtLTextOut meghívása nem csak azt a sort változtatja meg, amelyet éppen rajzol. Megfordítja a dokumentum olvasási irányának preferenciáját is jobbról balra irányúra, ami ugyanaz, amit Ön maga is beállítana a Direction tulajdonságon keresztül. Ez a beállító (setter) hozzáadja a vpDirection-t a dokumentum ViewerPreferences-éhez, amely megmondja a nézegetőnek, hogyan kell elrendezni a kétoldalas oldalpárokat (two-up spreads), és melyik oldalról indul egy szemközti oldalas (facing-page) elrendezés. Amikor az egész dokumentum arab vagy héber, pontosan ez az, amit szeretne, és ezt ingyen kapja meg

Éppen azért érdemes tudni róla, mert egyetlen oldalon láthatatlan. Ha a dokumentum többnyire balról jobbra haladó, de van benne egyetlen jobbról balra haladó blokk, az első RtLTextOut hívás továbbra is átbillenti a teljes fájl preferenciáját, és az Ön egyoldalas próbanyomatában (proof) semmi sem fogja ezt megmutatni. A tünet hetekkel később jelentkezik, amikor valaki kinyomtat egy kétoldalas (duplex) füzetet, és az oldalpárok tükrözve jönnek ki. Ha nem ezt szeretné, állítsa vissza a Direction tulajdonságot kifejezetten a jobbról balra tartó futtatás után:

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

Egy olyan dokumentum esetében, amely valóban jobbról balra olvasható, hagyja békén. A lényeg az, hogy tudja, a hívásnak dokumentumszintű hatása van, így a füzet-meglepetés soha nem következik be

Regisztrálja azt a betűtípust, amit szállít, ne azt, amelyikről reméli, hogy telepítve van

Az átrendezésnek semmi jelentősége nincs, ha a betűtípusnak (font) nincsenek kirajzolható glifái. A klasszikus hiba egy olyan jelentés, amely hibátlanul renderelődik a fejlesztő gépén, ahol az Arial Unicode MS történetesen jelen van, de üres dobozok soraként jelenik meg az ügyfél szerverén, ahol a Windows csendben egy olyan betűtípust helyettesített be, amely egyáltalán nem rendelkezik arab fedettséggel (coverage). A gyógymód az, hogy ne bízzon többé a telepített rendszer betűtípusokban, és regisztráljon egy olyat, amelyet az alkalmazással együtt szállít

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

Két határvonal (boundary) is együtt jár a regisztrációval. A RegisterUnicodeTTF segítségével behozott betűtípus beágyazódik (embedded), és a HotPDF beágyazott Unicode-kezeléséhez a dokumentumnak PDF 1.5 vagy újabb verzión kell lennie; ez csak akkor okoz problémát, ha a folyamatban (downstream) valami ragaszkodik a PDF 1.4-hez, de ha igen, a hiba csendes (silent). A másik inkább jogi, mint technikai jellegű: A TrueType fájlok beágyazási engedély (embedding-permission) biteket hordoznak, és egy olyan betűkép (face), amely a képernyőn jól néz ki, lehet, hogy olyan licenccel rendelkezik, amely megtiltja az ügyféldokumentumokban való szállítását. A beágyazás előtt erősítse meg a licencet, ne egy panasz után

Egy teljes konzol példa

A darabokat összerakva, itt van egy önálló program, amely egy oldalt ír egy arab sorral, egy héber sorral és egy vegyes sorral, amely egy latin terméknevet hordoz. Minden blokk beállítja a saját karakterkészletét, majd logikai sorrendben rajzol

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.

Futtassa le, és nyissa meg az eredményt. Az arab és héber sorok jobbról balra olvashatók, a betűk ott kapcsolódnak, ahol az írás (script) összekapcsolja őket, az utolsó sorban pedig a HotPDF token balról jobbra ül az arab futtatáson belül, ami a specifikációnak megfelelő eredmény, még akkor is, ha meglep mindenkit, aki először lát kétirányú (bidirectional) elrendezést. Ezt az utolsó pontot érdemes beleírni az elfogadási kritériumokba (acceptance criteria), mielőtt egy anyanyelvi olvasó felülvizsgálná a kimenetet, mert a környező íráshoz képest a "rossz" irányban olvasandó beágyazott futtatás az a dolog, amelyet a leggyakrabban jelentenek hibaként (bug), holott nem az

A kimenet ellenőrzése

Egy olyan oldal, amely jónak tűnik, nem egyenlő azzal az oldallal, amely valóban jó, ezért ellenőrizze úgy, ahogyan egy későbbi (downstream) rendszer tenné. Másolja ki a szöveget a nézegetőből (viewer), és hasonlítsa össze a kódpontokat a forrás karakterlánccal; a helyes vizuális sorrend összekevert logikai sorrenddel egy valódi hibaüzemmód (failure mode). Futtassa le a nézegető dokumentumon belüli keresését egy olyan szóra, amelyet lát az oldalon. Ezután nyissa meg a fájlt egy olyan gépen, amelyen nincsenek meg a fejlesztési betűtípusok – ez az a gép, amely a legnagyobb valószínűséggel leleplez egy csendes helyettesítést. Ezek egyike sem helyettesíti egy anyanyelvi beszélő által végzett, egy valódi dokumentum elolvasását, amely olyan problémákat is észrevesz, amiket semmilyen szintetikus teszt karakterlánc nem fog, ezért tegye be ezt a felülvizsgálatot a naptárba, mielőtt a formátumot kiszállítja

Az RtLTextOut kezeli a kétirányú átrendezést és az arab kontextuális kapcsolódást, amely lefedi a jobbról balra készített jelentés- és dokumentummunkák nagy többségét. Ahol megáll, azok az írások (scripts), amelyeknek az átrendezésnél és az összekapcsolásnál többre van szükségük, mint például az indiai családok, valamint az opcionális OpenType funkciók, amelyek egyetlen glifát helyettesítenek, a Arab és RTL szövegformálás a HotPDF-fel című kísérő cikkben található glifa-lefedettségi és formálási részletek mellett vannak feltérképezve

Az itt bemutatott RtLTextOut, SetFont és RegisterUnicodeTTF hívások a Delphihez és C++Builderhez készült HotPDF komponens részét képezik

A frissített útmutató pontosítja a charset szerepét, az RTLTextOut viselkedését, a Direction mellékhatását és a szállított betűtípus regisztrálását