Teknisk artikkel

Styre PDF-fontsubstitusjon i Delphi med PDFium

PDFium Component lar en Delphi-applikasjon avgjøre hvilke fontbyte som brukes når en PDF refererer til en font den ikke bygger inn. ConfigureSystemFontProvider installerer en IPdfSystemFontProvider-implementasjon som mottar hver eneste fontkartleggingsforespørsel PDFium gjør, komplett med fontnavn, vekt, kursivflagg, tegnsett og pitch-familie, og svarer med TrueType-, TrueType Collection- eller OpenType-bytene som skal brukes

Dette finnes fordi ikke-innebygde fonter er et gjengivelseslotteri. En PDF som navngir Arial og bygger inn ingenting, gjengis med Arial på en arbeidsstasjon, med en metrikk-kompatibel erstatning på en Linux-server, og med hva enn vertskartleggeren finner på et låst container-image. Den samme fakturaen ser forskjellig ut på hver, linjeskift flytter seg, og en kunde mottar et dokument som ikke stemmer med den arkiverte kopien

Hvorfor ikke bare installere fontene på serveren?

Noen ganger er det svaret, og når det er det, velg det. Men det svikter i tre vanlige situasjoner. Lisensiering kan forby å installere en font på en server for automatisert gjengivelse. Container-images bygges ofte om på nytt, og en font installert for hånd forsvinner ved neste utrulling. Og regulerte arbeidsflyter trenger at gjengivelsesstakken kan reproduseres fra artefakter under versjonskontroll, noe en maskinomfattende fontinstallasjon ikke er

En leverandør adresserer alle tre ved å flytte avgjørelsen inn i applikasjonen din. Fonter leveres som ressurser du kontrollerer, kartleggingspolicyen er kode du kan gjennomgå, og den samme binærfilen gjengir identisk overalt fordi ingenting avhenger av hva som tilfeldigvis er installert

Installere en leverandør

Konfigurasjon må skje før biblioteket lastes inn. PDFium tar imot en systemfont-info-struktur ved initialisering og beholder håndtak den deler ut etterpå, så å bytte leverandør mens dokumenter er åpne, ville ugyldiggjort fonthåndtak PDFium fremdeles holder på; komponenten avviser det rett ut i stedet for å la det korrumpere en gjengivelse:

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 kartlegging: fontnavn pluss vekt og kursiv avgjør
  // hvilken fil vi leverer for denne forespørselen
  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-byte
  Font.Charset := Request.Charset;
  Font.TTCIndex := 0;                     // indeks inni en samling
end;

var
  Policy: TPdfSystemFontPolicy;
begin
  Policy := TPdfSystemFontPolicy.Default;
  Policy.AllowDefaultFallback := False;   // verten avgjør alt
  Policy.AllowFaceSubstitution := False;  // avvis et annet fontnavn
  Policy.MaxFontBytes := 32 * 1024 * 1024;
  Policy.MaxCacheEntries := 64;

  ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
  // Last biblioteket og åpne dokumenter først nå
end;

Nedrivning kjøres i motsatt rekkefølge: leverandøren kobles fra PDFium først, deretter lastes biblioteket ut. Å hoppe over frakoblingen etterlater native fonthåndtak som peker på Pascal-objekter som er i ferd med å bli frigjort, noe som er den klassiske nedstengnings-access-violation-en i kode som blander referansetalte grensesnitt med et C-bibliotek

Hva policyflaggene egentlig avgjør

AllowDefaultFallback er bryteren mellom to driftsmoduser. Med den av, feiler en forespørsel leverandøren avslår, ganske enkelt, noe som er hva du vil ha mens du beviser at hver font i et korpus er gjort rede for: ethvert hull blir synlig umiddelbart i stedet for å bli tildekket. Med den på, delegeres uløste forespørsler til kartleggeren returnert av FPDF_GetDefaultSystemFontInfo, mens omverdenen fremdeles ser én ensartet håndtak-omslag, med fontnavn, tegnsett, tabelldata og fontsletting korrekt rutet etter opprinnelse

