Tehnični članak

Deljenje dokumentov PDF s PDFium Component v Delphiju

PDFium Component vam za deljenje dokumentov PDF ponuja eno samo metodo: ImportPages. Vse drugo, pa naj ločujete eno samo stran, režete na poljubnih mejah ali sledite dokumentovi lastni strukturi zaznamkov, so le različni načini odločanja, katere številke strani gredo v katero izhodno datoteko. Mehanika ostaja ista. Če to razumete zgodaj, si prihranite veliko napačnih ovinkov

Kako deluje zanka deljenja

Vzorec je isti ne glede na to, kako izvorni dokument razdelite. Ustvarite svež primerek TPdf, na njem pokličite CreateDocument, da v pomnilniku pripravite prazen PDF, z ImportPages uvozite želene strani, rezultat shranite in nato pred naslednjo ponovitvijo Active ponastavite na False. Prav ta zadnji korak ljudje spregledajo: CreateDocument dokumenta, ki je še v pomnilniku, ne zapre implicitno, zato morate svoj izhod shraniti in Active := False ponastaviti izrecno, preden ga pokličete znova; predhodna ponastavitev ohrani stanje čisto in dobro določeno. Zunanji primerek TPdf se uporablja skozi vse ponovitve, kar pri velikih opravilih ohranja pritisk razporejanja nizek

Diagram zanke deljenja v PDFium Component v Delphiju: CreateDocument, ImportPages iz vira samo za branje, preverjen SaveAs in ponastavitev Active pred vsako novo ponovitvijo
Karkoli določa skupine, zanka ostane enaka: uvozite strani, shranite s preverjenim rezultatom in nato ponastavite Active, da naslednji CreateDocument začne iz čistega stanja

Takole je videti deljenje po straneh, skrčeno na bistvo:

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 niz številk strani, štetih od 1; vstavna točka 1 = prvi položaj
      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;   // ponastavi pred naslednjim CreateDocument
    end;
  finally
    PdfOut.Free;
  end;
end;

Parameter Range metode ImportPages je isti oblike niza, kot ga PDFium uporablja interno: z vejicami ločen seznam številk strani ali z vezaji ločenih obsegov, vsi šteti od 1. '3' uvozi stran 3. '1-5' uvozi strani od 1 do 5 po vrsti. '2,5,8' uvozi te tri strani. Tretji parameter je od 1 šteti položaj vstavljanja v ciljnem dokumentu; podana 1 uvožene strani vedno postavi na začetek sicer prazne datoteke, kar je tukaj tisto, kar želite

Deljenje po obsegih strani

Kadar klicatelj poda seznam, kot je 1-12,13-24,25-36, ga razčlenite v pare začetek/konec in poženete isto zanko, pri čemer niz obsega sestavite iz vsakega para:

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;

Preverjanje, preden pridete do ImportPages, je tu pomembno. ImportPages vrne False, kadar številka strani v nizu obsega presega Source.PageCount, vendar ne sproži izjeme in ne ustvari delne izhodne datoteke, ki bi jo zaznali že po imenu. Preverite vrnjeno vrednost metode SaveAs in odpovedi beležite posebej; obseg, ki ustvari prazno izhodno datoteko, ni očitno napačen, dokler je kdo ne odpre

Deljenje na mejah zaznamkov

Tretji pristop uporabi dokumentovo lastno strukturo namesto od zunaj podanega seznama. Vsak zaznamek najvišje ravni nosi ciljno številko strani; razdelek, ki ga določa, teče od te strani do ene pred stranjo naslednjega zaznamka ali do konca dokumenta pri zadnjem vnosu

Diagram preslikave zaznamkov PDF najvišje ravni v izračunane obsege strani in izhodne datoteke pri deljenju s PDFium Component v Delphiju, vključno z zaznamkom zunaj obsega, ki se preskoči
Razdelek teče od strani vsakega zaznamka najvišje ravni do ene strani pred naslednjim zaznamkom, vnosi, ki kažejo čez konec, pa se preskočijo, namesto da bi ustvarili prazne datoteke
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 pokvarjen razdelek namesto pisanja prazne datoteke
      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 brez zaznamkov ni napačno stanje, ki bi ga bilo vredno uporabniku predstaviti kot napako; pomeni le, da ta način deljenja nima s čim delati. Varovalo Length(Bm) = 0 to obravnava tiho. Vredno predstaviti pa je primer, ko je številka strani zaznamka zunaj obsega dokumenta, kar se zgodi v pokvarjenih datotekah, kjer oris po brisanju strani ni bil nikoli posodobljen. Preverjanje meja pri StartPage in EndPage take vnose preskoči, namesto da bi metodi ImportPages podalo smetnjaški obseg

Poimenovanje izhodnih datotek in ponastavitev Active

Varnost imen datotek, izpeljanih iz zaznamkov, potrebuje izrecno pozornost. Naslovi zaznamkov lahko vsebujejo znake, ki so veljavni v nizu PDF, ne pa tudi v poti datotečnega sistema. Najmanj, kar naredite, je, da pred sestavljanjem izhodne poti zamenjate poševnico, nasprotno poševnico in dvopičje. V okolju Windows so prepovedani še *, ?, ", <, > in |; preprosta zanka čez nespremenljiv nabor jih pokrije, ne da bi vlekli notri regularne izraze

Vrstica Active := False na koncu vsake ponovitve si zasluži poudarek, ker je edina neočitna zahteva tega vzorca. CreateDocument ne zapre implicitno tistega, kar je odprto. Če je Active ob ponovnem zagonu metode CreateDocument še vedno True, dokument, ki je še v pomnilniku, ni bil nikoli pravilno zaprt ali shranjen, in v tem stanju se na dobro določeno vedenje ne morete zanesti, zato pred začetkom naslednjega dokumenta izrecno shranite in ponastavite. Predstavljajte si to kot par k try/finally: blok finally sprosti zunanji objekt, Active := False pa med ponovitvami zanke ponastavi stanje notranjega dokumenta

Poraba pomnilnika pri velikem opravilu deljenja ostane s tem pristopom ploščata, ker naenkrat v pomnilniku nikoli ne držite več kot enega izhodnega dokumenta. Izvorni dokument ostane ves čas odprt in samo za branje; ImportPages podatke strani skopira v novi dokument, ne da bi vira spremenil. Če je vir šifriran, ga pred zanko odprite z geslom in kopirane strani v vsaki izhodni datoteki bodo nešifrirane, kar je za razdeljeni izhod, razdeljen različnim prejemnikom, običajno pravo vedenje

Še nekaj o metodi SaveAs: vrne vrednost tipa Boolean. Neobstoječa izhodna mapa, pot z znaki, ki jih operacijski sistem zavrne, ali poln disk bodo vsi povzročili, da SaveAs vrne False brez sprožitve izjeme. Pri paketnem opravilu, ki 200-stranski dokument razdeli v 200 enostranskih datotek, je tiho odpoved na strani 147 zlahka spregledati. Vrnjeno vrednost preverite pri vsakem klicu in ob koncu zanke preštejte uspehe proti pričakovani skupni vsoti

Prikazani metodi ImportPages in CreateDocument sta del paketa PDFium Component za Delphi in C++Builder