PDFium Component umožňuje delphi aplikácii rozhodnúť, ktoré bajty písma sa použijú, keď PDF odkazuje na písmo, ktoré nevkladá. ConfigureSystemFontProvider nainštaluje implementáciu IPdfSystemFontProvider, ktorá prijíma každú požiadavku na mapovanie písma, akú PDFium vytvorí, kompletnú s názvom rezu, hrúbkou, príznakom kurzívy, znakovou sadou a rodinou rozstupu, a odpovedá bajtmi TrueType, TrueType Collection alebo OpenType, ktoré sa majú použiť
Toto existuje preto, lebo nevložené písma sú lotéria vykresľovania. PDF, ktoré pomenúva Arial a nič nevkladá, sa vykreslí s Arialom na pracovnej stanici, s metricky kompatibilnou náhradou na serveri s Linuxom a s čímkoľvek, čo nájde hostiteľský mapper na uzamknutom kontajnerovom obraze. Rovnaká faktúra vyzerá na každom inak, zalomenia riadkov sa posúvajú a zákazník dostane dokument, ktorý sa nezhoduje s archivovanou kópiou
Prečo jednoducho nenainštalovať písma na server?
Niekedy je to odpoveď, a keď je, využite ju. No zlyháva v troch bežných situáciách. Licencovanie môže zakazovať inštaláciu písma na server na automatizované vykresľovanie. Kontajnerové obrazy sa prestavujú často a písmo nainštalované ručne zmizne pri ďalšom nasadení. A regulované pracovné postupy vyžadujú, aby bol vykresľovací zásobník reprodukovateľný z artefaktov pod správou verzií, čo inštalácia písma na úrovni celého systému nie je
Poskytovateľ rieši všetky tri tým, že presúva rozhodnutie do vašej aplikácie. Písma sa dodávajú ako zdroje, ktoré máte pod kontrolou, politika mapovania je kód, ktorý môžete skontrolovať, a ten istý binárny súbor vykresľuje identicky všade, pretože nič nezávisí od toho, čo je práve nainštalované
Inštalácia poskytovateľa
Konfigurácia musí prebehnúť pred načítaním knižnice. PDFium prijíma štruktúru informácií o systémových písmach pri inicializácii a následne si uchováva handle, ktoré potom vydáva, takže výmena poskytovateľa počas otvorených dokumentov by znehodnotila handle písiem, ktoré PDFium ešte drží; komponent to rovno odmieta namiesto toho, aby to nechal poškodiť vykresľovanie:
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
// Deterministické mapovanie: názov rezu plus hrúbka a kurzíva rozhodujú,
// ktorý súbor pre túto požiadavku dodáme
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); // kompletné bajty sfnt alebo TTC
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // index vo vnútri kolekcie
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // hostiteľ rozhoduje o všetkom
Policy.AllowFaceSubstitution := False; // odmietnuť odlišný názov rezu
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// Až teraz načítajte knižnicu a otvárajte dokumenty
end;
Ukončenie prebieha v opačnom poradí: poskytovateľ sa najprv odpojí od PDFium, potom sa knižnica uvoľní z pamäte. Vynechanie odpojenia ponechá natívne handle písiem ukazujúce na objekty Pascalu, ktoré sa práve chystajú uvoľniť, čo je klasická access violation pri ukončení v kóde, ktorý kombinuje rozhrania s počítaním referencií s knižnicou v C
Čo v skutočnosti rozhodujú príznaky politiky
AllowDefaultFallback je prepínač medzi dvoma prevádzkovými režimami. Keď je vypnutý, požiadavka, ktorú poskytovateľ odmietne, jednoducho zlyhá, čo je presne to, čo chcete, keď dokazujete, že každé písmo v korpuse je zohľadnené: akákoľvek medzera sa okamžite prejaví namiesto toho, aby sa zamaskovala. Keď je zapnutý, nevyriešené požiadavky sa delegujú na mapper vrátený funkciou FPDF_GetDefaultSystemFontInfo, zatiaľ čo vonkajší svet stále vidí jeden jednotný obal handle, s názvom rezu, znakovou sadou, dátami tabuľky a mazaním písma smerovaným správne podľa pôvodu
AllowFaceSubstitution riadi, či poskytovateľ smie odpovedať s iným názvom rezu, než aký bol požadovaný. Vypnutím sa náhrada stáva explicitným rozhodnutím namiesto náhody, čo je dôležité, keď dokument pomenúva písmo, ktorého metriky sa líšia natoľko, že to zmení stránkovanie
Komponent validuje každú odpoveď poskytovateľa skôr, než sa dostane k PDFium: prázdne dáta sa odmietajú, nadmerne veľké písma sa odmietajú voči MaxFontBytes, kontroluje sa index TTC a jednotlivé tabuľky sfnt sa poskytujú z adresára písma, keď PDFium žiada o tabuľku namiesto celého súboru. Táto posledná schopnosť znamená, že poskytovateľ môže odovzdať kompletný súbor písma a nechať komponent odpovedať na dopyty na úrovni tabuľky namiesto vystavovania surových objektov Pascalu naprieč rozhraním C ABI
Cachovanie bez visiacich dát písiem
Požiadavky na mapovanie písma sa počas vykresľovania neustále opakujú, takže odpovede sa cachujú s kľúčom pokrývajúcim každý parameter výberu písma a vyraďujú sa obmedzeným poradím najmenej nedávno použitých (LRU). Jemnosťou je životnosť: PDFium môže stále čítať bajty písma, ktorého záznam v cache bol práve vyradený
Cache ukladá dynamické polia s počítaním referencií a každý natívny handle si drží vlastný snímok, takže vyradenie zníži počet referencií namiesto uvoľnenia práve používanej pamäte. Callback na mazanie uvoľní handle a udržuje aktívny počet. Prakticky to znamená, že MaxCacheEntries sa dá ladiť kvôli pamäti bez akéhokoľvek rizika, že sa dáta vytiahnu spod prebiehajúceho vykresľovania
Volá sa poskytovateľ na mojom vlákne?
Nie, nie nevyhnutne. PDFium môže volať mapper z vlastných pracovných vlákien, takže implementácia musí byť bezpečná pre vlákna. Zdieľané počítadlá, cache a sledovanie konfigurácie sú vo vnútri komponentu každé chránené vlastnou kritickou sekciou, no kód vo vnútri ResolveFont musíte spraviť bezpečným vy sami
Najbezpečnejšia podoba je poskytovateľ, ktorý sa nedotýka žiadneho meniteľného zdieľaného stavu: čítať z tabuľky zostavenej pri štarte, načítať bajty zo súboru alebo zdroja, vrátiť. Ak vyhľadávanie potrebuje vlastnú zdieľanú cache, zaistite ju. A výnimky ponechajte vo vnútri svojej implementácie, keďže výnimka Pascalu nesmie nikdy odvíjať zásobník cez PDFium; komponent ju zachytáva na hranici C ABI a prevádza na zlyhanie alebo voliteľnú predvolenú náhradu, no spoliehanie sa na to ako na bežný riadiaci tok stojí výkon a skrýva chyby. Pravidlá vláknovania pre zvyšok komponentu sa riadia rovnakými zásadami ako v časti disciplína zámku vykresľovania
Dokazovanie mapovania v produkcii
Štatistiky menia náhradu písiem z hádania na niečo, o čom môžete tvrdiť konkrétne fakty. GetSystemFontProviderStatistics hlási, či je poskytovateľ nakonfigurovaný a nainštalovaný, koľko požiadaviek na mapovanie bolo vytvorených a ako boli uspokojené — rozdelené na zásahy cache, zásahy poskytovateľa a zásahy predvolenej náhrady —, spolu s odmietnutými odpoveďami, zlyhanými požiadavkami, živými handle a cachovanými písmami:
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]));
// Pri behu overovania zhody s vypnutou náhradou akýkoľvek zásah náhrady
// alebo zlyhaná požiadavka znamená, že dokument odkazoval na písmo, ktoré nedodávame
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
Rastúci počet RejectedProviderResponses je signál, že poskytovateľ odpovedá dátami, ktoré politika odmieta, zvyčajne nadmerne veľkým súborom alebo nahradeným rezom, a oplatí sa naň nastaviť upozornenie, pretože takéto požiadavky ticho degradujú na náhradu alebo zlyhanie. Na diagnostiku, ktoré písma dokument skutočne potrebuje pred zostavením mapovacej tabuľky, cesta inšpekcie v časti analýza vlastností písiem PDF vypíše vložené aj nevložené písma pre každý dokument
Zabezpečovanie písiem, vykresľovanie a extrakcia textu zdieľajú tú istú inštanciu knižnice naprieč Delphi, C++Builder a Lazarus; podrobnosti nasadenia opisuje stránka PDFium Component pre Delphi