PDFium Component nudi jednu metodu za deljenje PDF dokumenata: ImportPages. Sve ostalo, bilo da izdvajate pojedinačnu stranicu, sečete na proizvoljnim granicama ili pratite sopstvenu strukturu obeleživača dokumenta, predstavlja samo različite načine za odlučivanje o tome koji brojevi stranica idu u koju izlaznu datoteku. Mehanika ostaje ista. Ako to razumete na samom početku, izbeći ćete mnoge pogrešne korake
Kako funkcioniše petlja za deljenje
Šablon je isti bez obzira na to kako delite izvorni dokument. Kreirajte novu instancu klase TPdf, pozovite CreateDocument na njoj da biste inicijalizovali prazan PDF u memoriji, uvezite željene stranice pomoću ImportPages, sačuvajte rezultat, a zatim resetujte Active na False pre sledeće iteracije. Taj poslednji korak je onaj koji programeri često zaborave: bez resetovanja, sledeći poziv CreateDocument nadovezuje sadržaj na dokument koji je još uvek u memoriji, umesto da počne ispočetka. Spoljna instanca klase TPdf se ponovo koristi kroz sve iteracije, što smanjuje opterećenje memorije kod velikih poslova
Evo kako izgleda deljenje stranicu po stranicu svedeno na najosnovnije:
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;
Parametar Range za ImportPages koristi isti format stringa koji PDFium koristi interno: lista brojeva stranica razdvojenih zarezima ili opsezi razdvojeni crticom, svi zasnovani na indeksu 1. Vrednost '3' uvozi stranicu 3. Vrednost '1-5' uvozi stranice od 1 do 5 po redu. Vrednost '2,5,8' uvozi te tri stranice. Treći parametar je pozicija umetanja u odredišni dokument zasnovana na indeksu 1; prosleđivanje vrednosti 1 uvek postavlja uvezene stranice na početak inače prazne datoteke, što je upravo ono što ovde želite
Deljenje po opsezima stranica
Kada pozivalac prosledi listu kao što je 1-12,13-24,25-36, vi je analizirate u parove početak/kraj i pokrećete istu petlju, gradeći string opsega iz svakog para:
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;
Validacija pre pozivanja metode ImportPages je ovde veoma važna. Metoda ImportPages vraća False kada broj stranice u stringu opsega premašuje Source.PageCount, ali ne podiže izuzetak i ne generiše delimičnu izlaznu datoteku koju biste mogli detektovati samo po nazivu. Proverite povratnu vrednost metode SaveAs i zabeležite neuspehe zasebno; opseg koji proizvodi praznu izlaznu datoteku nije očigledno pogrešan sve dok ga neko ne otvori
Deljenje na granicama obeleživača
Treći pristup koristi sopstvenu strukturu dokumenta umesto spoljne liste. Svaki obeleživač najvišeg nivoa sadrži ciljni broj stranice; odeljak koji on definiše proteže se od te stranice do stranice neposredno pre sledećeg obeleživača, ili do kraja dokumenta za poslednju stavku
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;
Dokument koji nema obeleživače ne predstavlja grešku koju vredi prikazivati korisniku; to jednostavno znači da ovaj režim deljenja nema na osnovu čega da radi. Provera Length(Bm) = 0 to rešava u tišini. Ono što vredi prijaviti jeste situacija kada je broj stranice obeleživača van opsega dokumenta, što se dešava u oštećenim datotekama gde struktura dokumenta nije ažurirana nakon brisanja stranica. Provera granica za StartPage i EndPage preskače takve stavke umesto da prosledi neispravan opseg metodi ImportPages
Imenovanje izlaznih datoteka i resetovanje Active zastavice
Bezbednost naziva datoteka za nazive izvedene iz obeleživača zahteva posebnu pažnju. Nazivi obeleživača mogu sadržati znakove koji su dozvoljeni u PDF stringu, ali nisu dozvoljeni u putanji sistema datoteka. Najmanje što treba da uradite je da zamenite kosu crtu, obrnutu kosu crtu i dvotačku pre kreiranja izlazne putanje. Na Windows-u su takođe zabranjeni znakovi *, ?, ", <, > i |; jednostavna petlja kroz fiksni skup znakova rešava ovaj problem bez potrebe za regularnim izrazima
Linija Active := False na kraju svake iteracije zaslužuje posebnu pažnju jer je to jedini neočigledan zahtev u ovom šablonu. Metoda CreateDocument ne zatvara implicitno ono što je već otvoreno. Ako je Active i dalje True kada se CreateDocument ponovo pokrene, PDFium odbacuje trenutni dokument i započinje novi bez prijavljivanja greške, ali je takvo ponašanje nedefinisano u specifičnim slučajevima i namera je jasnija kada eksplicitno izvršite resetovanje. Posmatrajte to kao par uz try/finally blok: konačni blok oslobađa spoljni objekat, dok Active := False resetuje stanje unutrašnjeg dokumenta između iteracija petlje
Korišćenje memorije tokom velikog posla deljenja ostaje konstantno sa ovim pristupom jer u memoriji nikada ne držite više od jednog izlaznog dokumenta istovremeno. Izvorni dokument ostaje otvoren i dostupan samo za čitanje tokom celog procesa; ImportPages kopira podatke o stranicama u novi dokument bez izmene izvornog. Ako je izvorni dokument šifrovan, otvorite ga sa lozinkom pre petlje i kopirane stranice u svakoj izlaznoj datoteci biće dešifrovane, što je obično željeno ponašanje za deljeni izlaz koji se distribuira različitim primaocima
Još jedna stvar u vezi sa metodom SaveAs: ona vraća Boolean vrednost. Izlazni direktorijum koji ne postoji, putanja sa znakovima koje operativni sistem odbija ili nedostatak prostora na disku prouzrokovaće da SaveAs vrati False bez podizanja izuzetka. U grupnom poslu koji deli dokument od 200 stranica na 200 pojedinačnih datoteka, tihi neuspeh na 147. stranici je lako prevideti. Proverite povratnu vrednost pri svakom pozivu i uporedite broj uspešnih deljenja sa očekivanim ukupnim brojem kada se petlja završi
Metode ImportPages i CreateDocument prikazane ovde deo su komponente PDFium Component za Delphi i C++Builder