Technisch artikel

Webfonts in PDF: WOFF2 en variabele assen in Delphi

PDF Library for Delphi accepteert de lettertypen die een webproject al meelevert. Het importeert WOFF2-containers, somt de ontwerpassen van een variabel lettertype op als JSON, en instantieert een gekozen set coördinaten voor de assen tot een statisch lettertype dat de gewone trajecten voor insluiten, subsetten en extractie afhandelen zonder iets van variaties te weten. Zowel TrueType-contouren als CFF2-contouren worden gedekt

De situatie die dit oplost, is veelvoorkomend en irritant. De typografie van een merk is gedefinieerd in een designsysteem, het designsysteem levert variabele WOFF2-bestanden, en de PDF-generator kan noch de container noch de variatiedata lezen. De gebruikelijke workaround — de foundry vragen om statische TTF-instanties van elk daadwerkelijk gebruikt gewicht — kost weken en levert bestanden op die niemand synchroon houdt

Waarom is WOFF2 niet gewoon een gecomprimeerd lettertype?

WOFF2 behandelen als een Brotli-wrapper en decomprimeren levert bytes op die geen geldige sfnt zijn. Het formaat staat ook getransformeerde en herordende tabellen toe: glyf, loca en hmtx kunnen in een herschikte vorm worden opgeslagen die moet worden teruggedraaid voordat het lettertype parseerbaar is. Een algemene decompressor kan dat niet

Op Windows biedt het platform al sinds Windows 10 versie 1703 containeranalyse en uitpakken, dus gebruikt de library de systeemimplementatie in plaats van nog een decoder mee te leveren. Die beslissing houdt de implementatie vrij van een extra DLL en betekent dat het terugdraaien van de transformatie wordt afgehandeld door dezelfde code die de eigen tekststack van het besturingssysteem gebruikt. Op andere platformen volgt WOFF1 nog steeds een pure Pascal-traject en meldt WOFF2 expliciet als niet-ondersteund, wat een eerlijk antwoord is in plaats van een stilzwijgend beschadigd lettertype

Het importtraject valideert zowel vóór als na het decoderen: de 48-byte header, de containerlengte, de gereserveerde velden, de gedeclareerde sfnt-grootte en een plafond van 512 MiB worden allemaal eerst gecontroleerd, en de gedecodeerde streamgrootte wordt achteraf vergeleken met de declaratie. Een webfont is gedownloade content, en de twee faalmodi die het waard zijn te blokkeren, zijn een groottemismatch en een decompressiebom

Dit pijplijndiagram toont hoe de PDF-library voor Delphi een WOFF2-webfont importeert via headervalidatie, systeemdecodering, het terugdraaien van de sfnt-transformatie en een grootte-audit na het decoderen
WOFF2-containers komen binnen als gedownloade content, dus valideert de import de header en de gedeclareerde groottes zowel vóór als na het decoderen
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 en TTC komen allemaal binnen via dezelfde aanroep
    FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
    if FontID <= 0 then
      raise Exception.Create('font could not be imported');

    // Wat kan dit lettertype eigenlijk?
    if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
      Log(AxesJSON);   // tags, minimum, standaard, maximum per as

    // Bak één ontwerplocatie in tot een statisch lettertype
    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;

Wat instancing moet herbouwen, en wat het verwijdert

Een variabel lettertype bewaart één standaardcontour per glyph plus deltas die geïndexeerd zijn op positie in de ontwerpruimte. Een statische instantie produceren betekent die deltas interpoleren op de gevraagde coördinaten, de punten verplaatsen, en vervolgens alles herberekenen dat ervan is afgeleid

Voor TrueType-contouren betekent dat het parsen van fvar, avar, gvar, HVAR en MVAR, het toepassen van tuple-deltas met gedeelde en private puntnummering, en het interpoleren van punten die geen expliciete delta dragen. Horizontale fantoompunten bepalen samen met HVAR de advance width en de linker side bearing. Bij samengestelde glyphs moeten eerst hun componentafhankelijkheden en puntmappings worden opgelost, waarna translatie, schaal, matrix en aanhechtingsoffsets worden toegepast — met dieptelimieten en cyclusdetectie, want een kwaadaardige componentgraaf zou anders eindeloos recursief doorlopen

Metadata verschuift ook mee, en dit is het onderdeel dat de meeste implementaties overslaan. MVAR werkt de globale metrics bij in hhea, OS/2 en post, zodat regelafstand, onderstrepingspositie en cursiefhoek overeenkomen met de instantie in plaats van met de standaard. De assen wght, wdth, ital en slnt werken bovendien de gewichtsklasse, breedteklasse, selectievlaggen, macStyle en cursiefhoek bij, zodat een tool die het statische lettertype inspecteert de stijl rapporteert die u hebt aangevraagd. Een lettertype waarvan de geometrie vet is en waarvan de metadata regular zegt, wordt overal downstream verkeerd gelabeld

De output plettert vervolgens samengestelde contouren en verwijdert de tabellen die niet meer van toepassing zijn: de variatietabellen, hints, device tables en elke handtekening. Glyphgrenzen, de samenvattende metrics van hhea, de hintplafonds van maxp, checksums per tabel en de checksum van het hele lettertype worden allemaal opnieuw berekend

