PDFium Component sætter nu FPDF_FORMFILLINFO.version til 2 for hvert form-fill-miljø, den initialiserer, for den version, en native PDFium-build accepterer, er en egenskab ved den build, ikke ved det dokument, der åbnes. En XFA-aktiveret pdfium.v8.dll afviser version 1 blankt, så en almindelig AcroForm-PDF åbnet gennem den plejede at fejle i FPDFDOC_InitFormFillEnvironment uden nogen XFA i sigte. V3.116.0-fixet er lille, men fejlen bag det er en generel en og det værd at navngive: Et protokolversionsfelt beskriver det hukommelseslayout, modparten forventer, og det må aldrig udledes af, om du tilfældigvis behøver de features, layoutet bærer
Hvorfor fejler FPDFDOC_InitFormFillEnvironment på en almindelig PDF med pdfium.v8.dll?
Miljøet fejler, fordi en XFA-aktiveret PDFium-build validerer version-feltet, før den gør noget som helst andet, og den gamle wrapper-logik gav den en 1, når det aktuelle dokument ikke var en XFA-form. Symptomet i en Delphi-vært er en EPdfError raiset fra TPdf.InitializeFormFill med beskeden Cannot initialize form fill environment, kastet under åbning af en almindelig faktura- eller skatteformular, der ikke har andet end AcroForm-tekstfelter. Samme fil åbner fint mod den almindelige pdfium.dll. Samme DLL åbner et rigtigt XFA-dokument fint. Kun kombinationen af V8-builden og et ikke-XFA-dokument knækker, hvilket er præcis den kombination, en vært lander i, efter den har slået EnableV8Engine til for at få AcroForm-JavaScript, eller efter autovalget i LoadDocument allerede har forpligtet processen til pdfium.v8.dll for en tidligere XFA-fil. Den forpligtelse er proces-dækkende: EnableV8Engine læses før det første LoadLibrary, og når XFA-builden først er indlæst, går hver senere almindelig PDF gennem samme miljøopsætning mod samme binære fil. Værten gjorde intet forkert; wrapperen stillede det forkerte spørgsmål, da den udfyldte recorden. Hvis du stadig er ved at beslutte, hvilken binær fil du overhovedet skal shippe, dækker vores note om at deployere PDFium-DLL'en og diagnosticere load-fejl valget mellem plain og V8, og denne artikel antager, at V8-builden allerede er i processen
Hvad lover version-feltet i FPDF_FORMFILLINFO egentlig?
FPDF_FORMFILLINFO.version fortæller PDFium, hvilke felter af recorden den må læse, og den offentlige header fpdf_formfill.h binder de acceptable værdier til, hvordan biblioteket er kompileret, frem for til dokumentet. I parafrase har kontrakten tre dele. Version 1 dækker de stabile callbacks fra FFI_Invalidate til FFI_DoGoToAction plus m_pJsPlatform-pointeren. En build uden XFA-modulet accepterer enten 1 eller 2, og med 2 kalder den også de yderligere eksperimentelle callbacks. En build med XFA-modulet kræver 2, punktum, og headeren gentager det krav to gange, som om den ventede, at folk ville overse det. Intet sted nævner kontrakten dokumentet. Versionen er en udtalelse om recorden, du allokerede: Med en 2 lover du, at hukommelsen efter m_pJsPlatform findes og holder enten gyldige funktionspointere eller NULL
Version 2-regionen er der, hele XFA-maskineriet bor. Den starter med xfa_disabled, en FPDF_BOOL, headeren beskriver som ignoreret under version 2 og kun meningsfuld, når XFA-modulet er kompileret ind, og fortsætter med sytten funktionspointere, FFI_DisplayCaret til FFI_DoURIActionWithKeyboardModifier. Hver af dem er dokumenteret som påkrævet for XFA og ellers skal sættes til NULL. Den formulering er nøglen til hele fixet. NULL er ikke en fejltilstand for de slots; det er den dokumenterede tilstand for en vært, der ikke driver XFA. En record, der er ryddet med FillChar og derefter markeret som version 2, opfylder kontrakten på en ikke-XFA-build præcis lige så godt som en version-1-record gør, og den er den eneste record, en XFA-build tager imod
Det gamle valg bandt ABI'en til dokumentet
Defekten var én enkelt betingelse, der så fornuftig ud i isolation. TPdf.InitializeFormFill beregner et RuntimeReady-flag ud fra tre fakta: Dokumentet rapporterer en XFA-formtype gennem TPdf.XFA, XFA-strenghelperne er opløst gennem XfaFeaturesAvailable, og V8-eksporterne er opløst gennem V8FeaturesAvailable. Før v3.116.0 valgte samme flag også versionen
// v3.115.0 og tidligere: ABI-versionen fulgte dokumentet
RuntimeReady := XFA and XfaFeaturesAvailable and V8FeaturesAvailable;
if RuntimeReady then
FFormFillInfo.Info.version := 2
else
FFormFillInfo.Info.version := 1;
// ... og runtime-missing-grenen fastgjorde den igen
else if XFA then
begin
FFormFillInfo.Info.version := 1;
FFormFillInfo.Info.xfa_disabled := 1;
if Assigned(FOnXfaRuntimeMissing) then
FOnXfaRuntimeMissing(Self);
end;
Læs den med headeren i hånden, og fejlen er åbenlys. RuntimeReady er False for hvert almindeligt AcroForm-dokument, så hvert almindeligt dokument annoncerede version 1. På pdfium.dll er det fint. På pdfium.v8.dll, som er XFA-builden, tjekker PDFium feltet, finder det under de krævede 2 og returnerer en null-FPDF_FORMHANDLE, som CheckPdf forvandler til exceptionen ovenfor. Den gamle kodes hensigt var defensiv: Behold version 1, så en XFA-build aldrig læser de ikke-tildelte version-2-slots. Den forsvarede sig mod et problem, headeren allerede udelukker, og skabte et, headeren eksplicit advarer om. Den korrigerede kode afgør versionen én gang, på forhånd, ud fra hvad recorden fysisk er
procedure TPdf.InitializeFormFill;
var
RuntimeReady: Boolean;
begin
FXfaRuntimeUsable := False;
FXfaPageCountOverride := -1; // sentinel: brug det statiske sidetræ
if not FormFill then
Exit;
FillChar(FFormFillInfo, SizeOf(FFormFillInfo), 0);
FFormFillInfo.Pdf := Self;
// Den komplette version 2-record allokeres og ryddes ovenfor. PDFium
// accepterer version 2 uden XFA og kræver den i hver XFA-aktiveret
// build, også når dette dokument ikke indeholder nogen XFA-form.
FFormFillInfo.Info.version := 2;
FFormFillInfo.Info.xfa_disabled := 1;
// RuntimeReady gater XFA-callbacks og xfa_disabled, aldrig version.
RuntimeReady := XFA and XfaFeaturesAvailable and V8FeaturesAvailable;
...
Hvor RuntimeReady stadig hører hjemme: callbacks og xfa_disabled
RuntimeReady beholder sit arbejde som porten for XFA-adfærd; den rører simpelthen ikke længere record-layoutet. Version 1-callbacks — FFI_Invalidate, FFI_SetTimer, FFI_GetPage, FFI_DoURIAction, FFI_DoGoToAction og resten af den blok — kobles ubetinget, fordi AcroForm og XFA begge afhænger af dem. De sytten version 2-pointere tildeles kun inde i RuntimeReady-grenen, sammen med xfa_disabled := 0. Når dokumentet er XFA, men runtimen ikke er der, forbliver recorden på version 2 med xfa_disabled på 1 og version 2-slots efterladt NULL, og wrapperen raiser OnXfaRuntimeMissing, så værten kan foreslå genstart på pdfium.v8.dll. Når miljøet findes, kaldes FPDF_LoadXFA kun, når RuntimeReady var True, og kun en True-returværdi sætter FXfaRuntimeUsable, hvilket er det, TPdf.XfaRuntimeAvailable rapporterer
if RuntimeReady then
begin
FFormFillInfo.Info.xfa_disabled := 0; // 0 = XFA slået til
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;
// ... FFI_UploadTo til FFI_DoURIActionWithKeyboardModifier
end
else if XFA then
begin
// Runtime utilgængelig: Behold version 2, lad XFA være slået fra, sig det til værten.
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;
To detaljer i den blok er lette at ramme forkert, når du skriver din egen binding. FXfaPageCountOverride nulstilles til -1 som sentinel, før noget som helst sker, så PageCount falder tilbage til det statiske sidetræ, indtil FFI_PageEvent rapporterer en repaginering; et nul dér ville lydløst hævde et tomt dokument. Og hver af version 2-callbacks er en statisk cdecl-rutine, der genfinder den ejende TPdf fra recorden og opsluger enhver Pascal-exception, før den returnerer til PDFium, hvilket er disciplinen, vores note om at hærde PDFium-ABI'en i Delphi staver ud for FFI_OpenFile. Intet ved versionsændringen afslapper nogen af reglerne
Er version 2 sikker, når DLL'en ikke har noget XFA-modul?
Ja, og grunden ligger i recorden, ikke i et løfte fra biblioteket. På en ikke-XFA-build siger headeren, at version 2 også får de eksperimentelle callbacks kaldt, så spørgsmålet er, hvad PDFium finder, når den kigger. TPdfFormFillInfo er en packed record, hvis Info-medlem er den komplette FPDF_FORMFILLINFO inklusive hvert version 2-felt, og InitializeFormFill rydder det hele med FillChar, før den rører en eneste byte. Så på en almindelig pdfium.dll med et almindeligt dokument ser biblioteket version 2, xfa_disabled sat og NULL i hver eksperimentel slot, hvilket er præcis den tilstand, headeren foreskriver for en vært, der ikke implementerer XFA. Der er ingen trunkeret record for biblioteket at læse forbi, fordi recorden aldrig var kortere end version 2. Den gamle logik forsvarede et layout-mismatch, som Pascal-deklarationen allerede havde elimineret
Grænsen, der er det værd at sige ærligt, er den, recorden ikke kan dække. Version 2 på et almindeligt dokument tænder ikke for JavaScript, XFA-scripting eller nogen af værts-events bag de callbacks. m_pJsPlatform kobles kun på, når V8FeaturesAvailable er True, XFA forbliver slået fra, medmindre RuntimeReady var True, og TPdf.XFA fortsætter med at rapportere formtypen fra FPDF_GetFormType uanset hvad miljøet forhandlede. En vært, der vil vide, om dynamisk XFA faktisk renderer, bør blive ved med at læse XfaRuntimeAvailable, efter Active bliver True, som vores note om at detektere XFA-formularer og udtrække XFA-pakker anbefaler, frem for at udlede noget af versionsfeltet
procedure TMainForm.PdfXfaRuntimeMissing(Sender: TObject);
begin
// Udløses fra InitializeFormFill, når dokumentet er XFA, men den indlæste
// pdfium.dll ikke kan køre enginen. Form-miljøet åbner stadig,
// for version 2 blev sendt alligevel; kun XFA-runtimen er slået fra.
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; // kaster ikke længere på en almindelig PDF under pdfium.v8.dll
if Pdf.XFA and not Pdf.XfaRuntimeAvailable then
ShowStaticXfaWarning;
end;
Protokolversion og feature-tilgængelighed er to forskellige akser
Den generelle regel, der falder ud af dette fix, er, at et versionsfelt i en callback-struktur besvarer spørgsmålet "hvor stor er denne record, og hvad må du læse fra den", mens feature-detektering besvarer "hvilke af de slots vil gøre noget nyttigt". Det første er fastlagt af den native binærfil og af den Pascal-deklaration, du kompilerede imod. Det andet varierer pr. dokument, pr. DLL-eksporttabel og pr. værtskonfiguration. At folde de to sammen til én boolean er fristende, fordi XFA-casen tilfældigvis behøver begge, men i det øjeblik en build håndhæver en minimumsversion knækker sammenfoldningen for hvert dokument, der ikke behøver featuren. XFA-formularer, beskrevet i ISO 32000-1 §12.7.8 som en XML-payload, der lever ved siden af AcroForm-ordbogen, er featuren her; record-layoutet er protokollen, og PDFium har ret til at insistere på layoutet, før den nogensinde ser på filen. Samme form viser sig alle steder, hvor et C-bibliotek versionerer sine strukturer: En viewer-info-blok, en render-options-record, en platform-callback-tabel. Det sikre mønster er det, den korrigerede InitializeFormFill følger. Deklarér det nyeste layout, du forstår, rydd den fuldstændigt, sæt versionen til at matche det layout ubetinget, og lad så capability-tjek afgøre, hvilke slots der skal udfyldes. Tilføjer en fremtidig PDFium-header en version 3, er ændringen i deklarationen og i den ene tildeling, ikke i en dokumentafhængig gren, der bliver forkert for den kombination, ingen testede
Den korrigerede form-fill-initialisering ships i PDFium Component til Delphi, Lazarus og C++Builder, og den gælder på Win32 og Win64 lige, da begge builds deler samme record-deklaration. Vælger din applikation allerede pdfium.v8.dll til JavaScript-drevne AcroForms, er dette den ændring, der lader den åbne resten af dit PDF-arkiv gennem samme binærfil uden special-casing af form-miljøet