Premik bloka polj obrazca iz lanskoletne predloge na letošnjo postavitev je točka, kjer izmenjavi FDF in XFDF prenehata zadoščati: vrednosti prispejo, tokovi videza, dejanja izračuna in privzeti viri pa ne. PDFiumPas se na ta primer odzove z GraftPdfAcroForm, ki klonira celoten graf predmetov polj iz enega PDF in ga zapiše v drugega
Razlog, da izvoz na ravni podatkov tega ne zmore, je strukturen. Polje ni zapis, je podgraf. ISO 32000-1 §12.7 definira slovar interaktivnega obrazca, ki nosi /Fields, /CO, /DR in /DA, §12.7.3 definira slovarje polj, ki visijo pod njim, §12.5.6.19 pa definira anotacije widgetov, ki tem poljem dajo vidno okno na strani. XFDF prenese liste te strukture. Presaditev prenese samo strukturo
Zakaj kopiranje tabele /Fields nikoli ni dovolj
Kopiranje /Fields iz enega dokumenta v drugega da obrazec, ki je pokvarjen na vsak zanimiv način, ker tabela nosi posredne sklice in nič drugega. ISO 32000-1 §7.3.10 naredi posredni predmet naslovljiv s številko predmeta plus generacijo, te številke pa so smiselne le znotraj datoteke, iz katere prihajajo. Prilepite tabelo čez in vsak sklic v njej bodisi visi bodisi, še huje, tiho razreši na nepovezan predmet, ki slučajno zaseda ta režo v cilju. Pod vsakim sklicem sedi graf, ki je hkrati deljen in cikličen. Slovar polja kaže na svoje otroke, vsak otrok kaže nazaj na svoj /Parent, widget kaže na svoje tokove videza in na stran, ki ga nosi prek /P, tokovi videza kažejo na pisave v privzetem slovarju virov obrazca, slovarji dodatnih dejanj pod /AA pa kažejo na še več predmetov. Dva widgeta na različnih straneh rutinsko delita eno pisavo in en videzni XObject. Torej mora pravilna presaditev prehoditi ta graf, klonirati vsak dosegljiv predmet točno enkrat, preusmeriti /P vsakega widgeta na preslikano ciljno stran in dodati klonirani widget v tabelo /Annots te strani — sicer polje obstaja v obrazcu in je na strani nevidno. Če ste raziskovali razliko med poljem, njegovim widgetom in anotacijo strani, ki ga prikaže, naša opomba o indeksu widgetov proti indeksu anotacij pokriva točno to ločnico
Kaj GraftPdfAcroForm potrebuje od vas?
Potrebuje tri ločene toke in izrecno preslikavo strani. GraftPdfAcroForm vzame Source, Destination in Output kot ločene instance TStream, tabelo TPdfGraftPageMappings, zapis TPdfAcroFormGraftOptions, izbirno TPdfCrossDocumentGraftMap in izhodni TPdfAcroFormGraftReport. Vrne Boolean namesto da bi sprožil izjemo, ob spodletelosti pa poročilo nosi razlog v ErrorMessage. Preslikava strani je na obeh straneh eniško osnovana in ni izpeljana: vsaka izvorna stran, ki nosi widget, ki ga nameravate presaditi, se mora pojaviti v njej. Podati nil za karto presaditve je upravičeno — funkcija takrat ustvari in sprosti zasebno karto za trajanje klica — TPdfAcroFormGraftOptions.Default pa daje CollisionPolicy nastavljen na pagcpReject, RenamePrefix na Imported_, MaxObjects 100000, MaxDepth 128 in AllowSignedDestination na False. Zadnje tri so proračuni in obstajajo, ker graf predmetov, ki ga boste prehodili, prihaja iz datoteke, ki je niste napisali
uses
Classes, SysUtils, FPdfCompress;
var
Source, Destination, Output: TMemoryStream;
Options: TPdfAcroFormGraftOptions;
Mappings: TPdfGraftPageMappings;
Report: TPdfAcroFormGraftReport;
begin
Source := TMemoryStream.Create;
Destination := TMemoryStream.Create;
Output := TMemoryStream.Create;
try
Source.LoadFromFile('claim-template-2025.pdf');
Destination.LoadFromFile('claim-layout-2026.pdf');
Source.Position := 0;
Destination.Position := 0;
Options := TPdfAcroFormGraftOptions.Default;
SetLength(Mappings, 2);
Mappings[0].SourcePageNumber := 1;
Mappings[0].DestinationPageNumber := 1;
Mappings[1].SourcePageNumber := 2;
Mappings[1].DestinationPageNumber := 3;
if GraftPdfAcroForm(Source, Destination, Output, Mappings,
Options, nil, Report) then
Output.SaveToFile('claim-2026-with-fields.pdf')
else
raise Exception.Create(Report.ErrorMessage);
finally
Output.Free;
Destination.Free;
Source.Free;
end;
end;
Kako karta presaditve prepreči dvojni kloniranje deljene pisave?
TPdfCrossDocumentGraftMap hrani tabelo sklicev iz vira na cilj, katere ključi nosijo tako številko predmeta kot generacijo, rekurzivni kloner pa se je posvetuje, preden se spusti. Vrstni red operacij je tisto, kar naredi cikle varne: kloner najprej dodeli številko ciljnega predmeta in registrira preslikavo, šele nato prehodi otroške sklice izvornega predmeta. Starš, ki doseže otroka, ki kaže nazaj na svojega starša, najde starša že registriranega in vrne obstoječi ciljni sklic namesto rekurzije. Isto iskanje je tisto, ki naredi, da se pisava, tok videza ali dejanje, deljeno s šestimi widgeti, klonira enkrat in sklicuje šestkrat. Karta je vezana na izvorni dokument s hash SHA-256 izvornih bajtov, izpostavljen kot SourceIdentity. Če podate GraftPdfAcroForm karto, katere identiteta se ne ujema s podanim virom, zavrne klic, namesto da bi ponovno uporabil sklice, ki za to datoteko nikoli niso bili veljavni. Preslikave strani so vtočene v isto karto, preden se kloniranje začne, in točno tako se /P widgeta konča, da kaže na ciljno stran: izvorni predmet strani se že razreši na preslikani ciljni predmet strani, zato običajen prehod prepisovanja sklicev to opravi brez posebnega primera
uses
Classes, SysUtils, FPdfCompress, FPdfSha256;
var
GraftMap: TPdfCrossDocumentGraftMap;
SourceBytes: TBytes;
EntriesBefore: Integer;
begin
SetLength(SourceBytes, Source.Size);
Source.Position := 0;
if Length(SourceBytes) > 0 then
Source.ReadBuffer(SourceBytes[0], Length(SourceBytes));
GraftMap := TPdfCrossDocumentGraftMap.Create(
AnsiString(SHA256Hex(SHA256Bytes(SourceBytes))));
try
EntriesBefore := GraftMap.Count;
Source.Position := 0;
if not GraftPdfAcroForm(Source, Destination, Output, Mappings,
Options, GraftMap, Report) then
begin
// Vnosi, dodani s tem klicem, so bili povrnjeni;
// vse, registrirano pred njim, je še vedno nedotaknjeno.
Assert(GraftMap.Count = EntriesBefore);
WriteLn('graft refused: ', Report.ErrorMessage);
end;
finally
GraftMap.Free;
end;
end;
Ta povrnitev je poanta lastništva karte. PDFiumPas obravnava karto, ki jo poda klicatelj, transakcijsko: spodletela presaditev zavreže vnose, ki jih je ta klic dodal, in obdrži vsako preslikavo, ki je obstajala prej, zato ena zavrnitev nikoli ne pušča za sabo predpomnilnika sklicev na predmete, ki nikoli niso bili zapisani. Hranite pa eno karto na ciljni dokument — ciljna stran vsakega vnosa je številka predmeta v tej določeni datoteki in v drugi ne pomeni nič
Trki imen polj: zavrnitev ali preimenovanje
Popolnoma kvalificirana imena polj morajo ostati edinstvena znotraj obrazca, PDFiumPas pa ne bo ugibal, kaj ste mislili, ko se zaletijo. TPdfAcroFormCollisionPolicy ponuja točno dva odgovora. Pod pagcpReject, privzetim, prvo izvorno polje, katerega naslov že obstaja v cilju, prekine celotno presaditev z napako in pusti izhodni tok prazen. Pod pagcpRename se zaletavajoče izvorno polje preimenuje s predpono RenamePrefix in presaditev se nadaljuje, Report.RenamedFieldCount pa pove, kako pogosto se je to zgodilo
Options := TPdfAcroFormGraftOptions.Default;
Options.CollisionPolicy := pagcpRename;
Options.RenamePrefix := 'Y2025_';
Options.MaxObjects := 20000;
Options.MaxDepth := 64;
if GraftPdfAcroForm(Source, Destination, Output, Mappings,
Options, nil, Report) then
begin
WriteLn('source fields : ', Report.SourceFieldCount);
WriteLn('existing fields: ', Report.DestinationFieldCount);
WriteLn('grafted fields : ', Report.GraftedFieldCount);
WriteLn('renamed fields : ', Report.RenamedFieldCount);
WriteLn('cloned objects : ', Report.GraftedObjectCount);
WriteLn('reused objects : ', Report.ReusedObjectCount);
WriteLn('mapped pages : ', Report.MappedPageCount);
WriteLn('output bytes : ', Report.OutputByteCount);
end
else
WriteLn('graft refused : ', Report.ErrorMessage);
Preimenovanje ni brezplačno in odločiti se ga je treba namenoma, namesto da bi segli po njem, da bi napaka izginila. Preimenovano polje je drugo polje: vsak JavaScript v cilju, ki ga nagovarja po imenu, vsak vnos izračuna v /CO, ki ga je človek napisal proti staremu imenu, in vsak nadaljnji porabnik, ki ključi na ime polja, bo moral izvedeti za predpono. Če dva dokumenta resnično opisujeta isto polje, je poštena rešitev običajno uskladiti imena v zgornjem toku, ne ob času presaditve. Ko presaditev pristane, je prehod po združenem obrazcu, da potrdite, kaj ste dejansko dobili, naravni naslednji korak, navigacija po poljih obrazca v PDFiumPas pa pokriva to potovanje
Kje presaditev namenoma odpove zaprto
Vsak dvoumen pogoj je napaka, nikoli rezultat po najboljših močeh, in to je oblikovalska odločitev, vredna razumevanja, preden vas preseneči v produkciji. GraftPdfAcroForm vrne False, ponastavi izhodni tok in poroča razlog, ko naleti na kateri koli od teh
- Izvorni obrazec nosi vnos
/XFA— paketi XFA so vzporedni model obrazca in se ne dajo zreducirati na slovarje polj AcroForm - Widget živi na izvorni strani, ki nima vnosa v preslikavi strani, sicer pa bi se polje tiho spustilo ali priložilo napačni strani
- Preslikave strani so izven obsega, ali pa dve preslikavi ponovno uporabita isto izvorno ali ciljno stran
- Oba obrazca definira privzeti slovar virov
/DR, ker bi združevanje dveh imenskih prostorov virov tvegalo, da se obstoječe ime preusmeri na drugo pisavo - Graf predmetov preseže
MaxObjectsali rekurzija presežeMaxDepth - Cilj vsebuje podpis in
AllowSignedDestinationjeFalse - Podana karta presaditve pripada drugemu izvornemu dokumentu, ali pa izvorni sklic visi
Pot pisanja je enako previdna. PDFiumPas izda rezultat kot redko prirastno revizijo, priloženo cilju, nato ponovno materializira zapisani izhod in ponovno prebere njegov obrazec: če števec polj rezultata ni enak izvirnemu števcu polj cilja plus števcu vira, je celotna presaditev zavrnjena in izhod počiščen. Delno presajene datoteke nikoli ne dobite. Cena te politike je resnična — trk /DR ali podpisan cilj vas ustavi na mestu, rešiti ga morate sami, namesto da bi sprejeli združeno približanje — alternativa pa je obrazec, ki se odpre v redu in računa narobe
Kdaj je presajanje napačno orodje
Presaditev premika strukturo, zato jo uporabite, ko je struktura tisto, kar vam manjka. Če oba dokumenta že nosita isti nabor polj in potrebujete le premik vrednosti in anotacij med njima, je pot izvoza in uvoza v članku o podatkih obrazcev XFDF lažja, standardna in povrnljiva. Segnite po GraftPdfAcroForm, ko cilj sploh nima polj ali ima drugačen nabor, in potrebujete, da widgeti, tokovi videza, dejanja in vrstni red izračuna pridejo čez nedotaknjeni. Zadnja praktična opomba o identiteti: ker karta presaditve ključi na številko predmeta plus generacijo in je vezana na SHA-256 izvornih bajtov, ponovno shranjevanje ali optimiziranje vira med zagoni da drugačno identiteto in karto, ki ne velja več. Poslikajte vir, iz katerega presajate, in ga ohranite stabilnega za serijo; obravnavajte ga kot vhodni artefakt, ne kot nekaj, kar nočno opravilo sme prosto prepisati
GraftPdfAcroForm, TPdfCrossDocumentGraftMap in okoliško orodje PDF na ravni tokov prihaja s PDFiumPas Delphi PDFium Component za Delphi, C++Builder in Lazarus, kjer stran izdelka nosi celotno referenco API za možnosti presaditve, polja poročila in preostalo površino urejanja dokumentov