PDFium Component dabar FPDF_FORMFILLINFO.version nustato į 2 kiekvienai formų pildymo aplinkai, kurią inicijuoja, nes versija, kurią priima savoji PDFium versija, yra tos versijos savybė, o ne atidaromo dokumento. Su XFA paleistas pdfium.v8.dll 1 versiją atmeta iš karto, tad paprastas AcroForm PDF, atidarytas per jį, anksčiau krisdavo FPDFDOC_InitFormFillEnvironment viduje, nors XFA nebuvo nė pėdsako. v3.116.0 pataisa nedidelė, bet už jos slypinti klaida yra bendra ir verta įvardijimo: protokolo versijos laukas aprašo atminties išdėstymą, kurio tikisi kita pusė, ir jo niekada negalima išvesti iš to, ar atsitiktinai prireikė to išdėstymo teikiamų funkcijų
Kodėl FPDFDOC_InitFormFillEnvironment nepavyksta su paprastu PDF ir pdfium.v8.dll?
Aplinka nepavyksta, nes su XFA paleista PDFium versija version lauką patikrina pirmiau, nei padaro ką nors kita, o senoji apvalkalo logika jai paduodavo 1 visada, kai einamasis dokumentas nebuvo XFA forma. Požymis Delphi šeimininkėje yra EPdfError, išmetama iš TPdf.InitializeFormFill su pranešimu Cannot initialize form fill environment, kuri išmetama atidarant įprastą sąskaitą ar mokesčių formą, neturinčią nieko, išskyrus AcroForm teksto laukus. Tas pats failas atsidaro puikiai su paprastu pdfium.dll. Ta pati DLL puikiai atidaro tikrą XFA dokumentą. Krinta tik V8 versijos ir ne XFA dokumento derinys, o būtent į tokį derinį šeimininkė patenka įjungusi EnableV8Engine, kad gautų AcroForm JavaScript, arba po to, kai automatinis pasirinkimas LoadDocument viduje jau priskyrė procesą pdfium.v8.dll dėl ankstesnio XFA failo. Tas priskyrimas galioja visam procesui: EnableV8Engine perskaitomas prieš pirmą LoadLibrary, ir įkėlus XFA versiją kiekvienas vėlesnis paprastas PDF eina per tą patį aplinkos paruošimą prieš tą patį dvejetainį failą. Šeimininkė nieko blogo nepadarė; apvalkalas užpildydamas įrašą uždavė netinkamą klausimą. Jei dar tik sprendžiate, kurį dvejetainį failą apskritai platinti, mūsų pastaba apie PDFium DLL diegimą ir įkėlimo klaidų diagnostiką aptaria paprastos ir V8 versijos pasirinkimą, o šis straipsnis remiasi prielaida, kad V8 versija jau yra procese
Ką FPDF_FORMFILLINFO version laukas iš tikrųjų žada?
FPDF_FORMFILLINFO.version pasako PDFium, kuriuos įrašo laukus jam leidžiama skaityti, o viešoji antraštė fpdf_formfill.h priimtinas reikšmes sieja su tuo, kaip biblioteka buvo sukompiliuota, o ne su dokumentu. Perpasakojant, sutartis turi tris dalis. 1 versija apima stabilius atgalinius iškvietimus nuo FFI_Invalidate iki FFI_DoGoToAction plius m_pJsPlatform rodyklę. Versija be XFA modulio priima ir 1, ir 2, o su 2 ji dar kvies papildomus eksperimentinius atgalinius iškvietimus. Versija su XFA moduliu reikalauja 2 — ir taškas, o antraštė tą reikalavimą kartoja du kartus, tarsi tikėtųsi, kad žmonės jį praleis. Niekur sutartis nemini dokumento. Versija yra pareiškimas apie įrašą, kurį paskyrėte: su 2 jūs žadate, kad atmintis po m_pJsPlatform egzistuoja ir laiko arba galiojančias funkcijų rodykles, arba NULL
2 versijos srityje gyvena visa XFA mechanika. Ji prasideda xfa_disabled — FPDF_BOOL, kurį antraštė aprašo kaip ignoruojamą žemiau 2 versijos ir prasmingą tik tada, kai XFA modulis sukompiliuotas — ir tęsiasi septyniolika funkcijų rodyklių nuo FFI_DisplayCaret iki FFI_DoURIActionWithKeyboardModifier. Kiekviena iš jų dokumentuota kaip privaloma XFA ir kitu atveju nustatoma į NULL. Būtent ta formuluotė ir yra visos pataisos raktas. NULL tiems lizdams nėra klaidos būsena; tai dokumentuota būsena šeimininkei, kuri nevairuoja XFA. Įrašas, išvalytas su FillChar ir paskui pažymėtas kaip 2 versijos, sutartį ne XFA versijoje patenkina lygiai taip pat gerai kaip 1 versijos įrašas, ir jis yra vienintelis įrašas, kurį XFA versija apskritai priims
Senoji atranka siejo ABI su dokumentu
Defektas buvo vienas sąlyginis sakinys, atskirai paėmus atrodęs pagrįstai. TPdf.InitializeFormFill apskaičiuoja RuntimeReady požymį iš trijų faktų: dokumentas per TPdf.XFA praneša XFA formos tipą, XFA eilučių pagalbiniai metodai išspręsti per XfaFeaturesAvailable, o V8 eksportai išspręsti per V8FeaturesAvailable. Iki v3.116.0 tas pats požymis rinkdavosi ir versiją
// v3.115.0 ir ankstesnės: ABI versija sekė dokumentą
RuntimeReady := XFA and XfaFeaturesAvailable and V8FeaturesAvailable;
if RuntimeReady then
FFormFillInfo.Info.version := 2
else
FFormFillInfo.Info.version := 1;
// ... o šaka, kai vykdymo aplinkos nėra, ją vėl įtvirtindavo
else if XFA then
begin
FFormFillInfo.Info.version := 1;
FFormFillInfo.Info.xfa_disabled := 1;
if Assigned(FOnXfaRuntimeMissing) then
FOnXfaRuntimeMissing(Self);
end;
Perskaitykite tai su antrašte rankose — ir gedimas akivaizdus. RuntimeReady yra false kiekvienam paprastam AcroForm dokumentui, tad kiekvienas paprastas dokumentas paskelbdavo 1 versiją. Su pdfium.dll tai gerai. Su pdfium.v8.dll, kuri yra su XFA paleista versija, PDFium patikrina lauką, randa jį žemiau privalomo 2 ir grąžina nulinį FPDF_FORMHANDLE, kurį CheckPdf paverčia aukščiau minėta išimtimi. Senojo kodo ketinimas buvo gynybinis: laikyti 1 versiją, kad XFA versija niekada neskaitinėtų nepaskirtų 2 versijos lizdų. Jis gynėsi nuo problemos, kurią antraštė jau atmeta, ir susikūrė tokią, apie kurią antraštė aiškiai įspėja. Pataisytas kodas versiją nusprendžia vieną kartą, iš anksto, pagal tai, kas tas įrašas fiziškai yra
procedure TPdf.InitializeFormFill;
var
RuntimeReady: Boolean;
begin
FXfaRuntimeUsable := False;
FXfaPageCountOverride := -1; // žymuo: naudoti statinį puslapių medį
if not FormFill then
Exit;
FillChar(FFormFillInfo, SizeOf(FFormFillInfo), 0);
FFormFillInfo.Pdf := Self;
// Visas 2 versijos įrašas paskirtas ir išvalytas aukščiau. PDFium
// priima 2 versiją ir be XFA, o reikalauja jos kiekvienoje su XFA
// paleistoje versijoje, įskaitant atvejį, kai dokumente XFA formos nėra.
FFormFillInfo.Info.version := 2;
FFormFillInfo.Info.xfa_disabled := 1;
// RuntimeReady valdo XFA atgalinius iškvietimus ir xfa_disabled, bet ne versiją.
RuntimeReady := XFA and XfaFeaturesAvailable and V8FeaturesAvailable;
...
Kur RuntimeReady vis dar priklauso: atgaliniai iškvietimai ir xfa_disabled
RuntimeReady išlaiko savo pareigą būti XFA elgesio vartais; jis tiesiog nebelietė įrašo išdėstymo. 1 versijos atgaliniai iškvietimai — FFI_Invalidate, FFI_SetTimer, FFI_GetPage, FFI_DoURIAction, FFI_DoGoToAction ir likusi to bloko dalis — sujungiami besąlygiškai, nes nuo jų priklauso ir AcroForm, ir XFA. Septyniolika 2 versijos rodyklių priskiriama tik RuntimeReady šakoje, kartu su xfa_disabled := 0. Kai dokumentas yra XFA, o vykdymo aplinkos nėra, įrašas lieka 2 versijos su xfa_disabled lygiu 1 ir 2 versijos lizdais, paliktais NULL, o apvalkalas iškelia OnXfaRuntimeMissing, kad šeimininkė galėtų pasiūlyti paleisti iš naujo su pdfium.v8.dll. Kai aplinka jau egzistuoja, FPDF_LoadXFA kviečiamas tik tada, kai RuntimeReady buvo true, ir tik teisingas grąžinimas nustato FXfaRuntimeUsable, ką ir praneša TPdf.XfaRuntimeAvailable
if RuntimeReady then
begin
FFormFillInfo.Info.xfa_disabled := 0; // 0 = XFA įjungta
FFormFillInfo.Info.FFI_DisplayCaret := FormFillDisplayCaret;
FFormFillInfo.Info.FFI_GetCurrentPageIndex := FormFillGetCurrentPageIndex;
FFormFillInfo.Info.FFI_SetCurrentPage := FormFillSetCurrentPage;
FFormFillInfo.Info.FFI_GotoURL := FormFillGotoURL;
FFormFillInfo.Info.FFI_GetPageViewRect := FormFillGetPageViewRect;
FFormFillInfo.Info.FFI_PageEvent := FormFillPageEvent;
FFormFillInfo.Info.FFI_PopupMenu := FormFillPopupMenu;
FFormFillInfo.Info.FFI_OpenFile := FormFillOpenFile;
FFormFillInfo.Info.FFI_EmailTo := FormFillEmailTo;
// ... nuo FFI_UploadTo iki FFI_DoURIActionWithKeyboardModifier
end
else if XFA then
begin
// Vykdymo aplinkos nėra: palikti 2 versiją, XFA išjungtą, pranešti šeimininkei.
if Assigned(FOnXfaRuntimeMissing) then
FOnXfaRuntimeMissing(Self);
end;
FFormHandle := FPDFDOC_InitFormFillEnvironment(FDocument, FFormFillInfo.Info);
CheckPdf(FFormHandle <> nil, 'Cannot initialize form fill environment');
if RuntimeReady then
FXfaRuntimeUsable := FPDF_LoadXFA(FDocument) <> 0;
Du to bloko dalykus lengva padaryti neteisingai, kai rašote savo susiejimą. FXfaPageCountOverride atstatomas į -1 kaip žymuo prieš bet ką kita, tad PageCount nusileidžia į statinį puslapių medį, kol FFI_PageEvent praneša apie perpuslapiavimą; nulis ten tyliai paskelbtų tuščią dokumentą. O kiekvienas 2 versijos atgalinis iškvietimas yra statinė cdecl rutina, kuri atgauna priklausančią TPdf iš įrašo ir praryja bet kokią Pascal išimtį prieš grįždama į PDFium — tai disciplina, kurią mūsų pastaba apie PDFium ABI grūdinimą Delphi išdėsto FFI_OpenFile atveju. Versijos pakeitimas nesušvelnina nė vienos iš tų taisyklių
Ar 2 versija saugi, kai DLL neturi XFA modulio?
Taip, ir priežastis yra įraše, o ne bibliotekos pažade. Ne XFA versijoje antraštė sako, kad 2 versija priverčia kviesti ir eksperimentinius atgalinius iškvietimus, tad klausimas yra, ką PDFium randa pažiūrėjęs. TPdfFormFillInfo yra supakuotas įrašas, kurio Info narys yra visas FPDF_FORMFILLINFO, įskaitant kiekvieną 2 versijos lauką, o InitializeFormFill prieš paliesdamas nė vieną baitą viską išvalo su FillChar. Tad paprastoje pdfium.dll su paprastu dokumentu biblioteka mato 2 versiją, nustatytą xfa_disabled ir NULL kiekviename eksperimentiniame lizde — būtent tokią būseną antraštė ir numato šeimininkei, kuri neįgyvendina XFA. Nėra nukirpto įrašo, už kurio biblioteka galėtų nuskaityti, nes įrašas niekada nebuvo trumpesnis už 2 versiją. Senoji logika gynėsi nuo išdėstymo neatitikimo, kurį Pascal deklaracija jau buvo panaikinusi
Riba, kurią verta sąžiningai įvardyti, yra ta, kurios įrašas nepadengia. 2 versija paprastame dokumente neįjungia nei JavaScript, nei XFA skriptų, nei kurių nors šeimininkės įvykių, slypinčių už tų atgalinių iškvietimų. m_pJsPlatform prijungiamas tik tada, kai V8FeaturesAvailable yra true, XFA lieka išjungta, nebent RuntimeReady buvo true, o TPdf.XFA toliau praneša formos tipą iš FPDF_GetFormType, nepaisant to, dėl ko aplinka susitarė. Šeimininkė, norinti žinoti, ar dinaminė XFA tikrai bus atvaizduota, turėtų ir toliau skaityti XfaRuntimeAvailable po to, kai Active tampa true, kaip rekomenduoja mūsų pastaba apie XFA formų aptikimą ir XFA paketų ištraukimą, o ne išvesti ką nors iš versijos lauko
procedure TMainForm.PdfXfaRuntimeMissing(Sender: TObject);
begin
// Iškeliama iš InitializeFormFill, kai dokumentas yra XFA, bet įkelta
// pdfium.dll negali paleisti variklio. Formų aplinka vis tiek atsidaro,
// nes 2 versija buvo paduota bet kuriuo atveju; išjungta tik XFA aplinka.
StatusBar.SimpleText :=
'XFA form detected; restart with pdfium.v8.dll to enable dynamic rendering';
end;
procedure TMainForm.OpenDocument(const FileName: string);
begin
Pdf.Active := False;
Pdf.OnXfaRuntimeMissing := PdfXfaRuntimeMissing;
Pdf.FormFill := True;
Pdf.FileName := FileName;
Pdf.Active := True; // nebeišmeta išimties paprastam PDF su pdfium.v8.dll
if Pdf.XFA and not Pdf.XfaRuntimeAvailable then
ShowStaticXfaWarning;
end;
Protokolo versija ir funkcijų prieinamumas yra dvi skirtingos ašys
Bendra taisyklė, išplaukianti iš šios pataisos, yra ta, kad atgalinių iškvietimų struktūros versijos laukas atsako į klausimą „kokio dydžio šis įrašas ir ką iš jo galima skaityti", o funkcijų aptikimas atsako, „kurie iš tų lizdų duos ką nors naudingo". Pirmąjį fiksuoja savasis dvejetainis failas ir Pascal deklaracija, su kuria kompiliavotės. Antrasis kinta priklausomai nuo dokumento, nuo DLL eksportų lentelės ir nuo šeimininkės konfigūracijos. Suglausti abu į vieną loginę reikšmę vilioja, nes XFA atvejui atsitiktinai reikia abiejų, bet tą akimirką, kai versija reikalauja minimalios versijos, suglavinimas sugenda kiekvienam dokumentui, kuriam tos funkcijos nereikia. XFA formos, ISO 32000-1 §12.7.8 aprašytos kaip XML turinys, gyvenantis šalia AcroForm žodyno, čia yra ta funkcija; įrašo išdėstymas yra protokolas, ir PDFium turi teisę reikalauti išdėstymo dar prieš pažvelgdamas į failą. Ta pati forma pasirodo visur, kur C biblioteka versijuoja savo struktūras: peržiūros informacijos blokas, atvaizdavimo nustatymų įrašas, platformos atgalinių iškvietimų lentelė. Saugus šablonas yra tas, kurio laikosi pataisytas InitializeFormFill. Deklaruokite naujausią suprantamą išdėstymą, visiškai jį išvalykite, nustatykite versiją pagal tą išdėstymą besąlygiškai, o paskui leiskite galimybių patikroms nuspręsti, kuriuos lizdus užpildyti. Jei būsima PDFium antraštė pridės 3 versiją, keisis deklaracija ir tas vienas priskyrimas, o ne nuo dokumento priklausanti šaka, kuri bus klaidinga tam deriniui, kurio niekas neišbandė
Pataisytas formų pildymo inicijavimas platinamas su PDFium Component Delphi, Lazarus ir C++Builder, ir galioja tiek Win32, tiek Win64, nes abi versijos dalijasi ta pačia įrašo deklaracija. Jei jūsų programa jau pasirenka pdfium.v8.dll dėl JavaScript varomų AcroForm, tai yra pakeitimas, leidžiantis per tą patį dvejetainį failą atidaryti ir likusį PDF archyvą, be jokio atskiro formų aplinkos atvejo