PDFium VCL omogućava spajanje PDF datoteka kroz jednu metodu: ImportPages. Šablon je uvek isti: kreirajte prazan odredišni dokument, otvorite svaku izvornu datoteku, pozovite ImportPages da kopirate stranice, zatvorite izvor i ponovite. Kada se petlja završi, SaveAs upisuje rezultat na disk. Ne postoji poseban režim spajanja, niti konfiguracija koju treba menjati. Složenost leži u graničnim slučajevima, a ima ih nekoliko koji mogu iznenaditi bez upozorenja.
Glavna petlja
Sve što vam treba su dve TPdf instance. Jedna drži odredišni dokument, kreiran kao prazan pomoću CreateDocument. Druga otvara svaku izvornu datoteku redom. Ispod je procedura koja uzima listu putanja datoteka i upisuje spojeni izlaz na jednu putanju:
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 koristi 1-baziranu poziciju odredišta
for I := 0 to FileList.Count - 1 do
begin
PdfSrc.FileName := FileList[I];
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.CreateFmt('Ne mogu da otvorim: %s', [FileList[I]]);
PdfDest.ImportPages(
PdfSrc,
'1-' + IntToStr(PdfSrc.PageCount), // opseg celog dokumenta
InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
end;
PdfDest.SaveAs(OutputPath);
finally
PdfSrc.Free;
PdfDest.Free;
end;
end;
Dve stvari u ovom kodu je lako prevideti na prvo čitanje. Prva je način na koji PDFium prijavljuje neuspehe pri učitavanju. Active := True nikada ne izaziva izuzetak: ako datoteka nedostaje, ako je oštećena ili zaštićena lozinkom, PDFium interno hvata grešku i ostavlja Active na False. Bez eksplicitne provere na liniji 10, neispravna datoteka bi tiho ispala iz spajanja bez ikakvih naznaka u izlazu. Konačni PDF bi imao manje stranica nego što se očekuje, a vi ne biste znali koja datoteka je uzrok problema.
Druga stvar je brojač InsertAt. Treći argument za ImportPages je 1-bazirana pozicija u odredištu na koju se smešta prva uvezena stranica. Početak od 1 postavlja prvi izvorni dokument na početak inače prazne datoteke. Nakon svakog izvora, brojač se uvećava za PdfSrc.PageCount, tako da se sledeća grupa stranica dodaje nakon poslednje. Ako zaboravite da ga uvećate, svaki sledeći izvor će prepisati stranice na poziciji 1, ostavljajući vam samo poslednji dokument na listi.
Selektivni opsezi stranica
Ne morate uzeti svaku stranicu iz izvora. String opsega koji se prosleđuje kao drugi argument prati jednostavan format sa zarezom i crticom: "1-3" uzima stranice od 1 do 3, "2,4,6" bira tri specifične stranice, a "1-" označava stranicu 1 pa do kraja dokumenta. Opsezi se mogu kombinovati u jednom stringu, pa tako "1-3,5,7-" preskače stranice 4 i 6. Ovde je važna jedna suptilnost: brojevi se uvek odnose na stranice u izvornom dokumentu, počevši od 1, bez obzira na to gde te stranice završavaju u odredištu. Ako želite stranice od 40 do 50 iz kataloga od 200 stranica, string opsega je "40-50", a ne pozicija u odnosu na ono što se već nalazi u odredištu.
// Ekstrahujte naslovnu stranu plus sažetak od tri stranice iz dugačkog izveštaja
PdfSrc.FileName := 'annual-report.pdf';
PdfSrc.Active := True;
if PdfSrc.Active then
begin
// Stranica 1 je naslovna; stranice 3-5 su sažetak
PdfDest.ImportPages(PdfSrc, '1,3-5', InsertAt);
Inc(InsertAt, 4); // 1 naslovna + 3 stranice sažetka = 4 dodate stranice
PdfSrc.Active := False;
end;
Prilikom izračunavanja uvećanja za InsertAt, brojte stranice koje ste stvarno uvezli, a ne ukupan broj stranica izvora. Ako prosledite '1,3-5', uvezli ste 4 stranice, pa brojač uvećajte za 4. Uvećavanje za PdfSrc.PageCount bi ostavilo prazne pozicije u odredištu i postavilo sledeći izvorni dokument dalje u datoteci nego što je planirano.
Šta ImportPages čuva, a šta ne
Stranice kopirane pomoću ImportPages prenose svoj vidljivi sadržaj netaknut. Tekst, vektorska grafika, rasterske slike, ugrađeni fontovi i XObjekti obrazaca (form XObjects) se prenose kao deo toka sadržaja stranice. Anotacije na nivou stranice, uključujući komentare, isticanja i poteze mastilom, takođe se prenose jer se čuvaju unutar rečnika stranice, a ne na nivou dokumenta.
Metapodaci na nivou dokumenta su druga priča. Naslov, autor, tema i ključne reči u Info rečniku izvora ostaju iza. Odredišni dokument počinje sa praznim metapodacima nakon CreateDocument, pa ako je potrebno da ti podaci budu popunjeni u spojenom izlazu, morate ih dodeliti objektu PdfDest neposredno pre pozivanja metode SaveAs. Svojstva Title, Author, Subject, Keywords i Creator na klasi TPdf prihvataju obične stringove i upisuju ih u Info rečnik prilikom čuvanja.
Interaktivna polja obrazaca su složenija. Definicije polja AcroForm nalaze se u rečniku na nivou dokumenta, a ne unutar pojedinačnih tokova stranica. Kada ImportPages kopira stranicu koja sadrži polja obrasca, vizuelni izgled tih polja se prenosi jer je renderovan u tok sadržaja stranice, ali widgeti polja koji ih čine interaktivnim su deo AcroForm strukture i ne prenose se. U tipičnom spajanju, tekstualno polje iz izvornog dokumenta će prikazati vrednost koju je imalo u trenutku uvoza, ali se neće moći uređivati u spojenoj datoteci. Ako vam je potrebno da polja ostanu popunjiva, spljoštite ih (flatten) u svakom izvornom dokumentu pre uvoza: to ugrađuje trenutne vrednosti direktno u tok sadržaja i uklanja interaktivni sloj, pružajući čist vizuelni rezultat bez neispravnih widgeta u izlazu.
Šifrovane izvorne datoteke
Izvorni dokumenti zaštićeni lozinkom otvaraju se na isti način kao i nešifrovani, uz jedno dodatno svojstvo koje je potrebno prvo postaviti. Dodelite lozinku svojstvu PdfSrc.Password pre nego što postavite Active := True, i PDFium će je koristiti prilikom otvaranja:
PdfSrc.Password := 'user-password';
PdfSrc.FileName := 'protected.pdf';
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.Create('Pogrešna lozinka ili datoteka ne može da se otvori');
PdfDest.ImportPages(PdfSrc, '1-' + IntToStr(PdfSrc.PageCount), InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
Pogrešna lozinka dovodi do istog tihog ishoda Active = False kao i nedostajuća datoteka, pa je eksplicitna provera i ovde podjednako neophodna. Šifrovanje se ne prenosi na odredište: stranice uvezene iz zaštićenog izvora dospevaju u odredište kao nezaštićen sadržaj. Ako je spojenom izlazu takođe potrebno šifrovanje, konfigurišite ga na objektu PdfDest pre pozivanja SaveAs.
Čuvanje rezultata
SaveAs na TPdf prihvata ili putanju datoteke ili TStream. Za većinu spajanja, preopterećenje (overload) sa putanjom datoteke je ono što želite:
PdfDest.SaveAs('merged-output.pdf');
Opcioni drugi argument je TSaveOption koji kontroliše režim čuvanja. Podrazumevana vrednost, saNone, upisuje inkrementalno ažuriranje ako je dokument učitan iz datoteke ili potpuno ponovno upisivanje ako je kreiran iznova. Pošto je odredište izgrađeno pomoću CreateDocument uvek novo, izlaz će biti kompaktna datoteka sa jednom revizijom. Treći argument, TPdfVersion, omogućava vam da fiksirate zaglavlje PDF verzije kada imate nizvodne potrošače koji zahtevaju specifičnu verziju; ostavljanje na pvUnknown omogućava PDFium-u da izabere na osnovu sadržaja.
Metode ImportPages i SaveAs prikazane ovde su deo PDFium VCL komponente za Delphi i C++Builder.