Articol tehnic

Fonturi web în PDF: WOFF2 și axe variabile în Delphi

PDF Library for Delphi acceptă fonturile pe care un proiect web le livrează deja. Importă containere WOFF2, enumeră axele de design ale unui font variabil ca JSON, și instanțiază un set ales de coordonate de axă într-un font static, pe care căile obișnuite de încorporare, subsetting și extracție îl gestionează fără să știe nimic despre variații. Sunt acoperite atât contururile TrueType, cât și cele CFF2

Situația pe care o rezolvă acest lucru este comună și enervantă. Tipografia unui brand este definită într-un design system, design system-ul livrează fișiere WOFF2 variabile, iar generatorul de PDF nu poate citi nici containerul, nici datele de variație. Soluția obișnuită — a cere fonderiei instanțe TTF statice pentru fiecare grosime efectiv folosită — durează săptămâni și produce fișiere pe care nimeni nu le ține sincronizate

De ce WOFF2 nu este doar un font comprimat?

A trata WOFF2 ca un wrapper Brotli și a-l decomprima vă oferă bytes care nu sunt un sfnt valid. Formatul permite și tabele transformate și reordonate: glyf, loca și hmtx pot fi stocate într-o formă rearanjată, care trebuie inversată înainte ca fontul să fie parsabil. Un decompresor de uz general nu poate face asta

Pe Windows, platforma oferă analiza și despachetarea containerului începând cu Windows 10 versiunea 1703, așa că biblioteca folosește implementarea de sistem, în loc să livreze un alt decodor. Această decizie păstrează desfășurarea liberă de un DLL suplimentar și înseamnă că inversarea transformării este gestionată de același cod pe care îl folosește propriul stack de text al sistemului de operare. Pe alte platforme, WOFF1 încă parcurge o cale Pascal pură, iar WOFF2 raportează explicit ca nesuportat, ceea ce este un răspuns onest, nu un font corupt silențios

Calea de import validează atât înainte de decodare, cât și după: header-ul de 48 de bytes, lungimea containerului, câmpurile rezervate, dimensiunea sfnt declarată și un plafon de 512 MiB sunt toate verificate mai întâi, iar dimensiunea stream-ului decodat este comparată cu declarația ulterior. Un font web este conținut descărcat, iar cele două moduri de eșec care merită blocate sunt o nepotrivire de dimensiune și o bombă de decompresie

Această diagramă de pipeline arată cum biblioteca PDF pentru Delphi importă un font web WOFF2 prin validarea header-ului, decodarea de sistem, inversarea transformării sfnt și un audit de dimensiune post-decodare
Containerele WOFF2 sosesc ca și conținut descărcat, așa că importul validează header-ul și dimensiunile declarate atât înainte, cât și după decodare
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 intră toate prin același apel
    FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
    if FontID <= 0 then
      raise Exception.Create('font could not be imported');

    // Ce poate face efectiv acest font?
    if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
      Log(AxesJSON);   // tag-uri, minim, implicit, maxim per axă

    // Fixează o locație de design într-un font static
    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;

Ce trebuie să reconstruiască instanțierea, și ce elimină

Un font variabil stochează un contur implicit per glifă, plus delta-uri indexate după poziția în spațiul de design. Producerea unei instanțe statice înseamnă interpolarea acelor delta-uri la coordonatele cerute, mutarea punctelor, apoi recalcularea a tot ce derivă din ele

Pentru contururile TrueType, asta înseamnă parsarea fvar, avar, gvar, HVAR și MVAR, aplicarea delta-urilor de tip tuple cu numerotare de puncte partajate și private, și interpolarea punctelor care nu poartă niciun delta explicit. Punctele fantomă orizontale, împreună cu HVAR, determină lățimea de avans și side bearing-ul stâng. Glifele compozite au nevoie ca mai întâi să le fie rezolvate dependențele de componente și mapările de puncte, apoi să fie aplicate translația, scalarea, matricea și offset-urile de atașare — cu limite de adâncime și detecție de cicluri, pentru că altfel un graf de componente malițios ar recursa la nesfârșit

Metadatele se mută și ele, iar aceasta este partea pe care majoritatea implementărilor o omit. MVAR actualizează metricile globale din hhea, OS/2 și post, astfel încât spațierea rândurilor, poziția de subliniere și unghiul italic se potrivesc cu instanța, nu cu implicitul. Axele wght, wdth, ital și slnt actualizează suplimentar clasa de grosime, clasa de lățime, flag-urile de selecție, macStyle și unghiul italic, astfel încât un instrument care inspectează fontul static raportează stilul cerut de dumneavoastră. Un font a cărui geometrie este bold și ale cărui metadate spun regular va fi etichetat greșit peste tot în aval

Output-ul aplatizează apoi contururile compozite și elimină tabelele care nu se mai aplică: tabelele de variație, hint-urile, tabelele de dispozitiv și orice semnătură. Limitele glifelor, metricile de sumar din hhea, plafoanele de hint din maxp, checksum-urile per tabel și checksum-ul întregului font sunt toate recalculate

