PDFium Component, PDF bölme için size tek bir metot sunar: ImportPages. Diğer her şey, ister tek bir sayfayı ayırıyor, ister keyfi sınırlardan kesiyor, ister belgenin kendi yer imi yapısını izliyor olun, yalnızca hangi sayfa numaralarının her çıktı dosyasına gireceğine karar vermenin farklı yollarıdır. Mekanizma aynı kalır. Bunu erkenden anlamak sizi pek çok yanlış dönemeçten kurtarır
Bölme döngüsü nasıl çalışır
Desen, kaynak belgeyi nasıl böldüğünüzden bağımsız olarak aynıdır. Taze bir TPdf örneği oluşturun, bellekte boş bir PDF başlatmak için üzerinde CreateDocument çağırın, istediğiniz sayfaları ImportPages ile içe aktarın, sonucu kaydedin, ardından bir sonraki yinelemeden önce Active değerini False'a sıfırlayın. İnsanların kaçırdığı adım o son adımdır: CreateDocument, hâlâ bellekte olan belgeyi örtük olarak kapatmaz, bu yüzden onu yeniden çağırmadan önce çıktınızı açıkça kaydetmeli ve Active := False değerini sıfırlamalısınız; önce sıfırlamak durumu temiz ve iyi tanımlanmış tutar. Dış TPdf örneği tüm yinelemeler boyunca yeniden kullanılır, bu da büyük işlerde tahsis baskısını düşük tutar
İşte sayfa sayfa bölmenin özüne indirgenmiş hâli:
procedure SplitIntoPages(Source: TPdf; const OutputDir: string);
var
I: Integer;
PdfOut: TPdf;
OutFile: string;
begin
PdfOut := TPdf.Create(nil);
try
for I := 1 to Source.PageCount do
begin
PdfOut.CreateDocument;
// Range is a 1-based page number string; insertion point 1 = first position
if not PdfOut.ImportPages(Source, IntToStr(I), 1) then
raise Exception.CreateFmt('Failed to import page %d', [I]);
OutFile := OutputDir + '\page_' + Format('%.4d', [I]) + '.pdf';
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False; // reset before next CreateDocument
end;
finally
PdfOut.Free;
end;
end;
ImportPages'e verilen Range parametresi, PDFium'un dahili olarak kullandığı aynı dize biçimidir: virgülle ayrılmış bir sayfa numarası listesi veya kısa çizgiyle sınırlandırılmış aralıklar, hepsi 1 tabanlı. '3', 3. sayfayı içe aktarır. '1-5', 1'den 5'e kadarki sayfaları sırayla içe aktarır. '2,5,8' ise bu üç sayfayı içe aktarır. Üçüncü parametre, hedef belgedeki 1 tabanlı ekleme konumudur; 1 geçmek, içe aktarılan sayfaları her zaman başka türlü boş bir dosyanın başına yerleştirir ki burada istediğiniz de budur
Sayfa aralıklarına göre bölme
Çağıran taraf 1-12,13-24,25-36 gibi bir liste sağladığında, bunu başlangıç/bitiş çiftlerine ayrıştırır ve aynı döngüyü çalıştırırsınız, aralık dizesini her çiftten oluşturarak:
procedure SplitByRanges(Source: TPdf; const RangeList: array of string;
const OutputDir: string);
var
I: Integer;
PdfOut: TPdf;
OutFile: string;
begin
PdfOut := TPdf.Create(nil);
try
for I := 0 to High(RangeList) do
begin
PdfOut.CreateDocument;
if not PdfOut.ImportPages(Source, RangeList[I], 1) then
raise Exception.Create('Invalid page range: ' + RangeList[I]);
OutFile := Format('%s\section_%d.pdf', [OutputDir, I + 1]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
ImportPages'e ulaşmadan önce doğrulama burada önemlidir. ImportPages, aralık dizesindeki bir sayfa numarası Source.PageCount'u aştığında False döndürür, ancak bir istisna (exception) fırlatmaz ve yalnızca ada bakarak tespit edebileceğiniz kısmi bir çıktı dosyası da üretmez. SaveAs'in dönüş değerini kontrol edin ve başarısızlıkları ayrıca günlüğe kaydedin; boş bir çıktı dosyası üreten bir aralık, biri onu açana kadar bariz biçimde yanlış değildir
Yer imi sınırlarında bölme
Üçüncü yaklaşım, dışarıdan sağlanan bir liste yerine belgenin kendi yapısını kullanır. Her üst düzey yer imi bir hedef sayfa numarası taşır; tanımladığı bölüm, o sayfadan bir sonraki yer iminin sayfasından bir öncekine kadar, ya da son giriş için belgenin sonuna kadar uzanır
procedure SplitByBookmarks(Source: TPdf; const OutputDir: string);
var
Bm: TBookmarks;
I, StartPage, EndPage: Integer;
PdfOut: TPdf;
RangeStr, OutFile, SafeTitle: string;
begin
Bm := Source.Bookmarks;
if Length(Bm) = 0 then
Exit;
PdfOut := TPdf.Create(nil);
try
for I := 0 to High(Bm) do
begin
StartPage := Bm[I].PageNumber;
if I < High(Bm) then
EndPage := Bm[I + 1].PageNumber - 1
else
EndPage := Source.PageCount;
if (StartPage < 1) or (EndPage < StartPage) then
Continue;
RangeStr := Format('%d-%d', [StartPage, EndPage]);
PdfOut.CreateDocument;
if not PdfOut.ImportPages(Source, RangeStr, 1) then
begin
PdfOut.Active := False;
Continue; // skip a malformed section instead of writing an empty file
end;
SafeTitle := StringReplace(Bm[I].Title, '/', '_', [rfReplaceAll]);
SafeTitle := StringReplace(SafeTitle, ':', '_', [rfReplaceAll]);
OutFile := Format('%s\%02d_%s.pdf', [OutputDir, I + 1, SafeTitle]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Hiç yer imi olmayan bir belge, kullanıcıya böyle sunmaya değer bir hata durumu değildir; bu yalnızca bu bölme modunun üzerinde çalışacağı bir şeyin olmadığı anlamına gelir. Length(Bm) = 0 koruması bunu sessizce ele alır. Sunmaya değer olan şey, bir yer iminin sayfa numarasının belgenin aralığının dışında olmasıdır ki bu, sayfalar silindikten sonra ana hattın hiç güncellenmediği bozuk dosyalarda olur. StartPage ve EndPage üzerindeki sınır kontrolü, ImportPages'e çöp bir aralık geçmek yerine bu girişleri atlar
Çıktı dosyası adlandırması ve Active sıfırlaması
Yer iminden türetilen adlar için dosya adı güvenliği açık dikkat gerektirir. Yer imi başlıkları, bir PDF dizesinde geçerli ancak bir dosya sistemi yolunda geçerli olmayan karakterler içerebilir. En azından, çıktı yolunu oluşturmadan önce ileri eğik çizgi, ters eğik çizgi ve iki nokta üst üste karakterlerini değiştirin. Windows'ta *, ?, ", <, > ve | de yasaktır; sabit bir küme üzerinde basit bir döngü, bir regex getirmeden bunları kapsar
Her yinelemenin sonundaki Active := False satırı vurgulanmayı hak ediyor çünkü desendeki tek belirsiz olmayan gereksinim odur. CreateDocument, açık olan her neyse onu örtük olarak kapatmaz. CreateDocument yeniden çalıştığında Active hâlâ True ise, bellekte hâlâ olan belge hiçbir zaman düzgün kapatılmamış veya kaydedilmemiştir ve o durumda iyi tanımlanmış davranışa güvenemezsiniz, bu yüzden bir sonraki belgeye başlamadan önce açıkça kaydedin ve sıfırlayın. Bunu try/finally'nin eşi olarak düşünün: finally bloğu dış nesneyi serbest bırakır; Active := False ise döngü yinelemeleri arasında iç belge durumunu sıfırlar
Büyük bir bölme işinde bellek kullanımı bu yaklaşımla düz kalır çünkü aynı anda bir seferde birden fazla çıktı belgesini bellekte hiçbir zaman tutmuyorsunuzdur. Kaynak belge boyunca açık ve salt okunur kalır; ImportPages, kaynağı değiştirmeden sayfa verilerini yeni belgeye kopyalar. Kaynak şifreliyse, onu döngüden önce parolasıyla açın; her çıktı dosyasındaki kopyalanan sayfalar şifresiz olacaktır ki bu, farklı alıcılara dağıtılan bölünmüş çıktı için genellikle doğru davranıştır
SaveAs hakkında bir şey daha: bir Boolean döndürür. Var olmayan bir çıktı dizini, işletim sisteminin reddettiği karakterler içeren bir yol ya da diskin dolu olması, hepsi SaveAs'in bir istisna fırlatmadan False döndürmesine neden olur. 200 sayfalık bir belgeyi 200 tek sayfalık dosyaya bölen toplu bir işte, 147. sayfadaki sessiz bir başarısızlığı gözden kaçırmak kolaydır. Her çağrıda dönüş değerini kontrol edin ve döngü bittiğinde başarıları beklenen toplama göre sayın
Burada gösterilen ImportPages ve CreateDocument metotları, Delphi ve C++Builder için PDFium Component'in bir parçasıdır