Techninis straipsnis

Kelių PDF failų sujungimas į vieną dokumentą naudojant PDFium Component

PDFium Component siūlo PDF failų sujungimą naudojant vieną metodą: ImportPages. Šablonas visada yra toks pat: sukurkite tuščią paskirties dokumentą, atidarykite kiekvieną šaltinio failą, iškvieskite ImportPages, kad nukopijuotumėte puslapius, uždarykite šaltinį ir pakartokite. Kai ciklas baigiasi, SaveAs įrašo rezultatą į diską. Nėra jokio specialaus sujungimo režimo ar konfigūracijos, kurią reikėtų keisti. Sudėtingumas slypi kraštutiniuose atvejuose, ir yra keletas, kurie gali netikėtai sukelti problemų

Pagrindinis ciklas

Jums tereikia dviejų TPdf egzempliorių. Viename saugomas paskirties dokumentas, sukurtas tuščias su CreateDocument. Kitas atidaro kiekvieną šaltinio failą paeiliui. Žemiau pateikta procedūra, kuri priima failų kelių sąrašą ir įrašo sujungtą rezultatą į vieną kelią:

procedure MergeFiles(const FileList: TStrings; const OutputPath: string);
var
  PdfDest, PdfSrc: TPdf;
  InsertAt, I: Integer;
begin
  PdfDest := TPdf.Create(nil);
  PdfSrc  := TPdf.Create(nil);
  try
    PdfDest.CreateDocument;
    InsertAt := 1;  // ImportPages uses 1-based destination position

    for I := 0 to FileList.Count - 1 do
    begin
      PdfSrc.FileName := FileList[I];
      PdfSrc.Active   := True;

      if not PdfSrc.Active then
        raise Exception.CreateFmt('Cannot open: %s', [FileList[I]]);

      PdfDest.ImportPages(
        PdfSrc,
        '1-' + IntToStr(PdfSrc.PageCount),  // full document range
        InsertAt);

      Inc(InsertAt, PdfSrc.PageCount);
      PdfSrc.Active := False;
    end;

    PdfDest.SaveAs(OutputPath);
  finally
    PdfSrc.Free;
    PdfDest.Free;
  end;
end;

Skaitant pirmą kartą, lengva nepastebėti dviejų dalykų šiame kode. Pirmasis – tai, kaip PDFium praneša apie įkėlimo klaidas. Active := True niekada nesukelia išimties: jei failo trūksta, jis sugadintas arba apsaugotas slaptažodžiu, PDFium klaidą sugauna viduje ir palieka Active reikšmę False. Be aiškaus patikrinimo 10 eilutėje, blogas failas tyliai iškristų iš sujungimo be jokių požymių išvestyje. Galutinis PDF turėtų mažiau puslapių nei tikėtasi, ir jūs nežinotumėte, kuris failas buvo kaltininkas

Antrasis yra InsertAt skaitiklis. Trečiasis ImportPages argumentas yra 1 pagrindu nurodyta pozicija paskirties vietoje, kur atsiduria pirmasis importuotas puslapis. Pradedant nuo 1, pirmasis šaltinio dokumentas dedamas į tuščio failo pradžią. Po kiekvieno šaltinio skaitiklis pasislenka per PdfSrc.PageCount, todėl kita puslapių partija pridedama po paskutiniojo. Pamirškite jį padidinti, ir kiekvienas paskesnis šaltinis perrašys puslapius 1 pozicijoje, suteikdamas jums tik paskutinį dokumentą sąraše ir nieko daugiau

Atrankiniai puslapių diapazonai

Jums nereikia imti kiekvieno puslapio iš šaltinio. Diapazono eilutė, perduodama kaip antrasis argumentas, atitinka paprastą kablelio ir brūkšnelio formatą: "1-3" ima puslapius nuo 1 iki 3, "2,4,6" parenka tris konkrečius puslapius, o "1-" reiškia nuo 1 puslapio iki dokumento pabaigos. Diapazonus galima derinti vienoje eilutėje, todėl "1-3,5,7-" praleidžia 4 ir 6 puslapius. Čia svarbi viena subtilybė: skaičiai visada nurodo puslapius šaltinio dokumente, pradedant nuo 1, neatsižvelgiant į tai, kur tie puslapiai atsiduria paskirties vietoje. Jei norite puslapių nuo 40 iki 50 iš 200 puslapių katalogo, diapazono eilutė yra "40-50", o ne pozicija, susijusi su tuo, kas jau yra paskirties vietoje

// Extract cover plus a three-page executive summary from a long report
PdfSrc.FileName := 'annual-report.pdf';
PdfSrc.Active   := True;
if PdfSrc.Active then
begin
  // Page 1 is the cover; pages 3-5 are the summary
  PdfDest.ImportPages(PdfSrc, '1,3-5', InsertAt);
  Inc(InsertAt, 4);  // 1 cover + 3 summary pages = 4 pages added
  PdfSrc.Active := False;
