Tehnični članak

Izdelava bralnika PDF v Delphiju s PDFium Component

Bralnik PDF v Delphiju se v osnovi zanaša na dve komponenti in njuno medsebojno povezavo. Komponenta TPdf upravlja dokument: odpre datoteko, jo dešifrira in odgovarja na vprašanja o številu strani in metapodatkih. Komponenta TPdfView pa je vizualna kontrola, ki izriše strani na zaslonu ter upravlja drsenje, zoom in stran, ki jo uporabnik trenutno gleda. PDFium Component vključuje isti izrisovalnik, kot je vgrajen v brskalnik Chrome, zato se glifi, glajenje robov (anti-aliasing) in barve na platnu ujemajo s tem, kar vaši uporabniki že vidijo v svojem brskalniku. Delo ni v samem izrisovanju, temveč v povezovanju objekta dokumenta s pogledom, nalaganju brez sesutja pri poškodovanih ali z geslom zaščitenih datotekah ter zagotavljanju kontrol, zaradi katerih je bralnik videti dokončan: obračanje strani, spreminjanje zooma in prilagajanje strani oknu

Ta članek vas vodi skozi postopek gradnje po korakih. Vse tukaj opisano izrisuje po eno stran naenkrat, kar je tisto, kar večina delovnih procesov z dokumenti zahteva. Če potrebujete strani zložene v en neprekinjeno drseč stolpec, je to drugačna odločitev o postavitvi, ki jo obravnava ločen članek

Povezovanje TPdf s TPdfView

Postavite komponenti TPdf in TPdfView na obrazec, nato pa pogledu povejte, kateri dokument naj prikaže. Ta ena sama dodelitev predstavlja celotno povezavo med nevizualnim dokumentom in kontrolo, ki ga izrisuje

Arhitektura pregledovalnika PDF v Delphi, kjer TPdf ima dokument v lasti, TPdfView ga nariše, ena dodelitev lastnosti pa ju poveže na vrhu DLL PDFium
TPdf ima dokument v lasti, medtem ko ga TPdfView naslika, ena sama dodelitev pa poveže oba čez skupni pogon PDFium
procedure TFormMain.FormCreate(Sender: TObject);
begin
  // Pdf in PdfView sta bila postavljena pri oblikovanju.
  PdfView.Pdf := Pdf;                 // pogled izriše vse, kar dokument vsebuje
  PdfView.FitMode := pfmFitWidth;     // začni uporabnika pri smiselni povečavi
end;

Preden lahko zaženete karkoli od tega, mora biti na računalniku nameščena izvorna knjižnica PDFium. PDFium Component kliče datoteko pdfium32.dll or pdfium64.dll, odvisno od vaše ciljne platforme, dokument pa se preprosto ne bo odprl, če knjižnice DLL ni mogoče najti. Priložite ustrezno knjižnico DLL poleg svoje izvršljive datoteke ali jo postavite na mesto, kjer jo bo sistemski nalagalnik našel. Različice s podporo za V8 obstajajo le za PDF-je, ki vsebujejo JavaScript, ki ga želite izvesti (kar običajen bralnik ne počne), zato raje uporabite standardno knjižnico DLL, razen če imate konkreten razlog za nasprotno

Nalaganje dokumenta brez zaupanja vnosu

Prvi instinkt je običajno, da nalaganje ovijete v blok try/except in sproženo izjemo obravnavate kot napako. Ta instinkt je v tem primeru napačen, njegova uporaba pa ustvari bralnik, ki deluje dobro le, dokler mu nekdo ne posreduje poškodovane datoteke. Nastavitev Active := True ob napaki pri nalaganju ne sproži izjeme. PDFium Component ujame notranjo napako in pusti lastnost Active na vrednosti False, zato je edini zanesljiv način za preverjanje, ali se je dokument odprl, branje te lastnosti po njeni nastavitvi

Odločitveni potek nalaganja pregledovalnika PDFium v Delphi, kjer nastavitev Active nikoli ne sproži izjeme, tih false pomeni napačno geslo ali pokvarjeno datoteko, sledi pa en ponovni poskus gesla
Aktivacija nikoli ne vrže izjeme ob podboju, zato pregledovalnik prebere Active nazaj in odgovori na tihem false z enim samim ponovnim poskusom gesla
procedure TFormMain.OpenDocument(const FileName: string);
begin
  Pdf.FileName := FileName;
  Pdf.Active := True;                 // nikoli ne sproži izjeme; neuspeh pusti Active = False
  if not Pdf.Active then
  begin
    ShowMessage('Could not open ' + FileName);
    Exit;
  end;
  PdfView.PageNumber := 1;            // pogled sledi svoji lastni trenutni strani
  UpdatePageLabel;
end;

Pozorni morate biti na dve podrobnosti. Prva je, da lastnost PageNumber obstaja na obeh objektih in da sta neodvisni. Pdf.PageNumber je dokumentova predstava o trenutni strani; PdfView.PageNumber pa je stran, ki jo kontrola dejansko prikazuje, in to je tista, ki jo nastavite za pomikanje uporabnika skozi datoteko. Nastavitev ene ne premakne druge, zato bralnik vedno upravlja lastnost pogueda. Druga podrobnost je indeksiranje z začetkom pri 1: strani potekajo od 1 do Pdf.PageCount in ne od 0, kar lahko preseneti tiste, ki so navajeni na polja z začetnim indeksom nič

Upravljanje šifrirane datoteke

Šifrirani dokumenti se vključijo v isti postopek nalaganja. Če je geslo za odpiranje nastavljeno pred aktivacijo, se dokument ob odpiranju dešifrira; če je napačno ali manjka, ostane lastnost Active na False, enako kot pri poškodovani datoteki. Rešitev je torej, da uporabnika pozovete k vnosu gesla in znova poskusite z aktivacijo

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 nastavljeno pred 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;

