TPdf.SetFocusedFormFieldText i PDFium-komponenten skriver ind i det aktuelt fokuserede formularfelts levende redigeringsbuffer, og for en XFA-formular når den buffer aldrig datasets-pakken, der serialiseres til disk — så en værdi en bruger taster, og ens kode bekræfter blev accepteret, er i stilhed væk, næste gang filen åbner. AcroForm-felter har ikke det problem: det samme kald forpligter til feltets /V-post, i det øjeblik fokus flytter væk. En bruger, der udfylder en XFA-intake-formular, gemmer og genåbner for at finde beløbsfeltet tomt igen, rammer ikke en gengivelsesfejl — de rammer kanten af, hvad selve PDFium-motoren eksponerer til at skrive formulardata
Dette er et snævrere spørgsmål end at registrere en XFA-formular i første omgang, eller at få dens JavaScript til at køre: ikke "understøtter PDFium XFA" og ikke "hvordan kører jeg AcroForm-scripts" men specifikt, hvad der sker med en værdi, efter SetFocusedFormFieldText rapporterer succes. Den korte version er, at AcroForm og XFA ikke er to dialekter af den samme formularmodel, hvad angår PDFiums skrivevej — de er to formularmodeller med to helt forskellige forhold mellem, hvad en bruger taster, og hvad en gemning rent faktisk fanger, og at sammenblande de to er, hvad der gør et ét-linjes API-kald til en supportsag tre uger efter en kundes pilotudrulning går live. AcroForm-JavaScript-artiklen viser det ét-linjes kald og angiver AcroForm-versus-XFA-udfaldet i en kode-kommentar; denne bliver på den samme API og gennemgår den interne skrivevej, datasets-pakke-beviset for at XFA-skrivningen aldrig lander, hvorfor hullet sidder i selve PDFium frem for Delphi-bindingen, og en patch-din-egen-XML-workaround til dokumenter, der har brug for at redigeringen overlever en gemning
Hvordan skriver SetFocusedFormFieldText en feltværdi?
TPdf.SetFocusedFormFieldText fungerer ved at simulere en tastetryk-niveau-redigering, ikke ved at prikke en værdi direkte ind i dokumentmodellen. Internt kalder den FORM_SelectAllText for at markere det fokuserede felts nuværende indhold, derefter FORM_ReplaceSelection for at overskrive markeringen med den nye streng — de samme to operationer, en tastatur-drevet markér-alt-og-skriv ville udløse. Fordi skrivningen går gennem PDFiums interaktive tekst-redigeringsvej frem for rundt om den, udløses ethvert tastetryk-, format- eller beregnings-script bundet til feltet præcis, som det ville for et menneske der taster, hvilket er, hvad der gør API'et nyttigt til programmatisk formularudfyldning i en fremviser, der holder JavaScript levende. Læse-siden-modstykket er FocusedFormFieldText, bakket op af FORM_GetFocusedText, og den afspejler den samme levende buffer, SetFocusedFormFieldText lige skrev
if Pdf.FocusedFormFieldIndex >= 0 then
begin
if Pdf.SetFocusedFormFieldText('1284.50') then
Log('Buffer now reads: ' + Pdf.FocusedFormFieldText)
else
Log('No field is focused, or it does not accept text');
end
else
Log('Focus a field first - FocusFormField or a real click');
Hvorfor beholder AcroForm værdien, og XFA mister den?
AcroForm-tekst- og combo-felter forbliver, fordi PDFiums eget formular-udfyldnings-miljø forpligter redigeringsbufferen for dig: i det øjeblik feltet mister fokus, skrives bufferen ind i feltets /V-post, den samme nøgle enhver konform PDF-læser kigger på for at kende et felts gemte værdi. TPdf.ClearFormFieldFocus — som kalder FORM_ForceToKillFocus under motorhjelmen — tvinger den forpligtelse efter behov, så kode der sætter en værdi programmatisk, ikke behøver vente på et rigtigt museklik et andet sted i UI'et. Gem straks efter, og den nye tekst er del af dokument-objekt-grafen, før TPdf.SaveAs nogensinde kører, fordi /V er en rigtig post i en rigtig felt-ordbog, ikke noget boltet på efterfølgende
Pdf.FocusFormField(FieldIndex);
Pdf.SetFocusedFormFieldText('1284.50');
Pdf.ClearFormFieldFocus; // forces the /V commit now
Pdf.SaveAs('invoice-acroform.pdf');
// Reopen and confirm - this is an AcroForm document, so it holds
Pdf.Active := False;
Pdf.FileName := 'invoice-acroform.pdf';
Pdf.Active := True;
Pdf.FocusFormField(FieldIndex);
Assert(Pdf.FocusedFormFieldValue = '1284.50'); // passes
Hvor bor en XFA-feltredigering rent faktisk?
XFA-felter har ingen sådan kobling. Teksten en bruger taster, lander i en CPWL_Edit-buffer, der tilhører PDFiums XFA-gengivelses- og interaktionslag, og det lag har ingen kodevej, der kopierer bufferen tilbage ind i datasets-pakken gemt i PDF'en. TPdf.GetXfaDatasets gør hullet synligt: kald den før og efter en redigering af et XFA-felt, og de bytes, den returnerer, er identiske, fordi metoden læser den oprindelige pakke, dokumentet blev åbnet med, aldrig widgetens levende tilstand, man lige redigerede. Intet ved det er en caching-bug eller et opdaterings-timing-problem — datasets-pakken på disk og redigeringsbufferen i hukommelsen er simpelthen to forskellige stykker tilstand, PDFiums offentlige API aldrig forbinder
var
Before, After: TBytes;
begin
Before := Pdf.GetXfaDatasets;
Pdf.FocusFormField(FieldIndex);
Pdf.SetFocusedFormFieldText('1284.50');
After := Pdf.GetXfaDatasets;
// Before and After are byte-for-byte identical on an XFA document -
// the edit never touched the packet GetXfaDatasets reads from
end;
Er dette en PDFium-komponent-bug eller en PDFium-begrænsning?
Den manglende brik sidder i selve PDFium, ikke i Delphi-bindingen oven på den. PDFiums offentlige API har ingen FPDF_SetXFAPacket til at injicere en opdateret pakke og ingen FPDF_SaveAsXFA til at bede XFA-motoren om at serialisere sin nuværende DOM tilbage til datasets-XML før en gemning. FPDF_SaveAsCopy — eksporten der bakker TPdf.SaveAs — skriver den dokument-objekt-graf, PDFium allerede har; den har ingen krog til at bede XFA-motoren om at flushe sin levende tilstand først, fordi den krog ikke findes opstrøms. PDFium-komponenten kan ikke tilføje reconciliation, PDFium selv aldrig implementerede, og at levere en hjemmelavet DOM-til-XML-serializer, der gætter på PDFiums interne XFA-tilstand, ville være værre end det ærlige hul: det ville se ud som om det virker, indtil den næste PDFium-version ændrer noget, ingen uden for projektet kan se
Denne grænse dukkede op under den samme v2.13.2-audit, der byggede SetFocusedFormFieldText i første omgang. FORM_ReplaceSelection havde været bundet i DLL-import-tabellen i versioner uden nogensinde at blive kaldt fra Pascal-kode, og at tilføje skrivevejen, der endelig brugte den, er, hvad der gjorde persistens-hullet konkret nok til at dokumentere frem for teoretisk. Den samme audit-runde afslørede et urelateret, men beslægtet-i-ånd hul: AcroForm-JavaScript havde været tavst deaktiveret siden v2.13.0, fordi JS-platformen kun var koblet til inde i XFA-initialiserings-grenen, så almindelige AcroForm-dokumenter med app.alert eller beregnede felter aldrig fik nogen script-motor overhovedet. Den var reparerbar — at udvide JS-platformen til hvert dokument uanset XFA — og den blev sendt i den samme version; persistens-hullet dækket her var ikke reparerbart, af grundene ovenfor. JavaScript-fixen og host-veto-hændelserne omkring den er dækket i at køre AcroForm-JavaScript med PDFium-komponenten
Hvad bør man gøre ved det i Delphi?
For AcroForm-dokumenter er fixen intet mere end god vane: kald ClearFormFieldFocus (eller flyt på anden vis fokus væk), før SaveAs, når en værdi blev sat programmatisk, frem for at antage, at en senere UI-interaktion vil udløse forpligtelsen for dig. For et dokument, der måske er enten AcroForm eller XFA — hvilket er det almindelige tilfælde i en generel fremviser — tjek FormType eller den boolske XFA, før man lover en kalder, at en gemning vil holde, og læs registrering af XFA-formularer og udtrækning af XFA-pakker for det fulde sæt sonder, inklusive XFAF-tilfældet, hvor XFA-indhold er lagdelt over ellers almindelige AcroForm-widgets, der rent faktisk respekterer /V
For en ægte dynamisk XFA-formular, hvor de redigerede værdier skal overleve en gemning, er den interaktive redigeringsbuffer slet ikke det rigtige værktøj. Den holdbare vej er at behandle GetXfaDatasets som ens baseline, ikke ens resultat: læs den én gang, når dokumentet åbner, hold ens egen registrering af, hvad brugeren ændrede felt for felt — præcis de værdier, ens UI allerede har, da PDFium ikke vil give dem tilbage til dig bagefter — patch de ind i baseline-XML'en selv, og driv ens eget output. En skrivning, der går gennem XML ens egen kode kontrollerer, overlever en gemning, en CPWL_Edit-buffer aldrig kunne
function ExportEditedXfaValue(Pdf: TPdf; const FieldPath,
NewValue: string): TBytes;
var
DatasetsXml: string;
begin
// GetXfaDatasets ships with PDFium Component; PatchXmlNode below is
// your own helper over your own XML library, nothing PDFium provides
DatasetsXml := TEncoding.UTF8.GetString(Pdf.GetXfaDatasets);
DatasetsXml := PatchXmlNode(DatasetsXml, FieldPath, NewValue);
Result := TEncoding.UTF8.GetBytes(DatasetsXml);
end;
At fange hullet, før en kunde gør
TPdf.SaveAs returnerer True, uanset om en XFA-feltværdi overlevede, fordi gemningen fra PDFiums synspunkt genuint lykkedes — den skrev hver byte, den blev bedt om at skrive. Det gør netop dette til den slags defekt, der glider forbi en røgtest og når en kunde: intet kaster en fejl, intet logger, filen åbner fint, kun den specifikke værdi er forkert. En tur-retur-test, der rent faktisk genåbner den gemte fil og sammenligner feltets værdi — eller sammenligner GetXfaDatasets før og efter, jf. det tidligere eksempel — hører hjemme i regressionssuiten for enhver fremviser, der lader brugere redigere XFA-indhold, ikke kun de AcroForm-veje der tilfældigvis virker som standard
Intet af dette er en defekt at rapportere mod PDFium-komponenten, snarere en grænse at designe omkring: SetFocusedFormFieldText gør præcis, hvad dens navn siger, for begge formularmodeller, og forskellen i udfald spores rent til, hvad AcroForm og XFA hver især kobler den buffer til på PDFium-siden. API'et, fokus- og gemme-primitiverne, og pakke-læserne refereret her er en del af PDFium-komponenten til Delphi og C++Builder