Tekninen artikkeli

PDF-katseluohjelman (Viewer in Delphi with PDFium Component) rakentaminen (Build a PDF Viewer in Delphi with PDFium Component) Delphissä PDFium Component -komponentilla

PDF-katseluohjelma Delphissä tiivistyy kahteen komponenttiin ja niiden väliseen kytkentään. TPdf omistaa dokumentin: se avaa tiedoston, purkaa salauksen, ja vastaa kysymyksiin sivumäärästä ja metatiedoista. TPdfView on visuaalinen ohjausobjekti, joka piirtää sivuja näytölle ja käsittelee vierityksen, zoomauksen, ja sivun, jota käyttäjä parhaillaan katsoo. PDFium Component kietoo sisäänsä saman hahmonnusmoottorin, joka toimitetaan Chromen sisällä, joten kankaalle saamanne glyyfit, reunanpehmennys, ja väri vastaavat sitä, mitä käyttäjänne jo näkevät selaimessaan. Työ ei ole hahmonnuksessa. Se on dokumenttiobjektin liittämisessä näkymään, lataamisessa kaatumatta vioittuneella tai salasanasuojatulla tiedostolla, ja käyttäjälle sen kourallisen hallintaelementtejä antamisessa, jotka saavat katseluohjelman tuntumaan valmiilta: käännä sivua, vaihda zoomausta, sovita sivu ikkunaan

Tämä käy läpi kyseisen kokoonpanon siinä järjestyksessä, jossa sen todella rakennatte. Kaikki tässä hahmontaa yhden sivun kerrallaan, mitä useimmat dokumenttien työnkulut haluavat. Jos tarvitsette sivut pinottuna yhteen jatkuvasti vierivään sarakkeeseen, se on eri asetteluratkaisu eikä tämän artikkelin polku

TPdf:n kytkeminen TPdfView:hen

Pudottakaa TPdf ja TPdfView lomakkeelle, ja kertokaa sitten näkymälle, mitä dokumenttia näyttää. Tuo yksittäinen sijoitus on koko linkki ei-visuaalisen dokumentin ja sitä piirtävän ohjausobjektin välillä

Delphi-PDF-katselimen arkkitehtuuri, jossa TPdf omistaa asiakirjan, TPdfView maalaa sen ja yksi ominaisuuden sijoitus yhdistää ne PDFium-DLL:n päälle
TPdf omistaa dokumentin, kun taas TPdfView maalaa sen, ja yksi sijoitus yhdistää molemmat jaetun PDFium-moottorin yli
procedure TFormMain.FormCreate(Sender: TObject);
begin
  // Pdf ja PdfView pudotettiin suunnitteluaikana.
  PdfView.Pdf := Pdf;                 // näkymä piirtää mitä tahansa tämä dokumentti sisältää
  PdfView.FitMode := pfmFitWidth;     // aloita käyttäjä järkevässä zoomauksessa
end;

Ennen kuin mikään tästä toimii, PDFiumin natiivikirjaston on oltava koneella. PDFium Component kutsuu pdfium32.dll:ää tai pdfium64.dll:ää kohdealustastanne riippuen, ja dokumentti yksinkertaisesti kieltäytyy avautumasta, jos DLL:ää ei löydy. Toimittakaa vastaava DLL suoritettavan tiedostonne vierestä, tai sijoittakaa se paikkaan, josta järjestelmän lataaja sen löytää. V8-käyttöiset koontiversiot ovat olemassa vain PDF-tiedostoille, jotka kantavat JavaScriptiä, jota haluatte suorittaa, mitä tavallinen katseluohjelma ei tee, joten tarttukaa vakio-DLL:ään, ellei teillä ole konkreettista syytä olla tekemättä niin

Dokumentin lataaminen luottamatta syötteeseen

Vaisto on kietoa lataus try/except-lohkoon ja käsitellä heitetty poikkeus epäonnistumisena. Tuo vaisto on väärässä tässä, ja sen väärin ymmärtäminen tuottaa katseluohjelman, joka näyttää kunnossa olevalta, kunnes joku antaa sille rikkinäisen tiedoston. Active := True -asettaminen ei nosta poikkeusta latausvirheessä. PDFium Component nappaa sisäisen virheen ja jättää Active-arvon lukemaan False, joten ainoa rehellinen tapa tietää, avautuiko dokumentti, on lukea ominaisuus takaisin sen jälkeen, kun sen asetitte

