HotPDF iscrtava ugrađene PDF fontove u Delphiju bez instaliranja bilo čega na računalo: ugrađeni cjevovod za iscrtavanje glifova u HPDFGlyphRender.pas analizira programe fontova pohranjene unutar samog PDF-a — TrueType konture glyf iz FontFile2, CFF Type 2 nizove znakova (charstrings) iz FontFile3 i struje sadržaja glifova Type 3 — te ih reproducira kao popunjene GDI vektorske staze. Ovaj je članak dubinsko istraživanje vjernosti fonta iza iscrtavanja PDF stranica u TBitmap pomoću HotPDF-a: taj dio pokriva iscrtavač kao cjelinu, dok ovaj pokriva kako tekst na tim stranicama dobiva svoje točne oblike
Okviri (kvadratići), praznine ili suptilno pogrešni znakovi u iscrtanom PDF izlazu gotovo uvijek znače da je iscrtavač od operacijskog sustava zatražio font umjesto da koristi onaj ugrađen u datoteci. Pritužba stiže na isti način svaki put: dokument izgleda savršeno na računalu koje ga je proizvelo, a zatim ga kupac otvori na čistom poslužitelju ili zaključanoj radnoj površini, a japanski račun prikazuje 'tofu' kvadrate ili zamjenski izgledom sličan font pomiče svaki prijelaz retka. Fontovi nikada nisu bili na tom računalu — samo unutar PDF-a — a iscrtavač koji se zaustavlja na zamjeni fontovima sustava ne može ih vidjeti. Podskupovi fontova (subset fonts) čine stvar još gorom: podskup može nositi četrdeset glifova pod kodovima znakova dodijeljenim privatno toj jednoj datoteci, što je dodjela koju nijedan instalirani font ne dijeli
ISO 32000-1 §9.9 definira tri nositelja za ugrađeni program fonta u deskriptoru fonta: FontFile drži izvorni 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, ne ugrađuje ništa binarno — svaki je glif mala struja PDF sadržaja koja se izvršava na licu mjesta. Ova tri nositelja razlikuju se u matematici kontura (kvadratni B-splineovi naspram kubičnih charstringova naspram proizvoljnih operatora stranice), pa vjerni iscrtavač treba poseban interpreter za svaki, plus sloj kodiranja koji pretvara kodove znakova u ispravan indeks glifa prije nego što se dotakne bilo koja kontura
Kako HotPDF pretvara TrueType konture glyf-ova u GDI staze?
THPDFEmbeddedTTF u HPDFGlyphRender.pas čita tablicu loca kako bi locirao svaki zapis glifa, prolazi konture glyf točku po točku i emitira GDI stazu. Dvije TrueType konvencije zahtijevaju eksplicitno rukovanje. Prvo, uzastopne točke izvan krivulje (off-curve) impliciraju točku na krivulji u njihovoj središnjoj točki, a kontura čije su sve točke izvan krivulje počinje na sredini njezine zadnje i prve točke — ako preskočite bilo koje pravilo, zaobljeni glifovi dobivaju ravne plohe ili se raspadaju. Drugo, TrueType krivulje su kvadratni Bézierovi dok GDI-jev PolyBezierTo prima kubične krivulje, tako da je svaki kvadratni segment točno povišenog stupnja umjesto da bude spljošten u segmente linija
// 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
Povišenje stupnja je bez gubitaka (lossless): kubična krivulja prati identičnu krivulju, pa se iscrtana kontura podudara s onom koju sukladan čitač iscrtava iz iste tablice, na bilo kojem zumiranju. Preostali rad je postavljanje. Svaki je glif izrađen u jedinicama fonta (obično mreža od 1000 ili 2048 jedinica po em-u), a iscrtavač sastavlja matricu skaliranja, matricu teksta i trenutnu matricu transformaciju u jednu transformaciju glifa u uređaj (glyph-to-device) prije nego što se staza popuni. Redoslijed je ovdje važniji nego što se čini: ako iste tri matrice sastavite unatrag, svaki se glif skuplja prema ishodištu — stranica izgleda pogrešno, a stvarna pogreška je samo jedan redak matrične algebre
Kako interpreter Type 2 charstring-a rukuje CFF fontovima
THPDFEmbeddedCFF daje programima FontFile3 stvarni interpreter Type 2 charstring-a: analizira strukture CFF INDEX, Top DICT i Private DICT, zatim izvršava svaki charstring i emitira segmente staze izravno u GDI. OpenType omot (spremnik OTTO) se prvo uklanja kako bi se došlo do same CFF tablice; čiste struje CIDFontType0C i Type1C troše se izravno. Charstrings su kompaktan jezik stoga (stack language), a tri njegove konvencije odlučuju ostaje li interpreter sinkroniziran s tokom bajtova. Neobavezni prefiks širine znači da prvi operator koji čisti stog može nositi jedan dodatni vodeći operand. Operator hintmask implicira vstemhm kada su operandi još na stogu, a broj bajtova maske koje treba preskočiti ovisi o nakupljenom broju stabljika (stem count) — pogriješite u brojanju jednom i svaki sljedeći operacijski kod (opcode) bit će pogrešno pročitan. A pozivi podrutina dodaju odmak (bias) svom indeksu (107, 1131 ili 32768, ovisno o broju podrutina) prije pretraživanja, pa poziv bez odmaka završava na potpuno pogrešnoj podrutini
CFF s CID ključem dodaje jednu indirekciju koja zbunjuje jednostavne implementacije: kod znaka odabire CID, ali indeks charstring-a je GID, a charset fonta mapira GID u CID — pa iscrtavač gradi obrnuto CID-u-GID mapiranje prije crtanja i odabire Private DICT po glifu putem FDSelect za fontove koji nose nekoliko njih. Programi Type1C s ključem naziva (name-keyed), uobičajeni nositelj za jednostavne Type 1 fontove, umjesto toga razrješavaju jednobajtne kodove kroz ugrađeno kodiranje CFF programa ili kroz mehanizam kodiranja na razini PDF-a opisan u nastavku. Jedno iskreno upozorenje: interpreter čita hint operatore kako bi tok ostao sinkroniziran, ali ne izvršava hinting (zaglađivanje rubova), što je granica o kojoj se raspravlja na kraju
Što je Type 3 font i kako se crta?
Glif Type 3 uopće nije kontura — ISO 32000-1 §9.6.5 definira ga kao struju sadržaja, pa ga HotPDF iscrtava guranjem (pushing) stanja grafike, sastavljanjem matrice fonta, veličine fonta i matrice teksta na CTM, te izvršavanjem procedure glifa kroz isti interpreter operatora koji crta stranice, s vlastitim /Resources fonta u opsegu. Dva detalja specifikacije važna su za ispravnost. Širine /Widths Type 3 izražene su u prostoru glifa, a ne u prostoru teksta 1/1000 koji koristi svaki drugi tip fonta, tako da pomaci moraju proći kroz /FontMatrix — fontovi bar koda s matricom 0.01 inače bi radili korake pogrešne za red veličine. A procedura glifa koja se otvara operatorom d1 daje dva obećanja koja iscrtavač provodi: crtanje je izrezano (clipped) na deklarirani granični okvir (bounding box), a prema ISO 32000-1 §9.6.5.2 glif ignorira vlastite operatore boja i crta s trenutnom bojom ispune pozivatelja, tako da su rg, g, k i njihovi srodnici za crtanje linija unutar procedure potisnuti tijekom trajanja tog glifa. Preskočite pravilo o bojama i d1 font bar koda koji stranica ispisuje u plavoj boji ispada crn; preskočite izrezivanje i loše oblikovan glif crta izvan svoje ćelije
Kako kodovi znakova postaju ID-ovi glifova
Interpreteri kontura su samo pola posla, jer su bajtovi u tekstualnom nizu PDF-a kodovi znakova, a ne indeksi glifova, i ISO 32000-1 posvećuje dva pododjeljka tom mapiranju. Za jednostavne fontove, §9.6.6 propisuje strogi prioritet: niz /Differences nadjačava osnovno kodiranje (WinAnsiEncoding, MacRomanEncoding ili StandardEncoding), koje pak nadjačava vlastitu mapu programa fonta. HotPDF razrješava taj lanac u tablicu mapiranja koda u GID s 256 unosa, prevodeći nazive glifova u indekse glifova na tri načina: točnim podudaranjem skupa znakova (charset) unutar CFF programa, brojčanim nazivima gNN/glyphNN uzetim kao doslovni indeksi i Adobe Glyph List prevođenjem naziva u Unicode nakon čega slijedi pretraživanje cmap-a za TrueType programe. Za kompozitne fontove, §9.7 stavlja CIDToGIDMap na čelo: uobičajeni slučaj je /Identity, ali stavka može biti struja big-endian parova indeksiranih CID-om — a HotPDF-ov vlastiti Unicode izlaz koristi točno taj oblik struje za kompaktne podskupove, tako da putanja struje nije rijedak izuzetak
// /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 potrebno pretraživanje TrueType cmap-a, HotPDF prolazi kroz lanac zamjenskih rješenja (fallback) umjesto da vjeruje jednoj podtablici: prve su Windows Unicode podtablice (format 4, zatim format 12 za dodatne ravnine), zatim podtablica simbola (3,0) sa svojom F000 konvencijom privatnog područja preslikanom na niski bajt — što je razlog zašto font simbola poput Wingdings odgovara na obične ASCII kodove — a zatim naslijeđeni formati 6 i 0. Podtablice formata 2 namjerno se ne interpretiraju: one mapiraju naslijeđene višebajtne kodne stranice kao što su Shift-JIS i Big5, a ne Unicode, a moderni CJK fontovi ionako bez iznimke nose podtablicu formata 4 ili formata 12. Bilo koji kod koji ne preživi nijedan od ovih putova vraća se na GDI crtanje za taj pojedinačni glif, tako da jedan znak koji se ne može mapirati kvari samo jedan glif, a ne cijeli tekstualni tijek
Što ugrađeni put ne čini
Granice vrijedi jasno navesti. Hinting se ne izvršava — konture se popunjavaju onako kako su stvorene, što se ne razlikuje od iscrtavanja s hintingom na 150 DPI i više, ali se može razlikovati za piksel kod iscrtavača s hintingom na vrlo malim veličinama. Izvorni programi Type 1 u FontFile (eexec-kodirani charstrings) se ne interpretiraju, a osi OpenType varijabilnih fontova se ne primjenjuju; u oba slučaja, kao i kod oštećenog programa fonta ili tablice glyf bez ikakve upotrebljive cmap, vraća se na crtanje sistemskim fontom umjesto kvara cijele stranice. Isti pristup s fokusom na vjernost proširuje se i na druga mjesta u iscrtavaču — aksijalni i radijalni uzorci sjenčanja dobivaju isti tretman kakav zaslužuju gradijenti — a strana generiranja ima vlastitu priču o suptilnostima fontova u tome kako EndDoc raspoređuje podskupove fontova
Korištenje cjevovoda uopće ne zahtijeva kôd specifičan za font — svaki se gornji mehanizam automatski uključuje unutar poziva iscrtavanja stranice
var
Pdf: THotPDF;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('invoice-embedded-fonts.pdf') > 0 then
begin
// Embedded TrueType, CFF, and Type 3 fonts render from the
// file itself — nothing needs to be installed on this machine
Bmp := Pdf.RenderLoadedPageToBitmap(0, 144);
if Bmp <> nil then
try
Bmp.SaveToFile('page1.bmp');
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
Praktična posljedica je ona do koje je stalo vašoj korisničkoj podršci: PDF koji nosi svoje fontove iscrtava se s tim fontovima, na poslužitelju za izgradnju (build server), u Windows spremniku ili na radnoj površini korisnika koja nikada nije vidjela to pismo. Ugrađeni cjevovod za iscrtavanje glifova isporučuje se kao dio HotPDF komponente za Delphi i C++Builder — izvorne VCL knjižnice koja pokriva stvaranje, uređivanje, ekstrakciju teksta i iscrtavanje PDF-a bez ikakvih vanjskih ovisnosti o DLL-ovima