Műszaki cikk

PDF betűtípusok és szöveg: miért válnak a glifák dobozokká

Egy PDF, amely tökéletesen néz ki az ön gépén, de egy sor üres dobozként (empty boxes) jelenik meg valaki másén, a leggyakoribb betűtípus-hiba (font defect) a dokumentum szoftverekben, és ez szinte soha nem jelenti azt, hogy a szöveg hibás. A karakterek érintetlenek (intact), a kódolás rendben van, a glifák egyszerűen nincsenek ott. Ami megváltozott a két gép között, az az, hogy az operációs rendszer milyen betűtípusokat telepített, és a különbség (gap) egy hordozható és egy törékeny (fragile) fájl között egyetlen döntésen múlik, amelyet az oldal írásakor hoztak: hogy a betűtípus a PDF-en belül utazott-e, vagy feltételezték a jelenlétét a túloldalon (far end)

Annak megértése, hogy miért történik ez, és hogy egy különálló hiba (separate failure) miért eredményez kereshetőnek tűnő szöveget, amely halandzsaként (gibberish) másolódik ki, azt jelenti, hogy meg kell vizsgálni, hogyan tárolja a PDF a szöveget. Nem mondatokat tárol. Glifakódokat tárol, plusz egy betűtípus programot, plusz táblázatokat, amelyek leképezik (map) az egyiket a másikra, és minden megjelenítési (rendering) vagy kinyerési (extraction) hiba a három közötti résben (gap) él. Ami következik, az ennek a gépezetnek a bemutatása, az ISO 32000-re alapozva, azokkal a Delphi hívásokkal, amelyek ott vezérlik, ahol számítanak

Karakterek, kódok és glifák – ez három különböző dolog

A szókincs (vocabulary) megbotlasztja az embereket, mert a mindennapi beszéd három különböző elképzelést olvaszt (collapses) a "betű" szóba. A karakter (character) az írás absztrakt egysége, a nagy A betű gondolata, amelyet a Unicode U+0041-ként azonosít. A glifa (glyph) egy megrajzolt alakzat, az a görbe-és-szár (curve-and-stem) körvonal (outline), amelyet egy adott betűtípus használ a karakter ábrázolására. Közöttük helyezkedik el a kód: az a bájt vagy bájtok a tartalomfolyamban (content stream), amelyek megmondják a megjelenítőnek, hogy az aktuális betűtípus melyik glifáját fesse meg

A PDF kódokban dolgozik. Amikor egy tartalomfolyam egy karakterláncot mutat, ezek a bájtok indexek az aktív betűtípusba, nem pedig Unicode. A betűtípus kódolása (encoding) dönti el, hogy a 65-ös kód azt jelenti, hogy "rajzold le a 65-ös alá besorolt glifát", és ebből a műveletből semmi nem tudja, hogy az eredmény egy ember számára A betűnek tűnik. Ez az, ami miatt a PDF mindenhol egyformán jelenik meg, ahol megtalálja a glifákat, és emiatt a kinyerés is különálló probléma a megjelenítéstől: a rajzoláshoz csak kód-glifa, az olvasáshoz pedig kód-Unicode leképezésre van szükség, és ez a kettő két különböző táblázat, amelyek egymástól függetlenül (independently) eltérhetnek egymástól vagy hiányozhatnak

A betűtípusok (font types), amelyekkel valójában találkozni fog

Az ISO 32000 számos betűtípus szótár típust határoz meg, és a gyakorlatban az ön által kapott vagy generált dokumentum e három egyikét használja. Ha tudja, melyiket nézi, az megmagyarázza a legtöbb dolgot, ami elromolhat

A Type 1 az Adobe eredeti PostScript körvonal formátuma, amely köbös Bezier-görbékből épül fel. Az a tizennégy szabványos betűtípus, amelyet minden megfelelő (conforming) olvasónak biztosítania kell – a Helvetica, a Times, a Courier, a Symbol és a ZapfDingbats családok – Type 1-esek, és az egyiküket megnevező betűtípus szótár legálisan (legally) kihagyhatja a betűtípus programot. Ez az egyetlen eset, amikor egy betűtípus beágyazatlanul hagyása a specifikáció szerint (by specification) és nem pedig szerencse alapján biztonságos. Bármely más Type 1 betűkép (face) esetén a programot be kell ágyazni, különben a megjelenítő (viewer) valami mással helyettesíti azt, általában egy metrikusan hasonló, de vizuálisan eltérő betűtípussal

