Tehnički članak

Veb fontovi u PDF-u: WOFF2 i promenljive ose u Delphi-ju

PDF Library for Delphi prihvata fontove koje veb projekat već isporučuje. Uvozi WOFF2 kontejnere, nabraja dizajn-ose promenljivog fonta kao JSON, i instancira izabrani skup koordinata osa u statičan font koji obične putanje za ugrađivanje, formiranje podskupa i ekstrakciju obrađuju bez ikakvog znanja o varijacijama. Pokrivene su i TrueType konture i CFF2 konture

Situacija koju ovo rešava je česta i iritantna. Tipografija brenda je definisana u dizajn sistemu, dizajn sistem isporučuje promenljive WOFF2 fajlove, a generator PDF-a ne može pročitati ni kontejner ni podatke o varijaciji. Uobičajeno zaobilazno rešenje — traženje od livnice fontova statičnih TTF instanci svake stvarno korišćene debljine — traje nedeljama i proizvodi fajlove koje niko ne održava usklađenim

Zašto WOFF2 nije samo komprimovan font?

Tretiranje WOFF2 kao Brotli omotača i njegova dekompresija daje vam bajtove koji nisu validan sfnt. Format takođe dozvoljava transformisane i preraspoređene tabele: glyf, loca i hmtx mogu biti sačuvane u preraspoređenom obliku koji mora biti preokrenut pre nego što font postane parsibilan. Generički dekompresor to ne može da uradi

Na Windows-u platforma obezbeđuje analizu i raspakivanje kontejnera od Windows-a 10 verzije 1703, tako da biblioteka koristi sistemsku implementaciju umesto da isporučuje još jedan dekoder. Ta odluka drži implementaciju bez dodatnog DLL-a i znači da preokretanje transformacije obrađuje isti kod koji koristi sopstveni tekstualni stek operativnog sistema. Na drugim platformama, WOFF1 i dalje putuje čistom Pascal putanjom, a WOFF2 eksplicitno prijavljuje kao nepodržan, što je pošten odgovor umesto tiho oštećenog fonta

Putanja uvoza validira pre dekodiranja i posle njega: zaglavlje od 48 bajtova, dužina kontejnera, rezervisana polja, deklarisana veličina sfnt-a i gornja granica od 512 MiB se svi prvo proveravaju, a veličina dekodovanog stream-a se naknadno poredi sa deklaracijom. Veb font je preuzeti sadržaj, a dva načina neuspeha vredna blokiranja su neslaganje veličine i decompression bomba

Ovaj dijagram pipeline-a prikazuje kako Delphi PDF biblioteka uvozi WOFF2 veb font kroz validaciju zaglavlja, sistemsko dekodiranje, preokretanje sfnt transformacije i proveru veličine posle dekodiranja
WOFF2 kontejneri stižu kao preuzeti sadržaj, tako da uvoz validira zaglavlje i deklarisane veličine pre i posle dekodiranja
uses
  PDFlibrary;

var
  Lib: TPDFlib;
  FontID, InstanceID: Integer;
  AxesJSON: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;
    Lib.SetPageSize('A4');
    Lib.NewPage;

    // WOFF2, WOFF1, TTF, OTF i TTC svi ulaze kroz isti poziv
    FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
    if FontID <= 0 then
      raise Exception.Create('font could not be imported');

    // Šta ovaj font zapravo može da uradi?
    if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
      Log(AxesJSON);   // oznake, minimum, podrazumevano, maksimum po osi

    // Uspeci jednu dizajn-lokaciju u statičan font
    InstanceID := Lib.CreateOpenTypeFontVariation(FontID,
      'wght=620,wdth=87.5');

    Lib.DrawOpenTypeText(56, 760, InstanceID, 24, 'Quarterly results', 0);
    Lib.SaveToFile('report.pdf');
  finally
    Lib.Free;
  end;
end;

Šta instanciranje mora da ponovo izgradi, i šta uklanja

Promenljiv font čuva jednu podrazumevanu konturu po glifu plus delte indeksirane po poziciji u dizajn prostoru. Proizvodnja statične instance znači interpolaciju tih delti na traženim koordinatama, pomeranje tačaka, a zatim ponovno izračunavanje svega izvedenog iz njih

Za TrueType konture to znači parsiranje fvar, avar, gvar, HVAR i MVAR, primenu tuple delti sa deljenom i privatnom numeracijom tačaka, i interpolaciju tačaka koje ne nose eksplicitnu deltu. Horizontalne fantomske tačke zajedno sa HVAR određuju advance width i levi bočni razmak. Kompozitnim glifovima prvo treba razrešiti zavisnosti komponenti i mapiranja tačaka, a zatim primeniti translaciju, skaliranje, matricu i ofsete pripajanja — sa ograničenjima dubine i detekcijom ciklusa, jer bi zlonameran graf komponenti inače rekurzirao bez kraja

Metapodaci se takođe pomeraju, i ovo je deo koji većina implementacija preskače. MVAR ažurira globalne metrike u hhea, OS/2 i post, tako da razmak reda, pozicija podvlačenja i ugao kurziva odgovaraju instanci, a ne podrazumevanoj vrednosti. Ose wght, wdth, ital i slnt dodatno ažuriraju klasu debljine, klasu širine, zastavice selekcije, macStyle i ugao kurziva, tako da alat koji ispituje statičan font prijavljuje stil koji ste tražili. Font čija je geometrija bold, a čiji metapodaci kažu regular, biće pogrešno označen svuda dalje niz tok

