TPdf.SetFocusedFormFieldText i PDFium-komponenten skriver in i det för närvarande fokuserade formulärfältets levande redigeringsbuffert, och för ett XFA-formulär når den bufferten aldrig datasets-paketet som serialiseras till disk — så ett värde en användare skriver, och din kod bekräftar accepterades, är tyst borta nästa gång filen öppnas. AcroForm-fält har inte det här problemet: samma anrop committar till fältets /V-post i det ögonblick fokus flyttar bort. En användare som fyller i ett XFA-intagsformulär, sparar, och öppnar igen för att hitta beloppsfältet tomt igen stöter inte på en renderingsglitch — de stöter på kanten av vad PDFium-motorn själv exponerar för att skriva formulärdata
Det här är en smalare fråga än att först och främst upptäcka ett XFA-formulär, eller få dess JavaScript att köra: inte "stödjer PDFium XFA" och inte "hur kör jag AcroForm-skript" utan specifikt vad som händer med ett värde efter att SetFocusedFormFieldText rapporterar framgång. Kortversionen är att AcroForm och XFA inte är två dialekter av samma formulärmodell vad gäller PDFiums skrivväg — de är två formulärmodeller med två helt olika relationer mellan vad en användare skriver och vad en sparning faktiskt fångar, och att blanda ihop de två är vad som förvandlar ett enradigt API-anrop till ett supportärende tre veckor efter att en kunds pilotutplacering går live. AcroForm JavaScript-artikeln visar det enradiga anropet och anger AcroForm-mot-XFA-utfallet i en kodkommentar; den här stannar på samma API och går igenom den interna skrivvägen, datasets-paket-beviset att XFA-skrivningen aldrig landar, varför luckan sitter i PDFium självt snarare än Delphi-bindningen, och en patcha-din-egen-XML-lösning för dokument som behöver redigeringen att överleva en sparning
Hur skriver SetFocusedFormFieldText ett fältvärde?
TPdf.SetFocusedFormFieldText fungerar genom att simulera en tangenttryckningsnivå-redigering, inte genom att peta ett värde direkt in i dokumentmodellen. Internt anropar den FORM_SelectAllText för att markera det fokuserade fältets aktuella innehåll, sedan FORM_ReplaceSelection för att skriva över markeringen med den nya strängen — samma två operationer en tangentbordsdriven markera-allt-och-skriv skulle utlösa. Eftersom skrivningen går genom PDFiums interaktiva textredigeringsväg snarare än runt den, utlöses varje tangenttrycknings-, format-, eller beräkningsskript bundet till fältet exakt som det skulle för en människa som skriver, vilket är vad som gör API:et användbart för programmatisk formulärifyllning i en visare som håller JavaScript levande. Läs-sidans motsvarighet är FocusedFormFieldText, backad av FORM_GetFocusedText, och den speglar samma levande buffert SetFocusedFormFieldText just 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');
Varför behåller AcroForm värdet och XFA förlorar det?
AcroForm-text- och kombinationsfält sparas eftersom PDFiums egen formulärifyllningsmiljö committar redigeringsbufferten åt dig: i det ögonblick fältet förlorar fokus skrivs bufferten in i fältets /V-post, samma nyckel varje konform PDF-läsare tittar på för att veta ett fälts lagrade värde. TPdf.ClearFormFieldFocus — som anropar FORM_ForceToKillFocus under huven — tvingar fram den committen på begäran, så kod som sätter ett värde programmatiskt inte behöver vänta på ett riktigt musklick någon annanstans i UI:t. Spara omedelbart efteråt, och den nya texten är del av dokumentobjektgrafen innan TPdf.SaveAs någonsin körs, eftersom /V är en riktig post i en riktig fältordbok, inte något fastskruvat efteråt
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
Var lever en XFA-fältredigering egentligen?
XFA-fält har ingen sådan koppling. Texten en användare skriver hamnar i en CPWL_Edit-buffert som tillhör PDFiums XFA-rendrerings- och interaktionslager, och det lagret har ingen kodväg som kopierar bufferten tillbaka in i datasets-paketet lagrat i PDF:en. TPdf.GetXfaDatasets gör luckan synlig: anropa den före och efter en redigering på ett XFA-fält och byten den returnerar är identiska, eftersom metoden läser det ursprungliga paketet dokumentet öppnades med, aldrig det levande tillståndet hos widgeten du just redigerade. Inget om det är en cachebugg eller ett uppdateringstidsproblem — datasets-paketet på disk och redigeringsbufferten i minnet är helt enkelt två olika tillståndsdelar som PDFiums publika API aldrig kopplar samman
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;
Är det här en PDFium-komponentbugg eller en PDFium-begränsning?
Den saknade biten sitter i PDFium självt, inte i Delphi-bindningen ovanpå det. PDFiums publika API har ingen FPDF_SetXFAPacket för att injicera ett uppdaterat paket och ingen FPDF_SaveAsXFA för att be XFA-motorn serialisera sin aktuella DOM tillbaka till datasets-XML innan en sparning. FPDF_SaveAsCopy — exporten som backar TPdf.SaveAs — skriver ut dokumentobjektgrafen PDFium redan har; den har ingen krok för att be XFA-motorn spola sitt levande tillstånd först, eftersom den kroken inte existerar uppströms. PDFium-komponenten kan inte lägga till avstämning PDFium självt aldrig implementerade, och att leverera en hemmabyggd DOM-till-XML-serialiserare som gissar på PDFiums interna XFA-tillstånd skulle vara värre än den ärliga luckan: den skulle se ut att fungera tills nästa PDFium-version ändrar något ingen utanför projektet kan se
Den här gränsen dök upp under samma v2.13.2-granskning som byggde SetFocusedFormFieldText från första början. FORM_ReplaceSelection hade bundits i DLL-importtabellen under versioner utan att någonsin anropas från Pascal-kod, och att lägga till skrivvägen som äntligen använde den är vad som gjorde persistensluckan konkret nog att dokumentera snarare än teoretisk. Samma granskningsomgång hittade en orelaterad men i anda relaterad lucka: AcroForm JavaScript hade varit tyst avaktiverat sedan v2.13.0 eftersom JS-plattformen bara var kopplad inuti XFA-initialiseringsgrenen, så vanliga AcroForm-dokument med app.alert eller beräknade fält fick aldrig någon skriptmotor alls. Det var fixbart — att utöka JS-plattformen till varje dokument oavsett XFA — och det levererades i samma version; persistensluckan som täcks här var inte fixbar, av skälen ovan. JavaScript-fixen och värd-veto-händelserna runt den täcks i att köra AcroForm JavaScript med PDFium-komponenten
Vad bör du göra åt det i Delphi?
För AcroForm-dokument är fixen inget mer än god vana: anropa ClearFormFieldFocus (eller flytta annars bort fokus) innan SaveAs närhelst ett värde sattes programmatiskt, snarare än att anta att en senare UI-interaktion kommer utlösa committen åt dig. För ett dokument som kan vara antingen AcroForm eller XFA — vilket är det vanliga fallet i en generell visare — kontrollera FormType eller den booleska XFA innan du lovar en anropare att en sparning kommer hålla, och läs att upptäcka XFA-formulär och extrahera XFA-paket för hela uppsättningen prober, inklusive XFAF-fallet där XFA-innehåll är lagrat ovanpå annars vanliga AcroForm-widgetar som faktiskt respekterar /V
För ett genuint dynamiskt XFA-formulär där de redigerade värdena måste överleva en sparning är den interaktiva redigeringsbufferten inte rätt verktyg alls. Den hållbara vägen är att behandla GetXfaDatasets som din baslinje, inte ditt resultat: läs den en gång när dokumentet öppnas, håll din egen post över vad användaren ändrade fält för fält — exakt de värden ditt UI redan har, eftersom PDFium inte kommer ge dem tillbaka till dig i efterhand — patcha de in i baslinje-XML:en själv, och driv din egen utdata. En skrivning som går genom XML din egen kod kontrollerar överlever en sparning en CPWL_Edit-buffert aldrig kunde
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;
Att fånga luckan innan en kund gör det
TPdf.SaveAs returnerar True oavsett om ett XFA-fältvärde överlevde eller inte, eftersom sparningen ur PDFiums synvinkel genuint lyckades — den skrev varje byte den ombads skriva. Det gör det här till precis den typen av defekt som slinker förbi ett röktest och når en kund: inget kastas, inget loggas, filen öppnas fint, bara det specifika värdet är fel. Ett rundturs-test som faktiskt öppnar den sparade filen igen och jämför fältets värde — eller jämför GetXfaDatasets före och efter, enligt det tidigare exemplet — hör hemma i regressionssviten för varje visare som låter användare redigera XFA-innehåll, inte bara AcroForm-vägarna som råkar fungera som standard
Inget av detta är en defekt att rapportera mot PDFium-komponenten så mycket som en gräns att designa runt: SetFocusedFormFieldText gör precis vad dess namn säger för båda formulärmodellerna, och skillnaden i utfall spårar rent till vad AcroForm och XFA vardera kopplar den bufferten till på PDFium-sidan. API:et, fokus- och sparningsprimitiven, och paketläsarna refererade här är del av PDFium-komponenten för Delphi och C++Builder