AllowFaceSubstitution styrer om en leverandør kan svare med et annet fontnavn enn det som ble forespurt. Å slå den av gjør substitusjon til en eksplisitt beslutning snarere enn et uhell, noe som betyr noe når et dokument navngir en font hvis metrikk skiller seg nok til å endre paginering

Komponenten validerer hvert leverandørsvar før det når PDFium: tomme data avvises, overdimensjonerte fonter avvises mot MaxFontBytes, TTC-indeksen sjekkes, og individuelle sfnt-tabeller serveres fra fontkatalogen når PDFium spør om en tabell snarere enn om hele filen. Den siste egenskapen betyr at en leverandør kan overlevere en komplett fontfil og la komponenten svare på spørringer på tabellnivå, i stedet for å eksponere rå Pascal-objekter over C-ABI-en

Bufring uten dinglende fontdata

Fontkartleggingsforespørsler gjentar seg konstant under gjengivelse, så svar bufres med en nøkkel som dekker hver fontvalgsparameter, kastet ut etter avgrenset minst-nylig-brukt-rekkefølge. Det subtile er levetid: PDFium leser kanskje fremdeles bytene til en font hvis buffer-oppføring nettopp er kastet ut

Bufferen lagrer referansetalte dynamiske arrayer, og hvert native håndtak har sitt eget øyeblikksbilde, så utkasting fjerner en referanse i stedet for å frigjøre minne i bruk. Slette-tilbakekallet frigjør håndtaket og vedlikeholder en aktiv teller. I praksis betyr dette at MaxCacheEntries kan justeres for minne uten noen risiko for å trekke data ut under en pågående gjengivelse

Kalles leverandøren på min tråd?

Nei, ikke nødvendigvis. PDFium kan kalle kartleggeren fra sine egne arbeidstråder, så en implementasjon må være trådsikker. Delte tellere, bufferen og konfigurasjonsobservasjon er hver beskyttet inni komponenten av sin egen kritiske seksjon, men koden inni ResolveFont er din å gjøre sikker

Den tryggeste formen er en leverandør som ikke rører noen delt mutabel tilstand: les fra en tabell bygget ved oppstart, last byte fra en fil eller en ressurs, returner. Hvis et oppslag trenger en delt cache av din egen, sikre den. Og hold unntak inni implementasjonen din, siden et Pascal-unntak aldri må vikle seg gjennom PDFium-stakken; komponenten fanger opp ved C-ABI-grensen og konverterer til en feil eller en valgfri standard-fallback, men å stole på det som normal kontrollflyt koster ytelse og skjuler feil. Trådingsreglene for resten av komponenten følger de samme prinsippene som de i render-lock-disiplin

Bevise kartleggingen i produksjon

Statistikk gjør fontsubstitusjon om fra gjetning til noe du kan bekrefte. GetSystemFontProviderStatistics rapporterer om en leverandør er konfigurert og installert, hvor mange kartleggingsforespørsler som ble gjort, og hvordan de ble tilfredsstilt, delt inn i buffertreff, leverandørtreff og standard-fallback-treff, sammen med avviste svar, mislykkede forespørsler, aktive håndtak og bufrede fonter:

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 samsvarskjøring med fallback deaktivert betyr ethvert fallback-treff
  // eller mislykket forespørsel at et dokument refererte til en font 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-antall er signalet om at en leverandør svarer med data policyen avviser, som regel en overdimensjonert fil eller et substituert fontnavn, og det er verdt å varsle på, fordi de forespørslene stille degraderer til fallback eller feil. For å diagnostisere hvilke fonter et dokument faktisk trenger før du bygger kartleggingstabellen, lister inspeksjonsruten i analysere PDF-fontegenskaper opp innebygde og ikke-innebygde fonter per dokument

Fontforsyning, gjengivelse og tekstuthenting deler den samme biblioteksinstansen på tvers av Delphi, C++Builder og Lazarus; distribusjonsdetaljer er beskrevet på PDFium Component for Delphi-siden