Latauksen päätösvirta Delphi PDFium -katselimelle, jossa Activen asettaminen ei koskaan nosta poikkeusta, hiljainen false tarkoittaa väärää salasanaa tai vaurioitunutta tiedostoa, ja yksi salasanayritys seuraa
Aktivointi ei koskaan nosta poikkeusta epäonnistuessaan, joten katselin lukee Active-arvon takaisin ja vastaa hiljaiseen false-arvoon yhdellä salasanauudelleenyrityksellä
procedure TFormMain.OpenDocument(const FileName: string);
begin
  Pdf.FileName := FileName;
  Pdf.Active := True;                 // ei koskaan nosta poikkeusta; epäonnistuminen jättää Active = False
  if not Pdf.Active then
  begin
    ShowMessage('Could not open ' + FileName);
    Exit;
  end;
  PdfView.PageNumber := 1;            // näkymä seuraa omaa nykyistä sivuaan
  UpdatePageLabel;
end;

Kaksi asiaa ansaitsee huomiota. Ensimmäinen on, että PageNumber on olemassa molemmissa objekteissa ja ne kaksi ovat riippumattomia. Pdf.PageNumber on dokumentin käsitys nykyisestä sivusta; PdfView.PageNumber on sivu, jota ohjausobjekti todellisuudessa näyttää, ja se on se, jota asetatte siirtääksenne käyttäjää tiedoston läpi. Toisen asettaminen ei liikuta toista, joten katseluohjelma ohjaa aina näkymän ominaisuutta. Toinen on 1-pohjainen indeksointi: sivut kulkevat 1:stä Pdf.PageCount:iin, ei 0:sta, mikä yllättää kenet tahansa, joka on tottunut nollapohjaisiin taulukoihin

Salatun tiedoston käsittely

Salatut dokumentit taipuvat samaan latauspolkuun. Jos avaussalasana asetetaan ennen aktivointia, dokumentti purkaa salauksensa avautuessaan; jos se on väärä tai puuttuu, Active pysyy arvossa False aivan kuten vioittuneella tiedostolla. Joten toipuminen on kysyä salasanaa ja yrittää aktivointia uudelleen

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;       // on asetettava ennen 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;

Koska epäonnistuminen on hiljainen sekä väärälle salasanalle että vioittuneelle tiedostolle, ette voi erottaa niitä kahta pelkästä Active-arvosta. Käytännössä se on hyväksyttävää katseluohjelmalle: käyttäjä joko antaa oikean salasanan tai saa tietää, ettei tiedosto avaudu, ja viesti lukee saman kummassakin tapauksessa

Dokumentin selaaminen sivu kerrallaan

Dokumentin ollessa auki, navigointi on aritmetiikkaa PdfView.PageNumber:lla, jota rajoittaa Pdf.PageCount. Ainoa todellinen työ on rajaus, jotta painikkeet eivät koskaan työnnä sivua alueen ulkopuolelle ja ensimmäinen ja viimeinen painike pysyvät poissa käytöstä tiedoston päissä

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;

// neljä navigointipainiketta supistuvat kukin yhteen kutsuun
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;

"Siirry sivulle N" -tekstiruutu on sama GoToPage-kutsu syötettynä jäsennetyllä kokonaisluvulla, ja rajaus kattaa tapauksen, jossa käyttäjä kirjoittaa 9999 kymmensivuiseen tiedostoon. Pitäkää UpdatePageLabel ainoana paikkana, joka kirjoittaa "Sivu 3/12", jotta lukema ei koskaan ajaudu pois synkronoinnista siitä, mitä näkymä näyttää

Zoomaus: tarkat prosentit ja sovitustilat

Zoomaus TPdfView:ssä saapuu kahtena muotona, jotka vaikuttavat toisiinsa, ja tuon vuorovaikutuksen ymmärtäminen on ero hyvin käyttäytyvän zoomaushallinnan ja käyttäjää vastaan taistelevan välillä. Suora reitti on Zoom-ominaisuus, prosenttiluku, jossa 100 tarkoittaa todellista kokoa. Toinen reitti on FitMode, joka kertoo näkymälle laskea zoomauksen puolestanne ja pitää sen laskemisen ajan tasalla ikkunan koon muuttuessa

