Articolo tecnico

Font web in PDF: WOFF2 e assi variabili in Delphi

PDF Library for Delphi accetta i font che un progetto web distribuisce già. Importa contenitori WOFF2, enumera gli assi di design di un font variabile come JSON, e istanzia un insieme scelto di coordinate d'asse in un font statico che i normali percorsi di incorporamento, subsetting ed estrazione gestiscono senza sapere nulla delle variazioni. Sono coperti sia i contorni TrueType sia i contorni CFF2

La situazione che questo risolve è comune e fastidiosa. La tipografia di un brand è definita in un design system, il design system distribuisce file WOFF2 variabili, e il generatore PDF non può leggere né il contenitore né i dati di variazione. La soluzione alternativa abituale — chiedere alla fonderia istanze TTF statiche di ogni peso effettivamente usato — richiede settimane e produce file che nessuno mantiene sincronizzati

Perché WOFF2 non è semplicemente un font compresso?

Trattare WOFF2 come un semplice wrapper Brotli e decomprimerlo produce byte che non sono un sfnt valido. Il formato permette anche tabelle trasformate e riordinate: glyf, loca e hmtx possono essere memorizzate in una forma riorganizzata che deve essere invertita prima che il font sia analizzabile. Un decompressore generico non può farlo

Su Windows, la piattaforma fornisce l'analisi e lo scompattamento del contenitore fin da Windows 10 versione 1703, quindi la libreria usa l'implementazione di sistema anziché distribuire un altro decodificatore. Questa decisione mantiene il deployment libero da una DLL aggiuntiva e significa che l'inversione della trasformazione è gestita dallo stesso codice che usa lo stack di testo del sistema operativo. Su altre piattaforme, WOFF1 percorre ancora un percorso Pascal puro e WOFF2 viene esplicitamente segnalato come non supportato, il che è una risposta onesta invece di un font silenziosamente corrotto

Il percorso di importazione valida prima e dopo la decodifica: l'intestazione di 48 byte, la lunghezza del contenitore, i campi riservati, la dimensione sfnt dichiarata e un tetto di 512 MiB vengono tutti controllati per primi, e la dimensione dello stream decodificato viene confrontata con la dichiarazione in seguito. Un font web è contenuto scaricato, e le due modalità di fallimento che vale la pena bloccare sono una discrepanza di dimensione e una bomba di decompressione

Questo diagramma di pipeline mostra come la libreria PDF per Delphi importa un font web WOFF2 tramite validazione dell'intestazione, decodifica di sistema, inversione della trasformazione sfnt e un controllo delle dimensioni dopo la decodifica
I contenitori WOFF2 arrivano come contenuto scaricato, quindi l'importazione valida l'intestazione e le dimensioni dichiarate prima e dopo la decodifica
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 e TTC entrano tutti attraverso la stessa chiamata
    FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
    if FontID <= 0 then
      raise Exception.Create('font could not be imported');

    // Cosa può fare realmente questo font?
    if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
      Log(AxesJSON);   // tag, minimo, default, massimo per asse

    // Fissa una posizione di design in un font statico
    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;

Cosa deve ricostruire l'instanziazione, e cosa rimuove

Un font variabile memorizza un contorno predefinito per glifo più delta indicizzati per posizione nello spazio di design. Produrre un'istanza statica significa interpolare quei delta alle coordinate richieste, spostare i punti, e poi ricalcolare tutto ciò che ne deriva

Per i contorni TrueType questo significa analizzare fvar, avar, gvar, HVAR e MVAR, applicare delta di tupla con numerazione di punti condivisa e privata, e interpolare i punti che non portano alcun delta esplicito. I punti fantasma orizzontali insieme a HVAR determinano la larghezza di avanzamento e il side bearing sinistro. I glifi composti richiedono prima la risoluzione delle loro dipendenze di componente e delle mappature dei punti, poi l'applicazione di traslazione, scala, matrice e offset di attacco — con limiti di profondità e rilevamento di cicli, perché altrimenti un grafo di componenti malevolo ricorrerebbe senza fine

Si spostano anche i metadati, e questa è la parte che la maggior parte delle implementazioni salta. MVAR aggiorna le metriche globali in hhea, OS/2 e post, così l'interlinea, la posizione della sottolineatura e l'angolo corsivo corrispondono all'istanza anziché al default. Gli assi wght, wdth, ital e slnt aggiornano inoltre la classe di peso, la classe di larghezza, i flag di selezione, macStyle e l'angolo corsivo, così uno strumento che ispeziona il font statico riporta lo stile che avete richiesto. Un font la cui geometria è bold e i cui metadati dicono regular verrà etichettato erroneamente ovunque a valle

L'output appiattisce poi i contorni composti e rimuove le tabelle che non si applicano più: le tabelle di variazione, gli hint, le tabelle device e qualsiasi firma. I limiti dei glifi, le metriche di riepilogo hhea, i tetti degli hint maxp, i checksum per tabella e il checksum dell'intero font vengono tutti ricalcolati

