Ein PDF-Viewer in Delphi läuft auf zwei Komponenten und die Verbindung zwischen ihnen hinaus. TPdf besitzt das Dokument: Es öffnet die Datei, entschlüsselt sie und beantwortet Fragen zur Seitenanzahl und zu den Metadaten. TPdfView ist das visuelle Steuerelement, das Seiten auf dem Bildschirm darstellt und Scrollen, Zoom sowie die Seite verarbeitet, die der Benutzer gerade betrachtet. Die PDFium-Komponente verpackt dieselbe Rendering-Engine, die in Chrome enthalten ist, sodass die Glyphen, das Anti-Aliasing und die Farben, die Sie auf der Arbeitsfläche erhalten, mit dem übereinstimmen, was Ihre Benutzer bereits in ihrem Browser sehen. Die Arbeit liegt nicht im Rendering. Es geht darum, das Dokumentobjekt mit der Ansicht zu verbinden, es zu laden, ohne abzustürzen, wenn es sich um eine beschädigte oder passwortgeschützte Datei handelt, und dem Benutzer die Handvoll Steuerelemente zu geben, die dafür sorgen, dass sich ein Viewer wie ein fertiges Programm anfühlt: die Seite umblättern, den Zoom ändern, die Seite an das Fenster anpassen
Dieser Leitfaden führt Sie durch diese Montage in der Reihenfolge, in der Sie sie tatsächlich durchführen. Alles hier rendert jeweils eine einzelne Seite, was für die meisten Dokument-Workflows gewünscht wird. Wenn Sie Seiten in einer kontinuierlich scrollenden Spalte stapeln müssen, ist dies eine andere Layout-Entscheidung und nicht der hier behandelte Weg
Verbinden von TPdf mit TPdfView
Platzieren Sie ein TPdf und ein TPdfView auf dem Formular und geben Sie dann der Ansicht an, welches Dokument angezeigt werden soll. Diese einzige Zuweisung ist die gesamte Verknüpfung zwischen dem nicht sichtbaren Dokument und dem Steuerelement, das es darstellt
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;
Bevor dies alles abläuft, muss die native PDFium-Bibliothek auf dem Rechner vorhanden sein. Die PDFium-Komponente ruft abhängig von Ihrer Zielplattform in pdfium32.dll oder pdfium64.dll auf. Das Dokument lässt sich schlichtweg nicht öffnen, wenn die DLL nicht gefunden werden kann. Liefern Sie die passende DLL zusammen mit Ihrer ausführbaren Datei aus oder platzieren Sie sie dort, wo der System-Loader sie findet. Die V8-fähigen Builds existieren nur für PDFs, die JavaScript enthalten, das Sie ausführen möchten, was ein normaler Viewer nicht tut. Greifen Sie also zur Standard-DLL, es sei denn, Sie haben einen konkreten Grund, dies nicht zu tun
Laden eines Dokuments, ohne der Eingabe zu vertrauen
Der Instinkt rät, den Ladevorgang in ein Try/Except zu verpacken und eine ausgelöste Ausnahme als Fehler zu behandeln. Dieser Instinkt ist hier falsch, und ihn falsch umzusetzen, führt zu einem Viewer, der in Ordnung aussieht, bis ihm jemand eine defekte Datei übergibt. Das Festlegen von Active := True löst bei einem Ladefehler keine Ausnahme aus. Die PDFium-Komponente fängt den internen Fehler ab und belässt Active auf False. Die einzig ehrliche Methode, um festzustellen, ob das Dokument geöffnet wurde, besteht also darin, die Eigenschaft nach dem Festlegen zurückzulesen
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;
Zwei Dinge verdienen Aufmerksamkeit. Erstens existiert PageNumber auf beiden Objekten und die beiden sind unabhängig voneinander. Pdf.PageNumber ist das Konzept des Dokuments von einer aktuellen Seite; PdfView.PageNumber ist die Seite, die das Steuerelement tatsächlich anzeigt, und diese legen Sie fest, um den Benutzer durch die Datei zu bewegen. Das Einstellen der einen verschiebt die andere nicht. Daher steuert ein Viewer immer die Eigenschaft der Ansicht. Zweitens die 1-basierte Indizierung: Seiten laufen von 1 bis Pdf.PageCount, nicht von 0, was jeden überrascht, der nullbasierte Arrays gewohnt ist
Umgang mit einer verschlüsselten Datei
Verschlüsselte Dokumente fügen sich in denselben Ladepfad ein. Wenn das Passwort zum Öffnen vor der Aktivierung festgelegt wird, wird das Dokument beim Öffnen entschlüsselt; wenn es falsch ist oder fehlt, bleibt Active genau wie bei einer fehlerhaften Datei auf False stehen. Die Wiederherstellung besteht also darin, nach einem Passwort zu fragen und die Aktivierung erneut zu versuchen
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;
Da der Fehler sowohl bei einem falschen Passwort als auch bei einer beschädigten Datei stillschweigend abläuft, können Sie die beiden allein anhand von Active nicht unterscheiden. In der Praxis ist das für einen Viewer akzeptabel: Der Benutzer gibt entweder das richtige Passwort ein oder erfährt, dass sich die Datei nicht öffnen lässt, und die Meldung lautet in beiden Fällen gleich
Durch das Dokument blättern
Wenn das Dokument geöffnet ist, ist die Navigation eine Arithmetik für PdfView.PageNumber, begrenzt durch Pdf.PageCount. Die einzige wirkliche Arbeit ist das Begrenzen, damit die Schaltflächen die Seite niemals außerhalb des Bereichs schieben und die erste und letzte Schaltfläche am Anfang und Ende der Datei deaktiviert bleiben
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;
Ein Textfeld "Gehe zu Seite N" ist derselbe GoToPage-Aufruf, der mit einem analysierten Integer-Wert gefüttert wird, und die Begrenzung deckt den Fall ab, in dem der Benutzer 9999 in eine Datei mit zehn Seiten eintippt. Belassen Sie UpdatePageLabel als einzigen Ort, an dem "Seite 3 von 12" steht, damit die Anzeige nie aus dem Tritt mit dem gerät, was die Ansicht zeigt
Zoom: explizite Prozentsätze und Passmodi
Der Zoom bei TPdfView kommt in zwei Ausprägungen, die interagieren, und das Verständnis dieser Interaktion ist der Unterschied zwischen einer Zoomsteuerung, die sich gut verhält, und einer, die mit dem Benutzer kämpft. Der direkte Weg ist die Eigenschaft Zoom, ein Prozentsatz, wobei 100 die tatsächliche Größe bedeutet. Der andere Weg ist FitMode, wodurch die Ansicht angewiesen wird, den Zoom für Sie zu berechnen und ihn bei einer Größenänderung des Fensters immer wieder neu zu berechnen
// 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
Hier ist der Teil, über den die Leute stolpern. Das direkte Zuweisen von Zoom setzt FitMode auf pfmNone zurück. Das ist das richtige Verhalten, kein Fehler: In dem Moment, in dem der Benutzer exakte 150 % wählt, kann die Ansicht nicht mehr „An Breite anpassen“ einhalten, da die beiden Anforderungen in Konflikt geraten. Die Konsequenz für Ihre Benutzeroberfläche ist, dass eine Vergrößerungsschaltfläche und eine Schaltfläche zum Anpassen an die Seite sich gegenseitig ausschließende Zustände sind und die Symbolleiste den aktiven Modus sichtbar machen sollte. Wenn der Benutzer auf "An Seite anpassen" klickt, stellen Sie FitMode ein. Wenn auf einen numerischen Zoom geklickt wird, stellen Sie Zoom ein und lassen Sie ihn den Anpassungsmodus (FitMode) von sich aus löschen
Wenn Sie den Fit-Wert lieber selbst berechnen möchten, beispielsweise um einen Zoom-Schieberegler mit dem aktuellen Fit-Prozentsatz zu versehen, geben Ihnen die Helper pro Seite die Zahlen, ohne den Modus zu ändern. PageWidthZoom[N], PageZoom[N] und ActualSizeZoom[N] geben den Prozentsatz zurück, der die Seite N in die Breite einpassen, sie als Ganzes einpassen oder in tatsächlicher Größe rendern würde
// 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;
Was ein fertiger Viewer tatsächlich benötigt
Der obige Viewer umfasst ein paar Dutzend Zeilen und erledigt bereits die Aufgaben, die ein Dokument-Workflow erfordert: eine Datei öffnen, eine beschädigte Datei überstehen, eine Seite anzeigen, zwischen Seiten wechseln und die Vergrößerung von Hand oder per Passmodus ändern. PDFium erledigt die schwierigen Teile stillschweigend. Eingebettete Schriftarten werden aufgelöst, Anmerkungen und Formularfelder werden dort gezeichnet, wo das Dokument sie platziert, und die angezeigte Seite entspricht der, die ein Chrome-Benutzer sehen würde, da dieselbe Engine beide rendert
Ausgehend von dieser Basis erfolgen die Ergänzungen schrittweise und nicht strukturell. Die Textauswahl und -suche wird aus derselben Textebene ausgelesen, die PDFium bereits aufbaut. Metadaten wie Pdf.Title und Pdf.Author sind eine einzige Eigenschaft vom Auslesen entfernt. Drehung und Graustufen sind Rendering-Optionen, die Sie übergeben, wenn Sie eine Seite in eine Bitmap zeichnen. Nichts davon ändert das hier vorhandene Rückgrat, bei dem es sich um das Dokumentobjekt, die Ansicht und den verbindenden Lade-dann-Navigieren-Ablauf handelt. Sorgen Sie dafür, dass dieses Rückgrat stimmt, und der Rest ist Dekoration
Die durchgängig verwendeten Komponenten TPdf und TPdfView sind Bestandteil der PDFium-Komponente für Delphi und C++Builder, die den vollständigen Viewer als Referenz auf ihrer Produktseite enthält