Teknisk artikel

Byg en PDF-fremviser i Delphi med PDFium Component

En PDF-fremviser i Delphi bunder i to komponenter og forbindelsen mellem dem. TPdf ejer dokumentet: den åbner filen, dekrypterer den og besvarer spørgsmål om sideantal og metadata. TPdfView er den visuelle kontrol, der tegner sider på skærmen og håndterer rulning, zoom og den side, brugeren i øjeblikket kigger på. PDFium Component indkapsler den samme renderingsmaskine, der leveres indeni Chrome, så de glyphs, anti-aliasing og farver, du får på dit lærred, matcher det, dine brugere allerede ser i deres browser. Arbejdet ligger ikke i renderingen. Det ligger i at forbinde dokumentobjektet til visningen, at indlæse uden at gå ned på en beskadiget eller adgangskodebeskyttet fil, og at give brugeren den håndfuld kontroller, der får en fremviser til at føles færdig: skift side, ændre zoom, tilpas siden til vinduet

Dette gennemgår denne samling i den rækkefølge, du rent faktisk bygger den. Alt her renderes en enkelt side ad gangen, hvilket er, hvad de fleste dokumentarbejdsgange ønsker. Hvis du har brug for sider stablet i en kontinuerligt rullende kolonne, er det en anden layoutbeslutning og ikke den vej, vi går her

Forbindelse af TPdf til TPdfView

Placer en TPdf og en TPdfView på formularen, og fortæl derefter visningen, hvilket dokument der skal vises. Denne ene tildeling er hele linket mellem det ikke-visuelle dokument og den kontrol, der tegner det

procedure TFormMain.FormCreate(Sender: TObject);
begin
  // Pdf and PdfView were dropped at design time.
  PdfView.Pdf := Pdf;                 // the view paints whatever this document holds
  PdfView.FitMode := pfmFitWidth;     // start the user at a sensible zoom
end;

Før noget af dette kører, skal det indfødte PDFium-bibliotek være på maskinen. PDFium Component kalder ind i pdfium32.dll eller pdfium64.dll afhængigt af din målplatform, og dokumentet nægter simpelthen at åbne, hvis DLL'en ikke kan findes. Lever den matchende DLL ved siden af din eksekverbare fil, eller placer den, hvor systemindlæseren vil finde den. De V8-aktiverede builds findes kun for PDF'er, der bærer JavaScript, du vil udføre, hvilket en simpel fremviser ikke gør, så ræk ud efter standard DLL'en, medmindre du har en konkret grund til at lade være

Indlæsning af et dokument uden at stole på inputtet

Instinktet er at pakke indlæsningen ind i en try/except og behandle en kastet undtagelse som en fejl. Det instinkt er forkert her, og at gøre det forkert producerer en fremviser, der ser fin ud, indtil nogen rækker den en ødelagt fil. At sætte Active := True kaster ikke en undtagelse ved en indlæsningsfejl. PDFium Component fanger den interne fejl og efterlader Active stående på False, så den eneste ærlige måde at vide, om dokumentet åbnede, er at læse egenskaben tilbage, efter du har sat den

procedure TFormMain.OpenDocument(const FileName: string);
begin
  Pdf.FileName := FileName;
  Pdf.Active := True;                 // never raises; failure leaves Active = False
  if not Pdf.Active then
  begin
    ShowMessage('Could not open ' + FileName);
    Exit;
  end;
  PdfView.PageNumber := 1;            // the view tracks its own current page
  UpdatePageLabel;
end;

To ting fortjener opmærksomhed. Den første er, at PageNumber eksisterer på begge objekter, og de to er uafhængige. Pdf.PageNumber er dokumentets opfattelse af en aktuel side; PdfView.PageNumber er den side, kontrollen rent faktisk viser, og det er den, du sætter for at flytte brugeren gennem filen. At sætte den ene flytter ikke den anden, så en fremviser driver altid visningens egenskab. Den anden er den 1-baserede indeksering: sider løber fra 1 til Pdf.PageCount, ikke fra 0, hvilket fanger enhver, der er vant til nul-baserede arrays

Håndtering af en krypteret fil

Krypterede dokumenter foldes ind i den samme indlæsningssti. Hvis åbne-adgangskoden er indstillet før aktivering, dekrypteres dokumentet, når det åbnes; hvis den er forkert eller mangler, forbliver ActiveFalse præcis, som den gør for en beskadiget fil. Så gendannelsen er at anmode om en adgangskode og prøve aktiveringen igen

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;       // must be set before 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;

