PDFium komponentas suteikia vieną metodą PDF padalijimui: ImportPages. Viskas kita, nesvarbu, ar išskiriate vieną puslapį, pjaustote ties savavališkomis ribomis, ar sekate paties dokumento žymų (bookmarks) struktūrą, yra tik skirtingi būdai nuspręsti, kurie puslapių numeriai pateks į kiekvieną išvesties failą. Pati mechanika išlieka ta pati. Supratę tai anksti, sutaupysite daug klaidingų žingsnių
Kaip veikia padalijimo ciklas
Šablonas yra tas pats, nepriklausomai nuo to, kaip padalijate šaltinio dokumentą. Sukurkite naują TPdf egzempliorių, iškvieskite jam CreateDocument, kad atmintyje inicijuotumėte tuščią PDF, importuokite norimus puslapius su ImportPages, išsaugokite rezultatą, tada prieš kitą iteraciją atstatykite Active į False. Šis paskutinis žingsnis yra tas, kurį žmonės praleidžia: CreateDocument numatytai neuždaro dokumento, vis dar esančio atmintyje, todėl jūs privalote išsaugoti savo išvestį ir aiškiai atstatyti Active := False prieš iškviečiant jį dar kartą; pirmiausia atstačius, būsena išlieka švari ir gerai apibrėžta. Išorinis TPdf egzempliorius yra pakartotinai naudojamas visose iteracijose, o tai sumažina atminties paskirstymo apkrovą didelėse užduotyse
Štai kaip atrodo padalijimas po vieną puslapį, apribotas iki svarbiausių dalykų:
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;
Parametras Range funkcijai ImportPages yra to paties kodo formato, kokį PDFium naudoja viduje: kableliais atskirtas puslapių numerių arba brūkšneliu atskirtų diapazonų sąrašas, visi indeksuojami nuo 1. '3' importuoja 3 puslapį. '1-5' importuoja puslapius nuo 1 iki 5 iš eilės. '2,5,8' importuoja tuos tris puslapius. Trečiasis parametras yra nuo 1 indeksuojama įterpimo pozicija paskirties dokumente; perdavus 1, importuoti puslapiai visada dedami kito tuščio failo pradžioje, o to jums čia ir reikia
Padalijimas pagal puslapių diapazonus
Kai iškviestojas pateikia tokį sąrašą kaip 1-12,13-24,25-36, jūs jį išanalizuojate į pradžios / pabaigos poras ir paleidžiate tą patį ciklą, konstruodami diapazono eilutę iš kiekvienos poros:
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 prieš pasiekiant ImportPages čia yra svarbi. ImportPages grąžina False, kai puslapio numeris diapazono eilutėje viršija Source.PageCount, bet ji nekelia išimties ir nesukuria dalinio išvesties failo, kurį galėtumėte aptikti vien pagal pavadinimą. Patikrinkite SaveAs grąžinamą reikšmę ir registruokite nesėkmes atskirai; diapazonas, kuris sukuria tuščią išvesties failą, nėra akivaizdžiai neteisingas, kol kas nors jo neatidaro
Padalijimas ties žymų ribomis
Trečiasis požiūris naudoja paties dokumento struktūrą, o ne išoriškai pateiktą sąrašą. Kiekviena aukščiausio lygio žyma turi paskirties puslapio numerį; skyrius, kurį ji apibrėžia, eina nuo to puslapio iki puslapio prieš kitą žymą arba iki dokumento pabaigos paskutiniam įrašui
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;
Dokumentas, kuriame nėra žymų, nėra klaidos būsena, kurią verta rodyti vartotojui kaip tokią; tai tiesiog reiškia, kad šis padalijimo režimas neturi su kuo dirbti. Length(Bm) = 0 apsauga tai tyliai apdoroja. Ką verta rodyti, tai kai žymos puslapio numeris yra už dokumento diapazono ribų, o tai atsitinka sugadintuose failuose, kur struktūra niekada nebuvo atnaujinta po puslapių ištrynimo. Ribų patikrinimas ties StartPage ir EndPage praleidžia tuos įrašus, užuot perdavęs šiukšlių diapazoną į ImportPages
Išvesties failų pavadinimai ir Active atstatymas
Failo vardo saugumui iš žymų kilusiems pavadinimams reikia aiškaus dėmesio. Žymų pavadinimuose gali būti simbolių, kurie galioja PDF eilutėje, bet ne failų sistemos kelyje. Bent jau pakeiskite pasvirąjį brūkšnį, atvirkštinį brūkšnį ir dvitaškį prieš sudarydami išvesties kelią. Windows sistemoje *, ?, ", <, > ir | taip pat yra draudžiami; paprastas ciklas per fiksuotą aibę juos apdoroja neįtraukiant reguliariųjų išraiškų
Active := False eilutė kiekvienos iteracijos pabaigoje nusipelno pabrėžimo, nes tai yra vienintelis neakivaizdus reikalavimas šiame šablone. CreateDocument numatytai neuždaro to, kas atidaryta. Jei Active vis dar yra True, kai CreateDocument paleidžiamas vėl, atmintyje esantis dokumentas niekada nebuvo tinkamai uždarytas ar išsaugotas, ir toje būsenoje negalite pasikliauti gerai apibrėžtu elgesiu, todėl išsaugokite ir aiškiai atstatykite prieš pradėdami kitą dokumentą. Galvokite apie tai kaip apie porą try/finally: finally blokas atlaisvina išorinį objektą; Active := False atstato vidinę dokumento būseną tarp ciklo iteracijų
Atminties naudojimas visoje didelėje padalijimo užduotyje išlieka tolygus su šiuo požiūriu, nes jūs niekada atmintyje nelaikote daugiau nei vieno išvesties dokumento vienu metu. Šaltinio dokumentas lieka atidarytas ir skirtas tik skaitymui visą laiką; ImportPages nukopijuoja puslapio duomenis į naują dokumentą nepakeisdamas šaltinio. Jei šaltinis yra užšifruotas, atidarykite jį su jo slaptažodžiu prieš ciklą ir nukopijuoti puslapiai kiekviename išvesties faile bus neužšifruoti, kas paprastai yra tinkamas elgesys padalytai išvesčiai, platinamai skirtingiems gavėjams
Dar vienas dalykas apie SaveAs: jis grąžina Boolean. Išvesties katalogas, kurio nėra, kelias su OS atmetamais simboliais arba disko pilnumo būsena privers SaveAs grąžinti False nekeliant išimties. Paketinėje užduotyje, kuri padalija 200 puslapių dokumentą į 200 vieno puslapio failų, lengva nepastebėti tylios nesėkmės ties 147 puslapiu. Patikrinkite grąžinamą reikšmę kiekvieno iškvietimo metu ir suskaičiuokite sėkmingus atvejus su laukiamu bendru skaičiumi, kai ciklas baigiasi
Čia parodyti ImportPages ir CreateDocument metodai yra PDFium Component, skirto Delphi ir C++Builder, dalis