PDFium Component lader en Delphi-applikation beslutte, hvilke skrifttype-bytes der bruges, når en PDF refererer til en skrifttype, den ikke indlejrer. ConfigureSystemFontProvider installerer en IPdfSystemFontProvider-implementering, der modtager hver skrifttypeafbildningsanmodning, PDFium foretager, komplet med skrifttypenavn, vægt, kursivflag, tegnsæt og pitch-familie, og svarer med de TrueType-, TrueType Collection- eller OpenType-bytes, der skal bruges
Dette findes, fordi ikke-indlejrede skrifttyper er et rendering-lotteri. En PDF, der navngiver Arial og indlejrer intet, renderer med Arial på en arbejdsstation, med en metrikkompatibel erstatning på en Linux-server, og med hvad end værtsafbilderen finder på et fastlåst container-image. Den samme faktura ser forskellig ud på hver, linjeskift flytter sig, og en kunde modtager et dokument, der ikke matcher den arkiverede kopi
Hvorfor ikke bare installere skrifttyperne på serveren?
Nogle gange er det svaret, og når det er, så gør det. Men det fejler i tre almindelige situationer. Licensiering kan forbyde at installere en skrifttype på en server til automatiseret rendering. Container-images genbygges hyppigt, og en skrifttype installeret manuelt forsvinder med den næste udrulning. Og regulerede arbejdsgange kræver, at renderingsstakken kan reproduceres ud fra artefakter under versionsstyring, hvilket en maskinbred skrifttypeinstallation ikke kan
En udbyder adresserer alle tre ved at flytte beslutningen ind i din applikation. Skrifttyper leveres som ressourcer, du kontrollerer, afbildningspolitikken er kode, du kan gennemgå, og den samme binærfil renderer identisk overalt, fordi intet afhænger af, hvad der tilfældigvis er installeret
Installation af en udbyder
Konfiguration skal ske, før biblioteket indlæses. PDFium accepterer en systemskrifttype-info-struktur ved initialisering og beholder håndtag, den udleverer bagefter, så at bytte en udbyder, mens dokumenter er åbne, ville ugyldiggøre skrifttypehåndtag, PDFium stadig holder; komponenten afviser det fuldstændigt frem for at lade det ødelægge en 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
// Deterministisk afbildning: skrifttypenavn plus vægt og kursiv afgør,
// hvilken fil vi leverer til denne anmodning
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); // komplette sfnt- eller TTC-bytes
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // indeks inde i en samling
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // værten afgør alt
Policy.AllowFaceSubstitution := False; // afvis et andet skrifttypenavn
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// Indlæs først biblioteket og åbn dokumenter nu
end;
Nedlukning kører i modsat rækkefølge: udbyderen frakobles først fra PDFium, derefter aflæsses biblioteket. At springe frakoblingen over efterlader native skrifttypehåndtag, der peger på Pascal-objekter, som er ved at blive frigivet, hvilket er den klassiske nedlukningsadgangskrænkelse i kode, der blander referencetalte grænseflader med et C-bibliotek
Hvad politikflagene rent faktisk afgør
AllowDefaultFallback er kontakten mellem to driftstilstande. Med den slået fra fejler en anmodning, udbyderen afviser, simpelthen, hvilket er, hvad man vil have, mens man beviser, at hver skrifttype i et korpus er redegjort for: ethvert hul bliver synligt øjeblikkeligt i stedet for at blive tilsløret. Med den slået til delegeres uløste anmodninger til afbilderen returneret af FPDF_GetDefaultSystemFontInfo, mens omverdenen stadig ser én ensartet håndtag-wrapper, med skrifttypenavn, tegnsæt, tabeldata og skrifttypesletning dirigeret korrekt efter oprindelse
AllowFaceSubstitution styrer, om en udbyder må svare med et andet skrifttypenavn end det, der blev anmodet om. At slå den fra gør substitution til en eksplicit beslutning frem for et uheld, hvilket betyder noget, når et dokument navngiver en skrifttype, hvis metrics adskiller sig nok til at ændre paginering
Komponenten validerer hvert udbydersvar, før det når PDFium: tomme data afvises, oversized skrifttyper afvises mod MaxFontBytes, TTC-indekset tjekkes, og individuelle sfnt-tabeller betjenes fra skrifttypemappen, når PDFium spørger om en tabel frem for om hele filen. Den sidste evne betyder, at en udbyder kan overdrage en komplet skrifttypefil og lade komponenten besvare forespørgsler på tabelniveau, i stedet for at eksponere rå Pascal-objekter på tværs af C-ABI'en
Caching uden dinglende skrifttypedata
Skrifttypeafbildningsanmodninger gentages konstant under rendering, så svar caches med en nøgle, der dækker hver skrifttypevalgsparameter, udsat efter afgrænset least-recently-used-rækkefølge. Finessen er levetid: PDFium kan stadig læse bytene fra en skrifttype, hvis cache-post netop er blevet udsat
Cachen gemmer referencetalte dynamiske arrays, og hvert native håndtag holder sit eget øjebliksbillede, så udsættelse fjerner en reference frem for at frigive hukommelse i brug. Slette-callbacket frigiver håndtaget og vedligeholder en aktiv tælling. I praksis betyder det, at MaxCacheEntries kan justeres for hukommelse uden nogen risiko for at trække data væk under en igangværende rendering
Bliver udbyderen kaldt på min tråd?
Nej, ikke nødvendigvis. PDFium kan kalde afbilderen fra sine egne arbejdertråde, så en implementering skal være trådsikker. Delte tællere, cachen og konfigurationsobservation er hver især beskyttet inde i komponenten af deres egen kritiske sektion, men koden inde i ResolveFont er dit ansvar at gøre sikker
Den sikreste form er en udbyder, der ikke rører nogen mutabel delt tilstand: læs fra en tabel bygget ved opstart, indlæs bytes fra en fil eller en ressource, returnér. Hvis et opslag har brug for en delt cache af din egen, så beskyt den. Og hold undtagelser inde i din implementering, da en Pascal-undtagelse aldrig må folde sig op gennem PDFium-stakken; komponenten fanger ved C-ABI-grænsen og konverterer til en fejl eller en valgfri standardfallback, men at stole på det som normal kontrolflow koster ydeevne og skjuler fejl. Trådreglerne for resten af komponenten følger de samme principper som dem i render-lock-disciplin
Bevisning af afbildningen i produktion
Statistik gør skrifttypesubstitution fra gætværk til noget, man kan assertere på. GetSystemFontProviderStatistics rapporterer, om en udbyder er konfigureret og installeret, hvor mange afbildningsanmodninger der blev foretaget, og hvordan de blev opfyldt, opdelt i cache-hits, udbyder-hits og standardfallback-hits, sammen med afviste svar, mislykkede anmodninger, levende håndtag og cachede skrifttyper:
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]));
// I en konformitetskørsel med fallback deaktiveret betyder ethvert
// fallback-hit eller mislykket anmodning, at et dokument refererede
// til en skrifttype, vi ikke leverer
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
Et stigende RejectedProviderResponses-tal er signalet om, at en udbyder svarer med data, politikken afviser, som regel en oversized fil eller et substitueret skrifttypenavn, og det er værd at advare om, fordi de anmodninger stiltiende degraderer til fallback eller fejl. Til at diagnosticere, hvilke skrifttyper et dokument rent faktisk har brug for, før man bygger afbildningstabellen, lister inspektionsruten i analyse af PDF-skrifttypeegenskaber indlejrede og ikke-indlejrede skrifttyper pr. dokument
Skrifttypeforsyning, rendering og tekstudtræk deler den samme biblioteksinstans på tværs af Delphi, C++Builder og Lazarus; udrulningsdetaljer er beskrevet på siden PDFium Component til Delphi