A PDFium Component egyetlen módszert (method) ad a PDF felosztására: ImportPages. Minden más – akár egyetlen oldalt különít el, akár tetszőleges határokon vág, vagy a dokumentum saját könyvjelző-struktúráját követi – csupán különböző módja annak eldöntésére, hogy mely oldalszámok kerüljenek az egyes kimeneti fájlokba. A mechanika ugyanaz marad. Ennek korai megértése sok tévutat megspórol
Hogyan működik a felosztási hurok (split loop)
A minta ugyanaz, függetlenül attól, hogyan osztja fel a forrásdokumentumot (source document). Hozzon létre egy friss TPdf példányt, hívja meg rajta a CreateDocument-et egy üres PDF memóriában történő inicializálásához, importálja a kívánt oldalakat az ImportPages segítségével, mentse az eredményt, majd állítsa vissza az Active-ot False-ra a következő iteráció (iteration) előtt. Ez az utolsó lépés az, amit az emberek gyakran kihagynak: a CreateDocument mindig új dokumentumot indít, de ha az Active még mindig True, amikor újra lefut, hallgatólagosan (implicitly) eldobja a még memóriában lévő dokumentumot, így az előzetes visszaállítás tisztán és jól definiáltan tartja az állapotot. A külső TPdf példány minden iteráció során újrahasznosításra kerül, ami alacsonyan tartja a lefoglalási nyomást (allocation pressure) nagy feladatoknál
Íme, hogyan néz ki az oldalankénti (page-by-page) felosztás a lényegre csupaszítva:
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
PdfOut.ImportPages(Source, IntToStr(I), 1);
OutFile := OutputDir + '\page_' + Format('%.4d', [I]) + '.pdf';
PdfOut.SaveAs(OutFile);
PdfOut.Active := False; // reset before next CreateDocument
end;
finally
PdfOut.Free;
end;
end;
A Range paraméter az ImportPages-hez ugyanaz a karakterlánc formátum, amelyet a PDFium belsőleg használ: oldalszámok vagy kötőjellel elválasztott tartományok vesszővel elválasztott listája, mind 1-alapú. A '3' a 3. oldalt importálja. Az '1-5' sorrendben importálja az 1-től 5-ig terjedő oldalakat. A '2,5,8' ezt a három oldalt importálja. A harmadik paraméter az 1-alapú beillesztési pozíció (insertion position) a céldokumentumban; az 1 átadása az importált oldalakat mindig egy egyébként üres fájl elejére helyezi, és ön pontosan ezt akarja itt
Felosztás oldaltartományok (page ranges) szerint
Ha a hívó egy olyan listát ad meg, mint az 1-12,13-24,25-36, ön kezdő/vég párokra bontja (parse), és ugyanazt a hurkot futtatja, minden párból felépítve a tartomány (range) karakterláncot:
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;
PdfOut.ImportPages(Source, RangeList[I], 1);
OutFile := Format('%s\section_%d.pdf', [OutputDir, I + 1]);
PdfOut.SaveAs(OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
A validáció az ImportPages elérése előtt itt számít. Az ImportPages False értéket ad vissza, ha a tartomány karakterláncban lévő oldalszám meghaladja a Source.PageCount értékét, de nem vet fel kivételt, és nem állít elő olyan részleges kimeneti fájlt sem, amelyet pusztán név alapján észlelni tudna. Ellenőrizze a SaveAs visszatérési értékét, és naplózza (log) a hibákat külön; egy olyan tartomány, amely üres kimeneti fájlt eredményez, nem nyilvánvalóan hibás, amíg valaki meg nem nyitja
Felosztás könyvjelző-határokon (bookmark boundaries)
A harmadik megközelítés a dokumentum saját struktúráját használja egy kívülről (externally) megadott lista helyett. Minden legfelső szintű (top-level) könyvjelző hordoz egy cél oldalszámot; az általa meghatározott szakasz (section) ettől az oldaltól a következő könyvjelző oldala előtti oldalig, vagy az utolsó bejegyzés esetén a dokumentum végéig tart
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;
PdfOut.ImportPages(Source, RangeStr, 1);
SafeTitle := StringReplace(Bm[I].Title, '/', '_', [rfReplaceAll]);
SafeTitle := StringReplace(SafeTitle, ':', '_', [rfReplaceAll]);
OutFile := Format('%s\%02d_%s.pdf', [OutputDir, I + 1, SafeTitle]);
PdfOut.SaveAs(OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Egy könyvjelzők nélküli dokumentum nem olyan hibaállapot (error condition), amelyet érdemes lenne a felhasználó felé akként felszínre hozni (surfacing); ez csak azt jelenti, hogy ennek a felosztási módnak nincs miből dolgoznia. A Length(Bm) = 0 őr (guard) ezt csendben kezeli. Amit viszont érdemes felszínre hozni, az az, amikor egy könyvjelző oldalszáma kívül esik a dokumentum tartományán; ez olyan rosszul formázott (malformed) fájloknál fordul elő, ahol a vázlatot (outline) soha nem frissítették az oldalak törlése után. A határellenőrzés (bounds check) a StartPage és az EndPage értékeken átugorja (skips) ezeket a bejegyzéseket ahelyett, hogy szemét (garbage) tartományt adna át az ImportPages-nek
Kimeneti fájl elnevezése és az Active visszaállítása
A könyvjelzőkből származtatott nevek (bookmark-derived names) fájlnév-biztonsága kifejezett (explicit) figyelmet igényel. A könyvjelzők címei tartalmazhatnak olyan karaktereket, amelyek érvényesek egy PDF karakterláncban, de nem érvényesek egy fájlrendszer-útvonalon (filesystem path). Legalább a perjelet, a visszaperjelet (backslash) és a kettőspontot cserélje le a kimeneti útvonal felépítése előtt. Windowson a *, ?, ", <, > és | szintén tilos; egy egyszerű hurok egy rögzített halmazon lefedi őket anélkül, hogy reguláris kifejezést (regex) kellene bevonnia
Az Active := False sor minden iteráció végén hangsúlyt érdemel, mert ez az egyetlen nem nyilvánvaló követelmény a mintában. A CreateDocument nem zárja be hallgatólagosan azt, ami éppen nyitva van. Ha az Active még mindig True, amikor a CreateDocument újra lefut, a PDFium hiba nélkül eldobja az aktuális dokumentumot, és újat indít, de a viselkedés peremesetekben (edge cases) implementáció-függő (implementation-defined), és a szándék világosabb, ha explicit módon állítja vissza. Gondoljon rá úgy, mint a try/finally párjára: a finally blokk felszabadítja a külső objektumot; az Active := False visszaállítja a belső dokumentum állapotát a hurok iterációi között
A memóriahasználat egy nagy felosztási feladat (split job) során lapos (flat) marad ezzel a megközelítéssel, mert ön soha nem tart egynél több kimeneti dokumentumot a memóriában egyszerre. A forrásdokumentum végig nyitva és csak olvasható marad; az ImportPages oldaladatokat másol az új dokumentumba a forrás módosítása nélkül. Ha a forrás titkosított, nyissa meg a jelszavával a hurok előtt, és az egyes kimeneti fájlokba másolt oldalak titkosítatlanok (unencrypted) lesznek, ami általában a helyes viselkedés a különböző címzetteknek szétosztott felosztott kimenetnél
Még egy dolog a SaveAs-ről: egy Boolean (logikai) értéket ad vissza. Egy nem létező kimeneti könyvtár, egy olyan útvonal, amelynek karaktereit az operációs rendszer elutasítja, vagy egy betelt lemez állapota mind azt eredményezi, hogy a SaveAs False értéket ad vissza kivétel felvetése (raising an exception) nélkül. Egy kötegelt feladatban (batch job), amely egy 200 oldalas dokumentumot 200 egyoldalas fájlra oszt fel, egy csendes hiba (silent failure) a 147. oldalon könnyen figyelmen kívül hagyható. Ellenőrizze a visszatérési értéket minden hívásnál, és számolja össze a sikereket a várt végösszeghez (expected total) képest, amikor a hurok befejeződik
Az itt bemutatott ImportPages és CreateDocument metódusok a Delphihez és C++Builderhez készült PDFium Component részét képezik
A frissített felosztási útmutató a split loop működését, az oldaltartományok és könyvjelző-határok szerinti vágást, valamint az Active visszaállítását rendezi egységbe