Műszaki cikk

PDF-betűtípus-helyettesítés vezérlése Delphiben a PDFiummal

A PDFium Component lehetővé teszi, hogy egy Delphi-alkalmazás eldöntse, mely betűtípus-bájtok kerülnek felhasználásra, amikor egy PDF olyan betűtípusra hivatkozik, amelyet nem ágyaz be. A ConfigureSystemFontProvider egy IPdfSystemFontProvider implementációt telepít, amely megkapja a PDFium által kezdeményezett minden betűtípus-leképezési kérést, a betűkép-névvel, súllyal, dőlt jelzővel, karakterkészlettel és lépésközcsaláddal együtt, és a felhasználandó TrueType, TrueType Collection vagy OpenType bájtokkal válaszol

Ez azért létezik, mert a be nem ágyazott betűtípusok renderelési lottó. Egy PDF, amely Arialt nevez meg, és semmit nem ágyaz be, egy munkaállomáson Ariallal renderelődik, egy Linux-szerveren egy metrikakompatibilis helyettesítővel, egy lezárt konténerképen pedig azzal, amit a gazdagép leképezője éppen talál. Ugyanaz a számla mindegyiken másképp néz ki, a sortörések elmozdulnak, egy ügyfél pedig olyan dokumentumot kap, amely nem egyezik az archivált példánnyal

Egy Delphi PDF, amely nem beágyazott Arial betűkészletet nevez meg, három gépen eltérően renderel az alapértelmezett gazdaillesztő alatt, de azonosan, ha a PDFium kötegezett betűkészletbájtokat kap IPdfSystemFontProvidertől
A PDFium akkor kér fontbájtokat, amikor egy dokumentum olyan fontot nevez meg, amelyet nem ágyaz be; a szolgáltató a gazda-leképező gépenkénti találgatását olyan bájtokra cseréli, amelyeket Ön maga szállít

Miért ne csak telepítsük a betűtípusokat a szerverre?

Néha ez a válasz, és amikor az, válaszd ezt. Ám három gyakori helyzetben csődöt mond. A licencelés tilthatja egy betűtípus szerverre telepítését automatizált rendereléshez. A konténerképeket gyakran újraépítik, és egy kézzel telepített betűtípus eltűnik a következő telepítéssel. A szabályozott munkafolyamatoknak pedig arra van szükségük, hogy a renderelési verem verziókövetett artefaktumokból reprodukálható legyen, amit egy gépszintű betűtípus-telepítés nem tud biztosítani

Egy szolgáltató mindhármat kezeli azzal, hogy a döntést átteszi a saját alkalmazásodba. A betűtípusok általad irányított erőforrásokként érkeznek, a leképezési szabályzat átvizsgálható kód, ugyanaz a bináris pedig mindenhol azonosan renderel, mert semmi nem függ attól, mi van éppen telepítve

Egy szolgáltató telepítése

A konfigurálásnak a könyvtár betöltése előtt kell megtörténnie. A PDFium inicializáláskor fogad el egy rendszer-betűtípus-információs struktúrát, és megtartja az utána kiadott leírókat, így egy szolgáltató lecserélése nyitott dokumentumok mellett érvénytelenítené azokat a betűtípus-leírókat, amelyeket a PDFium még tart; a komponens ezt egyenesen elutasítja, ahelyett hogy hagyná egy renderelés sérülését:

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
  // Determinisztikus leképezés: a betűkép-név, a súly és a dőlt döntik el,
  // melyik fájlt szolgáltatjuk erre a kérésre
  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);   // teljes sfnt vagy TTC bájtok
  Font.Charset := Request.Charset;
  Font.TTCIndex := 0;                     // index egy gyűjteményen belül
end;

var
  Policy: TPdfSystemFontPolicy;
