Teknisk artikel

Webbtypsnitt i PDF: WOFF2 och variabla axlar i Delphi

PDF Library for Delphi accepterar de typsnitt ett webbprojekt redan levererar. Den importerar WOFF2-behållare, räknar upp ett variabelt typsnitts designaxlar som JSON, och instansierar en vald uppsättning axelkoordinater till ett statiskt typsnitt som de vanliga vägarna för inbäddning, delmängdsbildning och extraktion hanterar utan att veta något om variationer. Både TrueType-konturer och CFF2-konturer täcks

Situationen detta adresserar är vanlig och irriterande. Ett varumärkes typografi definieras i ett designsystem, designsystemet levererar variabla WOFF2-filer, och PDF-generatorn kan varken läsa behållaren eller variationsdatan. Den vanliga lösningen — att be typsnittsgjuteriet om statiska TTF-instanser av varje vikt som faktiskt används — tar veckor och producerar filer ingen håller synkroniserade

Varför är WOFF2 inte bara ett komprimerat typsnitt?

Att behandla WOFF2 som ett Brotli-omslag och packa upp det ger dig byte som inte är en giltig sfnt. Formatet tillåter också transformerade och omordnade tabeller: glyf, loca och hmtx kan lagras i en omarrangerad form som måste återställas innan typsnittet går att tolka. En generell uppackare klarar inte det

På Windows har plattformen tillhandahållit behållaranalys och uppackning sedan Windows 10 version 1703, så biblioteket använder systemimplementationen snarare än att leverera ytterligare en avkodare. Det beslutet håller driftsättningen fri från en extra DLL och innebär att transformåterställningen hanteras av samma kod operativsystemets egen textstack använder. På andra plattformar går WOFF1 fortfarande en ren Pascal-väg och WOFF2 rapporterar uttryckligen som ej stött, vilket är ett ärligt svar snarare än ett tyst korrumperat typsnitt

Importvägen validerar både innan den avkodar och efteråt: det 48-byte stora huvudet, behållarlängden, de reserverade fälten, den deklarerade sfnt-storleken och ett 512 MiB-tak kontrolleras alla först, och den avkodade strömstorleken jämförs mot deklarationen efteråt. Ett webbtypsnitt är nedladdat innehåll, och de två felläge värda att blockera är en storleksavvikelse och en uppackningsbomb

Det här pipelinediagrammet visar hur Delphi-PDF-biblioteket importerar ett WOFF2-webbtypsnitt genom huvudvalidering, systemavkodning, återställning av sfnt-transformen och en storlekskontroll efter avkodning
WOFF2-behållare anländer som nedladdat innehåll, så importen validerar huvudet och de deklarerade storlekarna före och efter avkodning
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 och TTC går alla in via samma anrop
    FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
    if FontID <= 0 then
      raise Exception.Create('font could not be imported');

    // Vad kan det här typsnittet faktiskt göra?
    if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
      Log(AxesJSON);   // taggar, minimum, standard, maximum per axel

    // Bränn in en designposition till ett statiskt typsnitt
    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;

Vad instansiering måste bygga om, och vad den tar bort

Ett variabelt typsnitt lagrar en standardkontur per glyf plus deltan indexerade efter position i designrymden. Att producera en statisk instans innebär att interpolera dessa deltan vid de begärda koordinaterna, flytta punkterna, och sedan omberäkna allt som härletts från dem

För TrueType-konturer betyder det att parsa fvar, avar, gvar, HVAR och MVAR, tillämpa tupeldeltan med delad och privat punktnumrering, och interpolera de punkter som inte bär något explicit delta. Horisontella fantompunkter tillsammans med HVAR avgör förskjutningsbredd och vänster sidoavstånd. Sammansatta glyfer behöver få sina komponentberoenden och punktmappningar upplösta först, sedan tillämpas translation, skala, matris och fästpunktsoffset — med djupgränser och cykeldetektering, eftersom en illvillig komponentgraf annars skulle rekursera utan slut

Metadata flyttas också, och det är delen de flesta implementationer hoppar över. MVAR uppdaterar de globala metrikerna i hhea, OS/2 och post, så radavstånd, understrykningsposition och kursivvinkel matchar instansen snarare än standardvärdet. Axlarna wght, wdth, ital och slnt uppdaterar dessutom viktklassen, breddklassen, urvalsflaggorna, macStyle och kursivvinkeln, så ett verktyg som inspekterar det statiska typsnittet rapporterar den stil du bad om. Ett typsnitt vars geometri är fet och vars metadata säger normal kommer att felmärkas överallt nedströms

Utdatan plattar sedan till sammansatta konturer och tar bort de tabeller som inte längre gäller: variationstabellerna, hintar, enhetstabeller och eventuell signatur. Glyfgränser, hhea-sammanfattningsmetriker, maxp-hinttaken, tabellvisa kontrollsummor och kontrollsumman för hela typsnittet omberäknas alla

