PDFium Component vám na rozdeľovanie PDF dáva jedinú metódu: ImportPages. Všetko ostatné, či už izolujete jednu stranu, režete na ľubovoľných hraniciach, alebo sledujete vlastnú štruktúru záložiek dokumentu, sú len rôzne spôsoby, ako rozhodnúť, ktoré čísla strán pôjdu do ktorého výstupného súboru. Mechanika zostáva rovnaká. Pochopiť to skoro ušetrí veľa slepých uličiek
Ako funguje deliaci cyklus
Vzor je rovnaký bez ohľadu na to, ako zdrojový dokument delíte. Vytvorte novú inštanciu TPdf, zavolajte na nej CreateDocument, čím sa v pamäti inicializuje prázdne PDF, naimportujte požadované strany cez ImportPages, výsledok uložte a potom pred ďalšou iteráciou nastavte Active späť na False. Práve tento posledný krok ľuďom uniká: CreateDocument implicitne nezatvára dokument, ktorý je stále v pamäti, takže svoj výstup musíte uložiť a explicitne nastaviť Active := False skôr, než ho zavoláte znovu; resetovanie ako prvé udrží stav čistý a dobre definovaný. Vonkajšia inštancia TPdf sa používa opakovane vo všetkých iteráciách, čo pri veľkých úlohách drží tlak na alokácie nízko
Takto vyzerá rozdeľovanie po stranách zbavené všetkého zbytočného:
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 je reťazec s číslami strán od jednotky; bod vloženia 1 = prvá pozícia
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 pred ďalším CreateDocument
end;
finally
PdfOut.Free;
end;
end;
Parameter Range metódy ImportPages má rovnaký formát reťazca, aký PDFium používa interne: čiarkami oddelený zoznam čísel strán alebo rozsahov oddelených spojovníkom, všetko počítané od jednotky. '3' naimportuje stranu 3. '1-5' naimportuje strany 1 až 5 v poradí. '2,5,8' naimportuje tieto tri strany. Tretím parametrom je pozícia vloženia v cieľovom dokumente počítaná od jednotky; hodnota 1 vždy umiestni naimportované strany na začiatok inak prázdneho súboru, čo je presne to, čo tu chcete
Rozdeľovanie podľa rozsahov strán
Keď volajúci dodá zoznam ako 1-12,13-24,25-36, rozanalyzujete ho na dvojice začiatok/koniec a spustíte ten istý cyklus, pričom reťazec rozsahu zostavíte z každej dvojice:
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;
Tu záleží na overení ešte pred tým, než sa dostanete k ImportPages. Metóda ImportPages vráti False, keď číslo strany v reťazci rozsahu presiahne Source.PageCount, ale nevyvolá výnimku a ani nevyprodukuje čiastočný výstupný súbor, ktorý by ste rozpoznali už podľa názvu. Kontrolujte návratovú hodnotu metódy SaveAs a zlyhania logujte zvlášť; rozsah, ktorý vyprodukuje prázdny výstupný súbor, nie je zjavne chybný, kým ho niekto neotvorí
Rozdeľovanie na hraniciach záložiek
Tretí prístup využíva vlastnú štruktúru dokumentu namiesto zvonka dodaného zoznamu. Každá záložka najvyššej úrovne nesie cieľové číslo strany; sekcia, ktorú definuje, siaha od tejto strany po stranu pred stranou nasledujúcej záložky, alebo pri poslednej položke po koniec dokumentu
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; // preskočiť poškodenú sekciu namiesto zápisu prázdneho súboru
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 bez záložiek nie je chybovým stavom, ktorý by sa oplatilo používateľovi ohlásiť ako chybu; znamená to len, že tento režim rozdeľovania nemá z čoho vychádzať. Poistka Length(Bm) = 0 to potichu ošetrí. Ohlásiť sa oplatí to, keď číslo strany záložky leží mimo rozsahu dokumentu, čo sa stáva v poškodených súboroch, kde osnova po vymazaní strán nikdy nebola aktualizovaná. Kontrola hraníc pre StartPage a EndPage takéto položky preskočí namiesto toho, aby metóde ImportPages podala nezmyselný rozsah
Pomenúvanie výstupných súborov a reset Active
Bezpečnosť názvov odvodených od záložiek si vyžaduje výslovnú pozornosť. Názvy záložiek môžu obsahovať znaky, ktoré sú v reťazci PDF platné, ale v ceste súborového systému nie. Minimálne nahraďte lomku, spätnú lomku a dvojbodku skôr, než zostavíte výstupnú cestu. Na Windowse sú zakázané aj *, ?, ", <, > a |; jednoduchý cyklus nad pevnou množinou ich pokryje bez ťahania regulárnych výrazov
Riadok Active := False na konci každej iterácie si zaslúži dôraz, pretože je jedinou nesamozrejmou požiadavkou celého vzoru. CreateDocument implicitne nezatvára to, čo je otvorené. Ak je Active pri opätovnom spustení CreateDocument stále True, dokument, ktorý je v pamäti, nebol nikdy poriadne zatvorený ani uložený a v takom stave sa nemôžete spoliehať na dobre definované správanie, takže pred začatím ďalšieho dokumentu explicitne uložte a resetujte. Berte to ako náprotivok k try/finally: blok finally uvoľní vonkajší objekt, Active := False resetuje stav vnútorného dokumentu medzi iteráciami cyklu
Spotreba pamäte pri veľkej deliacej úlohe zostáva pri tomto prístupe plochá, pretože v pamäti nikdy nedržíte viac než jeden výstupný dokument naraz. Zdrojový dokument zostáva celý čas otvorený a len na čítanie; ImportPages kopíruje dáta strán do nového dokumentu bez toho, aby zdroj menila. Ak je zdroj šifrovaný, otvorte ho s heslom pred cyklom a skopírované strany v každom výstupnom súbore budú nešifrované, čo je zvyčajne správne správanie pre rozdelený výstup distribuovaný rôznym príjemcom
Ešte jedna vec k metóde SaveAs: vracia Boolean. Neexistujúci výstupný adresár, cesta so znakmi, ktoré operačný systém odmieta, alebo zaplnený disk zhodne spôsobia, že SaveAs vráti False bez vyvolania výnimky. V dávkovej úlohe, ktorá delí 200-stranový dokument na 200 jednostranových súborov, sa tiché zlyhanie na strane 147 ľahko prehliadne. Kontrolujte návratovú hodnotu pri každom volaní a po skončení cyklu porovnajte počet úspechov s očakávaným celkom
Metódy ImportPages a CreateDocument ukázané tu sú súčasťou komponentu PDFium Component pre Delphi a C++Builder