begin
  Policy := TPdfSystemFontPolicy.Default;
  Policy.AllowDefaultFallback := False;   // a gazdagép dönt mindenről
  Policy.AllowFaceSubstitution := False;  // egy eltérő betűkép-név elutasítása
  Policy.MaxFontBytes := 32 * 1024 * 1024;
  Policy.MaxCacheEntries := 64;

  ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
  // Csak most töltsd be a könyvtárat, és nyisd meg a dokumentumokat
end;

A leépítés fordított sorrendben fut: először a szolgáltatót választja le a PDFiumról, majd a könyvtárat tölti ki. A leválasztás kihagyása olyan natív betűtípus-leírókat hagy hátra, amelyek éppen felszabadításra kerülő Pascal-objektumokra mutatnak, ami a klasszikus leállítási hozzáférési hiba az olyan kódban, amely referenciaszámlált interfészeket kever egy C könyvtárral

Mit döntenek el valójában a szabályzat-jelzők?

Az AllowDefaultFallback a kapcsoló két üzemmód között. Kikapcsolva egy olyan kérés, amelyet a szolgáltató elutasít, egyszerűen elbukik, ami akkor kívánatos, ha azt bizonyítod, hogy egy korpusz minden betűtípusa számba van véve: bármilyen hiányosság azonnal láthatóvá válik ahelyett, hogy elsimulna. Bekapcsolva a feloldatlan kérések az FPDF_GetDefaultSystemFontInfo által visszaadott leképezőhöz kerülnek átirányításra, miközben a külvilág továbbra is egyetlen egységes leíró-burkolatot lát, ahol a betűkép-név, a karakterkészlet, a táblázatadatok és a betűtípus-törlés forrás szerint helyesen kerül irányításra

Az AllowFaceSubstitution szabályozza, hogy egy szolgáltató válaszolhat-e a kérttől eltérő betűkép-névvel. Kikapcsolása a helyettesítést explicit döntéssé teszi véletlen helyett, ami akkor számít, amikor egy dokumentum olyan betűtípust nevez meg, amelynek metrikái eléggé eltérnek ahhoz, hogy megváltoztassák a lapozást

A komponens minden szolgáltatói választ ellenőriz, mielőtt az elérné a PDFiumot: az üres adatokat elutasítja, a túlméretezett betűtípusokat elutasítja a MaxFontBytes alapján, ellenőrzi a TTC-indexet, az egyedi sfnt táblákat pedig a betűtípus-könyvtárból szolgálja ki, amikor a PDFium egy táblázatot kér a teljes fájl helyett. Ez az utolsó képesség azt jelenti, hogy egy szolgáltató átadhat egy teljes betűtípusfájlt, és hagyhatja, hogy a komponens válaszoljon a táblázatszintű lekérdezésekre, ahelyett hogy nyers Pascal-objektumokat tenne ki a C ABI-n keresztül

PDFium Component érvényesítési kapuk ellenőrzik az üres adatot, MaxFontBytes méretet, TTC indexet és arckicserélést, mielőtt a bájtok elérnék a PDFiumot, az AllowDefaultFallback pedig dönt a gazdatartalék és a sikertelen kérés között
Az érvényesítés a komponensen belül fut, mielőtt bármely szolgáltatói bájt elérné a PDFiumot; tartalék nélkül egy leképezhetetlen font hangosan bukik meg helyettesítővel való renderelés helyett

Gyorsítótárazás lógó betűtípusadatok nélkül

A betűtípus-leképezési kérések rendereléskor folyamatosan ismétlődnek, ezért a válaszokat egy minden betűtípus-kiválasztási paramétert lefedő kulccsal gyorsítótárazza, korlátozott legrégebben-használt sorrendben kiürítve. A finomság az élettartam: a PDFium még olvashatja egy olyan betűtípus bájtjait, amelynek gyorsítótár-bejegyzését éppen most ürítették ki

A gyorsítótár referenciaszámlált dinamikus tömböket tárol, és minden natív leíró a saját pillanatképét tartja, így a kiürítés egy hivatkozást ejt el, nem szabadít fel használatban lévő memóriát. A törlési visszahívás felszabadítja a leírót, és fenntart egy aktív számlálót. Gyakorlatilag ez azt jelenti, hogy a MaxCacheEntries memóriára hangolható anélkül, hogy fennállna a veszélye, hogy adatokat húznánk ki egy folyamatban lévő renderelés alól