Fordi fejlen er stille for både en dårlig adgangskode og en beskadiget fil, kan du ikke adskille de to alene ud fra Active. I praksis er det acceptabelt for en fremviser: brugeren angiver enten den rigtige adgangskode eller lærer, at filen ikke vil åbne, og beskeden lyder ens uanset hvad

Gennembladring af dokumentet

Når dokumentet er åbent, er navigation aritmetik på PdfView.PageNumber begrænset af Pdf.PageCount. Det eneste reelle arbejde er at fastspænde, så knapperne aldrig skubber siden uden for rækkevidde, og den første og sidste knap forbliver deaktiveret i enderne af filen

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;

// the four navigation buttons reduce to one call each
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;

En "gå til side N" tekstboks er det samme GoToPage kald fodret fra et parset heltal, og fastspændingen dækker tilfældet, hvor brugeren skriver 9999 i en fil på ti sider. Behold UpdatePageLabel som det eneste sted, der skriver "Side 3 af 12", så udlæsningen aldrig driver ud af synkronisering med, hvad visningen viser

Zoom: eksplicitte procenter og tilpasningstilstande

Zoom på TPdfView kommer i to varianter, der interagerer, og at forstå interaktionen er forskellen mellem en zoomkontrol, der opfører sig pænt, og en, der kæmper mod brugeren. Den direkte rute er egenskaben Zoom, en procentdel hvor 100 betyder faktisk størrelse. Den anden rute er FitMode, som fortæller visningen at beregne zoomen for dig og fortsætte med at genberegne den, når vinduet ændrer størrelse

// fixed magnifications
PdfView.Zoom := 100;     // actual size
PdfView.Zoom := 50;      // half
PdfView.Zoom := 200;     // double

// let the view size the page to the window, and keep it sized on resize
PdfView.FitMode := pfmFitWidth;   // page width fills the control
PdfView.FitMode := pfmFitPage;    // whole page visible
PdfView.FitMode := pfmActualSize; // 1:1 with the document's points

Her er den del, der snyder folk. Direkte tildeling af Zoom nulstiller FitMode til pfmNone. Det er korrekt adfærd, ikke en fejl: i det øjeblik brugeren vælger præcis 150 %, kan visningen ikke længere samtidig ære "tilpas til bredde," fordi de to anmodninger er i konflikt. Konsekvensen for din brugergrænseflade er, at en zoom-ind-knap og en tilpas-til-side-knap er udelukkende tilstande, og værktøjslinjen bør gøre den aktive tilstand synlig. Når brugeren klikker på tilpas-til-side, skal du sætte FitMode; når de klikker på en numerisk zoom, skal du sætte Zoom og lade den rydde tilpasningstilstanden på egen hånd

Hvis du hellere selv vil beregne tilpasningsværdien, måske for at indstille en zoomskyder med den aktuelle tilpasningsprocent, giver pr.-side-hjælperne dig tallene uden at ændre tilstanden. PageWidthZoom[N], PageZoom[N] og ActualSizeZoom[N] returnerer den procentdel, der ville tilpasse side N til bredden, tilpasse den som helhed, eller rendere den i faktisk størrelse

// seed a zoom readout from the fit-to-width value of the current page
var
  FitPercent: Double;
begin
  FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
  ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;

Hvad en færdig fremviser egentlig har brug for

Ovenstående fremviser er et par dusin linjer, og den gør allerede det arbejde, en dokumentarbejdsgang kræver: åbne en fil, overleve en dårlig, vise en side, flytte mellem sider og ændre forstørrelsen manuelt eller ved tilpasning. PDFium klarer de svære dele i stilhed. Indlejrede skrifttyper løses, annotationer og formularfelter males, hvor dokumentet placerer dem, og den side, du ser, matcher den, en Chrome-bruger ville se, fordi det er den samme maskine, der tegner begge

Ud fra denne base er tilføjelserne trinvise snarere end strukturelle. Tekstvalg og søgning læser fra det samme tekstlag, som PDFium allerede bygger; metadata som Pdf.Title og Pdf.Author er én egenskabslæsning væk; rotation og gråtone er render-indstillinger, du overfører, når du tegner en side til et bitmap. Ingen af disse ændrer den rygrad, du har her, som er dokumentobjektet, visningen og indlæs-derefter-naviger flowet, der forbinder dem. Få den rygrad rigtig, og resten er dekoration

TPdf og TPdfView komponenterne, der bruges overalt, er en del af PDFium Component for Delphi og C++Builder, som har den fulde fremviser-reference på sin produktside