Izlaz zatim spljoštava kompozitne konture i uklanja tabele koje se više ne primenjuju: tabele varijacije, hint-ove, device tabele i svaki potpis. Granice glifova, sumarne metrike hhea, gornje granice hint-a u maxp, kontrolne sume po tabeli i kontrolna suma celog fonta se sve ponovo izračunavaju

Ovaj dijagram prikazuje kako instanciranje promenljivog fonta ponovo izgrađuje TrueType face interpolacijom gvar delti, proširivanjem kompozitnih glifova, ponovnim izračunavanjem advance-a i prepisivanjem metapodataka stila pre uklanjanja tabela varijacije
Instanciranje ponovo izgrađuje sve što su delte dodirnule, zatim uklanja mehaniku varijacije tako da obično ugrađivanje vidi običan statičan font

CFF2 dolazi drugačijim putem do istog mesta

CFF2 se ne može direktno ugraditi kao PDF font program: njegovi charstring-ovi ne nose širine, i oslanja se na okolne OpenType tabele za redosled glifova i metrike. Zato instanciranje CFF2 fonta umesto toga proizvodi CID-keyed CFF OpenType font, koji postojeće putanje za crtanje, ugrađivanje i formiranje podskupa već obrađuju

Učitavanje CFF2 fonta odmah proizvodi statičnu instancu podrazumevanih koordinata za tekući dokument, dok se originalni CFF2 sfnt zadržava na objektu fonta tako da nabrajanje osa i kasnije instanciranje i dalje vide pun dizajn prostor. Svaki glif se pojedinačno de-subrutinizuje tokom instanciranja, što potpuno uklanja aritmetiku varijacije i omogućava postojećem alatu za formiranje podskupa da radi nepromenjeno. Kompromis je da statična instanca može biti nešto veća od one koja je zadržala deljene subrutine — poštena cena za to što nije potrebna druga implementacija formiranja podskupa

Verifikacija instance pre nego što joj poverujete

Instanciranje je vrsta funkcije koja može biti suptilno pogrešna na načine koje nijedan izuzetak ne prijavljuje. Tri provere hvataju gotovo sve

Izvezite instancu i otvorite je u alatu za fontove. SaveFontToFile upisuje zadržani font program, tako da instanca u memoriji može biti izbačena bez prethodne proizvodnje PDF-a. Izvezeni fajl treba čisto da se parsira, ne sme sadržati tabele varijacije, i treba da prijavi metapodatke stila koje ste tražili

Uporedite advance vrednosti na nepodrazumevanoj lokaciji. Ako vaš pipeline može proizvesti istu instancu na drugi način, uporedite širine advance-a i granice krivih glif po glif; referentna implementacija ovde se poklopila sa nezavisnim instanciranjem fonta od 313 glifova sa dve ose sa maksimalnom razlikom od nula kroz svaki glif

I izvucite tekst nazad iz generisanog PDF-a. Instanca koja se ugrađuje, ali ne proizvodi izdvojiv tekst, izgubila je svoje ToUnicode mapiranje, što je bitno za pretragu i pristupačnost dugo nakon što je vizuelna provera prošla. Šira putanja ekstrakcije obrađena je u članku ekstrakcija teksta, slika i fontova

Ovaj dijagram prikazuje tri provere verifikacije za generisanu instancu promenljivog fonta: izbacivanje izvezenog fajla fonta, poređenje advance vrednosti glifova sa nezavisnim instanciranjem i izvlačenje teksta nazad iz PDF-a
Suptilno pogrešna instanca ne baca nijedan izuzetak, tako da se izvezeni fajl, advance vrednosti glifova i izdvojeni tekst redom verifikuju
// Izbaci generisanu instancu za ispitivanje ili ponovnu upotrebu
Lib.SaveFontToFile('Inter-620-87.ttf');

// Ako tražite koordinate van deklarisanog opsega, one se ograničavaju (clamp),
// pa čitajte axis JSON umesto da pretpostavljate granice fonta
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}

Praktične napomene za pipeline brend fontova

Keširajte instance po skupu koordinata. Svaki različit skup koordinata je poseban font program, tako da dokument koji meša debljinu 400 i debljinu 620 ugrađuje dva podskupa umesto jednog deljenog face-a. Ponovljeno generisanje iste instance je izgubljen posao; generisanje desetina skoro identičnih debljina je izgubljena veličina fajla

Merite sa instancom kojom ćete crtati. Condensed instanca ima uže advance vrednosti od podrazumevane, tako da svaki raspored koji je merio pre instanciranja merio je pogrešne širine. Ovo je jedan najčešći uzrok teksta koji prelazi granice svoje kutije samo u produkciji

Zadržite fallback putanju za glifove kojih vašem brend fontu nedostaje. Promenljiv brend font tipično pokriva latinicu i malo šta drugo, a izveštaj koji sadrži ime klijenta na ćirilici će inače pokazati nedostajuće glifove; automatski mehanizam fallback-a opisan je u članku automatski fallback fonta za nedostajuće glifove. A kada je cilj manji fajl, a ne konkretna debljina, formiranje podskupa obavlja više posla nego instanciranje, kako je obrađeno u članku optimizacija veličine PDF fajla i formiranje podskupa fonta

Uvoz veb fontova, instanciranje promenljivih fontova, formiranje podskupa i fallback deo su istog pipeline-a za fontove za Delphi, C++Builder i Free Pascal; potpuna lista funkcija nalazi se na stranici PDF Library za Delphi