PDFium Component for Delphi įjungus BrotliEnabled arba IsolatePerDocument TPdfLibraryConfiguration viduje, pridėtoji Skia varianta anksčiau pereidavo į AGG atvaizduotoją be jokios klaidos, nes abi parinktys pakelia FPDF_LIBRARY_CONFIG iki versijos, kurioje PDFium m_RendererType skaito pažodžiui. Nuo v3.123.0 numatytasis atvaizduotojas lieka DLL sava numatytasis, o nuo v3.125.0 Skia arba Fontations prašymas, kurio DLL negali įvykdyti, kelia pagaunamą EPdfError vietoj proceso žūties
Nė viena klaida nesipaskelbė pati. Pirmoji duodavo puslapius, atrodančius gerai – tiesiog atvaizduotus kitu rasterizatoriumi, su kiek kita glotninimo ir teksto kraštine negu variante, kurį išleidote ir išbandėte. Antroji paskelbėsi – garsiai, nukeldama hosto procesą iš savosios inicijacijos vidaus. Abi ateina iš tos pačios vietos: versijuotos C struktūros, kurios laukai skaitosi tik tada, kai versijos numeris to nori, ir kurios nulinės reikšmės nėra „nenustatyta“, o tikri pasirinkimai
Kaip FPDF_LIBRARY_CONFIG nusprendžia, kurį atvaizduotoją naudos PDFium?
FPDF_InitLibraryWithConfig į m_RendererType žiūri tik tada, kai struktūros Version laukas 4 ar aukštesnis, ir nuo tos versijos reikšmę naudoja tiksliai taip, kaip parašyta. Žemiau versijos 4 PDFium lauką ignoruoja ir ima varianto numatytąjį – Skia variantuose, sukompiliuotuose su PDF_USE_SKIA, ir AGG visur kitur
Kiekvienas vėlesnis laukas seka tą pačią struktūrą. Struktūra augo po vieną gebėjimą, ir kiekvienas gebėjimas atkeldavo kartu su nauju versijos numeriu. PDFium Component savąją struktūrą LoadLibrary viduje stato iš jūsų TPdfLibraryConfiguration ir versiją kelia tik tiek, kiek reikalauja jūsų nustatytos parinktys
| Struktūros versija | Pridedamas laukas | Nustato |
|---|---|---|
| 2 | m_pIsolate, m_v8EmbedderSlot | Visada rašoma; V8Isolate, V8EmbedderSlot |
| 3 | m_pPlatform | V8Platform ne nil |
| 4 | m_RendererType | Renderer kitoks nei prpDefault |
| 5 | m_FontLibraryType | FontBackend kitoks nei pfbpDefault |
| 6 | m_BrotliEnabled | BrotliEnabled = True |
| 7 | m_IsolatePerDocument | IsolatePerDocument = True |
Spąstai – paskutinėse dviejose eilutėse. Versijos kaupiamosios: versijos 6 struktūra yra ir versijos 4, ir versijos 5 struktūra, tad PDFium skaito m_RendererType ir m_FontLibraryType, nors prašėte vien Brotli. Kas tuomet tupi tuose dviejuose laukuose, tas tampa atvaizduotoju ir šriftų backendu – norėjote jų rinktis ar ne
Kodėl Brotli įjungimas persijungė atvaizduotoją į AGG?
Iki v3.123.0 PDFium Component prpDefault atveju į m_RendererType rašydavo FPDF_RENDERERTYPE_AGG, tad bet kokia struktūrą iki versijos 6 ar 7 pakėlusi konfigūracija Skia variante priverstinai dėdavo AGG. pdfium.dll ir pdfium.v8.dll vykdymo aplinkos, keliaujančios su komponentu, yra Skia variantai, tad tai ištikdavo numatytąjį išdėstymą, o ne kokį egzotiškąjį
Susiejimas, kai buvo parašytas, atrodė nekaltas. Ant versijos 2 ar 3 laukas niekada neskaitomas, tad prpDefault iš tiesų reiškė „ką DLL bedarytų“. Kai tik BrotliEnabled (versija 6) arba IsolatePerDocument (versija 7) įėjo į žaidimą, tas pats kodas „be nuostatų“ pavertė aiškiu AGG prašymu. Nieko nesugriuvo. PDFium inicijavosi normaliai, atvaizdavo kiekvieną puslapį ir negrąžino jokio klaidos kodo, nes jo akimis kvientėjas buvo paprašęs AGG ir AGG gavo
Pikselių maišos kodas permainą padaro matomą ten, kur ekrano kopijos ne. Atvaizdavus to paties pavyzdinio dokumento pirmąjį puslapį po trijomis konfigūracijomis gauta:
- Numatytoji konfigūracija: maišos kodas
502D77C3711B4ACF BrotliEnabled= True suRenderer, paliktu antprpDefault: maišos kodasF75B5EB4728ADE87- Aiškus
prpAgg: maišos kodasF75B5EB4728ADE87– identiškas Brotli paleidimui
v3.123.0 pataisa – viešoji funkcija PdfNativeRendererType, kuri TPdfRendererPreference išsprendžia į reikšmę, rašomą į m_RendererType. prpAgg ir prpSkia susieja vienas su vienu. prpDefault dabar susieja su Skia, kai pakrautoji DLL eksportuoja FPDF_RenderPageSkia, ir su AGG kitu atveju. Tas eksportas kompiliuojamas toje pačioje PDF_USE_SKIA sąlygoje kaip ir pats Skia numatytasis, tad tai vienintelė varianto savybė, kurią galima stebėti iš DLL išorės. Po pataisymo Brotli konfigūracija duoda tą patį maišos kodą kaip numatytoji
Šriftų backendas tos pačios problemos niekada neturėjo. m_FontLibraryType skaitomas nuo versijos 5, o jo nulinė reikšmė, FPDF_FONTBACKENDTYPE_FREETYPE, yra ir PDFium numatytasis, kai laukas apskritai neskaitomas. FreeType rašymas pfbpDefault atveju todėl tiksliai atkuria savąjį numatytąjį. Nulinės reikšmės ne visada blogos – jos tiesiog niekada nėra automatiškai teisingos
Su v3.123.0 ar naujesne starto kodas, kurį rašytumėte natūraliai, dabar daro tai, ką sako:
uses
PDFium;
procedure ConfigurePdfiumAtStartup;
var
Config: TPdfLibraryConfiguration;
begin
// Turi vykti prieš bet ką pakraunant savąją biblioteką
Config := TPdfLibraryConfiguration.Default;
Config.BrotliEnabled := True; // pakelia FPDF_LIBRARY_CONFIG iki versijos 6
// Renderer lieka prpDefault: išsprendžiama į Skia variantuose, eksportuojančiuose
// FPDF_RenderPageSkia, ir į AGG vien AGG variantuose
SetLength(Config.UserFontPaths, 1);
Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
ConfigurePdfLibrary(Config);
end;
Atsiminkite, jog BrotliEnabled PDF 2.0 /BrotliDecode srautus padaro išskaitomus tik tada, kai pati DLL sukonstruota su PDF_ENABLE_BROTLI. Vėliavėlė – prašymas, ir variante be Brotli palaikymo ji nieko nedaro. TPdfLibraryConfiguration.Hardened sutampa su Default, išskyrus AllowMachineTime = False, kas draudžia dokumento JavaScript skaityti tikrąjį laikrodį; protinga pradžia serveriniam nepatikimų failų apdorojimui
Kas nutinka, kai prašote backendą, kurio DLL neturi?
PDFium klaidos negrąžina atvaizduotojui ar šriftų backendui, kurių variante nėra: FPDF_InitLibraryWithConfig žlugdo savąjį CHECK, kuris Windows aplinkoje pasirodo kaip stabdžio taško išimtis ir, be struktūrinio išimčių doroklio aplink kvietimą, baigia procesą. Antraštė sako tą patį, įspėdama, kad nepalaikoma reikšmė „panašiai žlugs su neišvengiamu strigtimu“
Du konkretūs atvejai – vien AGG variantas, gaunantis FPDF_RENDERERTYPE_SKIA, ir variantas be Fontations, gaunantis FPDF_FONTBACKENDTYPE_FONTATIONS. Pridėtoji Skia vykdymo aplinka priklauso antrajai grupei: ji atvaizduoja su Skia, bet šriftams naudoja FreeType. prpSkia prašymas kartu su pfbpFontations ant jos Delphi pusėje duodavo External exception 80000003. Kai derintojas ar išimčių doroklė atsitiktinai tai sugauna, padėtis vis tiek neištaisoma:
- PDFium lieka pusiau inicijuota
- Viso proceso konfigūracija jau užantspauduota, tad
ConfigurePdfLibraryatmeta pataisytąją konfigūraciją - Bandymas iš naujo su kita konfigūracija tame pačiame procese nebepavyks
Tai – atvirkščias Brotli klaidos nesėkmės atvejis. Ten lauke tupėjo reikšmė, kurios niekas nepasirinko, ir PDFium ją tyliai priėmė. Čia lauke tupi reikšmė, kurią kvientėjas pasirinko sąmoningai, ir PDFium apie jos niekų nenori girdėti. Abu dalykai – problemos, kurias aplinkas turi išspręsti prieš savąjį kvietimą, nes po jo lieka nebegautino
Kaip PDFium Component pirmiau patikrina Skia ir Fontations
Nuo v3.125.0 LoadLibrary konfigūraciją patikrina po DLL eksportų susiejimo ir prieš kviesdamas FPDF_InitLibraryWithConfig, o nepalaikomą atvaizduotoją ar šriftų backendą paverčia EPdfError su žinute, įvardijančia kaltąjį nustatymą ir alternatyvas. DLL iškraunama, konfigūracija atspaudžiama, tad kvientėjas gali rinktis kitus nustatymus ir krauti dar kartą
Pats sprendimas gyvena grynojoje funkcijoje PdfLibraryConfigurationSupportError, imančioje konfigūraciją plius du Boolean, aprašančius variantą, ir grąžinančioje tuščią eilutę, kai derinys saugus. Kadangi ji savosios būsenos neliečia, ją galite kviesti iš savųjų testų su bet kuriuo gebėjimų deriniu. LoadLibrary viduje du Boolean ateina iš skirtingų rūšių įrodymų, ir jie nusipelno skirtingo pasitikėjimo:
- Skia aptinkama iš
FPDF_RenderPageSkiaeksporto buvimo – to paties ženklo, kuriuo naudojasiPdfNativeRendererType. Eksportas ir Skia atvaizduotojas kompiliuojami vienoje sąlygoje, tad patikra tiksli - Fontations savo eksporto neturi. Vienintelis paliktas pėdsakas – Rust šriftų crate'ai, kuriuos jis suvilioja į dvejetainį failą, tad PDFium Component pakrautąją bibliotekos failą išskenuoja ieškodamas crate pavadinimų
skrifairread-fonts(taip patread_fonts). Skenuojama tik kai prašomapfbpFontations, o neperskaitomas failas skaitosi kaip „be Fontations“
Fontations patikra – heuristika, ir ji gali klysti viena kryptimi: Fontations variantas, nukirptas nuo kiekvienos tų eilučių, būtų atmetamas, nors galėjo veikti. Tas kompromisas padarytas tyčia. Klaidingas atmetimas kainuoja išimtį, kurią sugaunate, ir atsitraukimą į FreeType. Klaidingas priėmimas kainuoja procesą
Atspaudimas svarbus ne mažiau už patikrą. LoadLibrary konfigūraciją užspaudžia pačioje krovimo pradžioje, tad be atstatymo gebėjimų atmetimas paliktų ConfigurePdfLibrary atsakant į kiekvieną pakartojimą su EPdfError „PDFium library configuration is already sealed“. Atmetimo kelias pirmiau kviečia UnloadLibrary; jo FPDF_DestroyLibrary kvietimas tą akimirką saugus, nes PDFium dar neinicijuota ir grąžina atsakymą nedelsdama. Kiti krovimo nesėkmės, tokios kaip dingusi DLL ar architektūros nesutapimas, antspaudą palieka, tad pakartojimų kilpa turi abi atskirti:
uses
SysUtils, PDFium;
function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
Config: TPdfLibraryConfiguration;
begin
Config := TPdfLibraryConfiguration.Default;
Config.Renderer := prpSkia;
ConfigurePdfLibrary(Config);
try
PDFium.LoadLibrary; // su modulio kvalifikatoriumi: Windows.LoadLibrary turi tą patį vardą
Result := prpSkia;
except
on E: EPdfError do
begin
// Gebėjimų atmetimas iškrauna DLL ir atspaudžia konfigūraciją.
// DLL, visai nepakrauta, lieka užspausta: pakartojimas nepadės
if PdfLibraryConfigurationSealed then
raise;
Config.Renderer := prpAgg;
ConfigurePdfLibrary(Config);
PDFium.LoadLibrary;
Result := prpAgg;
end;
end;
end;
Atkreipkite dėmesį į aiškųjį PDFium.LoadLibrary. Modulyje, naudojančiame ir Windows, arba Winapi.Windows, nekvalifikuotasis LoadLibrary išsiresolvina į tai, kuri module paskutinė uses sąraše; kai tai Win32 funkcija, kvietimas be argumentų nesukompiliuos – argumentų skaičiaus klaida, apie PDFium nesakančią nieko
Patikra, vykstanti dar anksčiau
ConfigurePdfLibrary kai kuriuos derinius atmeta dar prieš bet kokią DLL, visus su EPdfError. Aiškus FontBackend, įskaitant pfbpFreeType, reikalauja Renderer = prpSkia, nes PDFium šriftų backendą svarsto tik Skia atvaizduotojui. IsolatePerDocument reikalauja, kad V8Isolate būtų nil, nes PDFium kiekvienam dokumentui sukuria savąjį izoliatą ir žlugdo savąjį CHECK, jei jam dar vieną įduodate. Tuščios eilutės UserFontPaths viduje atmetamos. Ir bet koks kvietimas po pirmojo krovimo bandymo žlunga su „PDFium library configuration is already sealed“
Tas paskutinis variantas turi praktinę išvadą: DLL pirmiau iščiuopti ir tik paskui sukonfigūruoti negalima. GetSkiaRenderCapabilities, V8FeaturesAvailable, dokumento atvėrimas ir dauguma kitų įėjimo taškų viduje kviečia LoadLibrary, kuri konfigūraciją užspaudžia vietoje. Vėliau kviečiama UnloadLibrary jos irgi neatveria. Pirmiau sukonfigūruokite, paskui pakraukite, paskui klauskite – būtent ta tvarka, kurios turėtų laikytis diagnostinė procedūra:
uses
SysUtils, PDFium, FPdfView;
function DescribePdfiumState: string;
var
Config: TPdfLibraryConfiguration;
Renderer: string;
begin
Config := GetPdfLibraryConfiguration; // kopija, saugi apžiūrėti
if not PDFium.Loaded then
begin
if PdfLibraryConfigurationSealed then
Exit('PDFium failed to load; configuration is sealed');
Exit('PDFium not loaded; configuration can still change');
end;
// Tas pats sprendimas, kurį LoadLibrary taikė statydamas FPDF_LIBRARY_CONFIG
if PdfNativeRendererType(Config.Renderer,
GetSkiaRenderCapabilities.PageRender) = FPDF_RENDERERTYPE_SKIA then
Renderer := 'Skia'
else
Renderer := 'AGG';
Result := Format('Renderer=%s Brotli=%s IsolatePerDocument=%s',
[Renderer, BoolToStr(Config.BrotliEnabled, True),
BoolToStr(Config.IsolatePerDocument, True)]);
end;
Tos eilutės įrašymas į žurnalą kartą starto metu pigus, ir tai pirmasis dalykas, kurio norėsite remonto talone su „tekstas serveryje atrodo kitaip“. PDFium.Loaded kvalifikuotas ta pačia priežastimi kaip LoadLibrary: formos ar komponento metodo viduje plikasis Loaded prisiriša prie TComponent.Loaded
Du būdai, kaip versijuotoji C konfigūracijos struktūra suyra
Kiekviena versijuotoji konfigūracijos struktūra – ar tai FPDF_LIBRARY_CONFIG, Win32 cbSize įrašas, ar papildinio ABI – žlunga dviem simetriškomis kryptimis, ir aplinkas turi saugotis abiejų. Pirmoji – užpildyti lauką, paliekant versiją per žemą; antroji – pakelti versiją, paliekant lauką nulinėje reikšmėje, kurią biblioteka skaito kaip sąmoningą pasirinkimą
- Laukas nustatytas, versija per žema. Įrašykite
m_BrotliEnabled= 1 į versijos 2 struktūrą, ir PDFium į jį niekada nepažiūrės. Kvietimas pavyks, o Brotli srautai lieka neišskaitomi. Gynyba – išvesti versiją iš realiai naudojamų laukų, ką daroLoadLibrary, o ne įkalti vieną į kodą - Versija pakankama, nulinis laukas reiškia kažką. Pakelkite versiją iki 6, ir kiekvienas laukas iki versijos 6 dabar gyvas.
FillCharsunulinam_RendererTypeįFPDF_RENDERERTYPE_AGG– tikrą atvaizduotoją, o ne „nenustatyta“. Gynyba – kiekvieną lauką, kurį dengia pasirinktoji versija, rašyti su sąmoninga reikšme, o „numatytąjį“ spręsti prieš tikrąjį variantą, o ne manyti
Trečia taisyklė seka reikšmėms, gebančioms sugriauti kviečiamąjį: patikrinkite jas prieš tai, ką dvejetainis failas geba, dar prieš kvietimą, imdami stipriausią prieinamą įrodymą, ir būkite sąžiningi kode ir dokumentacijoje, kai tas įrodymas – heuristika. Eksportuotas simbolis – įrodymas. Crate pavadinimas eilučių lentelėje – geras spėjimas
Trumpa atmintinė: PDFium Component bibliotekos konfigūracija
- Kvieskite
ConfigurePdfLibrarykartą, prieš bet ką pakraunant DLL; bet kokia gebėjimų užklausa ar dokumento atvėrimas ją užspaudžia - Atnaujinkite iki v3.123.0 ar naujesnės, jei nustatote
BrotliEnabledarbaIsolatePerDocumentir iš pridėtųjų vykdymo aplinkų tikitės Skia išvesties Rendererpalikite antprpDefault, nebent reikia konkretaus rasterizatoriaus; jis dabar kiekvienoje struktūros versijoje išsprendžiamas į varianto numatytąjį- Naudokite
PdfNativeRendererTypesuGetSkiaRenderCapabilities.PageRender, kad užregistruotumėte, kuris atvaizduotojas iš tikrųjų aktyvus - Tikėkitės
EPdfError, o ne strigimo, dėlprpSkiaant vien AGG DLL arpfbpFontationsant be Fontations DLL v3.125.0 ar naujesnėje - Po gebėjimų atmetimo
PdfLibraryConfigurationSealedyra False, ir galite konfigūruoti iš naujo; po nepavykusio DLL krovimo lieka True - Fontations aptikimą laikykite heuristika ir turėkite FreeType atsarginį kelią
- Rašykite
PDFium.LoadLibraryirPDFium.Loadedsu modulio vardu, kad išvengtumėte Win32 irTComponentvardų susidūrimų
Jei DLL žlunga dar prieš konfigūracijai tampant svarbia, pradėkite nuo PDFium DLL krovimo nesėkmių diagnostikos Delphi, o kaip komponentas kiekvienoje platformoje randa tinkamąjį dvejetainį failą – žiūrėkite PDFium savosios bibliotekos krovimas bet kuriame tiksle. Kai atvaizduotojas jau apsispręsta, atvaizdavimo podėlis ir sklandaus mastelio taktikos dengia, kaip peržiūrovoje išlaikyti greitą puslapių atvaizdavimą
PDFium Component apgaubia PDFium variklį Delphi ir C++Builder aplinkai su tokiais konfigūracijos patikrinimais, tad savoji inicijacija žlunga kaip Pascal išimtis, kurią galite apdoroti, o ne kaip proceso išėjimas. Produkto detalės ir atsiuntimai yra PDFium Component for Delphi produkto puslapyje