Tehnički članak

Izrada PDF čitača u Delphi-ju pomoću PDFium Component-a

PDF čitač (viewer) u Delphi-ju svodi se na dve komponente i njihovo međusobno povezivanje. TPdf poseduje dokument: otvara datoteku, dešifruje je i odgovara na pitanja o broju stranica i metapodacima. TPdfView je vizuelna kontrola koja iscrtava stranice na ekranu i upravlja skrolovanjem, zumiranjem i stranicom koju korisnik trenutno gleda. PDFium Component obavija isti mehanizam za renderovanje koji se isporučuje unutar Chrome-a, tako da se glifovi, anti-aliasing i boje koje dobijete na platnu poklapaju sa onim što vaši korisnici već vide u svom veb-pregledaču. Posao nije u renderovanju. On je u povezivanju objekta dokumenta sa prikazom, učitavanju bez rušenja programa na oštećenoj ili lozinkom zaštićenoj datoteci i pružanju korisniku nekoliko kontrola koje čine da čitač izgleda kompletno: okretanje stranice, promena zumiranja, uklapanje stranice u prozor

Ovaj članak vas vodi kroz to sklapanje redosledom kojim ga stvarno gradite. Sve ovde renderuje po jednu stranicu u isto vreme, što je ono što većina tokova rada sa dokumentima zahteva. Ako su vam potrebne stranice naslagane u jednoj koloni koja se neprekidno skroluje, to je drugačija odluka o rasporedu i nije put koji je ovde prikazan

Povezivanje TPdf sa TPdfView

Prevucite TPdf i TPdfView na formu, a zatim recite prikazu koji dokument da prikaže. Ta jedna dodela vrednosti je cela veza između nevizuelnog dokumenta i kontrole koja ga iscrtava

Arhitektura Delphi PDF pregledača gde TPdf poseduje dokument, TPdfView ga slikra, i jedna dodela svojstva ih povezuje iznad PDFium DLL-a
TPdf poseduje dokument dok TPdfView boji njega, i jedna dodela povezuje dvojicu preko deljenog PDFium motora
procedure TFormMain.FormCreate(Sender: TObject);
begin
  // Pdf i PdfView su postavljeni na formu u vreme dizajniranja.
  PdfView.Pdf := Pdf;                 // prikaz iscrtava šta god ovaj dokument sadrži
  PdfView.FitMode := pfmFitWidth;     // pokreće korisnika na razumnom zumu
end;

Pre nego što bilo šta od ovoga proradi, PDFium izvorna biblioteka mora biti na računaru. PDFium Component poziva pdfium32.dll or pdfium64.dll u zavisnosti od vaše ciljne platforme, i dokument jednostavno odbija da se otvori ako se DLL ne može pronaći. Isporučite odgovarajući DLL pored vaše izvršne datoteke ili ga postavite tamo gde će ga sistemski učitavač pronaći. Verzije sa omogućenim V8 motorom postoje samo za PDF-ove koji nose JavaScript koji želite da izvršite, što običan čitač ne radi, pa se opredelite za standardni DLL osim ako nemate konkretan razlog za suprotno

Učitavanje dokumenta bez poverenja u ulazne podatke

Instinkt je da se učitavanje obavije u try/except blok i da se bačeni izuzetak tretira kao neuspeh. Taj instinkt je ovde pogrešan, a njegovo pogrešno korišćenje proizvodi čitač koji izgleda u redu sve dok mu neko ne prosledi oštećenu datoteku. Postavljanje Active := True ne podiže izuzetak u slučaju neuspeha učitavanja. PDFium Component hvata internu grešku i ostavlja Active na False, tako da je jedini iskren način da saznate da li je dokument otvoren čitanje tog svojstva nakon što ga postavite

