TPdf.SetFocusedFormFieldText in PDFium Component schrijft naar de levende bewerkingsbuffer van het momenteel gefocuste formulierveld, en voor een XFA-formulier bereikt die buffer nooit het datasets-pakket dat naar schijf wordt geserialiseerd — dus een waarde die een gebruiker intypt, en die uw code bevestigt te hebben geaccepteerd, is stilzwijgend verdwenen de volgende keer dat het bestand opent. AcroForm-velden hebben dit probleem niet: dezelfde aanroep commit naar het /V-item van het veld op het moment dat de focus wegbeweegt. Een gebruiker die een XFA-intakeformulier invult, opslaat, en heropent om te ontdekken dat het bedragveld weer leeg is, treft geen renderingsglitch — die treft de rand van wat de PDFium-engine zelf blootstelt voor het schrijven van formulierdata
Dit is een nauwere vraag dan het detecteren van een XFA-formulier om te beginnen, of het draaiend krijgen van de JavaScript ervan: niet "ondersteunt PDFium XFA" en niet "hoe voer ik AcroForm-scripts uit" maar specifiek wat er met een waarde gebeurt nadat SetFocusedFormFieldText succes rapporteert. De korte versie is dat AcroForm en XFA voor het schrijfpad van PDFium geen twee dialecten van hetzelfde formuliermodel zijn — het zijn twee formuliermodellen met twee compleet verschillende relaties tussen wat een gebruiker typt en wat een opslag daadwerkelijk vastlegt, en de twee door elkaar halen is wat een aanroep van één regel API verandert in een supportticket drie weken nadat de pilot-implementatie van een klant live is gegaan. Het artikel over AcroForm-JavaScript toont de aanroep van één regel en stelt de uitkomst van AcroForm versus XFA in een codecommentaar; dit artikel blijft bij diezelfde API en doorloopt het interne schrijfpad, het datasets-pakket-bewijs dat de XFA-schrijfbewerking nooit landt, waarom het gat in PDFium zelf zit in plaats van in de Delphi-binding, en een patch-uw-eigen-XML-workaround voor documenten waarvan de bewerking een opslag moet overleven
Hoe schrijft SetFocusedFormFieldText een veldwaarde?
TPdf.SetFocusedFormFieldText werkt door een bewerking op toetsaanslagniveau te simuleren, niet door een waarde rechtstreeks in het documentmodel te duwen. Intern roept het FORM_SelectAllText aan om de huidige inhoud van het gefocuste veld te selecteren, vervolgens FORM_ReplaceSelection om de selectie te overschrijven met de nieuwe string — dezelfde twee bewerkingen die een toetsenbordgedreven selecteer-alles-en-typ zou triggeren. Omdat de schrijfbewerking via het interactieve tekstbewerkingspad van PDFium loopt in plaats van eromheen, gaat elk toetsaanslag-, format-, of calculate-script dat aan het veld is gebonden precies af zoals het voor een typende mens zou gaan, wat de API bruikbaar maakt voor programmatisch formulieren invullen in een viewer die JavaScript actief houdt. De leeskant-tegenhanger is FocusedFormFieldText, ondersteund door FORM_GetFocusedText, en die weerspiegelt dezelfde levende buffer waar SetFocusedFormFieldText net naar schreef
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');
Waarom behoudt AcroForm de waarde en verliest XFA deze?
AcroForm-tekst- en combo-velden blijven behouden omdat de eigen form-fill-omgeving van PDFium de bewerkingsbuffer voor u commit: op het moment dat het veld de focus verliest, wordt de buffer geschreven naar het /V-item van het veld, dezelfde sleutel waar elke conforme PDF-lezer naar kijkt om de opgeslagen waarde van een veld te kennen. TPdf.ClearFormFieldFocus — dat onder de motorkap FORM_ForceToKillFocus aanroept — dwingt die commit op verzoek af, zodat code die programmatisch een waarde instelt niet hoeft te wachten op een echte muisklik ergens anders in de UI. Sla meteen erna op, en de nieuwe tekst maakt deel uit van de documentobjectgraaf voordat TPdf.SaveAs ooit draait, omdat /V een echt item is in een echte velddictionary, geen naderhand aangeplakt iets
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
Waar leeft een XFA-veldbewerking eigenlijk?
XFA-velden hebben geen dergelijke bedrading. De tekst die een gebruiker intypt, komt terecht in een CPWL_Edit-buffer die tot de XFA-render- en interactielaag van PDFium behoort, en die laag heeft geen codepad dat de buffer terugkopieert naar het datasets-pakket dat in de PDF is opgeslagen. TPdf.GetXfaDatasets maakt het gat zichtbaar: roep het aan vóór en na een bewerking op een XFA-veld en de bytes die het teruggeeft zijn identiek, omdat de methode het originele pakket leest waarmee het document is geopend, nooit de levende toestand van het widget dat u net hebt bewerkt. Niets daaraan is een cachingbug of een refresh-timingprobleem — het datasets-pakket op schijf en de bewerkingsbuffer in het geheugen zijn gewoon twee verschillende stukjes toestand die de publieke API van PDFium nooit verbindt
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;
Is dit een bug in PDFium Component of een beperking van PDFium?
Het ontbrekende stuk zit in PDFium zelf, niet in de Delphi-binding erbovenop. De publieke API van PDFium heeft geen FPDF_SetXFAPacket om een bijgewerkt pakket te injecteren en geen FPDF_SaveAsXFA om de XFA-engine te vragen zijn huidige DOM terug te serialiseren naar datasets-XML vóór een opslag. FPDF_SaveAsCopy — de export die achter TPdf.SaveAs zit — schrijft de documentobjectgraaf uit die PDFium al heeft; het heeft geen haak om de XFA-engine te vragen zijn levende toestand eerst door te spoelen, omdat die haak stroomopwaarts niet bestaat. PDFium Component kan geen verzoening toevoegen die PDFium zelf nooit heeft geïmplementeerd, en een zelfgebouwde DOM-naar-XML-serializer uitbrengen die raadt naar de interne XFA-toestand van PDFium zou erger zijn dan het eerlijke gat: het zou lijken te werken totdat de volgende PDFium-versie iets verandert dat niemand buiten het project kan zien
Deze grens kwam naar boven tijdens dezelfde v2.13.2-audit die SetFocusedFormFieldText om te beginnen bouwde. FORM_ReplaceSelection was al versies lang gebonden in de DLL-importtabel zonder ooit vanuit Pascal-code te zijn aangeroepen, en het toevoegen van het schrijfpad dat deze eindelijk gebruikte, is wat het persistentiegat concreet genoeg maakte om te documenteren in plaats van theoretisch. Dezelfde auditronde bracht een ongerelateerd maar in geest verwant gat aan het licht: AcroForm-JavaScript was stilzwijgend uitgeschakeld sinds v2.13.0 omdat het JS-platform alleen was aangesloten binnen de XFA-initialisatietak, dus gewone AcroForm-documenten met app.alert of berekende velden kregen helemaal geen scriptengine. Dat was oplosbaar — het JS-platform uitbreiden naar elk document ongeacht XFA — en het kwam uit in dezelfde versie; het hier behandelde persistentiegat was niet oplosbaar, om bovenstaande redenen. De JavaScript-fix en de host-veto-gebeurtenissen eromheen worden behandeld in het draaien van AcroForm-JavaScript met PDFium Component
Wat moet u eraan doen in Delphi?
Voor AcroForm-documenten is de oplossing niets meer dan een goede gewoonte: roep ClearFormFieldFocus aan (of verplaats de focus anderszins weg) vóór SaveAs wanneer een waarde programmatisch is ingesteld, in plaats van aan te nemen dat een latere UI-interactie de commit voor u zal triggeren. Voor een document dat ofwel AcroForm ofwel XFA kan zijn — wat het gangbare geval is in een viewer voor algemeen gebruik — controleert u FormType of de boolean XFA voordat u een aanroeper belooft dat een opslag zal beklijven, en leest u het detecteren van XFA-formulieren en het extraheren van XFA-pakketten voor de volledige set probes, inclusief het XFAF-geval waar XFA-inhoud gelaagd zit over verder gewone AcroForm-widgets die /V wel eerbiedigen
Voor een echt dynamisch XFA-formulier waarbij de bewerkte waarden een opslag moeten overleven, is de interactieve bewerkingsbuffer helemaal niet het juiste gereedschap. Het duurzame pad is GetXfaDatasets als uw basislijn te behandelen, niet als uw resultaat: lees het één keer wanneer het document opent, houd uw eigen registratie bij van wat de gebruiker per veld heeft veranderd — precies de waarden die uw UI al heeft, aangezien PDFium ze u achteraf niet zal teruggeven — patch die zelf in de basislijn-XML, en drijf uw eigen uitvoer aan. Een schrijfbewerking die via XML loopt die uw eigen code beheert, overleeft een opslag die een CPWL_Edit-buffer nooit zou kunnen
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;
Het gat opvangen voordat een klant dat doet
TPdf.SaveAs geeft True terug ongeacht of een XFA-veldwaarde overleefde, omdat vanuit het oogpunt van PDFium de opslag werkelijk slaagde — het schreef elke byte die het werd gevraagd te schrijven. Dat maakt dit precies het soort defect dat langs een smoke-test glipt en een klant bereikt: niets werpt een uitzondering, niets logt, het bestand opent prima, alleen de specifieke waarde is verkeerd. Een round-trip-test die het opgeslagen bestand daadwerkelijk heropent en de waarde van het veld vergelijkt — of GetXfaDatasets vóór en na vergelijkt, volgens het eerdere voorbeeld — hoort thuis in de regressiesuite voor elke viewer die gebruikers XFA-inhoud laat bewerken, niet alleen de AcroForm-paden die toevallig standaard werken
Niets hiervan is zozeer een defect om tegen PDFium Component te melden als wel een grens om omheen te ontwerpen: SetFocusedFormFieldText doet precies wat de naam ervan zegt voor beide formuliermodellen, en het verschil in uitkomst is netjes te herleiden tot waar AcroForm en XFA elk die buffer aan de PDFium-kant aan koppelen. De API, de focus- en opslag-primitieven, en de pakketlezers waarnaar hier wordt verwezen, maken deel uit van de PDFium Component voor Delphi en C++Builder