Premeštanje bloka polja obrasca sa šablona prošle godine na raspored ove godine je mesto gde FDF i XFDF odlasci i povratci više nisu dovoljni: vrednosti stignu, ali appearance tokovi, računske akcije i podrazumevani resursi ne. PDFiumPas odgovara na taj slučaj sa GraftPdfAcroForm, koji klonira ceo graf objekata polja iz jednog PDF-a i upisuje ga u drugi
Razlog što izvoz na nivou podataka ne može ovo je strukturni. Polje nije zapis, nego podgraf. ISO 32000-1 §12.7 definiše rečnik interaktivnog obrasca koji drži /Fields, /CO, /DR i /DA, §12.7.3 definiše rečnike polja koji vise ispod njega, a §12.5.6.19 definiše widget anotacije koje tim poljima daju vidljiv okvir na stranici. XFDF prenosi listove te strukture. Presađivanje prenosi samu strukturu
Zašto kopiranje /Fields niza nikad nije dovoljno
Kopiranje /Fields iz jednog dokumenta u drugi proizvodi obrazac koji je pokvaren na svaki zanimljiv način, jer niz drži indirektne reference i ništa više. ISO 32000-1 §7.3.10 čini indirektan objekt adresabilnim brojem objekta plus generacijom, i ti brojevi imaju značenje samo unutar fajla iz kojeg su došli. Zalepite niz preko i svaka referenca u njemu ili visi, ili gore, tiho se rešava na nepovezani objekt koji slučajno zauzima taj slot u odredištu. Ispod svake reference sedi graf koji je i deljen i cikličan. Rečnik polja pokazuje na svoju decu, svako dete pokazuje nazad na svoj /Parent, widget pokazuje na svoje appearance tokove i na stranicu koja ga nosi kroz /P, appearance tokovi pokazuju na fontove u podrazumevanom resursnom rečniku obrasca, i additional-action rečnici pod /AA pokazuju na još objekata. Dva widget-a na različitim stranicama rutinski dele jedan font i jedan appearance XObject. Zato ispravno presađivanje mora da prošeta tim grafom, klonira svaki dostižan objekat tačno jednom, preusmeri /P svakog widget-a na preslikanu odredišnu stranicu, i doda klonirani widget u /Annots niz te stranice — inače polje postoji u obrascu i nevidljivo je na stranici. Ako ste pratili razliku između polja, njegovog widget-a i stranične anotacije koja ga prikazuje, naša napomena o widget indeksu naspram indeksu anotacije pokriva baš to razdvajanje
Šta GraftPdfAcroForm traži od vas?
Traži tri odvojena toka i eksplicitno preslikavanje stranica. GraftPdfAcroForm prima Source, Destination i Output kao odvojene TStream instance, TPdfGraftPageMappings niz, TPdfAcroFormGraftOptions zapis, opcionu TPdfCrossDocumentGraftMap, i out TPdfAcroFormGraftReport. Vraća Boolean umesto da diže izuzetak, i pri neuspehu izveštaj nosi razlog u ErrorMessage. Preslikavanje stranica je 1-bazirano sa obe strane i ne izvodi se: svaka izvorna stranica koja nosi widget koji nameravate da presadite mora se pojaviti u njemu. Prosleđivanje nil za graft mapu je legitimno — funkcija tada stvara i oslobađa privatnu za trajanje poziva — a TPdfAcroFormGraftOptions.Default daje CollisionPolicy postavljen na pagcpReject, RenamePrefix postavljen na Imported_, MaxObjects od 100000, MaxDepth od 128 i AllowSignedDestination postavljen na False. Poslednja tri su budžeti, i postoje jer graf objekata koji ćete prošetati došao je iz fajla koji niste vi pisali
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 graft mapa izbegava kloniranje deljenog fonta dvaput?
TPdfCrossDocumentGraftMap drži tabelu referenci iz izvora u odredište čiji ključevi nose i broj objekta i generaciju, i rekurzivni kloner je konsultuje pre nego što krene naniže. Redosled operacija je ono što čini cikluse bezbednima: kloner alocira odredišni broj objekta i registruje preslikavanje prvo, pa šeta reference dece izvornog objekta. Roditelj koji stigne do deteta koje pokazuje nazad na roditelja pronalazi roditelja već registrovanim i vraća postojeću odredišnu referencu umesto da se rekurzira. Isti lookup je ono što font, appearance tok ili akciju deljenu među šest widget-a čini kloniranim jednom i referenciranim šest puta. Mapa je vezana za izvorni dokument SHA-256 hešom izvornih bajtova, izloženim kao SourceIdentity. Ako uručite GraftPdfAcroForm mapi čiji identitet se ne poklapa sa izvorom koji ste prosledili, odbija poziv umesto da ponovo koristi reference koje nikad nisu bile važeće za ovaj fajl. Preslikavanja stranica se seju u istu mapu pre nego što kloniranje počne, što je upravo način na koji /P widget-a završi pokazujući na odredišnu stranicu: izvorni objekat stranice se već rešava na preslikani odredišni objekat stranice, pa običan prolaz prepisivanja referenci rukuje time bez posebnog slučaja
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
// Unosi dodati ovim pozivom su vraćeni nazad;
// sve registrovano pre njega je i dalje netaknuto.
Assert(GraftMap.Count = EntriesBefore);
WriteLn('graft refused: ', Report.ErrorMessage);
end;
finally
GraftMap.Free;
end;
end;
Ta povratka je poenta posedovanja mape sami. PDFiumPas tretira mapu koju dostavlja pozivalac transakciono: neuspeo graft odbacuje unose koje je taj poziv dodao i zadržava svako preslikavanje koje je postojalo prethodno, pa jedan odbijen graft nikad ne ostavlja keš referenci na objekte koji nikad nisu upisani. Držite jednu mapu po odredišnom dokumentu, međutim — odredišna strana svakog unosa je broj objekta u tom konkretnom fajlu, i ne znači ništa u drugome
Sukobi imena polja: odbiti ili preimenovati
Potpuno kvalifikovana imena polja moraju ostati jedinstvena unutar obrasca, i PDFiumPas neće pogoditi šta ste mislili kada se sudare. TPdfAcroFormCollisionPolicy nudi tačno dva odgovora. Pod pagcpReject, podrazumevanim, prvo izvorno polje čiji naslov već postoji u odredištu prekida ceo graft sa greškom i ostavlja izlazni tok prazan. Pod pagcpRename, polje koje se sudari se preimenuje prefiksom RenamePrefix i graft nastavlja, sa Report.RenamedFieldCount koji vam kaže koliko često se to desilo
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 nije besplatno, i odlučite o njemu namerno umesto da posegnete za njim da bi greška nestala. Preimenovano polje je drugo polje: svaki JavaScript u odredištu koji ga adresira imenom, svaki računski unos u /CO koji je čovek napisao naspram starog imena, i svaki nizvodni potrošač koji se ključa na imenu polja moraće znati za prefiks. Ako dva dokumenta stvarno opisuju isto polje, iskrena popravka je obično usklađivanje imena uzvodno, ne u trenutku presađivanja. Kad graft slete, šetnja spojenim obrascem da potvrdite šta ste stvarno dobili je prirodan sledeći korak, a navigacija poljima obrasca u PDFiumPas-u pokriva to obilaženje
Gde presađivanje namerno pada zatvoreno
Svaka dvosmislena uslovost je greška, nikad rezultat po najboljem naporu, i to je dizajnerska odluka vredna razumevanja pre nego što vas iznenadi u produkciji. GraftPdfAcroForm vraća False, resetuje izlazni tok i izveštava razlog kada udari u bilo koji od ovih
- Izvorni obrazac nosi
/XFAunos — XFA paketi su paralelni model obrasca i ne mogu se svesti na AcroForm rečnike polja - Widget živi na izvornoj stranici koja nema unos u preslikavanju stranica, što bi inače tiho ispustilo polje ili ga prikačilo na pogrešnu stranicu
- Preslikavanja stranica su van opsega, ili dva preslikavanja ponovo koriste istu izvornu ili odredišnu stranicu
- Oba obrasca definišu podrazumevani resursni rečnik
/DR, jer spajanje dva imenska prostora resursa bi rizikovalo preusmeravanje postojećeg imena na drugi font - Graf objekata premašuje
MaxObjectsili rekurzija premašujeMaxDepth - Odredište sadrži potpis i
AllowSignedDestinationjeFalse - Dostavljena graft mapa pripada drugom izvornom dokumentu, ili izvorna referenca visi
Putanja upisa je jednako konzervativna. PDFiumPas emituje rezultat kao redak inkrementalnu reviziju dodatu odredištu, pa ponovo materijalizuje upisani izlaz i ponovo čita njegov obrazac: ako se broj polja rezultata ne poklapa sa izvornim brojem polja odredišta plus izvora, ceo graft se odbacuje i izlaz se briše. Nikad ne dobijete delimično presađen fajl. Cena te politike je stvarna — sudar /DR ili potpisano odredište vas stopira odmah, i morate to razrešiti sami umesto da prihvatite spojenu aproksimaciju — ali alternativa je obrazac koji se lepo otvara i računa pogrešno
Kada je presađivanje pogrešan alat
Presađivanje premešta strukturu, pa ga koristite kada je struktura ono što vam nedostaje. Ako oba dokumenta već nose isti skup polja i treba vam samo prenos vrednosti i anotacija među njima, putanja izvoza i uvoza u članku o XFDF podacima obrasca je lakša, standardna i povrativa. Posegnite za GraftPdfAcroForm kada odredište nema nikakva polja, ili ima drugačiji skup, i treba vam da widget-i, appearance tokovi, akcije i redosled računanja stignu netaknuti. Poslednja praktična napomena o identitetu: pošto graft mapa ključa na broju objekta plus generaciji i vezana je za SHA-256 izvornih bajtova, ponovno čuvanje ili optimizacija izvora između pokretanja proizvodi drugi identitet i mapu koja više ne važi. Snimite izvor sa kojeg presađujete i držite ga stabilnim za seriju; tretirajte ga kao ulazni artefakt, ne kao nešto što noćni posao slobodno prepisuje
GraftPdfAcroForm, TPdfCrossDocumentGraftMap i okružni PDF alat na nivou tokova isporučuju se sa PDFiumPas Delphi PDFium Komponentom za Delphi, C++Builder i Lazarus, gde stranica proizvoda nosi potpunu API referencu za graft opcije, polja izveštaja i ostatak površine za uređivanje dokumenata