„PDFium VCL“ suteikia vieną PDF skaidymo metodą: ImportPages. Visa kita – nesvarbu, ar išskiriate vieną puslapį, karpote pagal pasirinktas ribas, ar sekate paties dokumento žymių (angl. bookmark) struktūrą – yra tik skirtingi būdai nuspręsti, kurie puslapių numeriai patenka į kiekvieną išvesties failą. Veikimo principas išlieka tas pats. Ankstyvas to supratimas padeda išvengti daugelio klaidingų sprendimų.
Kaip veikia skaidymo ciklas
Šablonas išlieka toks pat, nepriklausomai nuo to, kaip dalijate šaltinio dokumentą. Sukurkite naują TPdf egzempliorių, iškvieskite jo metodą CreateDocument, kad atmintyje inicijuotumėte tuščią PDF failą, importuokite norimus puslapius naudodami ImportPages, išsaugokite rezultatą, o prieš kitą iteraciją iš naujo nustatykite Active reikšmę į False. Paskutinis žingsnis yra tas, kurį žmonės dažnai pamiršta: neatlikus nustatymo iš naujo, kitas CreateDocument iškvietimas papildo vis dar atmintyje esantį dokumentą, užuot pradėjęs kurti naują. Išorinis TPdf egzempliorius pakartotinai naudojamas visose iteracijose, todėl didelių užduočių metu išlaikomas mažas atminties paskirstymo poreikis.
Štai kaip atrodo puslapio po puslapio skaidymas, supaprastintas iki esminių 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
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;
ImportPages parametras Range naudoja tą patį eilučių formatą, kurį PDFium naudoja viduje: kableliais atskirtų puslapių numerių arba brūkšneliais atskirtų diapazonų sąrašą, visi numeruojami nuo 1. Reikšmė '3' importuoja 3 puslapį. Reikšmė '1-5' importuoja puslapius nuo 1 iki 5 iš eilės. Reikšmė '2,5,8' importuoja šiuos tris puslapius. Trečiasis parametras yra įterpimo vieta tiksliniame dokumente (taip pat nuo 1); perdavus 1, importuoti puslapiai visada perkeliami į tuščio failo pradžią, o būtent to čia ir reikia.
Skaidymas pagal puslapių diapazonus
Kai kviečiančioji programa pateikia sąrašą, pavyzdžiui, 1-12,13-24,25-36, jį išskaidote į pradžios ir pabaigos poras bei vykdote tą patį ciklą, kurdami 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;
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;
Čia svarbu atlikti patikrą prieš iškviečiant ImportPages. Metodas ImportPages grąžina False, kai diapazono eilutėje nurodytas puslapio numeris viršija Source.PageCount, tačiau jis nesukelia išimties (angl. exception) 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, sukuriantis tuščią išvesties failą, nėra akivaizdžiai klaidingas, kol kas nors jo neatidaro.
Skaidymas pagal žymių ribas
Trečiasis būdas naudoja paties dokumento struktūrą, o ne išoriškai pateiktą sąrašą. Kiekviena pagrindinio lygio žymė turi susietą tikslinio puslapio numerį; jos apibrėžiamas skyrius tęsiasi nuo to puslapio iki puslapio prieš kitos žymės pradžią arba iki dokumento pabaigos paskutinio įrašo atveju.
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;
Jei dokumentas neturi jokių žymių, tai nėra klaida, apie kurią reikėtų pranešti naudotojui; tai tiesiog reiškia, kad šis skaidymo režimas neturi duomenų darbui. Apsauga Length(Bm) = 0 apdoroja šią situaciją tyliai. Vis dėlto, verta atkreipti dėmesį, kai žymės puslapio numeris yra už dokumento ribų. Tai nutinka sugadintuose failuose, kurių struktūra nebuvo atnaujinta ištrynus puslapius. Ribų patikra kintamiesiems StartPage ir EndPage praleidžia tokius įrašus, užuot perdavusi neteisingą diapazoną metodui ImportPages.
Išvesties failų pavadinimai ir Active nustatymas iš naujo
Iš žymių gautų failų pavadinimų saugumui reikia skirti ypatingą dėmesį. Žymių pavadinimuose gali būti simbolių, kurie yra leistini PDF eilutėje, bet netinka failų sistemos keliui. Prieš kurdami išvesties kelią, pašalinkite arba pakeiskite bent pasvirąjį brūkšnį, atvirkštinį pasvirąjį brūkšnį ir dvitaškį. Operacinėje sistemoje „Windows“ taip pat draudžiami simboliai *, ?, ", <, > ir |; paprastas ciklas per nustatytų simbolių rinkinį leidžia juos apdoroti nenaudojant reguliariųjų išraiškų.
Eilutė Active := False kiekvienos iteracijos pabaigoje reikalauja ypatingo dėmesio, nes tai yra vienintelis neakivaizdus šio šablono reikalavimas. Metodas CreateDocument implicitiškai neuždaro to, kas jau yra atidaryta. Jei iškviečiant CreateDocument iš naujo, Active vis dar yra True, PDFium be klaidos atmeta dabartinį dokumentą ir pradeda naują, tačiau kraštutiniais atvejais toks elgesys priklauso nuo realizacijos, o ketinimas yra daug aiškesnis, kai nustatymą iš naujo atliekate eksplicitiškai. Galvokite apie tai kaip apie try/finally porą: blokas „finally“ atlaisvina išorinį objektą, o Active := False iš naujo nustato vidinę dokumento būseną tarp ciklo iteracijų.
Atminties sąnaudos vykdant didelį skaidymo darbą išlieka stabilios, nes vienu metu atmintyje laikomas tik vienas išvesties dokumentas. Šaltinio dokumentas visą laiką lieka atidarytas tik skaitymui; ImportPages nukopijuoja puslapio duomenis į naują dokumentą nekeisdamas šaltinio. Jei šaltinio dokumentas yra šifruotas, prieš ciklą atidarykite jį su slaptažodžiu, ir nukopijuoti puslapiai kiekviename išvesties faile bus nešifruoti, o tai paprastai yra teisingas elgesys skaidant failus, skirtus platinti skirtingiems gavėjams.
Dar vienas dalykas apie SaveAs: šis metodas grąžina Boolean reikšmę. Jei išvesties katalogas neegzistuoja, kelyje yra operacinės sistemos atmetamų simbolių arba trūksta vietos diske, SaveAs grąžins False nesukeldamas išimties. Vykdant grupinę užduotį, kuri skaido 200 puslapių dokumentą į 200 atskirų failų, tyli nesėkmė 147-ajame puslapyje gali lengvai likti nepastebėta. Tikrinkite grąžinamą reikšmę kiekvieno iškvietimo metu ir, ciklui pasibaigus, palyginkite sėkmingų įrašymų skaičių su tikėtinu bendru skaičiumi.
Čia parodyti ImportPages ir CreateDocument metodai yra „Delphi“ ir „C++Builder“ skirtos bibliotekos PDFium VCL dalis.