end;

Apskaičiuodami InsertAt prieaugį, skaičiuokite puslapius, kuriuos iš tikrųjų importavote, o ne šaltinio puslapių skaičių. Jei perduodate '1,3-5', jūs importavote 4 puslapius, todėl perkelkite per 4. Perkėlimas per PdfSrc.PageCount paliktų tuščių paskirties pozicijų tarpą ir patalpintų kitą šaltinio dokumentą toliau į failą nei planuota

Ką ImportPages išsaugo ir ko ne

Puslapiai, nukopijuoti naudojant ImportPages, išlaiko savo matomą turinį nepažeistą. Tekstas, vektorinė grafika, rastriniai vaizdai, įterptieji šriftai ir formų XObjects perkeliami kaip puslapio turinio srautų dalis. Puslapio lygio anotacijos, įskaitant komentarus, paryškinimus ir rašalo brūkšnius, taip pat perkeliamos, nes jos saugomos puslapio žodyne, o ne dokumento lygiu

Dokumento lygio metaduomenys yra visai kita istorija. Pavadinimo, autoriaus, temos ir raktinių žodžių eilutės šaltinio Info žodyne perkeliamos nebus. Paskirties dokumentas po CreateDocument prasideda su tuščiais metaduomenimis, todėl, jei sujungtoje išvestyje reikia užpildyti tuos laukus, turite priskirti juos tiesiogiai PdfDest prieš iškviesdami SaveAs. Title, Author, Subject, Keywords ir Creator ypatybės objekte TPdf priima paprastas eilutes ir įrašo į Info žodyną išsaugant

Interaktyvūs formų laukai yra sudėtingesnis atvejis. „AcroForm“ laukų apibrėžimai gyvena dokumento lygio žodyne, o ne atskiruose puslapių srautuose. Kai ImportPages kopijuoja puslapį, kuriame yra formų laukų, vizualinė tų laukų išvaizda perkeliama, nes ji atvaizduojama puslapio turinio sraute, tačiau laukų valdikliai, darantys juos interaktyvius, yra „AcroForm“ struktūros dalis ir jie neperkeliami. Tipinio sujungimo metu šaltinio dokumento teksto laukas parodys reikšmę, kurią turėjo importavimo metu, tačiau jo nebus galima redaguoti sujungtame faile. Jei norite, kad laukai išliktų užpildyti, prieš importuodami kiekviename šaltinio dokumente juos „suplokštinkite“ (flatten): tai įrašo dabartines reikšmes į turinio srautą ir pašalina interaktyvią perdangą, suteikiant švarų vizualinį rezultatą be neveikiančių valdiklių išvestyje

Užšifruoti šaltinio failai

Slaptažodžiu apsaugoti šaltinio dokumentai atidaromi taip pat, kaip ir nešifruoti, tik pirmiausia reikia nustatyti vieną papildomą ypatybę. Priskirkite slaptažodį PdfSrc.Password prieš perjungdami Active := True, ir PDFium jį panaudos atidarymo metu:

PdfSrc.Password := 'user-password';
PdfSrc.FileName := 'protected.pdf';
PdfSrc.Active   := True;
if not PdfSrc.Active then
  raise Exception.Create('Wrong password or file cannot be opened');

PdfDest.ImportPages(PdfSrc, '1-' + IntToStr(PdfSrc.PageCount), InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;

Dėl neteisingo slaptažodžio gaunamas toks pat tylus Active = False rezultatas kaip ir trūkstant failo, todėl čia taip pat būtinas aiškus patikrinimas. Šifravimas neperkeliamas į paskirties vietą: puslapiai, importuoti iš apsaugoto šaltinio, atsiduria paskirties vietoje kaip neapsaugotas turinys. Jei sujungtai išvesčiai taip pat reikia šifravimo, sukonfigūruokite jį objekte PdfDest prieš iškviesdami SaveAs

Rezultato išsaugojimas

Objekto TPdf metodas SaveAs priima arba failo kelią, arba TStream. Daugeliui sujungimų failo kelio variantas yra tai, ko jums reikia:

PdfDest.SaveAs('merged-output.pdf');

Pasirenkamas antrasis argumentas yra TSaveOption, kuris valdo išsaugojimo režimą. Numatytasis saNone įrašo laipsnišką atnaujinimą, jei dokumentas buvo įkeltas iš failo, arba visišką perrašymą, jei jis buvo sukurtas naujas. Kadangi su CreateDocument sukurta paskirties vieta visada yra nauja, išvestis bus kompaktiškas vienos revizijos failas. Trečiasis argumentas TPdfVersion leidžia prisegti PDF versijos antraštę, kai turite tolesnių vartotojų, kuriems reikia konkrečios versijos; palikus jį pvUnknown, PDFium leidžiama pasirinkti atsižvelgiant į turinį

Čia parodyti metodai ImportPages ir SaveAs yra PDFium Component, skirto Delphi ir C++Builder, dalis