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

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

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