PDF Library for Delphi přijímá fonty, které webový projekt už používá. Importuje kontejnery WOFF2, vyjmenuje designové osy proměnného fontu jako JSON a instancuje zvolenou sadu souřadnic os do statického fontu, který obvyklé cesty vkládání, subsettingu a extrakce zpracují, aniž by o variacích cokoli věděly. Pokryty jsou obrysy TrueType i CFF2
Situace, kterou to řeší, je běžná a otravná. Typografie značky je definována v designovém systému, designový systém dodává soubory proměnného WOFF2 a generátor PDF neumí přečíst ani kontejner, ani data variací. Obvyklé řešení — požádat písmolijnu o statické instance TTF pro každou skutečně použitou tloušťku — trvá týdny a vyprodukuje soubory, které nikdo neudržuje synchronizované
Proč WOFF2 není jen komprimovaný font?
Zacházet s WOFF2 jako s obalem Brotli a dekomprimovat ho vám dá bajty, které nejsou platným sfnt. Formát navíc povoluje transformované a přeuspořádané tabulky: glyf, loca a hmtx lze uložit v přeuspořádané formě, kterou je nutné před parsováním fontu obrátit zpět. Obecný dekompresor to udělat nedokáže
Na Windows platforma poskytuje analýzu a rozbalení kontejneru od Windows 10 verze 1703, takže knihovna používá systémovou implementaci místo dodávání dalšího dekodéru. Toto rozhodnutí udržuje nasazení bez extra DLL a znamená, že obrácení transformace řeší stejný kód, který používá vlastní textový zásobník operačního systému. Na jiných platformách WOFF1 stále prochází čistě pascalovskou cestou a WOFF2 se výslovně hlásí jako nepodporovaný, což je čestná odpověď, ne tiše poškozený font
Importní cesta validuje před dekódováním i po něm: 48bajtová hlavička, délka kontejneru, rezervovaná pole, deklarovaná velikost sfnt a strop 512 MiB se kontrolují nejdřív a velikost dekódovaného streamu se poté porovná s deklarací. Webový font je stažený obsah a dva stojí za zablokování způsoby selhání jsou neshoda velikosti a dekompresní bomba
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 vstupují přes stejné volání
FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
if FontID <= 0 then
raise Exception.Create('font could not be imported');
// Co tento font vlastně umí?
if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
Log(AxesJSON); // značky, minimum, výchozí hodnota, maximum pro osu
// Zapečení jedné designové polohy do statického fontu
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;
Co musí instanciace znovu sestavit a co odstraňuje
Proměnný font ukládá jeden výchozí obrys na glyf plus delty indexované pozicí v designovém prostoru. Vytvoření statické instance znamená interpolovat tyto delty na požadovaných souřadnicích, posunout body a pak přepočítat vše, co je z nich odvozeno
U obrysů TrueType to znamená parsovat fvar, avar, gvar, HVAR a MVAR, aplikovat delty s hodnotami sdíleného i soukromého číslování bodů a interpolovat body, které nenesou žádnou explicitní deltu. Vodorovné fantomové body spolu s HVAR určují šířku posunu a levý boční odsazení. Složené glyfy potřebují nejdřív rozřešit své závislosti komponent a mapování bodů a pak aplikovat posun, škálování, matici a offsety připojení — s limity hloubky a detekcí cyklů, protože jinak by se škodlivý graf komponent mohl rekurzivně opakovat donekonečna
Metadata se přesouvají také, a to je část, kterou většina implementací přeskočí. MVAR aktualizuje globální metriky v hhea, OS/2 a post, takže rozestup řádků, pozice podtržení a úhel kurzívy odpovídají instanci, ne výchozímu stavu. Osy wght, wdth, ital a slnt navíc aktualizují třídu tloušťky, třídu šířky, příznaky výběru, macStyle a úhel kurzívy, takže nástroj zkoumající statický font nahlásí styl, o který jste žádali. Font, jehož geometrie je tučná a jehož metadata říkají normální, bude nesprávně označen ve všem navazujícím
Výstup pak zplošťuje složené obrysy a odstraňuje tabulky, které se už neuplatní: tabulky variací, hinty, tabulky zařízení a jakoukoli signaturu. Hranice glyfů, souhrnné metriky hhea, stropy hintů v maxp, kontrolní součty jednotlivých tabulek i celofontový kontrolní součet se všechny znovu přepočítají
CFF2 jde ke stejnému cíli jinou cestou
CFF2 nelze vložit přímo jako fontový program PDF: jeho charstrings nenesou žádné šířky a spoléhá na okolní tabulky OpenType pro pořadí glyfů a metriky. Instanciace fontu CFF2 proto vyprodukuje místo toho font OpenType CFF s klíčováním CID, který zvládnou existující cesty kreslení, vkládání a subsettingu
Načtení fontu CFF2 okamžitě vyprodukuje statickou instanci s výchozími souřadnicemi pro aktuální dokument, zatímco původní sfnt CFF2 zůstává zachovaný na objektu fontu, takže výčet os a pozdější instanciace stále vidí celý designový prostor. Každý glyf se během instanciace individuálně zbaví subrutin, což zcela odstraní aritmetiku variací a nechá existující subsetter pracovat beze změny. Cenou je, že statická instance může být o něco větší než ta, která si podržela sdílené subrutiny — férová cena za to, že nepotřebujete druhou implementaci subsettingu
Ověřování instance, než jí důvěřujete
Instanciace je typ funkce, který může být jemně špatný způsobem, jaký žádná výjimka nenahlásí. Tři kontroly zachytí téměř vše
Exportujte instanci a otevřete ji v nástroji pro fonty. SaveFontToFile zapíše zachovaný fontový program, takže lze instanci v paměti vypsat i bez vytvoření PDF. Exportovaný soubor by se měl čistě naparsovat, neobsahovat žádné tabulky variací a hlásit metadata stylu, o která jste žádali
Porovnejte posuny na nedefaultní pozici. Pokud váš pipeline dokáže vyprodukovat stejnou instanci jiným způsobem, porovnejte šířky posunu a hranice křivek glyf po glyfu; referenční implementace se zde shodovala s nezávislou instanciací 313glyfového dvouosého fontu s maximálním rozdílem nula napříč všemi glyfy
A extrahujte text zpátky z vygenerovaného PDF. Instance, která se vloží, ale nevyprodukuje extrahovatelný text, ztratila své mapování ToUnicode, což má význam pro vyhledávání a přístupnost ještě dlouho poté, co prošla vizuální kontrola. Širší cestu extrakce popisuje článek extrakce textu, obrázků a fontů
// Vypsání vygenerované instance ke kontrole nebo pro opětovné použití
Lib.SaveFontToFile('Inter-620-87.ttf');
// Požadavek na souřadnice mimo deklarovaný rozsah se ořízne,
// takže čtěte JSON os místo předpokladu limitů fontu
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}
Praktické poznámky pro pipeline firemního fontu
Ukládejte instance do mezipaměti podle sady souřadnic. Každá odlišná sada souřadnic je odlišný fontový program, takže dokument míchající tloušťku 400 a tloušťku 620 vloží dva subsety místo jednoho sdíleného řezu. Opakované generování stejné instance je zbytečná práce; generování desítek téměř identických tlouštěk je zbytečná velikost souboru
Měřte s instancí, se kterou budete kreslit. Kondenzovaná instance má užší posuny než výchozí, takže jakékoli rozvržení změřené před instanciací měřilo špatné šířky. Toto je jednou nejčastější příčina textu, který přeteče svůj rámeček jen v produkci
Ponechte si záložní cestu pro glyfy, které vaší značkovému fontu chybí. Proměnný firemní font obvykle pokrývá latinku a málo co jiného, a report obsahující cyrilské jméno zákazníka by jinak zobrazil chybějící glyfy; automatický záložní mechanismus popisuje článek automatická záloha fontu pro chybějící glyfy. A když je cílem menší soubor, ne konkrétní tloušťka, subsetting odvede víc práce než instanciace, jak popisuje článek optimalizace velikosti souboru PDF a subsetting fontů
Import webových fontů, proměnná instanciace, subsetting a záloha jsou součástí stejného fontového pipeline pro Delphi, C++Builder a Free Pascal; úplný seznam funkcí najdete na stránce PDF Library for Delphi