Tehnički članak

Renderovanje ugrađenih PDF fontova u Delphi-ju pomoću HotPDF-a

HotPDF renderuje ugrađene PDF fontove u Delphi-ju bez instaliranja bilo čega na mašini: ugrađeni cevovod za renderovanje glifova u unitu HPDFGlyphRender.pas analizira programe fontova sačuvane unutar samog PDF-a — TrueType glyf konture iz FontFile2, CFF Type 2 charstrings iz FontFile3 i tokove sadržaja glifova Type 3 — i ponovo ih iscrtava kao popunjene GDI vektorske putanje. Ovaj članak je duboko zaron u vernost fontova iza renderovanja PDF stranica u TBitmap pomoću HotPDF-a: taj članak pokriva renderovanje u celini, a ovaj se bavi time kako tekst na tim stranicama dobija svoj tačan oblik

Zašto se PDF renderuje sa kvadratićima umesto teksta?

Kvadratići (tofu), praznine ili suptilno pogrešni karakteri u renderovanom PDF izlazu skoro uvek znače da je program za renderovanje tražio font od operativnog sistema umesto da koristi onaj ugrađen u fajl. Problem se uvek manifestuje na isti način: dokument izgleda savršeno na mašini na kojoj je napravljen, a zatim ga korisnik otvori na čistom serveru ili na zaključanoj radnoj površini i faktura na japanskom prikazuje "tofu" kvadrate, ili zamenjeni sličan font pomera svaki prelom reda. Ti fontovi nikada nisu bili na toj mašini — samo unutar PDF-a — a program za renderovanje koji se zaustavlja na zameni sistemskim fontom ne može da ih vidi. Podskupovi fontova (subset fonts) čine stvar još gorom: podskup može da nosi četrdeset glifova pod kodovima karaktera koji su privatno dodeljeni tom konkretnom fajlu, što je dodela koju nijedan instalirani font ne deli

ISO 32000-1 §9.9 definiše tri nosioca za ugrađeni program fonta u deskriptoru fonta: FontFile sadrži originalni Type 1 program, FontFile2 TrueType program, a FontFile3 čisti CFF (Type1C ili CIDFontType0C) ili OpenType omot. Četvrta varijanta, Type 3 font iz ISO 32000-1 §9.6.5, uopšte ne ugrađuje ništa binarno — svaki glif je mali PDF tok sadržaja koji se izvršava na licu mesta. Tri nosioca se razlikuju u matematici kontura (kvadratni B-splajnovi naspram kubnih charstring-ova naspram proizvoljnih operatora stranice), tako da je vernom programu za renderovanje potreban poseban interpretator za svaki od njih, plus sloj kodiranja koji pretvara kodove karaktera u ispravan indeks glifa pre nego što se dodirne bilo koja kontura

Kako HotPDF konvertuje TrueType konture glyf-a u GDI putanje?

Klasa THPDFEmbeddedTTF u HPDFGlyphRender.pas čita tabelu loca kako bi locirala svaki zapis glifa, prolazi kroz konture glyf-a tačku po tačku i emituje GDI putanju. Dve konvencije TrueType-a zahtevaju eksplicitno rukovanje. Prvo, uzastopne tačke van krive (off-curve) podrazumevaju tačku na krivoj (on-curve) na njihovoj sredini, a kontura čije su sve tačke van krive počinje na sredini svoje poslednje i prve tačke — preskočite bilo koje od ovih pravila i zaobljeni glifovi dobijaju ravne delove ili se kolapsiraju. Drugo, TrueType krive su kvadratne Bezijerove krive dok GDI funkcija PolyBezierTo prima kubne krive, tako da se svaki kvadratni segment tačno podiže na viši stepen (degree elevation) umesto da se ravna u linijske segmente

// Exact degree elevation: quadratic (P0, Q, P2) -> cubic (P0, C1, C2, P2)
// C1 = P0 + 2/3 (Q - P0),  C2 = P2 + 2/3 (Q - P2)
C1.X := P0.X + 2 * (Q.X - P0.X) / 3;
C1.Y := P0.Y + 2 * (Q.Y - P0.Y) / 3;
C2.X := P2.X + 2 * (Q.X - P2.X) / 3;
C2.Y := P2.Y + 2 * (Q.Y - P2.Y) / 3;
// then PolyBezierTo with C1, C2, P2 — geometrically identical curve