A TrueType kvadratikus görbéket használ, és az Apple és a Microsoft világából származik. A legtöbb rendszer betűtípus ilyen, és ezeket fogja a leggyakrabban beágyazni. Egy egyszerű TrueType betűtípus a PDF-ben egybájtos (single-byte) kódokra korlátozódik, így egy ilyen betűtípus egyszerre legfeljebb 256 glifát tud megcímezni (address). Ez a felső határ (cap) a strukturális oka annak, hogy a CJK és más nagy szkriptek nem lovagolhatnak meg (ride on) egy egyszerű betűtípust

A Type 0, a kompozit (composite) vagy CID-kulcsú betűtípus, a válasz erre a korlátra. Több-bájtos kódokat és egy CMap-et használ, hogy azokat egy leszármazott CIDFonton (descendant CIDFont) keresztül irányítsa (route), amelynek körvonalai önmagukban TrueType vagy CFF/Type 1 formátumúak. Ez az egyetlen betűtípus, amely több ezer glifát is hordozhat, így minden olyan PDF, amely kínai, japán, koreai vagy széles többnyelvű keveréket (multilingual mix) tartalmaz, Type 0-t használ, függetlenül attól, hogy a szerző gondolt-e erre vagy sem. Az ár (trade) a komplexitás: több mozgó alkatrész (moving parts), amelyek közül többnek kell helyesnek lennie mind a megjelenítéshez, mind a kinyeréshez

Egy TrueType betűtípus 12, 18, 24 és 36 pontban megjelenítve egy PDF-ben, bemutatva, hogy egyetlen beágyazott körvonal (outline) bármilyen méretre skálázható

E kép mögött egy részlet hajtja (drives) a fájlméretet. Egy betűtípus körvonalak könyvtára (library of outlines), nem pedig fix méretű bittérképek (bitmaps), így ugyanaz a beágyazott program szolgálja ki az oldal minden pontméretét (point size). A skálázás (Scaling) egy rajzoláskor (at draw time) alkalmazott transzformáció, ami miatt egy címsor és annak törzsszövege (body text) egyetlen beágyazott betűképen (embedded face) osztozik, és ami miatt a beágyazás költsége (embedding cost) betűtípusonként, és nem méretenként értendő

A beágyazás a különbség a hordozható (portable) és a törékeny (fragile) között

A beágyazás (Embedding) azt jelenti, hogy a betűtípus program, a tényleges körvonal (outline) adatok folyamként (stream) íródnak be a PDF-be. Egy olvasó olyan gépen, amely sosem hallott az ön betűtípusáról, egyenesen a fájlból olvassa ki azokat a körvonalakat, és pontos glifákat rajzol ki. Hagyja ki a beágyazást, és arra fogad, hogy a célállomáson (destination) is megvan az azonos nevű betűtípus; ha nincs, a megjelenítő visszaesik (falls back) egy helyettesítőre (substitute). A szabványos tizennégy betűtípus (standard fourteen) esetében ez a helyettesítés definiált (defined) és jóindulatú (benign). Minden más esetben egy eltérő betűtípusnál (typeface) a közeli tévesztéstől (near-miss) egészen az üres doboz eredményéig terjedhet, amikor egyetlen helyettesítő sem fedi le teljesen a szkriptet

A HotPDF esetében a vezérlés (control) egyetlen tulajdonság, amelyet a dokumentum megnyitása előtt kell beállítani. A FontEmbedding megmondja a könyvtárnak, hogy azokat a betűképeket (faces), amelyekkel rajzol, csomagolja (pack) a fájlba:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'report.pdf';
    Pdf.Compression := cmFlateDecode;
    Pdf.FontEmbedding := True;          // a körvonalak (outlines) a fájlon belül utaznak
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Calibri', [], 11);
    Pdf.CurrentPage.TextOut(72, 760, 0, 'Ez ugyanúgy jelenik meg egy olyan gépen is, amelyen nincs Calibri.');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

A sorrendiség nem kozmetikai. A BeginDoc az, ahol a HotPDF rögzíti (commits) a dokumentum struktúrát, ezért a FontEmbedding értékének igaznak (true) kell lennie e hívás előtt. Rendelje hozzá utána, és nincs hiba, nincs figyelmeztetés, csak egy fájl, amely csendben elment a betűtípusai nélkül. Ez a legrosszabb fajta hiba (worst kind of bug): átmegy minden teszten a fejlesztő gépén, ahol a betűtípus történetesen (happens to be) telepítve van, és csak egy ügyfél gépén bukkan fel (surfaces), ahol nincs

