PDFium Component laat een Delphi-toepassing beslissen welke lettertypebytes worden gebruikt wanneer een PDF verwijst naar een lettertype dat het niet insluit. ConfigureSystemFontProvider installeert een IPdfSystemFontProvider-implementatie die elk lettertypemappingverzoek ontvangt dat PDFium doet, compleet met lettertypenaam, gewicht, cursiefvlag, charset en pitch family, en antwoordt met de TrueType-, TrueType Collection- of OpenType-bytes die moeten worden gebruikt
Dit bestaat omdat niet-ingesloten lettertypen een renderloterij zijn. Een PDF die Arial noemt en niets insluit, rendert met Arial op een werkstation, met een metriek-compatibele vervanging op een Linux-server, en met wat de hostmapper maar vindt op een afgesloten containerimage. Dezelfde factuur ziet er overal anders uit, regelafbrekingen verschuiven, en een klant ontvangt een document dat niet overeenkomt met het gearchiveerde exemplaar
Waarom niet gewoon de lettertypen op de server installeren?
Soms is dat het antwoord, en als dat zo is, kies er dan voor. Maar het faalt in drie veelvoorkomende situaties. Licentievoorwaarden kunnen verbieden een lettertype op een server te installeren voor geautomatiseerde rendering. Containerimages worden vaak opnieuw opgebouwd, en een handmatig geïnstalleerd lettertype verdwijnt bij de volgende deployment. En gereguleerde workflows vereisen dat de renderstack reproduceerbaar is vanuit artefacten onder versiebeheer, wat een systeembrede lettertype-installatie niet is
Een provider pakt alle drie aan door de beslissing naar uw toepassing te verplaatsen. Lettertypen worden geleverd als resources die u beheert, het mappingbeleid is code die u kunt controleren, en dezelfde binary rendert overal identiek omdat niets afhangt van wat toevallig geïnstalleerd is
Een provider installeren
Configuratie moet gebeuren voordat de bibliotheek wordt geladen. PDFium accepteert een systeemlettertype-infostructuur bij initialisatie en houdt de handles bij die het daarna uitgeeft, dus het verwisselen van een provider terwijl documenten open zijn, zou lettertypehandles die PDFium nog vasthoudt ongeldig maken; het component weigert dat ronduit in plaats van het een render te laten beschadigen:
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
// Deterministische mapping: lettertypenaam plus gewicht en cursief bepalen
// welk bestand we voor dit verzoek leveren
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); // complete sfnt- of TTC-bytes
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // index binnen een collectie
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // host beslist alles
Policy.AllowFaceSubstitution := False; // wijs een andere lettertypenaam af
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// Laad de bibliotheek en open documenten pas nu
end;
Afbraak verloopt in omgekeerde volgorde: de provider wordt eerst losgekoppeld van PDFium, dan wordt de bibliotheek uitgeladen. Het overslaan van de ontkoppeling laat native lettertypehandles wijzen naar Pascal-objecten die op het punt staan te worden vrijgegeven, wat de klassieke afsluit-access-violation is in code die referentiegetelde interfaces mengt met een C-bibliotheek
Wat de beleidsvlaggen eigenlijk bepalen
AllowDefaultFallback is de schakelaar tussen twee bedrijfsmodi. Met deze uitgeschakeld, faalt een verzoek dat de provider afwijst gewoon, wat is wat u wilt terwijl u bewijst dat elk lettertype in een corpus verantwoord is: elke leemte wordt onmiddellijk zichtbaar in plaats van te worden weggemoffeld. Met deze ingeschakeld, worden onopgeloste verzoeken gedelegeerd naar de mapper geretourneerd door FPDF_GetDefaultSystemFontInfo, terwijl de buitenwereld nog steeds één uniforme handle-wrapper ziet, met lettertypenaam, charset, tabelgegevens en lettertypeverwijdering correct doorgestuurd naar herkomst
AllowFaceSubstitution bepaalt of een provider mag antwoorden met een andere lettertypenaam dan de gevraagde. Deze uitschakelen maakt substitutie een expliciete beslissing in plaats van een ongeluk, wat ertoe doet wanneer een document een lettertype noemt waarvan de metrieken genoeg verschillen om paginering te veranderen
Het component valideert elke providerreactie voordat deze PDFium bereikt: lege gegevens worden geweigerd, te grote lettertypen worden geweigerd tegen MaxFontBytes, de TTC-index wordt gecontroleerd, en individuele sfnt-tabellen worden bediend vanuit de lettertypedirectory wanneer PDFium om een tabel vraagt in plaats van om het hele bestand. Die laatste mogelijkheid betekent dat een provider een compleet lettertypebestand kan overdragen en het component queries op tabelniveau laat beantwoorden, in plaats van ruwe Pascal-objecten over de C-ABI bloot te stellen
Caching zonder bungelende lettertypegegevens
Lettertypemappingverzoeken herhalen zich voortdurend tijdens rendering, dus antwoorden worden gecacht met een sleutel die elke lettertypeselectieparameter omvat, en verwijderd volgens een begrensde least-recently-used-volgorde. De subtiliteit zit in de levensduur: PDFium kan nog steeds de bytes lezen van een lettertype waarvan het cache-item net is verwijderd
De cache slaat referentiegetelde dynamische arrays op en elke native handle houdt zijn eigen momentopname vast, dus verwijdering laat een referentie los in plaats van geheugen vrij te geven dat in gebruik is. De delete-callback geeft de handle vrij en houdt een actieve telling bij. Praktisch betekent dit dat MaxCacheEntries kan worden afgestemd op geheugen zonder enig risico dat gegevens onder een lopende render worden weggetrokken
Wordt de provider op mijn thread aangeroepen?
Nee, niet noodzakelijk. PDFium kan de mapper aanroepen vanuit zijn eigen workerthreads, dus een implementatie moet thread-safe zijn. Gedeelde tellers, de cache en configuratieobservatie zijn elk binnen het component beschermd door hun eigen kritieke sectie, maar de code binnen ResolveFont moet u zelf veilig maken
De veiligste vorm is een provider die geen enkele muteerbare gedeelde status aanraakt: lees uit een tabel die bij opstarten is opgebouwd, laad bytes uit een bestand of resource, retourneer. Als een opzoeking een eigen gedeelde cache nodig heeft, beveilig die dan. En houd uitzonderingen binnen uw implementatie, aangezien een Pascal-uitzondering nooit door de PDFium-stack mag terugspoelen; het component vangt op bij de C-ABI-grens en zet om naar een fout of een optionele standaard fallback, maar hierop vertrouwen als normale controlestroom kost prestaties en verbergt bugs. Threadingregels voor de rest van het component volgen dezelfde principes als die in renderlock-discipline
De mapping in productie bewijzen
Statistieken veranderen lettertypevervanging van giswerk in iets waarop u assertions kunt uitvoeren. GetSystemFontProviderStatistics rapporteert of een provider is geconfigureerd en geïnstalleerd, hoeveel mappingverzoeken zijn gedaan, en hoe ze werden vervuld, opgesplitst in cache hits, provider hits en default-fallback-hits, samen met geweigerde reacties, mislukte verzoeken, actieve handles en gecachte lettertypen:
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 een conformiteitsrun met fallback uitgeschakeld, betekent elke fallback-hit of
// mislukt verzoek dat een document verwees naar een lettertype dat we niet leveren
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
Een stijgend aantal RejectedProviderResponses is het signaal dat een provider antwoordt met gegevens die het beleid weigert, meestal een te groot bestand of een vervangen lettertypenaam, en het is de moeite waard om hierop te alarmeren, omdat die verzoeken stilzwijgend degraderen naar fallback of falen. Om te achterhalen welke lettertypen een document daadwerkelijk nodig heeft voordat u de mappingtabel bouwt, geeft het inspectiepad in het analyseren van PDF-lettertype-eigenschappen ingesloten en niet-ingesloten lettertypen per document weer
Lettertypeprovisioning, rendering en tekstextractie delen dezelfde bibliotheekinstantie binnen Delphi, C++Builder en Lazarus; implementatiedetails worden beschreven op de PDFium Component voor Delphi-pagina