PDFium Component permette a un'applicazione Delphi di decidere quali byte di font vengono usati quando un PDF fa riferimento a un font che non incorpora. ConfigureSystemFontProvider installa un'implementazione di IPdfSystemFontProvider che riceve ogni richiesta di mappatura font che PDFium effettua, completa di nome del face, peso, flag corsivo, charset e famiglia di pitch, e risponde con i byte TrueType, TrueType Collection o OpenType da usare
Questo esiste perché i font non incorporati sono una lotteria di rendering. Un PDF che nomina Arial e non incorpora nulla si renderizza con Arial su una workstation, con un sostituto compatibile a livello di metriche su un server Linux, e con qualsiasi cosa il mapper dell'host trovi su un'immagine container bloccata. La stessa fattura appare diversa su ciascuno, le interruzioni di riga si spostano, e un cliente riceve un documento che non corrisponde alla copia archiviata
Perché non installare semplicemente i font sul server?
A volte questa è la risposta, e quando lo è, adottala. Ma fallisce in tre situazioni comuni. Le licenze possono vietare l'installazione di un font su un server per il rendering automatizzato. Le immagini container vengono ricostruite frequentemente e un font installato manualmente sparisce al prossimo deployment. E i workflow regolamentati richiedono che lo stack di rendering sia riproducibile da artefatti sotto controllo di versione, cosa che un'installazione di font a livello di macchina non è
Un provider affronta tutte e tre le situazioni spostando la decisione dentro la tua applicazione. I font vengono distribuiti come risorse che controlli, la policy di mappatura è codice che puoi revisionare, e lo stesso binario renderizza in modo identico ovunque perché nulla dipende da cosa è installato per caso
Installare un provider
La configurazione deve avvenire prima che la libreria venga caricata. PDFium accetta una struttura di informazioni sui font di sistema all'inizializzazione e mantiene gli handle che distribuisce in seguito, quindi scambiare un provider mentre i documenti sono aperti invaliderebbe gli handle dei font che PDFium detiene ancora; il componente rifiuta questo del tutto anziché lasciare che corrompa un rendering:
uses
PDFium;
type
TAppFontProvider = class(TInterfacedObject, IPdfSystemFontProvider)
public
function ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
end;
function TAppFontProvider.ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
var
Path: string;
begin
// Mappatura deterministica: nome del face più peso e corsivo decidono
// quale file distribuiamo per questa richiesta
Path := MapFaceToBundledFile(Request.FaceName, Request.Weight,
Request.Italic, Request.Charset);
Result := Path <> '';
if not Result then
Exit;
Font.FaceName := Request.FaceName;
Font.FontData := LoadFileBytes(Path); // byte sfnt o TTC completi
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // indice dentro una collezione
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // l'host decide tutto
Policy.AllowFaceSubstitution := False; // rifiuta un nome di face diverso
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// Solo ora carica la libreria e apri i documenti
end;
La chiusura avviene nell'ordine opposto: il provider viene prima scollegato da PDFium, poi la libreria viene scaricata. Saltare lo scollegamento lascia gli handle di font nativi puntare a oggetti Pascal che stanno per essere liberati, il che è la classica access violation di spegnimento in codice che mescola interfacce con conteggio di riferimenti a una libreria C
Cosa decidono realmente i flag della policy
AllowDefaultFallback è l'interruttore tra due modalità operative. Con esso disattivato, una richiesta che il provider rifiuta semplicemente fallisce, il che è ciò che si vuole mentre si dimostra che ogni font in un corpus è tracciato: qualsiasi lacuna diventa immediatamente visibile anziché essere mascherata. Con esso attivato, le richieste non risolte vengono delegate al mapper restituito da FPDF_GetDefaultSystemFontInfo, mentre il mondo esterno vede comunque un unico wrapper di handle uniforme, con nome del face, charset, dati di tabella ed eliminazione del font instradati correttamente in base all'origine
AllowFaceSubstitution governa se un provider può rispondere con un nome di face diverso da quello richiesto. Disattivarlo rende la sostituzione una decisione esplicita anziché un incidente, il che conta quando un documento nomina un font le cui metriche differiscono abbastanza da cambiare l'impaginazione
Il componente convalida ogni risposta del provider prima che raggiunga PDFium: i dati vuoti vengono rifiutati, i font sovradimensionati vengono rifiutati rispetto a MaxFontBytes, l'indice TTC viene controllato, e le singole tabelle sfnt vengono servite dalla directory dei font quando PDFium chiede una tabella anziché l'intero file. Quest'ultima capacità significa che un provider può consegnare un file font completo e lasciare che il componente risponda alle query a livello di tabella, invece di esporre oggetti Pascal grezzi attraverso l'ABI C
Caching senza dati di font penzolanti
Le richieste di mappatura font si ripetono costantemente durante il rendering, quindi le risposte vengono memorizzate in cache con una chiave che copre ogni parametro di selezione del font, espulse secondo un ordine least-recently-used limitato. La sottigliezza è la durata di vita: PDFium potrebbe star ancora leggendo i byte di un font la cui voce di cache è appena stata espulsa
La cache memorizza array dinamici a conteggio di riferimenti e ogni handle nativo mantiene il proprio snapshot, così l'espulsione rilascia un riferimento anziché liberare memoria in uso. Il callback di eliminazione rilascia l'handle e mantiene un conteggio attivo. In pratica, questo significa che MaxCacheEntries può essere regolato per la memoria senza alcun rischio di sottrarre dati da sotto un rendering in corso
Il provider viene chiamato sul mio thread?
No, non necessariamente. PDFium può chiamare il mapper dai propri worker thread, quindi un'implementazione deve essere thread-safe. Contatori condivisi, la cache e l'osservazione della configurazione sono ciascuno protetti dentro il componente dalla propria sezione critica, ma il codice dentro ResolveFont tocca a te renderlo sicuro
La forma più sicura è un provider che non tocca alcuno stato condiviso mutabile: leggi da una tabella costruita all'avvio, carica byte da un file o una risorsa, restituisci. Se una ricerca ha bisogno di una cache condivisa propria, proteggila. E mantieni le eccezioni dentro la tua implementazione, poiché un'eccezione Pascal non deve mai propagarsi attraverso lo stack di PDFium; il componente la cattura al confine dell'ABI C e la converte in un fallimento o in un fallback predefinito opzionale, ma affidarsi a questo come flusso di controllo normale costa prestazioni e nasconde bug. Le regole di threading per il resto del componente seguono gli stessi principi di quelle in disciplina del render lock
Dimostrare la mappatura in produzione
Le statistiche trasformano la sostituzione dei font da un'ipotesi in qualcosa su cui si può fare un'asserzione. GetSystemFontProviderStatistics riporta se un provider è configurato e installato, quante richieste di mappatura sono state fatte, e come sono state soddisfatte, suddivise in cache hit, provider hit e default fallback hit, insieme a risposte rifiutate, richieste fallite, handle attivi e font in cache:
var
Stats: TPdfSystemFontStatistics;
begin
Stats := GetSystemFontProviderStatistics;
Writeln(Format('requests=%d cache=%d provider=%d fallback=%d',
[Stats.MapRequests, Stats.CacheHits, Stats.ProviderHits,
Stats.DefaultFallbackHits]));
Writeln(Format('rejected=%d failed=%d handles=%d cached=%d',
[Stats.RejectedProviderResponses, Stats.FailedRequests,
Stats.ActiveHandles, Stats.CachedFonts]));
// In un'esecuzione di conformità con il fallback disattivato, qualsiasi fallback hit o
// richiesta fallita significa che un documento ha referenziato un font che non distribuiamo
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
Un conteggio crescente di RejectedProviderResponses è il segnale che un provider sta rispondendo con dati che la policy rifiuta, di solito un file sovradimensionato o un face sostituito, e vale la pena impostare un avviso su questo perché quelle richieste degradano silenziosamente a fallback o fallimento. Per diagnosticare quali font un documento effettivamente necessita prima di costruire la tabella di mappatura, il percorso di ispezione in analizzare le proprietà dei font PDF elenca i font incorporati e non incorporati per documento
Il provisioning dei font, il rendering e l'estrazione del testo condividono la stessa istanza di libreria tra Delphi, C++Builder e Lazarus; i dettagli di deployment sono descritti nella pagina di PDFium Component per Delphi