Tok odlučivanja učitavanja za Delphi PDFium pregledač gde postavljanje Active nikad ne podiže izuzetak, tiha nula znači pogrešnu lozinku ili oštećenu datoteku, i jedno ponavljanje lozinke sledi
Aktivacija nikada ne podiže na neuspeh, pa pregledač čita Active nazad i odgovara na tihu false jednim ponovnim pokušajem lozinke
procedure TFormMain.OpenDocument(const FileName: string);
begin
  Pdf.FileName := FileName;
  Pdf.Active := True;                 // nikada ne baca izuzetak; neuspeh ostavlja Active = False
  if not Pdf.Active then
  begin
    ShowMessage('Could not open ' + FileName);
    Exit;
  end;
  PdfView.PageNumber := 1;            // prikaz prati svoju sopstvenu trenutnu stranicu
  UpdatePageLabel;
end;

Dve stvari zaslužuju pažnju. Prva je da PageNumber postoji na oba objekta i da su oni nezavisni. Pdf.PageNumber je pojam dokumenta o trenutnoj stranici; PdfView.PageNumber je stranica koju kontrola zapravo prikazuje, i to je ona koju podešavate da biste pomerali korisnika kroz datoteku. Postavljanje jednog ne pomera drugo, tako da čitač uvek pokreće svojstvo prikaza. Druga stvar je indeksiranje koje počinje od 1: stranice idu od 1 do Pdf.PageCount, a ne od 0, što može iznenaditi svakoga ko je navikao na nizove čiji indeks počinje od nule

Rukovanje šifrovanom datotekom

Šifrovani dokumenti se uklapaju u istu putanju učitavanja. Ako se lozinka za otvaranje postavi pre aktivacije, dokument se dešifruje prilikom otvaranja; ako je pogrešna ili nedostaje, Active ostaje na False baš kao što se dešava sa oštećenom datotekom. Zato je rešenje da se zatraži lozinka i pokuša ponovna aktivacija

procedure TFormMain.OpenWithPassword(const FileName: string);
var
  Password: string;
begin
  Pdf.FileName := FileName;
  Pdf.Active := True;
  if not Pdf.Active then
  begin
    if InputQuery('Password required', 'Password:', Password) then
    begin
      Pdf.Password := Password;       // mora biti postavljeno pre Active := True
      Pdf.Active := True;
    end;
    if not Pdf.Active then
    begin
      ShowMessage('Unable to open the document.');
      Exit;
    end;
  end;
  PdfView.PageNumber := 1;
end;

Budući da je neuspeh tih i za pogrešnu lozinku i za oštećenu datoteku, ne možete ih razlikovati samo na osnovu svojstva Active. U praksi je to prihvatljivo za čitač: korisnik ili unese ispravnu lozinku ili sazna da se datoteka ne može otvoriti, a poruka glasi isto u oba slučaja

Kretanje kroz dokument po stranicama

Sa otvorenim dokumentom, navigacija je aritmetika na PdfView.PageNumber ograničena sa Pdf.PageCount. Jedini stvarni posao je ograničavanje (clamping), tako da dugmad nikada ne gurnu stranicu van opsega, a prva i poslednja dugmad ostaju onemogućena na krajevima datoteke

procedure TFormMain.GoToPage(NewPage: Integer);
begin
  if not Pdf.Active then
    Exit;
  if NewPage < 1 then
    NewPage := 1
  else if NewPage > Pdf.PageCount then
    NewPage := Pdf.PageCount;
  PdfView.PageNumber := NewPage;
  UpdatePageLabel;
end;

// sva četiri dugmeta za navigaciju svode se na po jedan poziv
procedure TFormMain.FirstClick(Sender: TObject);  begin GoToPage(1); end;
procedure TFormMain.PrevClick(Sender: TObject);   begin GoToPage(PdfView.PageNumber - 1); end;
procedure TFormMain.NextClick(Sender: TObject);   begin GoToPage(PdfView.PageNumber + 1); end;
procedure TFormMain.LastClick(Sender: TObject);   begin GoToPage(Pdf.PageCount); end;

Polje za unos teksta "idi na stranicu N" je isti poziv GoToPage koji se hrani iz parsiranog celog broja, a ograničenje pokriva slučaj kada korisnik ukuca 9999 u datoteci od deset stranica. Držite UpdatePageLabel kao jedino mesto koje ispisuje "Stranica 3 od 12" tako da očitavanje nikada ne ispadne iz sinhronizacije sa onim što prikazuje kontrola

Zumiranje: eksplicitni procenti i režimi uklapanja