Dit diagram toont hoe instancing van een variabel lettertype een TrueType-face herbouwt door gvar-deltas te interpoleren, samengestelde glyphs uit te breiden, advances opnieuw te berekenen en stijlmetadata te herschrijven voordat de variatietabellen worden verwijderd
Instancing herbouwt alles wat de deltas hebben geraakt, en verwijdert daarna het variatiemechanisme, zodat gewoon insluiten een simpel statisch lettertype ziet

CFF2 volgt een andere route naar dezelfde bestemming

CFF2 kan niet rechtstreeks als PDF-lettertypeprogramma worden ingesloten: de charstrings dragen geen breedtes, en het lettertype leunt voor glyphvolgorde en metrics op de omringende OpenType-tabellen. Instancing van een CFF2-lettertype produceert daarom in plaats daarvan een CID-keyed CFF OpenType-lettertype, dat de bestaande trajecten voor tekenen, insluiten en subsetten al afhandelen

Het laden van een CFF2-lettertype produceert onmiddellijk een statische instantie op standaardcoördinaten voor het huidige document, terwijl de oorspronkelijke CFF2-sfnt op het lettertypeobject bewaard blijft, zodat asopsomming en latere instancing nog steeds de volledige ontwerpruimte zien. Elke glyph wordt tijdens instancing individueel de-gesubroutineerd, wat de variatierekenkunde volledig verwijdert en de bestaande subsetter ongewijzigd laat werken. De prijs is dat een statische instantie iets groter kan zijn dan één die gedeelde subroutines behield — een eerlijke prijs voor het niet nodig hebben van een tweede subsetting-implementatie

Een instantie verifiëren voordat u haar vertrouwt

Instancing is het soort functie dat subtiel fout kan gaan op manieren waar geen enkele exception melding van maakt. Drie controles vangen bijna alles

Exporteer de instantie en open haar in een lettertypetool. SaveFontToFile schrijft het bewaarde lettertypeprogramma weg, zodat een in-memory-instantie kan worden weggeschreven zonder eerst een PDF te produceren. Het geëxporteerde bestand zou zonder problemen moeten parsen, geen variatietabellen mogen bevatten, en de stijlmetadata moeten rapporteren die u hebt aangevraagd

Vergelijk advances op een niet-standaardlocatie. Als uw pijplijn dezelfde instantie op een andere manier kan produceren, vergelijk dan advance widths en curvegrenzen glyph voor glyph; de referentie-implementatie hier kwam overeen met een onafhankelijke instancing van een lettertype met 313 glyphs en twee assen, met een maximaal verschil van nul over elke glyph

En extraheer de tekst weer uit de gegenereerde PDF. Een instantie die insluit maar geen extraheerbare tekst produceert, is haar ToUnicode-mapping kwijtgeraakt, wat van belang is voor zoeken en toegankelijkheid lang nadat de visuele controle is geslaagd. Het bredere extractietraject wordt behandeld in tekst-, afbeeldings- en lettertype-extractie

Dit diagram toont drie verificatiecontroles voor een gegenereerde instantie van een variabel lettertype: het geëxporteerde lettertypebestand wegschrijven, glyphadvances vergelijken met een onafhankelijke instancing, en tekst terug uit de PDF extraheren
Een subtiel foutieve instantie gooit geen exception, dus worden het geëxporteerde bestand, de glyphadvances en de geëxtraheerde tekst elk op hun beurt geverifieerd
// Schrijf een gegenereerde instantie weg voor inspectie of hergebruik
Lib.SaveFontToFile('Inter-620-87.ttf');

// Vraag om coördinaten buiten het gedeclareerde bereik en ze worden geclampt,
// lees dus de as-JSON in plaats van de grenzen van een lettertype aan te nemen
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}

Praktische opmerkingen voor een merklettertypepijplijn

Cache instanties per coördinatenset. Elke afzonderlijke set coördinaten is een afzonderlijk lettertypeprogramma, dus sluit een document dat gewicht 400 en gewicht 620 mengt twee subsets in plaats van één gedeelde face in. Dezelfde instantie herhaaldelijk genereren is verspilde moeite; tientallen bijna-identieke gewichten genereren is verspilde bestandsgrootte

Meet met de instantie waarmee u ook gaat tekenen. Een condensed instantie heeft smallere advances dan de standaard, dus heeft elke lay-out die vóór instancing heeft gemeten de verkeerde breedtes gemeten. Dit is verreweg de meest voorkomende oorzaak van tekst die alleen in productie zijn kader overschrijdt

Houd een fallback-traject aan voor de glyphs die uw merklettertype mist. Een variabel merklettertype dekt doorgaans Latijns en weinig meer, en een rapport met een Cyrillische klantnaam zal anders ontbrekende glyphs tonen; het automatische fallbackmechanisme wordt beschreven in automatische lettertypefallback voor ontbrekende glyphs. En wanneer het doel een kleiner bestand is in plaats van een specifiek gewicht, doet subsetting meer werk dan instancing, zoals behandeld in PDF-bestandsgrootte-optimalisatie en lettertypesubsetting

Webfontimport, variabele instancing, subsetting en fallback maken deel uit van dezelfde lettertypepijplijn voor Delphi, C++Builder en Free Pascal; de volledige functielijst staat op de PDF Library for Delphi-pagina