Det här diagrammet visar hur instansiering av ett variabelt typsnitt bygger om ett TrueType-typsnitt genom att interpolera gvar-deltan, expandera sammansatta glyfer, omberäkna förskjutningar och skriva om stilmetadata innan variationstabellerna tas bort
Instansiering bygger om allt deltana rörde, och tar sedan bort variationsmaskineriet så att vanlig inbäddning ser ett vanligt statiskt typsnitt

CFF2 tar en annan väg till samma plats

CFF2 kan inte bäddas in direkt som ett PDF-typsnittsprogram: dess charstrings bär inga bredder, och det förlitar sig på de omgivande OpenType-tabellerna för glyfordning och metriker. Så att instansiera ett CFF2-typsnitt producerar istället ett CID-nyckelbaserat CFF OpenType-typsnitt, vilket de befintliga vägarna för ritning, inbäddning och delmängdsbildning redan hanterar

Att ladda ett CFF2-typsnitt producerar omedelbart en statisk instans med standardkoordinater för det aktuella dokumentet, medan det ursprungliga CFF2-sfnt:et behålls på typsnittsobjektet så att axeluppräkning och senare instansiering fortfarande ser hela designrymden. Varje glyf av-subrutiniseras individuellt under instansiering, vilket helt tar bort variationsaritmetiken och låter den befintliga delmängdsbildaren arbeta oförändrad. Priset är att en statisk instans kan bli något större än en som behöll delade subrutiner — ett rimligt pris för att slippa en andra delmängdsbildningsimplementation

Att verifiera en instans innan du litar på den

Instansiering är den sortens funktion som kan bli subtilt fel på sätt inget undantag rapporterar. Tre kontroller fångar nästan allt

Exportera instansen och öppna den i ett typsnittsverktyg. SaveFontToFile skriver det behållna typsnittsprogrammet, så en instans i minnet kan dumpas utan att först producera en PDF. Den exporterade filen bör tolkas rent, inte innehålla några variationstabeller, och rapportera den stilmetadata du begärde

Jämför förskjutningar vid en icke-standardposition. Om din pipeline kan producera samma instans på ett annat sätt, jämför förskjutningsbredder och kurvgränser glyf för glyf; referensimplementationen här matchade en oberoende instansiering av ett tvåaxligt typsnitt med 313 glyfer med en maximal skillnad på noll över varje glyf

Och extrahera texten ur den genererade PDF:en igen. En instans som bäddas in men inte producerar någon extraherbar text har förlorat sin ToUnicode-mappning, vilket spelar roll för sökning och tillgänglighet långt efter att den visuella kontrollen godkändes. Den bredare extraktionsvägen täcks i text-, bild- och typsnittsextraktion

Det här diagrammet visar tre verifieringskontroller för en genererad instans av ett variabelt typsnitt: att dumpa den exporterade typsnittsfilen, jämföra glyfförskjutningar mot en oberoende instansiering och extrahera text ur PDF:en igen
En subtilt fel instans kastar inget undantag, så den exporterade filen, glyfförskjutningarna och den extraherade texten verifieras var för sig i tur och ordning
// Dumpa en genererad instans för inspektion eller återanvändning
Lib.SaveFontToFile('Inter-620-87.ttf');

// Be om koordinater utanför det deklarerade intervallet och de klipps av,
// så läs axel-JSON:en snarare än att anta ett typsnitts gränser
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}

Praktiska anmärkningar för en varumärkestypsnittspipeline

Cacha instanser efter koordinatuppsättning. Varje unik uppsättning koordinater är ett distinkt typsnittsprogram, så ett dokument som blandar vikt 400 och vikt 620 bäddar in två delmängder snarare än ett delat typsnitt. Att generera samma instans upprepade gånger är bortkastat arbete; att generera dussintals nästan identiska vikter är bortkastad filstorlek

Mät med den instans du kommer att rita med. En kondenserad instans har smalare förskjutningar än standardvärdet, så varje layout som mätte innan instansiering mätte fel bredder. Detta är den enskilt vanligaste orsaken till text som svämmar över sin ruta bara i produktion

Behåll en reservväg för de glyfer ditt varumärkestypsnitt saknar. Ett variabelt varumärkestypsnitt täcker vanligtvis latin och lite annat, och en rapport som innehåller ett kyrilliskt kundnamn kommer annars att visa saknade glyfer; den automatiska reservmekanismen beskrivs i automatisk typsnittsreserv för saknade glyfer. Och när målet är en mindre fil snarare än en specifik vikt gör delmängdsbildning mer nytta än instansiering, vilket täcks i optimering av PDF-filstorlek och delmängdsbildning av typsnitt

Import av webbtypsnitt, variabel instansiering, delmängdsbildning och reservtypsnitt är delar av samma typsnittspipeline för Delphi, C++Builder och Free Pascal; den fullständiga funktionslistan finns på sidan för PDF Library for Delphi