PDFium Component leidžia Delphi aplikacijai nuspręsti, kokie šrifto baitai naudojami, kai PDF failas nurodo šriftą, kurio jis neįterpia. ConfigureSystemFontProvider įdiegia IPdfSystemFontProvider realizaciją, kuri gauna kiekvieną PDFium daromą šrifto susiejimo užklausą, su šrifto pavadinimu, storiu, kursyvo požymiu, koduote ir tarpo šeima, ir atsako naudotinais TrueType, TrueType Collection arba OpenType baitais
Tai egzistuoja todėl, kad neįterpti šriftai yra atvaizdavimo loterija. PDF failas, kuris nurodo Arial ir nieko neįterpia, darbo stotyje atvaizduojamas su Arial, Linux serveryje – su metrikai suderintu pakaitalu, o uždaroje konteinerio nuotraukoje – su tuo, ką randa priimančiosios sistemos susiejimo mechanizmas. Ta pati sąskaita kiekvienoje aplinkoje atrodo kitaip, eilučių lūžiai pasislenka, o klientas gauna dokumentą, kuris nesutampa su archyvuota kopija
Kodėl tiesiog neįdiegus šriftų serveryje?
Kartais tai ir yra atsakymas, ir kai taip yra, rinkitės jį. Bet tai neveikia trijose įprastose situacijose. Licencijavimas gali drausti diegti šriftą serveryje automatiniam atvaizdavimui. Konteinerių atvaizdai dažnai perkuriami iš naujo, ir rankiniu būdu įdiegtas šriftas dingsta su kitu diegimu. O reguliuojamiems darbo srautams reikia, kad atvaizdavimo krūva būtų atkuriama iš artefaktų, esančių versijų kontrolėje, o šriftas, įdiegtas visos mašinos lygiu, tokiu nėra
Tiekėjas sprendžia visus tris atvejus perkeldamas sprendimą į jūsų aplikaciją. Šriftai keliauja kaip jūsų kontroliuojami resursai, susiejimo politika yra kodas, kurį galite peržiūrėti, ir tas pats vykdomasis failas atvaizduoja identiškai visur, nes niekas nepriklauso nuo to, kas atsitiktinai įdiegta
Tiekėjo įdiegimas
Konfigūracija turi įvykti prieš įkeliant biblioteką. PDFium priima sistemos šrifto informacijos struktūrą inicializacijos metu ir po to laiko saugiklius, kuriuos ji išdalija, todėl tiekėjo pakeitimas, kai dokumentai atidaryti, padarytų negaliojančiais šriftų saugiklius, kuriuos PDFium vis dar laiko; komponentas tai iškart atmeta, o ne leidžia sugadinti atvaizdavimą:
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
// Deterministinis susiejimas: šrifto pavadinimas plius storis ir
// kursyvas nusprendžia, kurį failą pateikiame šiai užklausai
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); // pilni sfnt arba TTC baitai
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // indeksas kolekcijos viduje
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // priimančioji sistema sprendžia viską
Policy.AllowFaceSubstitution := False; // atmesti kitokį šrifto pavadinimą
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// Tik dabar įkelkite biblioteką ir atidarykite dokumentus
end;
Išardymas vyksta priešinga tvarka: tiekėjas pirmiausia atjungiamas nuo PDFium, tada biblioteka iškeliama iš atminties. Praleidus atjungimą, liktų vietinių šriftų saugikliai, rodantys į Pascal objektus, kurie tuoj bus atlaisvinti, o tai klasikinis išjungimo prieigos pažeidimas kode, kuris maišo su nuorodomis skaičiuojamas sąsajas su C biblioteka
Ką iš tikrųjų nusprendžia politikos vėliavėlės
AllowDefaultFallback yra jungiklis tarp dviejų veikimo režimų. Jai išjungtai, užklausa, kurią tiekėjas atmeta, tiesiog nepavyksta – to ir norite, kai įrodinėjate, jog kiekvienas šriftas duomenų rinkinyje apskaitytas: bet kokia spraga tampa iškart matoma, o ne užtušuota. Jai įjungtai, neišspręstos užklausos perduodamos susiejimo mechanizmui, kurį grąžina FPDF_GetDefaultSystemFontInfo, o išorinis pasaulis vis tiek mato vieną vienodą saugiklio apvalkalą, su šrifto pavadinimu, koduote, lentelės duomenimis ir šrifto ištrynimo procedūra, teisingai nukreipta pagal kilmę
AllowFaceSubstitution valdo, ar tiekėjas gali atsakyti kitokiu šrifto pavadinimu, nei buvo prašyta. Jos išjungimas paverčia pakeitimą aiškiu sprendimu, o ne atsitiktinumu, ir tai svarbu, kai dokumentas nurodo šriftą, kurio metrika skiriasi pakankamai, kad pakeistų puslapiavimą
Komponentas patikrina kiekvieną tiekėjo atsakymą prieš jam pasiekiant PDFium: tušti duomenys atmetami, per dideli šriftai atmetami pagal MaxFontBytes, TTC indeksas tikrinamas, o atskiros sfnt lentelės pateikiamos iš šrifto katalogo, kai PDFium prašo lentelės, o ne viso failo. Ši paskutinė galimybė reiškia, kad tiekėjas gali perduoti pilną šrifto failą ir leisti komponentui atsakyti į lentelės lygio užklausas, o ne atskleisti žalius Pascal objektus per C ABI
Talpykla be pakibusių šrifto duomenų
Šrifto susiejimo užklausos nuolat kartojasi atvaizdavimo metu, todėl atsakymai talpinami su raktu, apimančiu kiekvieną šrifto pasirinkimo parametrą, ir išstumiami pagal apribotą seniausiai naudoto elemento (LRU) tvarką. Subtilumas yra gyvavimo trukmė: PDFium vis dar gali skaityti šrifto, kurio talpyklos įrašas ką tik buvo išstumtas, baitus
Talpykla saugo nuorodomis skaičiuojamus dinaminius masyvus, o kiekvienas vietinis saugiklis laiko savo pačio nuotrauką, todėl išstūmimas atmeta nuorodą, o ne atlaisvina naudojamą atmintį. Ištrynimo iškvietimas atlaisvina saugiklį ir palaiko aktyvų skaitiklį. Praktiškai tai reiškia, kad MaxCacheEntries galima derinti dėl atminties be jokios rizikos ištraukti duomenis iš po vykstančio atvaizdavimo
Ar tiekėjas kviečiamas mano gijoje?
Ne, nebūtinai. PDFium gali kviesti susiejimo mechanizmą iš savo darbo gijų, todėl realizacija turi būti saugi gijoms. Bendri skaitikliai, talpykla ir konfigūracijos stebėjimas kiekvienas apsaugotas komponento viduje savo pačių kritine sekcija, tačiau kodas ResolveFont viduje yra jūsų atsakomybė padaryti saugų
Saugiausia forma yra tiekėjas, kuris neliečia jokios keičiamos bendrinamos būsenos: skaito iš paleidimo metu sukurtos lentelės, įkelia baitus iš failo ar resurso, grąžina. Jei paieškai reikia savo bendrinamos talpyklos, apsaugokite ją. Ir laikykite išimtis savo realizacijos viduje, nes Pascal išimtis niekada neturi „išvynioti“ per PDFium dėklą; komponentas pagauna ties C ABI riba ir paverčia nesėkme arba pasirenkamu numatytuoju atsarginiu variantu, tačiau pasikliovimas tuo kaip įprastu valdymo srautu kainuoja našumą ir slepia klaidas. Kitos komponento dalies gijų taisyklės seka tuos pačius principus kaip ir straipsnyje atvaizdavimo užrakto disciplina
Susiejimo įrodymas gamyboje
Statistika paverčia šrifto pakeitimą iš spėjimo į kažką, ką galima patikrinti. GetSystemFontProviderStatistics praneša, ar tiekėjas sukonfigūruotas ir įdiegtas, kiek susiejimo užklausų buvo padaryta ir kaip jos buvo patenkintos, suskirstyta į talpyklos pataikymus, tiekėjo pataikymus ir numatytojo atsarginio varianto pataikymus, kartu su atmestais atsakymais, nepavykusiomis užklausomis, gyvais saugikliais ir talpinamais šriftais:
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]));
// Atitikties patikros vykdyme su išjungtu atsarginiu variantu bet
// koks atsarginio varianto pataikymas ar nepavykusi užklausa reiškia,
// kad dokumentas nurodė šriftą, kurio mes netiekiame
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
Kylantis RejectedProviderResponses skaičius yra signalas, kad tiekėjas atsako duomenimis, kuriuos politika atmeta – dažniausiai per dideliu failu arba pakeistu šriftu, ir apie tai verta įspėti, nes tokios užklausos tyliai nusmunka iki atsarginio varianto ar nesėkmės. Diagnozuojant, kokių šriftų dokumentui iš tikrųjų reikia prieš kuriant susiejimo lentelę, tikrinimo kelias straipsnyje PDF šriftų savybių analizė išvardija įterptus ir neįterptus kiekvieno dokumento šriftus
Šrifto tiekimas, atvaizdavimas ir teksto ištraukimas dalijasi tuo pačiu bibliotekos egzemplioriumi Delphi, C++Builder ir Lazarus aplinkose; diegimo detalės aprašytos PDFium Component Delphi puslapyje