Techninis straipsnis

PDF dokumentų padalijimas naudojant PDFium komponentą Delphi aplinkoje

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