A beágyazás az a pont is, ahol a licencelés (licensing) találkozik a mérnöki munkával (engineering). Egy betűtípus program olyan jelzőket (flags) hordoz, amelyek leírják, hogy szabadon, csak előnézethez, vagy egyáltalán nem ágyazható be. Ezen jelzők tiszteletben tartása (Honoring) az ön felelőssége, nem pedig a megjelenítőé (renderer's), és az "ez működött" nem ugyanaz, mint az "ez megengedett"

Részhalmaz-képzés (Subsetting): csak a felhasznált glifákat ágyazza be

A teljes beágyazás (Full embedding) a teljes betűtípus programot a fájlba írja. Egy nagy CJK TrueType betűkép (face) elérheti a több megabájtot is, és teljes egészében beágyazni tucatnyi karakter megjelenítéséhez oly módon pazarló (wasteful), amely egy többoldalas dokumentumban csak halmozódik (compounds). A részhalmaz-képzés (Subsetting) ezt azáltal oldja meg, hogy csak a dokumentum által hivatkozott glifákat írja ki, majd átnevezi a betűtípust egy hatbetűs címkével és egy plusz jellel, ez az ABCDEF+Calibri forma bármely részhalmazból álló (subsetted) PDF betűtípus listájában, így az olvasó soha nem téveszti össze a részleges betűképet (partial face) az azonos nevű teljes rendszer betűtípussal

A legtöbb generált dokumentum esetében a részhalmaz-képzés a megfelelő alapértelmezés. A fájlméretet a tartalommal arányosan (proportional) tartja, nem pedig a forrás betűtípussal (source font), ami leginkább a nagy többnyelvű (multilingual) betűtípusoknál számít, amelyek egyébként uralnák a fájlt. Az egyetlen kikötés (caveat), hogy egy részhalmaz csak azt tartalmazza, amit a létrehozásakor használtak. Ha egy későbbi (downstream) folyamat szöveget próbál hozzáadni egy részhalmazba (subsetted font) tett betűtípushoz, akkor a szükséges glifák esetleg nincsenek benne a fájlban; ez valós korlát (real constraint) valaki más PDF-jének inkrementális szerkesztésére vonatkozóan

Unicode betűtípusok és a CJK doboz (box) probléma

Amikor a szöveg nem egyszerű latin (plain Latin), az egyszerű betűtípus útvonala elfogy, és a megoldás egy Unicode-képes (Unicode-capable) betűtípus explicit regisztrálása, és hagyni, hogy a HotPDF egy Type 0 betűtípust építsen belőle. A RegisterUnicodeTTF egy TrueType fájlt tölt be útvonal alapján; ezt követően a regisztrált név úgy használható a SetFont metódusban, mint bármelyik másik:

Pdf.FontEmbedding := True;
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansCJKsc-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('NotoSansCJKsc-Regular', [], 14);
Pdf.CurrentPage.TextOut(72, 720, 0, '你好,世界 こんにちは 안녕하세요');
Pdf.EndDoc;

Két dolog dönti el, hogy ez működik-e, vagy sem (make or break this). A betűtípusnak le kell fednie a karakterláncban (string) lévő szkripteket: egy kizárólag latin TrueType nem növeszt kínai glifákat, csak mert ön ezt kérte, és az eredmény ismét üres doboz lesz, ezúttal azért, mert a glifa valóban (genuinely) nem létezik abban a betűképben. És a beágyazásnak bekapcsolva kell maradnia, mert egy regisztrált TTF-ből összeállított Type 0 betűtípus értelmetlen (meaningless) egy olyan olvasó számára, amelyik nem találja a körvonalakat (outlines). Vegyes (mixed) tartalmak esetében a tartós választás egy széles lefedettségű (broad-coverage) betűkép, a Noto és az Arial Unicode MS családok a szokásos válaszok, beágyazva és részhalmazként (subsetted)

A jobbról balra (Right-to-left) és az összetett szkriptek (complex scripts) egy formáló réteget (shaping layer) adnak a lefedettség tetejére. A HotPDF arab és héber nyelvekhez teszi elérhetővé (exposes) az RtLTextOut metódust, amely kezeli az irányított újrarendezést (directional reordering), így ön csak logikai sorrendet ad át, és hagyja, hogy a könyvtár elrendezze azt. Az arab nyelv helyes alkalmazása egyenlő lefedettség (coverage) plusz formázás (shaping) plusz irány (direction), vagyis három különálló dolog, és egy doboz ott azt jelentheti, hogy bármelyik is elromlott

A ToUnicode tábla: ahol a másolás és beillesztés (copy-paste) él

Minden fent leírt dolog a rajzolásra vonatkozik. A kinyerés (Extraction) a tükörképe (mirror image), és a maga okai miatt hiúsul meg (fails). Egy megjelenítő (viewer) a betűtípus kód-glifa leképezését (mapping) használja az oldal rendereléséhez, de amikor egy felhasználó kiválaszt egy szöveget és kimásolja azt, a megjelenítőnek vissza kell alakítania (turn back) ugyanezeket a kódokat Unicode-dá. Ez a fordított leképezés (reverse mapping) a ToUnicode CMap, a betűtípushoz csatolt opcionális folyam (stream)

Ha jelen van és helyes (correct), a kimásolt szöveg megfelelő (right) karakterként jelenik meg. Ha hiányzik vagy hibás, vagy ha a betűtípust egyedi glifakódokkal részhalmazozták (subsetted), és nem írtak hozzá ToUnicode-ot, az oldal tökéletesen néz ki, a vágólap (clipboard) pedig megtelik szeméttel (garbage): a glifakódok úgy lesznek beolvasva, mintha Unicode-ok lennének, amik egy egyedileg kódolt részhalmaz (custom-encoded subset) esetében nem azok. Ezért lehet egy beolvasott dokumentum, amely OCR szövegréteggel rendelkezik kereshető, míg egy gondatlan (careless) generátorból származó, digitálisan született (born-digital) PDF nem az. A megjelenítés és a kinyerés különböző táblázatokból merít (draw on), így egy fájl kielégítheti az egyiket, és megbukhat a másikon. Ha a kinyerés (extraction) számít a kimeneténél, kezelje a helyes ToUnicode leképezést követelményként, és ellenőrizze azt szöveg kimásolásával egy mintából, ahelyett, hogy bízna abban, hogy ott van

Hogyan diagnosztizáljunk gyorsan (diagnose fast) egy betűtípus-hibát

A hiba módja (failure mode) megmondja, hol keressen. Az üres dobozok (Empty boxes) egy másik gépen szinte mindig azt jelentik, hogy a betűtípust nem ágyazták be, ezért először ellenőrizze a beágyazást (embedding), másodszor pedig a glifák lefedettségét (coverage). Azok a dobozok, amelyek még a saját gépén is megjelennek, a lefedettségre mutatnak: a betűtípus nem tartalmazza azt a szkriptet, függetlenül a beágyazástól. A helyesen megjelenő (renders correctly), de értelmetlenségként (nonsense) kimásolódó szöveg ToUnicode probléma, nem pedig megjelenítési (rendering) probléma, és a betűtípusokkal vagy a beágyazással való babrálás (fiddling) nem fogja megoldani, mert a rajzolás soha nem romlott el. Egy kész fájl (finished file) elolvasásához nyissa meg azt az Acrobatban, és nézze meg a Dokumentumtulajdonságokat (Document Properties), majd a Betűtípusokat: egy egészséges (healthy) bejegyzés (entry) mutatja a típust, azt írja, hogy Beágyazott (Embedded) vagy Beágyazott Részhalmaz (Embedded Subset), és megnevezi a kódolást. Egy betűtípus, amelyet be kellene ágyazni, de nincs, ott bejelenti magát (announces itself), mielőtt egy ügyfél tenné ezt meg

Ezek közül egyik sem egzotikus, ha a karakter, a kód és a glifa közötti különbség (split) egyértelmű. Ágyazza be a betűtípusokat, amellyel rajzol, részhalmazozza a nagyokat (subset the large ones), nyúljon (reach for) egy Unicode betűképért és a RegisterUnicodeTTF-ért abban a pillanatban, ahogy a szöveg elhagyja a latint, és tartson meg egy helyes ToUnicode leképezést, ha valaki kinyerni (extract) fogja a szöveget. Csinálja ezeket helyesen, és a dobozok többé nem jelennek meg. Ami a környező mechanikát (surrounding mechanics) illeti, a minimális PDF anatómiája mutatja meg, hol található a betűtípus szótár (font dictionary) az objektumfában, és a dokumentumstruktúra bemutatása tárgyalja, hogyan oszlanak meg (shared) az erőforrások az oldalak között

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