Zumiranje na TPdfView dolazi u dva oblika koji međusobno komuniciraju, a razumevanje te interakcije predstavlja razliku između kontrole zumiranja koja se ponaša kako treba i one koja se bori protiv korisnika. Direktan put je svojstvo Zoom, procenat gde 100 označava stvarnu veličinu. Drugi put je FitMode, koji govori prikazu da sam izračuna zum za vas i da ga ponovo izračunava kako se prozor menja

Zoom i FitMode interakcija u PDFium Delphi pregledaču gde dodela tačnog Zoom-a briše FitMode u pfmNone a izbor režima prilagođavanja vraća zumiranje prikazu
Dodela tačnog zumiranja čisti režim uklopa i biranje režima uklopa vraća računanje zumiranja prikazu
// fiksna uveličanja
PdfView.Zoom := 100;     // stvarna veličina
PdfView.Zoom := 50;      // polovina
PdfView.Zoom := 200;     // dupliranje

// prepustite prikazu da odredi veličinu stranice prema prozoru, i da je održava pri promeni veličine
PdfView.FitMode := pfmFitWidth;   // širina stranice ispunjava kontrolu
PdfView.FitMode := pfmFitPage;    // vidljiva cela stranica
PdfView.FitMode := pfmActualSize; // 1:1 sa tačkama dokumenta

Evo dela gde se ljudi često sapliću. Direktan upis u Zoom poništava FitMode na pfmNone. To je ispravno ponašanje, a ne greška: onog trenutka kada korisnik izabere tačno 150%, prikaz više ne može da poštuje "uklapanje po širini", jer su ta dva zahteva u konfliktu. Posledica za vaš korisnički interfejs je da su dugme za zumiranje i dugme za uklapanje stranice međusobno isključiva stanja, a traka sa alatkama bi trebalo da učini aktivan režim vidljivim. Kada korisnik klikne na uklapanje stranice, postavite FitMode; kada klikne na numerički zum, postavite Zoom i pustite da on sam obriše režim uklapanja

Ako radije želite sami da izračunate vrednost uklapanja, možda da biste napunili klizač zumiranja trenutnim procentom uklapanja, pomoćne funkcije po stranici vam daju te brojeve bez promene režima. PageWidthZoom[N], PageZoom[N] i ActualSizeZoom[N] vraćaju procenat koji bi uklopio stranicu N po širini, uklopio je celu ili je renderovao u stvarnoj veličini

// inicijalizuje prikaz zuma na osnovu vrednosti uklapanja po širini trenutne stranice
var
  FitPercent: Double;
begin
  FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
  ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;

Šta je zapravo potrebno za gotov čitač

Originalni naslov preuveličava posao. Gore opisani čitač ima nekoliko desetina linija, a već radi posao koji je potreban toku rada sa dokumentima: otvara datoteku, preživljava neispravnu datoteku, prikazuje stranicu, kreće se među stranicama i menja uvećanje ručno ili uklapanjem. PDFium obavlja teške delove nečujno. Ugrađeni fontovi se razrešavaju, beleške (annotations) i polja obrazaca se iscrtavaju tamo gde ih dokument postavlja, a stranica koju vidite se poklapa sa onom koju bi korisnik Chrome-a video, jer ih iscrtava isti mehanizam

Od ove osnove, dodaci su inkrementalni, a ne strukturalni. Selekcija teksta i pretraga čitaju iz istog tekstualnog sloja koji PDFium već gradi; metapodaci kao što su Pdf.Title i Pdf.Author su na udaljenosti od jednog čitanja svojstva; rotacija i sivi tonovi (grayscale) su opcije renderovanja koje prosleđujete kada iscrtavate stranicu na bitmapu. Ništa od toga ne menja kičmu koju ovde imate, a to su objekat dokumenta, prikaz i tok učitavanja-pa-navigacije koji ih povezuje. Uradite tu kičmu kako treba i ostatak je samo dekoracija

TPdf i TPdfView komponente koje se koriste u celom tekstu deo su PDFium Component za Delphi i C++Builder, koji nosi kompletnu referencu čitača na svojoj stranici proizvoda