PDFium Component for Delphi fyller en AcroForm atomärt med TPdf.SetFormFieldValues: du skickar 1 till 256 typade requests adresserade per sida och widget, och komponenten tillämpar antingen varje värde i en dokumentersättning eller inga alls. När en request är ogiltig namnger TPdfFormValuesWriteReport.FailedRequest den request som sänkte batchen
Den garantien spelar störst roll i jobben ingen ser på. En nattlig import trycker in CRM-rader i ett skadeformulär, eller en utskriftstjänst lägger policypodata ovanpå en förtryckt mall. Kör de fält för fält och det första dåliga värdet, en kod som inte är en giltig radio-export, en sträng längre än fältets MaxLen, lämnar dig med en PDF som är halvt gammal data och halvt ny. Ingenting kraschade, så ingenting alarmiserade någon. Batch-API:t finns för att ett fel ska bli en ren vägran i stället för ett halvskrivet dokument
Varför är fält-för-fält-ifyllning farlig för dataimport?
Fält-för-fält-ifyllning är farlig för varje skrivning committar på egen hand, så ett fel mitt i loopen lämnar ett dokument som matchar varken källposten eller originalmallen. Det klassiska mönstret är en loop över FormField[Index]-tilldelningar, var och en genom PDFiums nativa form-ifyllningsmiljö en widget i taget. Värde 1 till 17 landar, värde 18 kastar, och anroparen måste nu veta exakt vilka värden som gick in för att kunna ångra dem
De flesta anropare skriver aldrig den ångrakoden, och de som gör det får den oftast subtilt fel: ett delat fält har redan ändrats på varje sida det syns på, och checkbox-tillstånd har redan vänts. Om sparningen körs i ett finally-block går den halvfylda filen till disken och ser komplett ut. I ett overprint-jobb är det värre än ingen fil, för den skrivs ut och någon signerar den
Det andra tysta problemet är fokus. Den klassiska sättaren driver PDFiums interaktiva redigerare: den dödar befintligt fokus, fokuserar målwidgeten, markerar all text, ersätter den och dödar fokus igen. Det är fint för ett headless-jobb, men i en viewer avslutar det också vilken redigeringssession användaren hade öppen i ett annat fält. Den typade skrivvägen som kom i PDFium Component 3.231.0 går inte genom nativt fältfokus alls, och 3.233.0 utökade den från enskilda fält till batcher över sidor
Hur tillämpar SetFormFieldValues en batch i en enda ersättning?
TPdf.SetFormFieldValues förbereder varje request mot en infångad snapshot av dokumentet, skriver alla fältdictionaryer i ett svep, och byter in det färdiga resultatet med en enda ersättning. Det ursprungliga dokumentet förblir levande till allra sista steget, så varje fel före det steget lämnar det orört
Sekvensen förklarar både garantien och kostnaden:
- Preflightkontroller körs först: dokumentet måste vara aktivt, requestantalet måste vara 1..256, ingen page-object- eller edit-journal-transaktion får vara öppen, inget nativt fält får hålla fokus, och dokumentet måste bevilja fill-forms- eller modify-behörighet
- Varje request valideras för sidintervall och indatabudget, och
FailedRequestföljer indexet som kontrolleras - Komponenten infångar en edit-snapshot, normaliserar AcroForm:en i den kopian, och tillämpar varje request i ordning, och skriver
/V,/Iför choice-val och/ASför varje widget som delar fältet - Ändringarna läggs till snapshoten som en enda sparse inkrementell uppdatering i ISO 32000-1 §7.5.6:s mening, så orelaterade objekt flyttas över byte för byte
- En separat verifierarinstans laddar kandidatbytena först; först efter att den öppnas rent urladdar komponenten det levande dokumentet och laddar det nya, med återfall till den ursprungliga snapshoten om den laddningen fallerar
Det interna ersättningssteget är samma maskineri komponenten använder för sina edit-journal-snapshots, vilket är varför undo- och kraschåterhämtningsjournalen och en batchskrivning inte kan överlappa: avsluta eller avbryt journaltransaktionen först. Automatiska livscykelåtgärder undertrycks medan dokumentet byts, så ett document-open-skript inte kan skriva om fält eller rycka fokus mitt i ersättningen
Bygga en typad requestbatch i Delphi
En batch är en dynamisk array av TPdfFormValueRequest-records, var och en med en 1-baserad Page, ett 0-baserat sidlokalt FieldIndex och en typad TPdfFormValue. Värde-recorden bär en Kind av fvkText, fvkChoice, fvkCheckBox eller fvkRadioButton, plus medlemmarna Text, ChoiceIndices och Checked använda enligt den typen
Importdata kommer oftast nycklad per fältnamn, så det första jobbet är att översätta namn till sid- och widgetadresser. FieldIndex räknar widget-annotationer på en sida i samma ordning som FormFieldCount och FormFieldInfo, så en enkel skanning funkar. Hjälpfunktionen tar den första widget den hittar för ett namn, vilket spelar roll för delade fält, som nästa sektion förklarar
type
TFieldAddress = record
Page, FieldIndex: Integer;
end;
function FindFieldAddress(Pdf: TPdf; const FieldName: WString;
out Address: TFieldAddress): Boolean;
var
P, I: Integer;
begin
Result := False;
for P := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := P;
for I := 0 to Pdf.FormFieldCount - 1 do
if Pdf.FormFieldInfo[I].Name = FieldName then
begin
Address.Page := P;
Address.FieldIndex := I;
Exit(True); // första widgeten bara: en request per fält
end;
end;
end;
procedure AddRequest(var Requests: TPdfFormValueRequests;
const FieldName: WString; Pdf: TPdf; const Value: TPdfFormValue);
var
Address: TFieldAddress;
N: Integer;
begin
if not FindFieldAddress(Pdf, FieldName, Address) then
raise Exception.CreateFmt('Template has no field "%s"', [FieldName]);
N := Length(Requests);
SetLength(Requests, N + 1);
Requests[N].Page := Address.Page;
Requests[N].FieldIndex := Address.FieldIndex;
Requests[N].Value := Value;
end;
Notera att AddRequest kastar på ett okänt namn i stället för att hoppa över det. Batchen validerar adresser, inte din mappning, så ett saknat mallfält måste fallera här eller fallerar det aldrig alls. Med adresserna lösta är det kort att bygga värdena och committa dem
var
Pdf: TPdf;
Requests: TPdfFormValueRequests;
Report: TPdfFormValuesWriteReport;
V: TPdfFormValue;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'claim-template.pdf';
Pdf.Active := True;
V := Default(TPdfFormValue);
V.Kind := fvkText;
V.Text := 'Maria Lopez';
AddRequest(Requests, 'Claimant.Name', Pdf, V);
V := Default(TPdfFormValue);
V.Kind := fvkCheckBox;
V.Checked := True;
AddRequest(Requests, 'Consent', Pdf, V);
V := Default(TPdfFormValue);
V.Kind := fvkRadioButton;
V.Text := 'Express'; // exportvärde för knappen som ska väljas
AddRequest(Requests, 'Shipping', Pdf, V);
V := Default(TPdfFormValue);
V.Kind := fvkChoice;
SetLength(V.ChoiceIndices, 1);
V.ChoiceIndices[0] := 2; // 0-baserat index i fältets Opt-array
AddRequest(Requests, 'Region', Pdf, V);
if Pdf.SetFormFieldValues(Requests, Report) then
Pdf.SaveAs('claim-filled.pdf')
else
LogBatchFailure(Requests, Report);
finally
Pdf.Free;
end;
end;
De typade typerna kontrolleras mot fälttypen innan något skrivs. Ett textvärde riktat mot en checkbox, ett radiovärde riktat mot en vanlig checkbox, eller en skrivning till en push-knapp avvisas alla. Choice-index måste vara giltiga, unika och stigande; mer än ett index kräver MultiSelect-flaggan, och fri text i ett choice-fält accepteras bara av en redigerbar kombobox. Ett textvärde längre än fältets MaxLen avvisas i stället för att trunkeras, och read-only-fält avvisar rakt av. För radiogrupper med NoToggleToOff-flaggan avvisas också ett tomt exportvärde, som betyder ”rensa gruppen”
Varför misslyckas skrivning till ett delat fält två gånger hela batchen?
TPdf.SetFormFieldValues avvisar två requests som löses till samma terminalfält, för de skulle båda skriva ett enda värde, och komponenten vägrar låta en request tyst skriva över en annan. Kontrollen körs på fältägaren, inte widgeten, så två olika widgets av ett fält på två olika sidor räknas ändå som en dubblett
Orsaken är fältmodellen i ISO 32000-1 §12.7.3.1. Ett terminalfält äger värdet; dess widget-annotationer är bara ställen där det värdet visas. Ett fält namngett PolicyNumber som syns i rubriken på varje sida är ett fält med flera widgets, och var och en av de widgeterna visar samma /V. En radiogrupp funkar på samma sätt: varje knapp är en widget, gruppen är fältet. Att skriva PolicyNumber via sid 1:ans widget och igen via sid 9:ans widget är ett fält skrivet två gånger, och varje regel som utser en vinnare gömmer en bugg i din mappning
Så batchen behandlar det som ett fel och namnger den andra requesten i FailedRequest. I praktiken är fixen uppströms: lös varje fältnamn till exakt en adress, som FindFieldAddress gör, och låt komponenten sprida värdet till de andra widgeterna. Den gör det som del av skrivningen, och sätter /AS på varje widget i en delad checkbox eller radiogrupp så att utseendetillståndet stämmer med fältvärdet, samma uppdelning som tas upp i artikeln om checkbox-fältvärden mot widget-tillstånd
Läsa felrapporten
En fallerad batch returnerar False, kastar inte, och lämnar originaldokumentet och varje fältvärde exakt som de var. TPdfFormValuesWriteReport rapporterar sedan Succeeded = False, noll för både UpdatedFields och UpdatedWidgets, det 0-baserade indexet för den trassliga requesten i FailedRequest, och en människoläsbar orsak i ErrorMessage
procedure LogBatchFailure(const Requests: TPdfFormValueRequests;
const Report: TPdfFormValuesWriteReport);
var
R: Integer;
begin
R := Report.FailedRequest;
if R >= 0 then
Log(Format('Request %d (page %d, widget %d) rejected: %s',
[R, Requests[R].Page, Requests[R].FieldIndex, Report.ErrorMessage]))
else
Log('Batch rejected as a whole: ' + Report.ErrorMessage);
end;
En FailedRequest på -1 betyder att avvisningen hör till transaktionen snarare än en request: nativt fokus var aktivt, dokumentet är signerat, formuläret är XFA, eller en behörighetskontroll fallerade. Ett icke-negativt värde pekar på en post. För ett aggregerat budgetöverskridande pekar den på requesten som sköt batchen över gränsen, vilket inte nödvändigtvis är requesten som är fel. Håll en parallell array av källkolumnnamn bredvid Requests och du kan vända index 14 direkt till ”rad 2087, kolumn ShipDate” i importloggen
Vid framgång är UpdatedFields lika med requestantalet och UpdatedWidgets räknar varje widget rörd, delade inkluderade, en billig rimlighetskontroll mot mallen
Förhandsvillkor och gränser du kommer stöta på
Det vanligaste avvisandet i en viewerapplikation är nativt fokus: skriver en användare i ett fält vägrar SetFormFieldValues innan den ens infångar snapshoten, och den väntande redigerarbufferten överlever intakt. Komponenten tappar inte fokus bakom din rygg, för det skulle antingen committa eller tappa text användaren inte blivit klar med. Anropa ClearFormFieldFocus med flit, vilket avslutar redigeringssessionen som ett klick utanför fältet skulle göra, och försök sedan igen
Budgeterna är fasta och dokumenterade, så du kan dimensionera importer mot dem:
- 1 till 256 requests per batch; dela större poster i flera batcher och acceptera att atomiciteten då gäller per batch
- Högst 262 144 UTF-16-enheter text och 4 096 choice-index per request
- Högst 1 048 576 UTF-16-enheter text och 16 384 choice-index för hela batchen
- De vanliga dokumentbudgeterna för snapshot-parsern gäller fortfarande, inklusive en källa på 64 MiB och 65 536 objekt
En lyckad ersättning ogiltigförklarar också nativa handles, sökningar och markeringar infångade före anropet, för dokumentet under är nytt. Sid- och visningsaktivering och logiska fältkursors hålls kvar; allt annat du cachat, som annotation- eller page-object-handles, måste hämtas igen
Hur skiljer detta sig från XFDF-import?
TPdf.ImportXFDF och TPdf.SetFormFieldValues stoppar båda data i fält, men XFDF-import är en tolerant sammanslagning medan batchen är en strikt transaktion. XFDF-import matchar värden till fält per namn, hoppar över namn den inte hittar, ignorerar dubbletter, och returnerar hur många poster den tillämpade. Det är precis rätt för att slå ihop en granskares kommentarer, där ett partiellt resultat fortfarande är användbart
Batchen gör det motsatta på varje punkt. Den adresserar widgets explicit, avvisar okända eller feltypade mål, behandlar dubbletter som fel, och har inget partiellt utfall. Använd XFDF när datan är en utbytesfil från ett annat verktyg; använd SetFormFieldValues när din egen kod äger mappningen och en halvtillämpad post vore en defekt
Vad täcker inte batchskrivningen?
Batchen är en avgränsad AcroForm-profil, och den säger nej i stället för att gissa utanför profilen. XFA-formulär avvisas och behöver sin dedikerade redigerare. Signerade dokument avvisas, för att skriva om fältdictionaryer under en befintlig signatur är ett signaturarbetsflöde, inte en ifyllningsoperation; krypterade snapshots ligger också utanför profilen. Felformade eller tvetydiga knapp-exportarrayer avvisas, och radio-exportvärden som matchar mer än en knapp vägras om inte gruppen sätter RadiosInUnison. Transaktionen normaliserar också direkta fältdictionaryer innan skrivningen, fallet bakom fältvärden som verkar återgå
Utseenden förtjänar en mening av omsorg. För ändrade text- och choice-widgeter kastar batchen det inaktuella /AP:et i stället för att leverera ett utseende som visar det gamla värdet, och den stänger av NeedAppearances så att PDFium inte bygger om varje orelaterad widget i formuläret. PDFium regenererar de saknade utseendena när den renderar. Går den fyllda PDF:en till en strikt utskriftspipeline som aldrig syntetiserar utseenden, rendera eller platta ut den genom PDFium först, och testa den vägen med dina egna mallar
SetFormFieldValues och de typade värde-recorderna följer med PDFium Component for Delphi and Lazarus, där produktsidan listar hela form-ifyllnings- och AcroForm-API:t bredvid rendering, redigering och viewer-kontroller