Ker je napaka tiha tako pri napačnem geslu kot pri poškodovani datoteki, ju ne morete razlikovati samo na podlagi lastnosti Active. V praksi je to za bralnik sprejemljivo: uporabnik bodisi vnese pravilno geslo bodisi ugotovi, da se datoteka ne bo odprla, sporočilo pa je v obeh primerih enako

Pomikanje po dokumentu

Ko je dokument odprt, je navigacija le matematično spreminjanje vrednosti PdfView.PageNumber v mejah Pdf.PageCount. Edino dejansko delo je omejevanje vrednosti (clamping), tako da gumbi nikoli ne potisnejo strani izven obsega, gumbi za prvo in zadnjo stran pa ostanejo onemogočeni na začetku in koncu 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;

// vsi štirje navigacijski gumbi se skrčijo na en klic vsak
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 vnos »prikaži stran N« uporablja enak klic GoToPage s pretvorjenim celim številom, omejitev pa pokriva primer, ko uporabnik vpiše 9999 v dokument z desetimi stranmi. Naj bo UpdatePageLabel edino mesto, ki izpiše »Stran 3 od 12«, da se izpis nikoli ne razkladi s tem, kar prikazuje pogled

Zoom: eksplicitni odstotki in načini prilagajanja

Zoom pri TPdfView je na voljo v dveh oblikah, ki medsebojno delujeta. Razumevanje tega delovanja določa razliko med zoom kontrolo, ki deluje pravilno, in tisto, ki se bori proti uporabniku. Neposredna pot je lastnost Zoom, ki predstavlja odstotek, pri čemer 100 pomeni dejansko velikost. Druga pot je FitMode, ki pogledu naroči, naj sam izračuna zoom in ga ponovno preračunava ob vsakem spreminjanju velikosti okna

Interakcija povečave in FitMode v pregledovalniku PDFium v Delphi, kjer dodelitev točne povečave počisti FitMode na pfmNone, izbira načina prilagajanja pa vrne povečavo pogledu
Dodelitev točne povečave počisti način prilagajanja, izbira načina prilagajanja pa vrne izračun povečave nazaj pogledu
// fiksne povečave
PdfView.Zoom := 100;     // dejanska velikost
PdfView.Zoom := 50;      // polovica
PdfView.Zoom := 200;     // dvojno

// pusti, da pogled prilagodi velikost strani oknu in jo ohranja ob spremembi velikosti
PdfView.FitMode := pfmFitWidth;   // širina strani zapolni kontrolo
PdfView.FitMode := pfmFitPage;    // vidna celotna stran
PdfView.FitMode := pfmActualSize; // 1:1 s točkami dokumenta

Tukaj je del, ki pogosto povzroča težave. Neposredna dodelitev vrednosti Zoom ponastavi FitMode na pfmNone. To je pravilno vedenje in ne napaka: v trenutku, ko uporabnik izbere natanko 150 %, pogled ne more več upoštevati nastavitve »prilagodi širini«, saj sta ti dve zahtevi v konfliktu. Posledica za vaš uporabniški vmesnik je, da sta gumb za povečavo in gumb za prilagajanje strani oknu medsebojno izključujoči se stanji, orodna vrstica pa mora prikazati aktivni način. Ko uporabnik klikne prilagajanje strani, nastavite FitMode; ko klikne številčni zoom, nastavite Zoom in pustite, da se način prilagajanja samodejno počisti

Če bi raje sami izračunali vrednost prilagajanja, na primer za nastavitev drsnika za zoom s trenutnim odstotkom prilagajanja, vam pomočniki za posamezne strani ponudijo te številke brez spreminjanja načina. Lastnosti PageWidthZoom[N], PageZoom[N] in ActualSizeZoom[N] vrnejo odstotek, ki bi stran N prilagodil širini, jo prikazal v celoti ali jo izrisal v dejanski velikosti

// nastavi izpis povečave iz vrednosti prilagajanja širini trenutne strani
var
  FitPercent: Double;
begin
  FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
  ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;

Kaj dokončan bralnik dejansko potrebuje

Prvotni naslov morda obeta več dela, kot je potrebno. Zgornji bralnik obsega le nekaj deset vrstic in že opravlja vse, kar zahteva delovni proces z dokumenti: odpre datoteko, preživi napake, prikaže stran, se pomika med stranmi in spreminja povečavo ročno ali s prilagajanjem. PDFium težje dele opravi tiho. Vgrajene pisave se pravilno razrešijo, opombe in obrazci se izrišejo tam, kamor jih dokument postavi, stran, ki jo vidite, pa se ujema s tisto, kar vidi uporabnik brskalnika Chrome, saj ju izrisuje isti motor

Iz tega izhodišča so vsi dodatki inkrementalni in ne strukturni. Izbira besedila in iskanje bereta iz iste plasti besedila, ki jo PDFium že gradi; metapodatki, kot sta Pdf.Title in Pdf.Author, so oddaljeni le eno branje lastnosti; vrtenje in siva lestvica (grayscale) sta možnosti izrisa, ki ju posredujete ob risanju strani na bitno sliko. Nič od tega ne spreminja ogrodja, ki ga imate tukaj, to pa so objekt dokumenta, pogled in povezovalni tok nalaganja in navigacije. Ko pravilno vzpostavite to ogrodje, je vse ostalo le še dekoracija

Komponenti TPdf in TPdfView, uporabljeni v članku, sta del paketa PDFium Component za Delphi in C++Builder, ki na svoji produktni strani ponuja celotno referenco bralnika