Questo diagramma mostra l'instanziazione di un font variabile che ricostruisce un font TrueType interpolando i delta gvar, espandendo i glifi composti, ricalcolando gli avanzamenti e riscrivendo i metadati di stile prima di rimuovere le tabelle di variazione
L'instanziazione ricostruisce tutto ciò che i delta hanno toccato, poi rimuove il meccanismo di variazione così l'incorporamento ordinario vede un semplice font statico

CFF2 percorre una strada diversa verso lo stesso risultato

CFF2 non può essere incorporato direttamente come programma di font PDF: le sue charstring non portano larghezze, e si basa sulle tabelle OpenType circostanti per l'ordine dei glifi e le metriche. Quindi istanziare un font CFF2 produce invece un font OpenType CFF con chiave CID, che i percorsi esistenti di disegno, incorporamento e subsetting già gestiscono

Caricare un font CFF2 produce immediatamente un'istanza statica a coordinate predefinite per il documento corrente, mentre l'sfnt CFF2 originale viene conservato sull'oggetto font così l'enumerazione degli assi e l'instanziazione successiva vedono ancora l'intero spazio di design. Ogni glifo viene de-subroutinizzato individualmente durante l'instanziazione, il che rimuove del tutto l'aritmetica di variazione e permette al subsetter esistente di funzionare invariato. Il compromesso è che un'istanza statica può essere leggermente più grande di una che ha mantenuto le subroutine condivise — un prezzo equo per non dover avere una seconda implementazione di subsetting

Verificare un'istanza prima di fidarvene

L'instanziazione è il tipo di funzionalità che può essere sottilmente sbagliata in modi che nessuna eccezione segnala. Tre controlli catturano quasi tutto

Esportate l'istanza e apritela in uno strumento per font. SaveFontToFile scrive il programma di font conservato, così un'istanza in memoria può essere scaricata su file senza prima produrre un PDF. Il file esportato dovrebbe analizzarsi correttamente, non contenere tabelle di variazione, e riportare i metadati di stile che avete richiesto

Confrontate gli avanzamenti in una posizione non predefinita. Se la vostra pipeline può produrre la stessa istanza in un altro modo, confrontate le larghezze di avanzamento e i limiti delle curve glifo per glifo; l'implementazione di riferimento qui ha corrisposto a un'instanziazione indipendente di un font a due assi con 313 glifi con una differenza massima di zero su ogni glifo

E riestraete il testo dal PDF generato. Un'istanza che si incorpora ma non produce testo estraibile ha perso la sua mappatura ToUnicode, il che conta per la ricerca e l'accessibilità ben oltre il superamento del controllo visivo. Il percorso di estrazione più ampio è trattato in estrazione di testo, immagini e font

Questo diagramma mostra tre controlli di verifica per un'istanza di font variabile generata: lo scaricamento su file del font esportato, il confronto degli avanzamenti dei glifi con un'instanziazione indipendente e l'estrazione del testo dal PDF
Un'istanza sottilmente sbagliata non solleva alcuna eccezione, quindi il file esportato, gli avanzamenti dei glifi e il testo estratto vengono ciascuno verificati a turno
// Scarica su file un'istanza generata per l'ispezione o il riutilizzo
Lib.SaveFontToFile('Inter-620-87.ttf');

// Richiedere coordinate fuori dall'intervallo dichiarato le fa limitare (clamp),
// quindi leggete il JSON degli assi invece di presumere i limiti di un font
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}

Note pratiche per una pipeline di font di brand

Mettete in cache le istanze per set di coordinate. Ogni set distinto di coordinate è un programma di font distinto, quindi un documento che mischia il peso 400 e il peso 620 incorpora due subset invece di un unico font condiviso. Generare ripetutamente la stessa istanza è lavoro sprecato; generare dozzine di pesi quasi identici è dimensione del file sprecata

Misurate con l'istanza con cui disegnerete. Un'istanza condensata ha avanzamenti più stretti del default, quindi qualsiasi layout che ha misurato prima dell'instanziazione ha misurato le larghezze sbagliate. Questa è la causa singola più comune di testo che deborda dal proprio riquadro solo in produzione

Mantenete un percorso di fallback per i glifi che il vostro font di brand non ha. Un font di brand variabile copre tipicamente il latino e poco altro, e un report contenente un nome cliente cirillico mostrerà altrimenti glifi mancanti; il meccanismo di fallback automatico è descritto in fallback automatico dei font per glifi mancanti. E quando l'obiettivo è un file più piccolo anziché un peso specifico, il subsetting fa più lavoro dell'instanziazione, come trattato in ottimizzazione della dimensione del file PDF e subsetting dei font

L'importazione dei font web, l'instanziazione variabile, il subsetting e il fallback fanno parte della stessa pipeline di font per Delphi, C++Builder e Free Pascal; l'elenco completo delle funzionalità è disponibile sulla pagina di PDF Library for Delphi