Formos laukų bloko perkėlimas iš pernai metų šablono ant šių metų maketo yra vieta, kur FDF ir XFDF apvalieji keliai nustoja pakakti: reikšmės atkeliauja, o atvaizdavimo srautai, skaičiavimo veiksmai ir numatytieji ištekliai — ne. PDFiumPas atsako į tą atvejį su GraftPdfAcroForm, kuris iš vieno PDF klonuoja visą laukų objektų grafą ir įrašo jį į kitą
Priežastis, kodėl duomenų lygio eksportas to negali, yra struktūrinė. Laukas nėra įrašas — jis yra pografas. ISO 32000-1 §12.7 apibrėžia interaktyvios formos žodyną, laikantį /Fields, /CO, /DR ir /DA, §12.7.3 apibrėžia po juo kabančius laukų žodynus, o §12.5.6.19 apibrėžia valdiklių anotacijas, kurios tiems laukams duoda matomą langelį puslapyje. XFDF neša tos struktūros lapus. Persodinimas neša pačią struktūrą
Kodėl /Fields masyvo kopijavimas niekada nepakanka
/Fields kopijavimas iš vieno dokumento į kitą pagamina formą, sugadintą visais įdomiais būdais, nes masyvas laiko netiesiogines nuorodas ir nieko kito. ISO 32000-1 §7.3.10 daro netiesioginį objektą adresuojamą objekto numeriu plus karta, ir tie numeriai prasmingi tik faile, iš kurio atėjo. Įklijuokite masyvą per, ir kiekviena jame esanti nuoroda arba kabo, arba, dar blogiau, tyliai išsprendžia į nesusijusį objektą, kuris nutiko užimti tą lizdą paskirtyje. Žemiau kiekvienos nuorodos sėdi grafas, ir bendras, ir ciklinis. Lauko žodynas rodo į savo vaikus, kiekvienas vaikas rodo atgal į savą /Parent, valdiklis rodo į savo atvaizdavimo srautus ir į puslapį, kuris jį neša per /P, atvaizdavimo srautai rodo į šriftus formos numatytajame išteklių žodyne, o papildomų veiksmų žodynai po /AA rodo į dar daugiau objektų. Du valdikliai skirtinguose puslapiuose reguliariai dalijasi vienu šriftu ir vienu atvaizdavimo XObject. Tad teisingas persodinimas turi paeiti tą grafą, klonuoti kiekvieną pasiekiamą objektą tiksliai kartą, perorientuoti kiekvieno valdiklio /P į suvienodintą paskirties puslapį ir pridėti klonuotą valdiklį į to puslapio /Annots masyvą — kitaip laukas egzistuoja formoje ir yra nematomas puslapyje. Jei esate gavęs skirtumą tarp lauko, jo valdiklio ir puslapio anotacijos, jį rodančios, mūsų pastaba apie valdiklio indeksą prieš anotacijos indeksą dengia būtent tą atskyrimą
Ko GraftPdfAcroForm jums reikalauja?
Jam reikia trijų atskirų srautų ir aiškaus puslapių susiejimo. GraftPdfAcroForm priima Source, Destination ir Output kaip atskiras TStream instancijas, TPdfGraftPageMappings masyvą, TPdfAcroFormGraftOptions įrašą, pasirinktiną TPdfCrossDocumentGraftMap ir out TPdfAcroFormGraftReport. Jis grąžina Boolean, o ne kelia, o nesėkmės atveju ataskaita neša priežastį ErrorMessage. Puslapių susiejimas abiejose pusėse skaičiuojamas nuo vieneto ir nėra išspęstas: kiekvienas šaltinio puslapis, nešantis valdiklį, kurį ketinate persodinti, turi jame pasirodyti. nil perdavimas persodinimo žemėlapiui yra teisėtas — funkcija tada sukuria ir išlaisvina privatų iškvietimo trukmei — o TPdfAcroFormGraftOptions.Default duoda jums CollisionPolicy nustatytą pagcpReject, RenamePrefix nustatytą Imported_, MaxObjects 100000, MaxDepth 128 ir AllowSignedDestination nustatytą False. Tie paskutiniai trys yra biudžetai, ir jie egzistuoja todėl, kad objektų grafas, kurį ketinate paeiti, atėjo iš failo, kurio jūs nerašėte
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;
Kaip persodinimo žemėlapis išvengia bendro šrifto klonavimo dukart?
TPdfCrossDocumentGraftMap laiko šaltinio-į-paskirtį nuorodų lentelę, kurios raktai neša ir objekto numerį, ir kartą, o rekursinis klonuotojas ją pasitikrina prieš besileisdamas. Operacijų tvarka yra tai, kas daro ciklus saugiais: klonuotojas alokuoja paskirties objekto numerį ir registruoja susiejimą pirmiausia, tada paeina šaltinio objekto vaikų nuorodas. Tėvas, pasiekęs vaiką, rodantį atgal į savo tėvą, randa tėvą jau užregistruotą ir grąžina esamą paskirties nuorodą, vietoj rekursijos. Tas pats lookupas yra tai, kas daro šriftą, atvaizdavimo srautą arba veiksmą, dalijamą šešių valdiklių, klonuotą kartą ir nurodytą šešis kartus. Žemėlapis prie šaltinio dokumento pririšamas SHA-256 šaltinio baitų maiša, atskleista kaip SourceIdentity. Jei perduodate GraftPdfAcroForm žemėlapį, kurio tapatybė neatitinka perdoto šaltinio, jis atmeta iškvietimą, vietoj nuorodų pakartotinio naudojimo, kurios šiam failui niekada negaliojo. Puslapių susiejimai įsėjami į tą patį žemėlapį prieš klonavimo pradžią, ir būtent taip valdiklio /P galiausiai rodo į paskirties puslapį: šaltinio puslapio objektas jau išsprendžia į suvienodintą paskirties puslapio objektą, tad paprastas nuorodų perrašymo perėjimas apdoroja tai be jokio ypatingo atvejo
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
// Šio iškvietimo pridėti įrašai buvo atšaukti;
// visa, kas užregistruota prieš jį, vis dar sveika.
Assert(GraftMap.Count = EntriesBefore);
WriteLn('graft refused: ', Report.ErrorMessage);
end;
finally
GraftMap.Free;
end;
end;
Tas atšaukimas yra esmė, kodėl verta valdyti žemėlapį patiems. PDFiumPas traktuoja kvietėjo perduodamą žemėlapį transakciškai: nepavykęs persodinimas išmeta įrašus, kuriuos tas iškvietimas pridėjo, ir išlaiko kiekvieną iš anksto egzistavusį susiejimą, tad vienas atmetimas niekada nepalieka nuorodų talpyklos į objektus, kurie niekada nebuvo įrašyti. Bet laikykite po vieną žemėlapį paskirties dokumentui — kiekvieno įrašo paskirties pusė yra objekto numeris tame konkrečiame faile, ir kitame jis nieko nereiškia
Lauko vardų susidūrimai: atmesti ar pervadinti
Pilnai kvalifikuoti laukų vardai privalo likti unikalūs formos viduje, ir PDFiumPas nespės, ką turėjote omenyje, kai jie susiduria. TPdfAcroFormCollisionPolicy siūlo tiksliai du atsakymus. Su pagcpReject, numatytuoju, pirmasis šaltinio laukas, kurio pavadinimas jau egzistuoja paskirtyje, nutraukia visą persodinimą su klaida ir palieka išvesties srautą tuščią. Su pagcpRename susiduriantis šaltinio laukas pervardinamas prijungiant RenamePrefix, ir persodinimas tęsiasi, su Report.RenamedFieldCount, pasakančiu, kaip dažnai tai nutiko
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);
Pervadinimas nėra nemokamas, ir jį turėtumėte nuspręsti sąmoningai, vietoj griovimo prie jo, kad klaida dingtų. Pervadintas laukas yra kitas laukas: bet koks paskirties JavaScript, adresuojantis jį vardu, bet koks /CO skaičiavimo įrašas, kurį žmogus parašė prieš seną vardą, ir bet koks žemyn esantis vartotojas, raktuojantis pagal lauko vardą, turės žinoti apie priešdelį. Jei du dokumentai iš tikrųjų apibūdina tą patį lauką, sąžiningas taisymas paprastai yra vardų susitaikymas aukšte aukščiau, o ne persodinimo metu. Kai persodinimas nusileidžia, sujungtos formos apėjimas, kad patvirtintumėte, ką tikrai gavote, yra natūralus kitas žingsnis, o formos laukų navigacija PDFiumPas dengia tą apėjimą
Kur persodinimas tyčia žlunga uždarai
Kiekviena dviprasmiška sąlyga yra klaida, niekada geriausių pastangų rezultatas, ir tai dizaino sprendimas, vertas supratimo prieš jums nustebinant produkcijoje. GraftPdfAcroForm grąžina False, atstato išvesties srautą ir praneša priežastį, kai užkliūna ant bet kurio iš šių
- Šaltinio forma neša
/XFAįrašą — XFA paketai yra paralelinis formos modelis ir negali būti suskleisti iki AcroForm laukų žodynų - Valdiklis gyvena šaltinio puslapyje, neturinčiame įrašo puslapių susiejime, kas kitaip tyliai numestų lauką arba priliptų jį prie neteisingo puslapio
- Puslapių susiejimai už ribų, arba du susiejimai pakartotinai naudoja tą patį šaltinio arba paskirties puslapį
- Abi formos apibrėžia numatytąjį išteklių žodyną
/DR, nes dviejų išteklių vardų erdvių suliejimas rizikuotų perorientuoti esamą vardą į kitą šriftą - Objektų grafas viršija
MaxObjectsarba rekursija viršijaMaxDepth - Paskirtis turi parašą, o
AllowSignedDestinationyraFalse - Perduotas persodinimo žemėlapis priklauso kitam šaltinio dokumentui, arba šaltinio nuoroda kabo
Rašymo kelias vienodai konservatyvus. PDFiumPas išduoda rezultatą kaip retą inkrementinę reviziją, prijungtą prie paskirties, tada vėl suvienija įrašytą išvestį ir perskaito jos formą: jei rezultato laukų skaičius nelygus paskirties originaliam laukų skaičiui plus šaltinio, visas persodinimas atmetamas, o išvestis išvaloma. Jūs niekada negausite dalinai persodinto failo. To politikos kaina reali — /DR susidūrimas arba pasirašyta paskirtis jus sustabdo visiškai, ir turite tai išspręsti patys, vietoj sujungto apytikslio priėmimo — bet alternatyva yra forma, kuri atsidaro gerai ir skaičiuoja neteisingai
Kada persodinimas yra netinkamas įrankis
Persodinimas perkelia struktūrą, tad naudokite jį, kai struktūra yra tai, ko jums trūksta. Jei abu dokumentai jau neša tą patį laukų rinkinį ir jums reikia tik perkelti reikšmes bei anotacijas tarp jų, eksporto ir importo kelias XFDF formos duomenų straipsnyje yra lengvesnis, standartinis ir atšaukiamas. Imkitės GraftPdfAcroForm, kai paskirtis visai neturi laukų arba turi kitokį rinkinį, ir jums reikia, kad valdikliai, atvaizdavimo srautai, veiksmai ir skaičiavimo tvarka atkeltų sveiki. Paskutinė praktinė pastaba dėl tapatybės: kadangi persodinimo žemėlapis raktuoja pagal objekto numerį plus kartą ir yra pririštas prie šaltinio baitų SHA-256, pakartotinis šaltinio išsaugojimas arba optimizavimas tarp paleidimų pagamina kitokią tapatybę ir žemėlapį, kuris nebėra taikomas. Momentinę kopiją darykite šaltiniui, iš kurio persodinate, ir laikykite jį stabilų partijai; traktuokite jį kaip įvesties artefaktą, o ne kaip dalyką, kurį naktinė užduotis laisvai perrašo
GraftPdfAcroForm, TPdfCrossDocumentGraftMap ir aplinkinis srauto lygio PDF įrankių rinkinys atkeliavo su PDFiumPas Delphi PDFium Component Delphi, C++Builder ir Lazarus, kur produkto puslapyje yra visa API nuoroda į persodinimo parinktis, ataskaitos laukus ir likusį dokumentų redagavimo paviršių