Podizanje stepena krive je bez gubitaka: kubna kriva prati identičnu putanju, tako da se renderovana kontura poklapa sa onim što usklađeni čitač crta iz iste tabele, na bilo kom nivou zumiranja. Preostali posao je pozicioniranje. Svaki glif je kreiran u jedinicama fonta (obično u mreži od 1000 ili 2048 jedinica po em), a program za renderovanje spaja matricu razmere, matricu teksta i trenutnu matricu transformaciju (CTM) u jednu transformaciju glifa-u-uređaj pre nego što se putanja popuni. Redosled je ovde važniji nego što se čini: spojite iste tri matrice unazad i svaki glif će se kolapsirati ka koordinatnom početku — što rezultira pogrešnim izgledom stranice gde je stvarni bag u samo jednoj liniji matrične algebre

Kako interpretator Type 2 charstring-a rukuje CFF fontovima

Klasa THPDFEmbeddedCFF daje programima FontFile3 pravi interpretator Type 2 charstring-a: analizira strukture CFF INDEX, Top DICT i Private DICT, a zatim izvršava svaki charstring i emituje segmente putanje direktno u GDI. OpenType omot (kontejner OTTO) se prvo uklanja da bi se došlo do čiste CFF tabele; čisti tokovi CIDFontType0C i Type1C se troše direktno. Charstrings su kompaktan jezik steka, i tri njegove konvencije odlučuju o tome da li će interpretator ostati sinhronizovan sa tokom bajtova. Opcioni prefiks širine znači da prvi operator koji čisti stek može nositi jedan dodatni vodeći operand. Operator hintmask podrazumeva vstemhm kada su operandi još uvek na steku, a broj bajtova maske koje treba preskočiti zavisi od akumuliranog broja stabala (stem count) — pogrešite broj samo jednom i svaki sledeći opkod će biti pogrešno pročitan. Takođe, pozivi podrutina dodaju odstupanje (bias) svom indeksu (107, 1131 ili 32768 u zavisnosti od broja podrutina) pre pretraživanja, tako da poziv bez odstupanja završava u potpuno pogrešnoj podrutini

CFF sa CID ključevima dodaje jedan nivo indirekcije na kojem jednostavne implementacije često padaju: kod karaktera bira CID, ali indeks charstring-a je GID, a charset fonta mapira GID u CID — tako da program za renderovanje gradi inverzno CID-u-GID mapiranje pre crtanja i bira Private DICT po glifu preko FDSelect za fontove koji nose nekoliko njih. Programi Type1C sa ključevima po imenu, uobičajeni nosioci za jednostavne Type 1 fontove, umesto toga razrešavaju jednobajtne kodove kroz ugrađeno kodiranje CFF programa ili kroz mehanizam kodiranja na nivou PDF-a opisan u nastavku. Jedna iskrena ograda: interpretator čita operatore nagoveštaja (hint operators) da bi održao sinhronizaciju toka, ali ne izvršava nagoveštavanje (hinting), što je ograničenje o kojem se govori na kraju

Šta je Type 3 font i kako se crta?

Glif Type 3 uopšte nije kontura — ISO 32000-1 §9.6.5 ga definiše kao tok sadržaja, pa ga HotPDF renderuje tako što čuva stanje grafike (push graphics state), postavlja matricu fonta, veličinu fonta i matricu teksta na CTM, i izvršava proceduru glifa kroz isti interpretator operatora koji crta stranice, sa sopstvenim rečnikom /Resources fonta u opsegu. Dva detalja iz specifikacije su važna za ispravnost. Širine /Widths kod Type 3 su izražene u prostoru glifa, a ne u tekstualnom prostoru 1/1000 koji koristi svaki drugi tip fonta, tako da pomeraji moraju proći kroz /FontMatrix — inače fontovi za bar-kodove sa matricom 0.01 prave pogrešne korake za čitav red veličine. Takođe, procedura glifa koja počinje operatorom d1 daje dva obećanja koja program za renderovanje sprovodi: crtanje je ograničeno (clipped) na deklarisani uokvirujući pravougaonik (bounding box), i prema ISO 32000-1 §9.6.5.2 glif ignoriše sopstvene operatore boja i crta se sa trenutnom bojom popune pozivaoca, tako da su rg, g, k i njihovi srodnici za iscrtavanje linija unutar procedure potisnuti tokom trajanja tog glifa. Preskočite pravilo o boja i font za bar-kod d1 koji stranica treba da ispiše plavom bojom izlazi crn; preskočite ograničenje i loše formiran glif crta izvan svoje ćelije

