Egy blokk űrlapmező áthelyezése a tavalyi sablonról az idei elrendezésre az a pont, ahol az FDF és XFDF oda-vissza utak már nem elegendők: az értékek megérkeznek, de a megjelenési streamek, számítási akciók és alapértelmezett erőforrások nem. A PDFiumPas ezt az esetet a GraftPdfAcroForm-dal válaszolja meg, amely az egész mezőobjektum-gráfot klónozza az egyik PDF-ből, és a másikba írja
Az ok, amiért egy adatszintű export ezt nem teheti meg, strukturális. Egy mező nem rekord, hanem részgráf. Az ISO 32000-1 §12.7 definiálja az interaktív űrlap szótárat, amely a /Fields-et, /CO-t, /DR-t és /DA-t tartja, a §12.7.3 a fölötte lógó mezőszótárakat, a §12.5.6.19 pedig a widget annotációkat, amelyek azoknak a mezőknek látható dobozt adnak egy oldalon. Az XFDF annak a struktúrának a leveleit hordozza. Az átültetés magát a struktúrát hordozza
Miért sosem elég a /Fields tömb másolása
A /Fields másolása az egyik dokumentumból a másikba minden érdekes módon törött űrlapot ad, mert a tömb közvetett hivatkozásokat tart és semmi mást. Az ISO 32000-1 §7.3.10 objektszám plusz generáció által címezhetővé tesz egy közvetett objektumot, és azok a számok csak abban a fájlban jelentősek, amelyből jöttek. Ragassza át a tömböt, és minden hivatkozás benne vagy lóg, vagy ami rosszabb, csendben egy olyan távoli objektumra oldódik fel, amely épp azt a helyet foglalja el a célban. Minden hivatkozás alatt olyan gráf ül, amely megosztott és ciklikus is. Egy mezőszótár a gyerekeire mutat, minden gyerek visszmutat a /Parent-jére, egy widget a megjelenési streamekre és arra az oldalra mutat, amely a /P-n át hordozza, a megjelenési streamek az űrlap alapértelmezett erőforrás-szótárának betűtípusaira mutatnak, és a /AA alatti kiegészítő akció szótárak még több objektumra mutatnak. Két widget különböző oldalakon rutinszerűen osztozik egy betűtípust és egy megjelenési XObjectet. Tehát egy helyes átültetésnek végig kell mennie azon a gráfön, minden elérhető objektumot pontosan egyszer klónoznia, minden widget /P-jét a leképezett céloldalra átírnia, és a klónozott widgetet hozzáadnia az oldal /Annots tömbjéhez — különben a mező létezik az űrlapban, és láthatatlan az oldalon. Ha üldözte a mező, widgetje és azt megjelenítő oldal-annotáció közötti különbséget, a widget index versus annotáció index jegyzetünk pontosan azt a szétválasztást fedi le
Mit kér el Öntől a GraftPdfAcroForm?
Három különböző streamet és explicit oldal-leképezést kér. A GraftPdfAcroForm Source, Destination és Output néven külön TStream példányokat, egy TPdfGraftPageMappings tömböt, egy TPdfAcroFormGraftOptions rekordot, egy opcionális TPdfCrossDocumentGraftMap-ot és egy out TPdfAcroFormGraftReport-ot fogad. Boolean-t ad vissza, kivételt nem dob, és hiba esetén a jelentés az okot a ErrorMessage-ben hordozza. Az oldal-leképezés mindkét oldalon 1 alapú, és nem következtetett: minden forrásoldal, amely átültetni kívánt widgetet hordoz, szerepelnie kell benne. nil átadása az átültetési térkép helyett legitim — a függvény akkor privát egyet készít és szabadít fel a hívás időtartamára —, a TPdfAcroFormGraftOptions.Default pedig CollisionPolicy-t ad pagcpReject-re állítva, RenamePrefix-et Imported_-re, MaxObjects-t 100000-en, MaxDepth-t 128-on és AllowSignedDestination-t False-ra. Az utolsó három keretek, és azért léteznek, mert az az objektumgráf, amelyet bejárni készül, olyan fájlból jött, amelyet Ön nem írt
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;
Hogyan kerüli el az átültetési térkép egy megosztott betűtípus kétszeri klónozását?
A TPdfCrossDocumentGraftMap forrás-cél hivatkozási táblát tart, amelynek kulcsai objektszámot és generációt egyaránt hordoznak, és a rekurzív klónozó megkéri, mielőtt leszállna. A műveleti sorrend teszi a ciklusokat biztonságossá: a klónozó először lefoglalja a célobjektum-számot és regisztrálja a leképezést, aztán bejárja a forrásobjektum gyerekhivatkozásait. Egy szülő, amely olyan gyerekhez ér, amely visszmutat a szülőjére, már regisztrált szülőt talál, és a meglévő célhivatkozást adja vissza, rekurzió helyett. Ugyanez a keresés az, amiért egy hat widget által megosztott betűtípus, megjelenési stream vagy akció egyszer klónozódik, és hatszor hivatkozik. A térkép a forrásdokumentumhoz a forrásbájtok SHA-256 hashén keresztül kötődik, SourceIdentity-ként kitéve. Ha olyan térképet ad a GraftPdfAcroForm-nak, amelynek identitása nem egyezik az átadott forrással, megtagadja a hívást, olyan hivatkozások újrahasznosítása helyett, amelyek sosem voltak érvényesek erre a fájlra. Az oldal-leképezések még a klónozás kezdete előtt bevetik ugyanabba a térképbe, ami pontosan az, ahogy egy widget /P-je a céloldalra mutat: a forrásoldal objektum már a leképezett céloldal objektumra oldódik fel, így a közönséges hivatkozás-átíró menet külön eset nélkül kezeli
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
// A hívás által hozzáadott bejegyzések visszagördültek;
// bármi, amit előtte regisztráltak, még sértetlen.
Assert(GraftMap.Count = EntriesBefore);
WriteLn('graft refused: ', Report.ErrorMessage);
end;
finally
GraftMap.Free;
end;
end;
Az a visszagördítés az értelme annak, hogy maga birtokolja a térképet. A PDFiumPas tranzakciósan kezeli a hívó által szolgáltatott térképet: egy elbukó átültetés eldobja azokat a bejegyzéseket, amelyeket az a hívás hozzáadott, és megtart minden korábban létező leképezést, így egy elutasítás sosem hagy maga után olyan objektumokra mutató hivatkozások gyorsítótárát, amelyeket sosem írtak. Azonban tartsanak egy térképet céldokumentumonként — minden bejegyzés cél oldala objektszám abban a konkrét fájlban, és egy másikban semmit sem jelent
Mezőnév-ütközések: elutasítás vagy átnevezés
A teljesen minősített mezőneveknek egyedinek kell maradniuk egy űrlapon belül, és a PDFiumPas nem találgatja meg, mit jelentett, amikor ütköznek. A TPdfAcroFormCollisionPolicy pontosan két választ kínál. pagcpReject alatt, az alapértelmezésben, az első forrásmező, amelynek címe már létezik a célban, hibával megszakítja az egész átültetést, és üresen hagyja a kimeneti streamet. pagcpRename alatt az ütköző forrásmező RenamePrefix előtaggal neveztetik át, és az átültetés folytatódik, a Report.RenamedFieldCount megmondva, milyen gyakran történt
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);
Az átnevezés nem ingyenes, és szándékosan kell döntenie róla, ahelyett hogy azért nyúlna érte, hogy egy hiba eltűnjön. Egy átnevezett mező más mező: minden JavaScript a célban, amely névvel címezi, minden /CO-beli számítási bejegyzés, amelyet ember írt a régi névre, és minden lefolyó fogyasztó, amely a mezőnévre kulcsol, tudnia kell az előtagról. Ha a két dokumentum valóban ugyanazt a mezőt írja le, az őszinte javítás általában a nevek egyeztetése feljebb, nem átültetéskor. Amint az átültetés landol, az egyesített űrlap bejárása annak megerősítésére, mit kapott valójában, a természetes következő lépés, és a űrlapmező navigáció a PDFiumPas-ban fedi le azt a bejárást
Hol hibázik az átültetés szándékosan zártan
Minden kétértelmű feltétel hiba, sosem legjobb-törekvésű eredmény, és ez tervezési döntés, amelyet érdemes megérteni, mielőtt a termelésben lep meg. A GraftPdfAcroForm False-ot ad vissza, alaphelyzetbe állítja a kimeneti streamet, és jelenti az okot, amikor ezek bármelyikébe ütközik
- A forrásűrlap
/XFAbejegyzést hordoz — az XFA csomagok párhuzamos űrlapmodell, és AcroForm mezőszótárakra nem redukálhatók - Egy widget olyan forrásoldalon él, amelynek nincs bejegyzése az oldal-leképezésben, különben a mező csendben eldobódna vagy rossz oldalhoz csatolódna
- Az oldal-leképezések tartományon kívül esnek, vagy két leképezés ugyanazt a forrás- vagy céloldalt használja újra
- Mindkét űrlap alapértelmezett erőforrás-szótárt,
/DR-t definiál, mert két erőforrás-névtér egyesítése kockáztatná, hogy egy meglévő név más betűtípusra mutat át - Az objektumgráf túllépi a
MaxObjects-t, vagy a rekurzió aMaxDepth-et - A cél aláírást tartalmaz, és az
AllowSignedDestinationFalse - A szolgáltatott átültetési térkép más forrásdokumentumhoz tartozik, vagy egy forráshivatkozás lóg
Az írási útvonal ugyanilyen konzervatív. A PDFiumPas az eredményt ritka inkrementális revízióként bocsátja ki a célhoz fűzve, aztán újramaterializálja a kiírt kimenetet, és újraolvassa annak űrlapját: ha az eredmény mezőszáma nem egyenlő a cél eredeti mezőszámával plusz a forráséval, az egész átültetés elutasításra kerül, és a kimenet törlődik. Soha nem kap részben átültetett fájlt. Az a politika ára valós — egy /DR ütközés vagy aláírt cél azonnal megállítja, és magának kell feloldania, egyesített közelítés elfogadása helyett —, de az alternatíva egy olyan űrlap, amely szépen megnyílik és rosszul számol
Mikor az átültetés a rossz eszköz
Az átültetés struktúrát mozgat, ezért akkor használja, amikor a struktúra az, ami hiányzik. Ha mindkét dokumentum már hordozza ugyanazt a mezőkészletet, és Önnek csak értékeket és annotációkat kell mozgatnia köztük, az XFDF űrlapadat cikkben lévő export- és importútvonal könnyebb, standard és visszafordítható. Nyúljon a GraftPdfAcroForm-hoz, amikor a célnak egyáltalán nincsenek mezői, vagy más készlete van, és szüksége van rá, hogy a widgetek, megjelenési streamek, akciók és számítási sorrend érintetlenül jöjjenek át. Egy utolsó gyakorlati jegyzet az identitásról: mivel az átültetési térkép objektszám plusz generáció alapján kulcsol, és a forrásbájtok SHA-256-jához kötődik, a forrás újramentése vagy optimalizálása futások között más identitást és olyan térképet ad, amely már nem alkalmazható. Pillanatkép a forrásról, amelyről átültet, és tartsa stabilan a köteghez; kezelje bemeneti artefaktumként, nem olyanként, amelyet egy éjszakai feladat szabadon átírhat
A GraftPdfAcroForm, TPdfCrossDocumentGraftMap és a környező streamszintű PDF eszközkészlet a Delphi, C++Builder és Lazarus számára készült PDFiumPas Delphi PDFium Component-dal száll, ahol a termékoldal hordozza a teljes API referenciát az átültetési lehetőségekhez, jelentésmezőkhöz és a dokumentumszerkesztési felület többi részéhez