Zoom- ja FitMode-vuorovaikutus PDFium Delphi -katselimessa, jossa tarkan Zoomin sijoittaminen tyhjentää FitModen arvoon pfmNone ja sopivan tilan valinta antaa zoomauksen takaisin näkymälle
Tarkan zoomin sijoittaminen tyhjentää sovitusmoodin, ja sovitusmoodin valinta antaa zoomin laskennan takaisin näkymälle
// kiinteät suurennokset
PdfView.Zoom := 100;     // todellinen koko
PdfView.Zoom := 50;      // puolet
PdfView.Zoom := 200;     // kaksinkertainen

// anna näkymän mitoittaa sivu ikkunaan, ja pitää se mitoitettuna koon muuttuessa
PdfView.FitMode := pfmFitWidth;   // sivun leveys täyttää ohjausobjektin
PdfView.FitMode := pfmFitPage;    // koko sivu näkyvissä
PdfView.FitMode := pfmActualSize; // 1:1 dokumentin pisteiden kanssa

Tässä on osa, joka kompastuttaa ihmisiä. Zoom-arvon suora asettaminen nollaa FitMode-arvon arvoon pfmNone. Se on oikeaa käytöstä, ei virhe: sillä hetkellä, kun käyttäjä valitsee tarkan 150 %:n, näkymä ei voi enää myöskään noudattaa "sovita leveyteen" -asetusta, koska nämä kaksi pyyntöä ovat ristiriidassa. Seuraus käyttöliittymällenne on, että zoomaa sisään -painike ja sovita sivulle -painike ovat toisensa poissulkevia tiloja, ja työkalupalkin tulisi tehdä aktiivinen tila näkyväksi. Kun käyttäjä napsauttaa sovita sivulle, asettakaa FitMode; kun hän napsauttaa numeerista zoomausta, asettakaa Zoom ja antakaa sen tyhjentää sovitustila itsestään

Jos haluatte mieluummin laskea sovitusarvon itse, ehkä alustaaksenne zoomausliukusäätimen nykyisellä sovitusprosentilla, sivukohtaiset apufunktiot antavat teille luvut muuttamatta tilaa. PageWidthZoom[N], PageZoom[N], ja ActualSizeZoom[N] palauttavat prosentin, joka sovittaisi sivun N leveyteen, sovittaisi sen kokonaan, tai hahmontaisi sen todellisessa koossa

// alusta zoomauslukema nykyisen sivun sovita-leveyteen-arvosta
var
  FitPercent: Double;
begin
  FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
  ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;

Mitä valmis katseluohjelma todella tarvitsee

Yllä oleva katseluohjelma on muutama tusina rivejä, ja se jo hoitaa tehtävän, jota dokumenttien työnkulku tarvitsee: avaa tiedosto, selviää huonosta sellaisesta, näyttää sivun, liikkuu sivujen välillä, ja muuttaa suurennosta käsin tai sovituksella. PDFium hoitaa vaikeat osat hiljaa. Upotetut fontit ratkeavat, kommentit ja lomakekentät piirtyvät sinne, minne dokumentti ne sijoittaa, ja näkemänne sivu vastaa sitä, jonka Chrome-käyttäjä näkisi, koska sama moottori piirtää molemmat

Tästä pohjasta lisäykset ovat asteittaisia eivätkä rakenteellisia. Tekstin valinta ja haku lukevat samasta tekstikerroksesta, jonka PDFium jo rakentaa; metatiedot kuten Pdf.Title ja Pdf.Author ovat yhden ominaisuuden luvun päässä; kierto ja harmaasävy ovat hahmonnusasetuksia, jotka annatte, kun piirrätte sivun bittikarttaan. Mikään näistä ei muuta selkärankaa, joka teillä on täällä, joka on dokumenttiobjekti, näkymä, ja niitä yhdistävä lataa-sitten-navigoi-kulku. Saakaa se selkäranka oikein ja loput ovat koristeita

TPdf- ja TPdfView-komponentit, joita käytetään läpi tämän artikkelin, ovat osa PDFium Component -tuotetta Delphille ja C++Builderille, joka kantaa täyden katseluohjelmareferenssin tuotesivullaan