Kako kodovi karaktera postaju ID-ovi glifova

Interpretatori kontura su samo polovina posla, jer su bajtovi u PDF tekstualnom stringu kodovi karaktera, a ne indeksi glifova, i ISO 32000-1 posvećuje dva podparagrafa ovom mapiranju. Za jednostavne fontove, §9.6.6 propisuje strogi prioritet: niz /Differences nadjačava osnovno kodiranje (WinAnsiEncoding, MacRomanEncoding ili StandardEncoding), koje nadjačava sopstvenu mapu programa fonta. HotPDF razrešava taj lanac u tabelu kodova-u-GID sa 256 unosa, prevodeći nazive glifova u indekse glifova na tri načina: tačnim podudaranjem skupa karaktera (charset) unutar CFF programa, numeričkim nazivima gNN/glyphNN koji se uzimaju kao doslovni indeksi, i prevodom imena-u-Unicode prema Adobe listi glifova (Adobe Glyph List) praćenim pretragom cmap tabele za TrueType programe. Za kompozitne fontove, §9.7 stavlja mapu CIDToGIDMap u nadležnost: uobičajen slučaj je /Identity, ali unos može biti tok (stream) parova u big-endian formatu indeksiranih po CID-u — a HotPDF-ov sopstveni Unicode izlaz koristi upravo tu formu toka za kompaktne podskupove, tako da ta putanja nije neki egzotični granični slučaj

// /CIDToGIDMap as a stream: big-endian Word pairs indexed by CID
if 2 * CID + 1 <= High(MapBytes) then
  GID := (MapBytes[2 * CID] shl 8) or MapBytes[2 * CID + 1]
else
  GID := 0;  // out of range maps to .notdef

Kada je potrebna pretraga TrueType cmap tabele, HotPDF prolazi kroz lanac rezervnih opcija (fallback chain) umesto da veruje jednoj podtabeli: Windows Unicode podtabele (format 4, zatim format 12 for dodatne ravni) dolaze na prvo mesto, zatim podtabela simbola (3,0) sa svojom konvencijom PUA (privatni opseg - Private Use Area) od F000 preslikanom na niski bajt — što je razlog zašto font simbola kao što je Wingdings odgovara na obične ASCII kodove — a zatim nasleđeni formati 6 i 0. Podtabele formata 2 se namerno ne interpretiraju: one mapiraju nasleđene višebajtne kodne stranice kao što su Shift-JIS i Big5, a ne Unicode, a moderni CJK fontovi ionako uvek nose podtabelu formata 4 ili formata 12. Svaki kod koji ne uspe da se razreši ni preko jedne od ovih ruta prelazi na GDI crtanje za taj pojedinačni glif, tako da jedan nemapirani karakter kvari samo jedan glif, a ne čitav blok teksta

Šta ugrađena putanja ne radi

Nagoveštavanje (hinting) se ne izvršava — konture se popunjavaju onako kako su kreirane, što se ne razlikuje od izlaza sa nagoveštavanjem na 150 DPI i više, ali se može razlikovati za jedan piksel na veoma malim veličinama. Originalni Type 1 programi u FontFile (eexec-šifrovani charstring-ovi) se ne interpretiraju, a ose OpenType varijabilnih fontova se ne primenjuju; u oba slučaja, kao i kod oštećenog programa fonta ili glyf tabele bez ikakve upotrebljive cmap tabele, vraća se na crtanje sistemskim fontom umesto da se stranica ne prikaže. Isti pristup koji daje prednost vernosti prikaza proteže se i na druge delove programa za renderovanje — aksijalni i radijalni šabloni senčenja dobijaju isti tretman kakav gradijenti i zaslužuju — a strana za generisanje ima svoju priču o detaljima fontova u članku o tome kako EndDoc uređuje podskupove fontova

Praktična posledica je upravo ono o čemu brine vaša tehnička podrška: PDF koji nosi svoje fontove renderuje se sa tim fontovima na serveru za bildovanje, u Windows kontejneru ili na radnoj površini korisnika koji nikada ranije nije imao taj font. Ugrađeni cevovod za renderovanje glifova isporučuje se kao deo HotPDF komponente za Delphi i C++Builder — izvorne VCL biblioteke koja pokriva kreiranje, uređivanje, ekstrakciju teksta i renderovanje PDF stranica bez spoljnih zavisnosti od DLL-ova