En PDF-visare i Delphi kokar ner till två komponenter och kopplingarna mellan dem. TPdf äger dokumentet: det öppnar filen, dekrypterar den och svarar på frågor om sidantal och metadata. TPdfView är den visuella kontrollen som målar upp sidor på skärmen och hanterar rullning, zoom och sidan användaren för närvarande tittar på. PDFium Component lindar (wraps) samma renderingsmotor som levereras inuti Chrome, så de glyfer, kantutjämning (anti-aliasing) och färg du får på duken matchar det som dina användare redan ser i sin webbläsare. Arbetet ligger inte i renderingen. Det ligger i att ansluta dokumentobjektet till vyn, ladda utan att krascha på en skadad eller lösenordsskyddad fil, och ge användaren den handfull kontroller som får en visare (viewer) att kännas färdig: vända sida, ändra zoomen, passa in (fit) sidan i fönstret
Detta går igenom den hopsättningen (assembly) i den ordning du faktiskt bygger den. Allt här renderar en enskild sida åt gången, vilket är vad de flesta dokumentarbetsflöden vill ha. Om du behöver sidor staplade i en kontinuerligt rullande kolumn är det ett annat layoutbeslut och inte vägen här
Att koppla TPdf till TPdfView
Släpp in en TPdf och en TPdfView i formuläret, berätta sedan för vyn vilket dokument den ska visa. Den enda tilldelningen är hela länken mellan det icke-visuella dokumentet och kontrollen som målar upp 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;
Innan något av detta körs måste PDFium:s inbyggda bibliotek finnas på maskinen. PDFium Component anropar pdfium32.dll eller pdfium64.dll beroende på din målplattform, och dokumentet vägrar helt enkelt att öppnas om DLL:en inte kan hittas. Skicka med den matchande DLL:en bredvid din körbara fil, eller placera den där systemets laddare hittar den. De V8-aktiverade byggena existerar bara för PDF-filer som bär på JavaScript du vill köra, vilket en vanlig visare inte gör, så sträck dig efter standard-DLL:en om du inte har en konkret anledning till att inte göra det
Att ladda ett dokument utan att lita på indatan
Instinkten är att linda in (wrap) laddningen i en try/except och behandla ett kastat undantag (exception) som misslyckande. Den instinkten är fel här, och att få det fel resulterar i en visare som ser bra ut tills någon ger den en trasig fil. Att ställa in Active := True ger (raises) inte något undantag vid ett laddningsfel. PDFium Component fångar det interna felet och lämnar Active stående på False, så det enda ärliga sättet att veta om dokumentet öppnades är att läsa tillbaka egenskapen efter att du ställt in 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;
Två saker förtjänar uppmärksamhet. Det första är att PageNumber finns på båda objekten och att de två är oberoende. Pdf.PageNumber är dokumentets uppfattning om en aktuell sida; PdfView.PageNumber är den sida kontrollen faktiskt visar, och det är den du ställer in för att flytta användaren genom filen. Att ställa in den ena flyttar inte den andra, så en visare driver alltid vyns egenskap. Det andra är den 1-baserade indexeringen: sidorna löper från 1 till Pdf.PageCount, inte från 0, vilket lurar alla som är vana vid nollbaserade arrayer
Att hantera en krypterad fil
Krypterade dokument smälter in i samma laddningsväg. Om det öppna lösenordet (open password) ställs in före aktivering dekrypteras dokumentet när det öppnas; om det är fel eller saknas stannar Active på False exakt som det gör för en korrupt fil. Så återhämtningen (recovery) är att fråga efter ett lösenord och försöka 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;
Eftersom misslyckandet är tyst för både ett felaktigt lösenord och en skadad fil, kan du inte skilja de två åt enbart utifrån Active. I praktiken är det acceptabelt för en visare: användaren tillhandahåller antingen rätt lösenord eller lär sig att filen inte kommer att öppnas, och meddelandet lyder likadant i båda fallen
Att bläddra igenom dokumentet
Med dokumentet öppet är navigering aritmetik på PdfView.PageNumber avgränsad av Pdf.PageCount. Det enda riktiga arbetet är klamring (clamping), så knapparna knuffar aldrig ut sidan ur intervall och de första och sista knapparna förblir inaktiverade i ändarna av 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 textruta för "gå till sida N" är samma GoToPage-anrop matat från ett tolkat heltal, och klamringen (clamp) täcker fallet där användaren skriver in 9999 i en tiosidig fil. Behåll UpdatePageLabel som den enda platsen som skriver ut "Sida 3 av 12" så avläsningen aldrig hamnar ur synk med vad vyn visar
Zoom: explicita procentsatser och passningslägen
Zooma på TPdfView anländer i två smaker som interagerar, och att förstå interaktionen är skillnaden mellan en zoomkontroll som uppför sig och en som kämpar mot användaren. Den direkta vägen är Zoom-egenskapen, en procentsats där 100 betyder faktisk storlek. Den andra vägen är FitMode (passningsläge), som talar om för vyn att den ska beräkna zoomen åt dig och fortsätta omberäkna (recomputing) den när fönstret ändrar storlek
// 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
Här är den del som sätter krokben för folk. Att tilldela Zoom återställer direkt FitMode till pfmNone. Det är korrekt beteende, inte en bugg: i samma ögonblick som användaren väljer exakt 150 %, kan vyn inte längre också respektera "anpassa till bredd" (fit to width), eftersom de två förfrågningarna kommer i konflikt. Konsekvensen för ditt gränssnitt är att en zooma-in-knapp och en anpassa-till-sida-knapp är ömsesidigt uteslutande (mutually exclusive) tillstånd, och verktygsfältet bör göra det aktiva läget synligt. När användaren klickar på anpassa-till-sida ställer du in FitMode; när de klickar på en numerisk zoom, ställ in Zoom och låt det rensa passningsläget (fit mode) på egen hand
Om du hellre beräknar passningsvärdet själv, kanske för att ge ett startvärde (seed) åt ett zoom-reglage med nuvarande passnings-procentsats, så ger dig per-sida-hjälparna siffrorna utan att ändra läget. PageWidthZoom[N], PageZoom[N] och ActualSizeZoom[N] returnerar den procentandel som skulle anpassa sida N till bredden, anpassa den hel, eller rendera den i faktisk storlek
// 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;
Vad en färdig visare faktiskt behöver
Visaren ovan är några dussin rader, och den gör redan det jobb som ett dokumentarbetsflöde behöver: öppna en fil, överlev en dålig fil, visa en sida, flytta mellan sidor och ändra förstoringen för hand eller genom anpassning (fit). PDFium gör de svåra delarna i tysthet. Inbäddade typsnitt löses upp, anteckningar (annotations) och blankettfält målar där dokumentet placerar dem, och sidan du ser matchar den en Chrome-användare skulle se, eftersom det är samma motor som ritar båda
Från denna bas är tilläggen (additions) inkrementella snarare än strukturella. Textmarkering och sökning läser från samma textlager som PDFium redan bygger; metadata som Pdf.Title och Pdf.Author är en egenskapsläsning (property read) bort; rotation och gråskala är renderingsalternativ du skickar med när du ritar en sida till en bitmapp. Inget av dessa ändrar ryggraden (the spine) du har här, vilken är dokumentobjektet, vyn och ladda-sedan-navigera-flödet som kopplar ihop dem. Få den ryggraden rätt så är resten dekoration
De TPdf- och TPdfView-komponenter som genomgående används är en del av PDFium Component för Delphi och C++Builder, som bär den fullständiga visare-referensen (viewer reference) på sin produktsida