Această diagramă arată instanțierea de font variabil reconstruind o față TrueType prin interpolarea delta-urilor gvar, extinderea glifelor compozite, recalcularea avansurilor și rescrierea metadatelor de stil, înainte de a elimina tabelele de variație
Instanțierea reconstruiește tot ce au atins delta-urile, apoi elimină mecanismul de variație, astfel încât încorporarea obișnuită vede un font static simplu

CFF2 ia o cale diferită spre același loc

CFF2 nu poate fi încorporat direct ca program de font PDF: charstring-urile lui nu poartă lățimi și se bazează pe tabelele OpenType înconjurătoare pentru ordinea glifelor și metrici. Așa că instanțierea unui font CFF2 produce, în schimb, un font OpenType CFF cu chei CID, pe care căile existente de desenare, încorporare și subsetting îl gestionează deja

Încărcarea unui font CFF2 produce imediat o instanță statică cu coordonate implicite pentru documentul curent, în timp ce sfnt-ul CFF2 original este păstrat pe obiectul font, astfel încât enumerarea axelor și instanțierea ulterioară văd în continuare întregul spațiu de design. Fiecare glifă este de-subrutinizată individual în timpul instanțierii, ceea ce elimină complet aritmetica de variație și permite subsetter-ului existent să funcționeze neschimbat. Compromisul este că o instanță statică poate fi ușor mai mare decât una care a păstrat subrutinele partajate — un preț corect pentru a nu avea nevoie de o a doua implementare de subsetting

Verificarea unei instanțe înainte de a avea încredere în ea

Instanțierea este genul de funcționalitate care poate fi subtil greșită în moduri pe care nicio excepție nu le raportează. Trei verificări prind aproape orice

Exportați instanța și deschideți-o într-un instrument de fonturi. SaveFontToFile scrie programul de font păstrat, astfel încât o instanță din memorie poate fi extrasă fără a produce mai întâi un PDF. Fișierul exportat ar trebui să se parseze curat, să nu conțină tabele de variație și să raporteze metadatele de stil pe care le-ați cerut

Comparați avansurile la o locație non-implicită. Dacă pipeline-ul dumneavoastră poate produce aceeași instanță în alt mod, comparați lățimile de avans și limitele curbelor, glifă cu glifă; implementarea de referință de aici a corespuns unei instanțieri independente a unui font cu 313 glife și două axe, cu o diferență maximă de zero pe fiecare glifă

Și extrageți textul înapoi din PDF-ul generat. O instanță care se încorporează, dar nu produce text extractibil, și-a pierdut maparea ToUnicode, ceea ce contează pentru căutare și accesibilitate mult după ce verificarea vizuală a trecut. Calea mai amplă de extracție este tratată în extracția de text, imagine și font

Această diagramă arată trei verificări pentru o instanță de font variabil generată: extragerea fișierului de font exportat, compararea avansurilor de glifă față de o instanțiere independentă și extragerea textului înapoi din PDF
O instanță subtil greșită nu aruncă nicio excepție, așa că fișierul exportat, avansurile de glifă și textul extras sunt fiecare verificate pe rând
// Extrage o instanță generată pentru inspecție sau reutilizare
Lib.SaveFontToFile('Inter-620-87.ttf');

// Cereți coordonate în afara intervalului declarat, și sunt limitate,
// deci citiți JSON-ul axei în loc să presupuneți limitele unui font
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}

Note practice pentru un pipeline de font de brand

Puneți instanțele în cache după setul de coordonate. Fiecare set distinct de coordonate este un program de font distinct, așa că un document care amestecă grosimea 400 și grosimea 620 încorporează două subseturi, nu o singură familie partajată. Generarea repetată a aceleiași instanțe este muncă irosită; generarea a zeci de grosimi aproape identice este dimensiune de fișier irosită

Măsurați cu instanța cu care veți desena. O instanță condensată are avansuri mai înguste decât cea implicită, astfel încât orice layout care a măsurat înainte de instanțiere a măsurat lățimile greșite. Aceasta este cea mai frecventă cauză unică pentru text care depășește caseta lui doar în producție

Păstrați o cale de fallback pentru glifele pe care fontul dumneavoastră de brand nu le are. Un font de brand variabil acoperă de obicei latina și puțin altceva, iar un raport care conține un nume de client în chirilic va afișa altfel glife lipsă; mecanismul automat de fallback este descris în fallback automat de font pentru glife lipsă. Iar când scopul este un fișier mai mic, nu o anumită grosime, subsetting-ul face mai multă treabă decât instanțierea, așa cum este tratat în optimizarea dimensiunii fișierului PDF și subsetting-ul de fonturi

Importul de fonturi web, instanțierea variabilă, subsetting-ul și fallback-ul fac parte din același pipeline de fonturi pentru Delphi, C++Builder și Free Pascal; lista completă de funcționalități se află pe pagina PDF Library for Delphi