A saját szálamon hívódik meg a szolgáltató?

Nem, nem feltétlenül. A PDFium a saját munkásszálairól is hívhatja a leképezőt, így egy implementációnak szálbiztosnak kell lennie. A megosztott számlálókat, a gyorsítótárat és a konfiguráció megfigyelését a komponens saját kritikus szekciója védi mindegyiket külön-külön, de a ResolveFont-on belüli kódot neked kell biztonságossá tenned

A legbiztonságosabb forma egy olyan szolgáltató, amely semmilyen módosítható megosztott állapotot nem érint: olvass egy induláskor felépített táblából, tölts be bájtokat egy fájlból vagy erőforrásból, adj vissza. Ha egy keresésnek saját megosztott gyorsítótárra van szüksége, védd azt. És tartsd bent a kivételeket a saját implementációdon belül, mivel egy Pascal-kivétel soha nem tekeredhet vissza a PDFium veremén keresztül; a komponens a C ABI-határon elkapja, és hibává vagy egy opcionális alapértelmezett visszaesésre alakítja, de ha erre normál vezérlésfolyamként támaszkodsz, az teljesítménybe kerül és hibákat rejt el. A komponens többi részének szálkezelési szabályai ugyanazokat az elveket követik, mint amelyeket a renderzár-fegyelem ismertet

A leképezés bizonyítása éles környezetben

A statisztikák a betűtípus-helyettesítést találgatásból olyan dologgá alakítják, amire állításokat tehetsz. A GetSystemFontProviderStatistics jelenti, hogy van-e konfigurálva és telepítve szolgáltató, hány leképezési kérés érkezett, és hogyan lettek kielégítve, gyorsítótár-találatokra, szolgáltatói találatokra és alapértelmezett visszaesési találatokra bontva, az elutasított válaszokkal, a sikertelen kérésekkel, az élő leírókkal és a gyorsítótárazott betűtípusokkal együtt:

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]));

  // Egy megfelelőségi futtatásban, ahol a visszaesés le van tiltva, bármilyen
  // visszaesési találat vagy sikertelen kérés azt jelenti, hogy egy dokumentum
  // olyan betűtípusra hivatkozott, amelyet nem szállítunk
  if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
    raise Exception.Create('unmapped font encountered - update the font set');
end;

Egy emelkedő RejectedProviderResponses szám az a jel, hogy egy szolgáltató olyan adattal válaszol, amelyet a szabályzat elutasít, általában egy túlméretezett fájl vagy egy helyettesített betűkép, és érdemes rá riasztást állítani, mert ezek a kérések csendben visszaesésre vagy hibára degradálódnak. Annak diagnosztizálásához, hogy egy dokumentumnak valójában mely betűtípusokra van szüksége, mielőtt felépítenéd a leképezési táblát, a PDF-betűtípus-tulajdonságok elemzése cikkben található vizsgálati útvonal dokumentumonként felsorolja a beágyazott és be nem ágyazott betűtípusokat

A betűtípus-ellátás, a renderelés és a szövegkinyerés ugyanazt a könyvtárpéldányt osztja meg Delphiben, C++Builderben és Lazarusban; a telepítési részleteket a PDFium Component for Delphi oldala ismerteti

A GetSystemFontProviderStatistics PDFium betűkészletleképezési kéréseket bont gyorsítótár-, szolgáltató- és tartaléktalálatokra Delphi megfelelőségi futásban, amely kivételt emel, ha DefaultFallbackHits vagy FailedRequests jelenik meg
A statisztikarekord a fontcserét olyanná teszi, amire egy megfelelőségi futás állíthat; bármilyen tartalék vagy megbukott kérés azt jelenti, hogy a dokumentumnak olyan font kellett, amelyet a készlet nem szállít