Her çağrıda ekleme konumu olarak 1 geçen bir birleştirme döngüsü, sayfa sayısı doğru ama belgeleri ters sırada olan bir dosya üretir. Listedeki son dosya 1. sayfaya düşer; çünkü 1. konum "geçerli ilk sayfadan önce ekle" demektir ve her yeni kaynak, öncekileri daha da geriye iter. Hiçbir şey kaybolmaz; tam da bu yüzden hata, hızlı bir sayfa sayımı kontrolünden sağ çıkar ve biri paketi gerçekten okuduğunda ortaya çıkar
ImportPages ile sayfa sayfa döngü
PDFium Component, Delphi'de bir birleşik PDF'i iki TPdf örneğinden kurar: CreateDocument ile boş oluşturulan bir hedef ve sırayla her girdi dosyasını açan bir kaynak. TPdf.ImportPages(Pdf, Range, PageNumber), Range'in adlandırdığı sayfaları Pdf'den hedefe kopyalar ve başarıda True döndürür. PageNumber 1 tabanlıdır ve varsayılanı 1'dir. İlk içe aktarılan sayfanın düşeceği hedef konumunu adlandırır; dolayısıyla 1 başa ekler ve PageCount + 1 sona ekler
Dosyaları liste sırasında tutmanın en temiz yolu, imleç takibini tamamen bırakıp hedefe sonunun nerede olduğunu sormaktır. Koşan bir InsertAt sayacı da iş görür ama gerçekten içe aktarılan sayfa sayısıyla ilerletilmelidir ve aralıklar işe karıştığında o sayıyı yanlış almak kolaydır. PdfDest.PageCount + 1 her zaman doğrudur:
procedure MergeFileList(const FileList: TStrings; const OutputPath: string);
var
PdfDest, PdfSrc: TPdf;
I: Integer;
begin
PdfDest := TPdf.Create(nil);
PdfSrc := TPdf.Create(nil);
try
PdfDest.CreateDocument;
for I := 0 to FileList.Count - 1 do
begin
PdfSrc.FileName := FileList[I];
PdfSrc.Active := True; // başarısızlıkta fırlatmaz
if not PdfSrc.Active then
raise Exception.CreateFmt('Cannot open %s', [FileList[I]]);
// 1 tabanlı konum: PageCount + 1 son sayfadan sonra ekler
if not PdfDest.ImportPages(PdfSrc,
'1-' + IntToStr(PdfSrc.PageCount), PdfDest.PageCount + 1) then
raise Exception.CreateFmt('Page import failed for %s', [FileList[I]]);
PdfSrc.Active := False; // FileName yalnızca etkin değilken değişebilir
end;
if not PdfDest.SaveAs(OutputPath) then
raise Exception.Create('Cannot write ' + OutputPath);
finally
PdfSrc.Free;
PdfDest.Free;
end;
end;
O döngüdeki iki kontrol gerçek ağırlık taşır. Active := True ataması hiç fırlatmaz: dosya eksikse, bozuksa ya da parola istiyorsa TPdf yükleme hatasını yakalar ve Active'i False'ta bırakır. Bileşen tutarlı bir kapalı durumda kalır ve bir form, bozuk dosyada çökmez. Ama kontrolü atlayan bir birleştirme döngüsü, yalnızca daha kısa bir çıktı üretir. İkinci kontrol, ImportPages'in Boolean sonucudur; False döner, ya aralık dizgesi ayrışmamıştır ya da konum PageCount + 1'i aşmıştır. Ayrıca FileName ile Password'ün yalnızca bileşen etkin değilken atanabildiğini de not edin; kaynağın her turun sonunda kapatılmasının nedeni budur
Aralık dizgeleri: ayrıştırıcı neyi kabul eder
Aralık argümanı, kaynak belgede 1'den numaralandırılmış tek sayfalar ve kapalı aralıkların virgülle ayrılmış listesidir. '1-3' ilk üç sayfayı alır, '2,4,6' üç belirli sayfayı alır ve '1,3-5' bir kapak artı üç sayfalık bir özet alır. Sayılar daima kaynağı gösterir; o sayfaların hedefte nereye düşeceği yalnızca konum argümanı tarafından belirlenir
Uçlu aralıklar, insanların tökezlediği yerdir. '7-' gibi bir dizge "7. sayfadan sona kadar" gibi görünür ama PDFium aralık ayrıştırıcısı bir aralığın iki ucunu da ister ve bütün dizgeyi reddeder. Ters çevrilmiş bir aralıkta ('5-3'), 0 sayfa numarasında ya da kaynağın sayfa sayısını aşan her sayfada da aynısı olur. Tek bir bozuk öğe, bütün çağrıyı düşürür: ImportPages False döndürür ve o dizgeden hiçbir sayfa kopyalanmaz. Bu yüzden yukarıdaki döngü '1-' + IntToStr(PdfSrc.PageCount) kurar; '1-' yazmaz
Sayfa indekslerini hâlihazırda bir dizide tutuyorsanız ImportPagesByIndex(Source, PageIndices, InsertAt) dizge adımını atlar. Yalnızca kural değişimine dikkat: hem indeksler hem InsertAt orada sıfır tabanlıdır; dolayısıyla sona eklemek InsertAt = PdfDest.PageCount olur ve kapak-artı-özet seçimi [0, 2, 3, 4]'e dönüşür. Boş bir dizi geçmek her sayfayı içe aktarır
Dosya listesi için tek çağrılı bir birleştirme var mı?
Var. TPdf.MergeFiles(Sources, Options, Report), her biri TPdfBatchMergeSource kaydı olarak TPdfBatchMergeSource.FromFile(FileName, PageRange, Password) ile kurulan bir dizi alır, onları tek tek açar ve ne olduğundan bir TPdfBatchMergeReport doldurur. Boş bir PageRange her sayfayı demektir. Aralık sözdizimi aynı virgül-aralık biçimidir ve bozuk bir aralık, sessiz bir atlama yerine raporda dosya başına bir hata olur
TPdfBatchMergeOptions.Default'un varsayılanları, herhangi birini değiştirmeden önce bilmeye değer. CreateDestination True'dur; etkin olmayan bir hedef sizin için oluşturulur. InsertAt -1'dir, yani sona ekle; başka her değer sıfır tabanlı bir indekstir ve ImportPagesByIndex'i eşler, ImportPages'i değil. ErrorPolicy bmepStop ve RollbackOnCancelOrFailure True'dur; dolayısıyla tek bir okunamayan dosya turu bitirir ve turun eklediği her sayfayı kaldırır. CopyViewerPreferences, başarıyla içe aktarılan ilk kaynağın görüntüleyici tercihlerini kopyalar
procedure BuildBundle(const OutputPath: string; Log: TStrings);
var
Dest: TPdf;
Sources: TPdfBatchMergeSources;
Options: TPdfBatchMergeOptions;
Report: TPdfBatchMergeReport;
I: Integer;
begin
SetLength(Sources, 3);
Sources[0] := TPdfBatchMergeSource.FromFile('cover.pdf');
Sources[1] := TPdfBatchMergeSource.FromFile('annual-report.pdf', '1,3-5');
Sources[2] := TPdfBatchMergeSource.FromFile('appendix.pdf', '', 'user-password');
Options := TPdfBatchMergeOptions.Default;
Options.ErrorPolicy := bmepSkip; // okunamayan dosyaların üzerinden atlayarak sürdür
Dest := TPdf.Create(nil); // etkin değil: MergeFiles onu oluşturur
try
if not Dest.MergeFiles(Sources, Options, Report) then
raise Exception.Create('Merge failed: ' + Report.ErrorMessage);
for I := 0 to High(Report.Issues) do
Log.Add(Format('%s: %s',
[Report.Issues[I].FileName, Report.Issues[I].MessageText]));
Log.Add(Format('%d pages from %d of %d files',
[Report.ImportedPageCount, Report.ImportedSourceCount, Report.SourceCount]));
Dest.SaveAs(OutputPath);
finally
Dest.Free;
end;
end;
Hangi hata politikasının kullanılacağı, eksik bir dosyanın çıktı için ne anlama geldiğine bağlıdır. Bir sözleşme paketi ya da fatura turu için, bir parçası olmayan paket yanlış bir belgedir: varsayılan bmepStop'u geri almayla birlikte koruyun. bmepSkip'e yalnızca elimizden geleni yapan koleksiyonlar için geçin; örneğin haftalık bir rapor özeti — ve o zaman bmrsCompletedWithErrors'u — Report.Status içindeki değeri — bir insanın bakması gereken bir şey sayın. Hangisini seçerseniz seçin Report.Issues'u okuyun: her girdi kaynak indeksini, dosya adını ve iletiyi taşır; bu, elle yazılmış döngünün kendi istisnalarıyla yanıtlamak zorunda olduğu "suçlu hangi dosyaydı" sorusunu yanıtlar
Uzun turlar için iki seçenek daha önemlidir. OnProgress ile CancellationToken, bir arayüzün geçerli dosyayı göstermesine ve turu durdurmasına izin verir; iptal edilen tur, başarısız bir tur gibi geri alınır. ResourcePolicy varsayılanı bmrpSourceScoped'dır; her kaynağın seçilen sayfalarını tek bir yerel çağrıyla içe aktarır, böylece o sayfaların paylaştığı font ve resimler bir kez kopyalanır. bmrpChunked ise PageChunkSize sayfalık gruplarla içe aktarır; paylaşılan kaynakların her grup için yeniden kopyalanması pahasına daha ince iptal noktaları sağlar
İçe aktarılan bir sayfa neyi taşır, kaynakta ne kalır
İçe aktarılan bir sayfa, sayfa sözlüğünden ulaşılabilen her şeyi getirir: içerik akışını, kaynaklarındaki fontları, resimleri ve form XObject'lerini ve açıklamalar, vurgulamalar ve mürekkep dahil ek açıklamalarını. Kaynakta belge düzeyinde yaşayan hiçbir şey gelmez, çünkü ISO 32000-1 onu sayfa ağacının dışında tutar
- Belge bilgi sözlüğü (ISO 32000-1 §14.3.3) geride kalır; dolayısıyla birleşik dosya, kaynakların Title, Author ya da Keywords değerlerinin hiçbirini taşımaz.
TitleileAuthor,TPdfüzerinde salt okunur özelliklerdir; bu, onu kapatmadan önce her kaynağın ne bildirdiğini günlüğe geçirmek için kullanışlı kılar - Ana hatlar (§12.3.3), varsayılan
OutlinePolicydeğeribmopDiscardolduğu için düşürülür;bmopReject, ana hatlı bir kaynağı yerine dosya başına bir hataya çevirir. Yer imlerini korumak istiyorsanızFPdfMergebirimiMergeFilesWithNavigationekler: ana hatları yeniden kurar, çakışan adlandırılmış hedefleri varsayılan olarakImported_önekiyle yeniden adlandırır ve sayfalar arası bağlantıları yeniden eşler; ana hat düzenleme ve sayfa yeniden eşleme yazısı o hedeflerin nasıl yeniden yazıldığını anlatır - Şifreleme (§7.6) kaynak dosyaya aittir. Korunan bir kaynak,
Password'u ile açılır amaCreateDocumentile yapılmış bir hedef şifresizdir ve içine kopyalanan sayfalar da öyledir. Birleşik dosyanın korunması gerekiyorsa onuSaveAsEncryptedile yazın,SaveAsile değil
Bir birleştirmeden sonra form alanları neden çalışmaz?
Form alanları bir PDF'in iki düzeyine bölünmüştür. Bir metin kutusu çizen widget ek açıklaması sayfanın üzerindedir; alanın kendisi ise adıyla ve değeriyle, kataloğun etkileşimli form sözlüğünün (ISO 32000-1 §12.7.2) /Fields dizisinde listelenir. Yerel bir sayfa içe aktarması widget'ı, öteki sayfa ek açıklamalarıyla birlikte kopyalar ama hiçbir şey onu hedefin alan ağacına eklemez. Kutu, son görünümünü çizmeye devam eder. Form modelini /AcroForm'dan kuran bir görüntüleyici ise widget'ı bağlayacak bir alan bulamaz; böylece değer düzenlenemez ve alan ağacını gezen her form işlemi onu atlar
TPdfBatchMergeOptions.FormPolicy, düz bir birleştirme için üç cevap verir. bmfpPreserveNative, varsayılanı, yukarıda anlatılan yerel içe aktarma sonucunu korur. bmfpFlatten, görünüm üretir ve form taşıyan her kaynağın seçilen sayfalarını içe aktarmadan önce bellekte düzleştirir; böylece değerler sıradan sayfa içeriğine dönüşür; diskteki kaynak dosya değişmez. bmfpReject, AcroForm ya da XFA formu taşıyan her kaynağı dosya başına bir hataya çevirir
Alanların doldurulabilir kalması gerekiyorsa MergeFilesWithForms'u, FPdfFormMerge biriminden kullanın. Sayfa içeriğini içe aktarır, widget'ları her kaynağın alan ağacından yeniden kurar ve o alanları hedef forma aşılar. Zor kısım alan adlarıdır. §12.7.3.2 altında aynı tam nitelikli ada sahip iki alan aynı alandır ve tek bir değer paylaşır; dolayısıyla bir faturadaki Total alanı ile bir sevkiyat notundaki Total alanı birbirinin üzerine yazardı. Varsayılan CollisionPolicy pagcpReject'tir ve birleştirmeyi ilk çatışmada durdurur:
uses
PDFium, FPdfCompress, FPdfFormMerge;
procedure MergeFillableForms(Dest: TPdf;
const Sources: TPdfBatchMergeSources; Log: TStrings);
var
Options: TPdfFormMergeOptions;
Report: TPdfFormMergeReport;
begin
Options := TPdfFormMergeOptions.Default;
// Çakışan adlar RenamePrefix'i alır (değiştirmezseniz 'Imported_')
Options.GraftOptions.CollisionPolicy := pagcpRename;
if Dest.MergeFilesWithForms(Sources, Options, Report) then
Log.Add(Format('%d fields grafted, %d renamed',
[Report.GraftedFieldCount, Report.RenamedFieldCount]))
else
Log.Add('Form merge failed: ' + Report.ErrorMessage);
end;
pagcpNamespace, her kaynağın alanlarını kendi öneki altına koyar (Imported artı kaynak numarası) ve pagcpMergeCompatible aynı adlı alanları yalnızca türleri ve seçenekleri uyuştuğunda birleştirir. MergeFilesWithForms, imza alanı içermeyen AcroForm kaynakları ve hedefleri üzerinde çalışır. XFA formlarını ve imzalı dosyaları açıkça reddeder; çünkü imzalı bir dosyayı yeniden yazmak, ISO 32000-1 §12.8.1'de anlatılan imzalı bayt aralığını geçersiz kılar. Açık bir hedefin ayrıca şifresiz olması gerekir: parola korumalı bir dosyaya birleştirmek başarısız olur, dolayısıyla korumayı sonradan SaveAsEncrypted ile ekleyin. Tek bir kaynakta iki kez seçilen bir sayfa — '1,1' gibi — açık bir RepeatedPagePolicy ister
Hedef belge, yalnızca hazırlanan sonuç yeniden açılıp kontrol edildikten sonra değiştirilir. Alan adları, değerleri ve türleri sayfa sayfa karşılaştırılır ve başarısız bir form birleştirmesi Dest'i olduğu gibi bırakır. AcroForm aşılama yazısı, çakışma işlemede daha derine iner
Birleşik belgeyi kaydetmek
SaveAs'in bir dosya aşırı yüklemesi ve bir TStream aşırı yüklemesi vardır; ikisi de isteğe bağlı bir TSaveOption (varsayılan saNone) ve bir TPdfVersion (varsayılan pvUnknown, başlığı kütüphaneye bırakır) alır. İkisi de Boolean döndürür ve bir toplu işte o sonuç, Active ile aynı kontrolü hak eder. Sürümü pv17 gibi bir şeyle sabitlemek, yalnızca aşağı akıştaki bir sistem belirli bir başlıkta ısrar ediyordur
Ters iş, bir dosyayı birkaç parçaya bölmek, aynı aralık dizgelerini ve aynı 1 tabanlı konumları kullanır; bölme yazısı onu adım adım gezer
ImportPages, MergeFiles ve burada anlatılan form farkında birleştirme, PDFium Component ile gelir; bu bileşen bu sayfa işlemlerini Delphi ve C++Builder uygulamalarına getirir