Bir form alanı bloğunu geçen yılın şablonundan bu yılın yerleşimine taşımak, FDF ve XFDF gidiş dönüşlerinin artık yeterli olmadığı noktadır: değerler gelir ama görünüm akışları, hesaplama eylemleri ve varsayılan kaynaklar gelmez. PDFiumPas bu duruma, tüm alan nesne grafiğini bir PDFden klonlayıp başka bir PDFe yazan GraftPdfAcroForm ile yanıt verir
Veri düzeyinde bir dışa aktarımın bunu yapamamasının nedeni yapısaldır. Bir alan bir kayıt değildir, bir alt graftır. ISO 32000-1 §12.7, /Fields, /CO, /DR ve /DA girdilerini tutan etkileşimli form sözlüğünü tanımlar; §12.7.3 onun altında asılı duran alan sözlüklerini tanımlar ve §12.5.6.19, bu alanlara bir sayfa üzerinde görünür bir kutu veren widget açıklamalarını tanımlar. XFDF o yapının yapraklarını taşır. Aşılama ise yapının kendisini taşır
/Fields dizisini kopyalamak neden asla yeterli olmaz?
/Fields dizisini bir belgeden başka birine kopyalamak, her ilginç yönden bozuk bir form üretir; çünkü dizi dolaylı başvurulardan başka bir şey tutmaz. ISO 32000-1 §7.3.10, bir dolaylı nesneyi nesne numarası artı nesil ile adreslenebilir kılar ve bu numaralar yalnızca geldikleri dosyanın içinde anlamlıdır. Diziyi karşı tarafa yapıştırdığınızda içindeki her başvuru ya sarkır ya da daha da kötüsü, hedefte o yuvayı işgal etmekte olan ilgisiz bir nesneye sessizce çözülür. Her başvurunun altında hem paylaşılan hem döngüsel bir grafik bulunur. Bir alan sözlüğü çocuklarına işaret eder, her çocuk /Parent ile ebeveynine geri işaret eder, bir widget görünüm akışlarına ve /P ile onu taşıyan sayfaya işaret eder, görünüm akışları formun varsayılan kaynak sözlüğündeki yazı tiplerine işaret eder ve /AA altındaki ek eylem sözlükleri daha da fazla nesneye işaret eder. Farklı sayfalardaki iki widget sıklıkla tek bir yazı tipini ve tek bir görünüm XObjectini paylaşır. Bu yüzden doğru bir aşılama o grafiği yürümeli, erişilebilir her nesneyi tam olarak bir kez klonlamalı, her widgetın /P başvurusunu eşlenen hedef sayfaya yönlendirmeli ve klonlanan widgetı o sayfanın /Annots dizisine eklemelidir — aksi takdirde alan formda var olur ve sayfada görünmez. Bir alan, widgetı ve onu görüntüleyen sayfa açıklaması arasındaki ayrımın peşindeyseniz, widget dizinine karşı açıklama dizini notumuz tam olarak bu ayrımı kapsar
GraftPdfAcroForm sizden ne ister?
Üç ayrı akış ve açık bir sayfa eşlemesi ister. GraftPdfAcroForm Source, Destination ve Output akışlarını ayrı TStream örnekleri olarak, bir TPdfGraftPageMappings dizisini, bir TPdfAcroFormGraftOptions kaydını, isteğe bağlı bir TPdfCrossDocumentGraftMap nesnesini ve bir çıktı TPdfAcroFormGraftReport alır. Özel durum yükseltmek yerine Boolean döndürür ve başarısızlıkta rapor nedeni ErrorMessage içinde taşır. Sayfa eşlemesi her iki tarafta da bir tabanlıdır ve çıkarım yapılmaz: aşılayacağınız bir widget taşıyan her kaynak sayfa içinde yer almalıdır. Aşılama haritası için nil geçmek meşrudur — işlev o zaman çağrı süresince kendine ait bir tane oluşturup serbest bırakır — ve TPdfAcroFormGraftOptions.Default size CollisionPolicy için pagcpReject, RenamePrefix için Imported_, 10000 MaxObjects, 128 MaxDepth ve AllowSignedDestination için False değerlerini verir. Bu son üçü bütçelerdir ve orada olmalarının nedeni, yürümek üzere olduğunuz nesne grafiğinin yazmadığınız bir dosyadan gelmesidir
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;
Aşılama haritası paylaşılan bir yazı tipinin iki kez klonlanmasını nasıl önler?
TPdfCrossDocumentGraftMap, anahtarları hem nesne numarasını hem nesli taşıyan kaynaktan hedefe bir başvuru tablosu tutar ve özyinelemeli klonlayıcı derinleşmeden önce ona danışır. İşlem sırası, döngüleri güvenli kılan şeydir: klonlayıcı hedef nesne numarasını ayırır ve eşlemeyi önce kaydeder, sonra kaynak nesnenin çocuk başvurularını yürür. Ebeveynine geri işaret eden bir çocuğa ulaşan bir ebeveyn, ebeveynin zaten kayıtlı olduğunu bulur ve özyineleme yerine var olan hedef başvurusunu döndürür. Aynı arama, altı widget tarafından paylaşılan bir yazı tipinin, görünüm akışının ya da eylemin bir kez klonlanıp altı kez başvurulmasını sağlayan şeydir. Harita, kaynak baytlarının SHA-256 karmasıyla kaynak belgeye bağlanır ve SourceIdentity olarak sunulur. GraftPdfAcroForm işlevine, kimliği geçtiğiniz kaynakla eşleşmeyen bir harita verirseniz, bu dosya için hiçbir zaman geçerli olmamış başvuruları yeniden kullanmak yerine çağrıyı reddeder. Sayfa eşlemeleri, klonlama başlamadan önce aynı haritaya tohum olarak eklenir; bir widgetın /P başvurusunun hedef sayfayı göstermesinin yolu tam olarak budur: kaynak sayfa nesnesi zaten eşlenen hedef sayfa nesnesine çözülür, böylece sıradan başvuru yeniden yazma geçişi onu özel bir durum olmadan halleder
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
// Bu çağrının eklediği girdiler geri alındı;
// ondan önce kaydedilen her şey hâlâ sağlam.
Assert(GraftMap.Count = EntriesBefore);
WriteLn('graft refused: ', Report.ErrorMessage);
end;
finally
GraftMap.Free;
end;
end;
Geri alım, haritaya kendinizin sahip olmasının noktasıdır. PDFiumPas, çağıran tarafından sağlanan bir haritaya işlemsel davranır: başarısız bir aşılama o çağrının eklediği girdileri atar ve önceden var olan her eşlemeyi korur; böylece tek bir reddetme, hiç yazılmamış nesnelere yönelik bir başvuru önbelleğini asla ardında bırakmaz. Yine de hedef belge başına bir harita tutun — her girdinin hedef tarafı o belirli dosyadaki bir nesne numarasıdır ve farklı bir dosyada hiçbir anlam ifade etmez
Alan adı çakışmaları: reddet ya da yeniden adlandır
Tam nitelikli alan adları bir formun içinde benzersiz kalmalıdır ve PDFiumPas, çakıştıklarında ne kastettiğinizi tahmin etmez. TPdfAcroFormCollisionPolicy tam olarak iki yanıt sunar. Varsayılan olan pagcpReject altında, başlığı hedefte zaten var olan ilk kaynak alanı tüm aşılamayı bir hatayla durdurur ve çıktı akışını boş bırakır. pagcpRename altında, çakışan kaynak alanı RenamePrefix önekiyle yeniden adlandırılır ve aşılama sürer; Report.RenamedFieldCount bunun ne kadar sık gerçekleştiğini söyler
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);
Yeniden adlandırma bedelsiz değildir ve bir hatayı yok etmek için ona uzanmak yerine bilinçli olarak karar vermelisiniz. Yeniden adlandırılan alan farklı bir alandır: hedefteki ona adla adres veren herhangi bir JavaScript, bir insanın eski ada karşı yazdığı /CO içindeki her hesaplama girdisi ve alan adına göre anahtarlanan her aşağı akım tüketicisi önekten haberdar olmalıdır. İki belge gerçekten aynı alanı tanımlıyorsa, dürüst onarım genellikle adları aşılama anında değil yukarı akışta mutabık kılmaktır. Aşılama gerçekleştikten sonra, gerçekte ne elde ettiğinizi doğrulamak için birleştirilmiş formu yürümek doğal sonraki adımdır ve PDFiumPas ile form alanı gezinimi bu gezinmeyi kapsar
Aşılamanın bilinçli olarak kapalı başarısız olduğu yerler
Her belirsiz koşul bir hatadır, asla elden gelen en iyi çaba sonucu değildir ve bu, üretimde sizi şaşırtmadan önce anlaşılmaya değer bir tasarım kararıdır. GraftPdfAcroForm şu durumlardan herhangi biriyle karşılaştığında False döndürür, çıktı akışını sıfırlar ve nedeni bildirir
- Kaynak form bir
/XFAgirdisi taşıyorsa — XFA paketleri paralel bir form modelidir ve AcroForm alan sözlüklerine indirgenemez - Bir widget, sayfa eşlemesinde girdisi olmayan bir kaynak sayfada yaşıyorsa; aksi takdirde alan sessizce düşer ya da yanlış sayfaya eklenirdi
- Sayfa eşlemeleri aralık dışıysa ya da iki eşleme aynı kaynak ya da hedef sayfayı yeniden kullanıyorsa
- Her iki form da bir varsayılan kaynak sözlüğü
/DRtanımlıyorsa; iki kaynak ad alanını birleştirmek var olan bir adı farklı bir yazı tipine yeniden yönlendirme riski taşır - Nesne grafiği
MaxObjectssınırını ya da özyinelemeMaxDepthsınırını aşarsa - Hedef bir imza içeriyorsa ve
AllowSignedDestinationFalseise - Sağlanan aşılama haritası farklı bir kaynak belgeye aitse ya da bir kaynak başvuru sarkıyorsa
Yazma yolu aynı ölçüde tutucudur. PDFiumPas sonucu hedefe eklenen seyrek bir artımlı düzeltme olarak yayar, sonra yazılan çıktıyı yeniden maddeselleştirir ve formunu yeniden okur: sonucun alan sayısı hedefin özgün alan sayısı artı kaynağınkine eşit değilse, tüm aşılama reddedilir ve çıktı temizlenir. Kısmen aşılanmış bir dosya asla elde etmezsiniz. Bu politikanın maliyeti gerçektir — bir /DR çakışması ya da imzalı bir hedef sizi doğrudan durdurur ve birleştirilmiş bir yaklaşımı kabul etmek yerine bunu kendiniz çözmeniz gerekir — ama alternatifi, sorunsuz açılan ama yanlış hesaplayan bir formdur
Aşılamanın yanlış araç olduğu durum
Aşılama yapıyı taşır; bu yüzden eksikliğini çektiğiniz şey yapı olduğunda kullanın. Her iki belge de zaten aynı alan kümesini taşıyorsa ve yalnızca değerleri ve açıklamaları aralarında taşımanız gerekiyorsa, XFDF form verisi makalesindeki dışa ve içe aktarım yolu daha hafif, standart ve tersine çevrilebilirdir. Hedefte hiç alan yoksa ya da farklı bir küme varsa ve widgetların, görünüm akışlarının, eylemlerin ve hesaplama sırasının bozulmadan karşıya geçmesini gerektiriyorsanız GraftPdfAcroForm işlevine uzanın. Kimlik konusunda son bir pratik not: aşılama haritası nesne numarası artı nesille anahtarlandığı ve kaynak baytlarının bir SHA-256 karmasına bağlandığı için, kaynağı çalıştırmalar arasında yeniden kaydetmek ya da iyileştirmek farklı bir kimlik ve artık uygulanamayan bir harita üretir. Aşıladığınız kaynağın anlık görüntüsünü alın ve toplu iş boyunca sabit tutun; onu bir girdi yapıtı olarak ele alın, gece işinin özgürce yeniden yazabileceği bir şey olarak değil
GraftPdfAcroForm, TPdfCrossDocumentGraftMap ve onu çevreleyen akış düzeyi PDF araç takımı, Delphi, C++Builder ve Lazarus için PDFiumPas Delphi PDFium Component ürünüyle birlikte sunulur; ürün sayfası aşılama seçeneklerinin, rapor alanlarının ve belge düzenleme yüzeyinin geri kalanının tam API başvurusunu taşır