Przeglądarka PDF w Delphi sprowadza się do dwóch komponentów i połączeń między nimi. TPdf jest właścicielem dokumentu: otwiera plik, odszyfrowuje go i odpowiada na pytania dotyczące liczby stron i metadanych. TPdfView to kontrolka wizualna, która rysuje strony na ekranie i obsługuje przewijanie, powiększanie oraz stronę, na którą obecnie patrzy użytkownik. Komponent PDFium opakowuje ten sam silnik renderujący, który znajduje się w Chrome, więc glify, antyaliasing i kolory, które otrzymujesz na płótnie, pasują do tego, co twoi użytkownicy widzą już w swojej przeglądarce. Praca nie polega na renderowaniu. Polega na połączeniu obiektu dokumentu z widokiem, ładowaniu bez awarii uszkodzonego lub chronionego hasłem pliku oraz daniu użytkownikowi garści elementów sterujących, które sprawiają, że przeglądarka wydaje się kompletna: zmiana strony, zmiana powiększenia, dopasowanie strony do okna
Ten materiał przeprowadza przez ten montaż w kolejności, w jakiej faktycznie się go buduje. Wszystko tutaj renderuje pojedynczą stronę na raz, co jest tym, czego pragnie większość przepływ pracy z dokumentami. Jeśli potrzebujesz stron ułożonych w jednej ciągle przewijanej kolumnie, to jest to inna decyzja dotycząca układu i nie jest to ta ścieżka
Łączenie TPdf z TPdfView
Upuść TPdf i TPdfView na formularzu, a następnie powiedz widokowi, który dokument ma wyświetlić. To jedno przypisanie to całe połączenie między niewizualnym dokumentem a kontrolką, która go rysuje
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;
Zanim cokolwiek z tego zostanie uruchomione, natywna biblioteka PDFium musi znajdować się na maszynie. Komponent PDFium wywołuje pdfium32.dll lub pdfium64.dll w zależności od platformy docelowej, a dokument po prostu odmawia otwarcia, jeśli nie można znaleźć biblioteki DLL. Dostarcz pasującą bibliotekę DLL obok pliku wykonywalnego lub umieść ją tam, gdzie znajdzie ją systemowy program ładujący. Kompilacje obsługujące V8 istnieją tylko dla plików PDF zawierających kod JavaScript, który chcesz wykonać, czego nie robi zwykła przeglądarka, więc sięgnij po standardową bibliotekę DLL, chyba że masz konkretny powód, by tego nie robić
Ładowanie dokumentu bez ufania danym wejściowym
Instynkt podpowiada, aby opakować ładowanie w try/except i traktować zgłoszony wyjątek jako błąd. Ten instynkt jest tutaj błędny, a popełnienie błędu skutkuje przeglądarką, która wygląda dobrze, dopóki ktoś nie poda jej uszkodzonego pliku. Ustawienie Active := True nie zgłasza wyjątku przy niepowodzeniu ładowania. Komponent PDFium przechwytuje błąd wewnętrzny i pozostawia Active na wartości False, więc jedynym uczciwym sposobem, by dowiedzieć się, czy dokument został otwarty, jest ponowne odczytanie tej właściwości po jej ustawieniu
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;
Na uwagę zasługują dwie rzeczy. Pierwsza to fakt, że PageNumber istnieje w obu obiektach i oba są niezależne. Pdf.PageNumber to wyobrażenie dokumentu o bieżącej stronie; PdfView.PageNumber to strona, którą kontrolka faktycznie wyświetla, i to tę wartość ustawiasz, by przemieszczać użytkownika po pliku. Ustawienie jednej nie zmienia drugiej, więc przeglądarka zawsze steruje właściwością widoku. Druga rzecz to indeksowanie od 1: strony biegną od 1 do Pdf.PageCount, a nie od 0, co zaskakuje każdego przyzwyczajonego do tablic opartych na zerze
Obsługa zaszyfrowanego pliku
Zaszyfrowane dokumenty podążają tą samą ścieżką ładowania. Jeśli hasło otwarcia zostanie ustawione przed aktywacją, dokument zostanie odszyfrowany podczas otwierania; jeśli jest błędne lub go brakuje, Active pozostaje False dokładnie tak, jak w przypadku uszkodzonego pliku. Rozwiązaniem jest więc wyświetlenie monitu o hasło i ponowna próba aktywacji
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;
Ponieważ niepowodzenie jest ciche zarówno w przypadku złego hasła, jak i uszkodzonego pliku, nie możesz odróżnić tych dwóch sytuacji tylko na podstawie Active. W praktyce jest to akceptowalne dla przeglądarki: użytkownik albo podaje właściwe hasło, albo dowiaduje się, że plik się nie otworzy, a komunikat w obu przypadkach brzmi tak samo
Stronicowanie dokumentu
Przy otwartym dokumencie nawigacja to arytmetyka na PdfView.PageNumber ograniczona przez Pdf.PageCount. Jedyną prawdziwą pracą jest ograniczenie wartości (clamping), tak aby przyciski nigdy nie wypchnęły strony poza zakres, a przyciski pierwszej i ostatniej strony pozostawały wyłączone na końcach pliku
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;
Pole tekstowe "idź do strony N" to to samo wywołanie GoToPage zasilane przetworzoną liczbą całkowitą, a ograniczenie obejmuje przypadek, gdy użytkownik wpisze 9999 do dziesięciostronicowego pliku. Zachowaj UpdatePageLabel jako jedyne miejsce, które wypisuje "Strona 3 z 12", aby odczyt nigdy nie stracił synchronizacji z tym, co pokazuje widok
Powiększenie: wyraźne wartości procentowe i tryby dopasowania
Powiększenie w TPdfView występuje w dwóch odmianach, które wchodzą ze sobą w interakcję, a zrozumienie tej interakcji stanowi różnicę między zachowującą się poprawnie kontrolką powiększenia, a taką, która walczy z użytkownikiem. Bezpośrednią drogą jest właściwość Zoom, procent, w którym 100 oznacza rzeczywisty rozmiar. Inną drogą jest FitMode, który każe widokowi obliczyć powiększenie za ciebie i ciągle je przeliczać w miarę zmiany rozmiaru okna
// 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
Oto część, w której ludzie popełniają błędy. Bezpośrednie przypisanie wartości Zoom resetuje FitMode do pfmNone. To poprawne zachowanie, a nie błąd: w momencie, gdy użytkownik wybiera dokładnie 150%, widok nie może już uwzględniać zasady "dopasuj do szerokości", ponieważ te dwa żądania są ze sobą sprzeczne. Konsekwencją dla interfejsu użytkownika jest to, że przyciski powiększenia i dopasowania do strony wykluczają się wzajemnie, a pasek narzędzi powinien uwidaczniać aktywny tryb. Kiedy użytkownik kliknie dopasowanie do strony, ustaw FitMode; kiedy kliknie powiększenie liczbowe, ustaw Zoom i pozwól, aby samo wyczyściło tryb dopasowania
Jeśli wolałbyś obliczyć wartość dopasowania samodzielnie, być może aby zainicjować suwak powiększenia z bieżącym procentem dopasowania, pomocniki obsługujące strony dostarczą ci odpowiednich liczb bez zmieniania trybu. PageWidthZoom[N], PageZoom[N] i ActualSizeZoom[N] zwracają procent powiększenia, który dopasowałby N-tą stronę do szerokości, wyświetlił ją w całości lub wyrenderował w rzeczywistym rozmiarze
// 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;
Czego tak naprawdę potrzebuje ukończona przeglądarka
Powyższa przeglądarka ma kilkadziesiąt linii, a już wykonuje pracę, jakiej potrzebuje przepływ pracy z dokumentami: otwiera plik, przeżywa otwarcie złego pliku, pokazuje stronę, porusza się między stronami i zmienia powiększenie ręcznie lub przez dopasowanie. PDFium bezszelestnie wykonuje trudne części. Osadzone czcionki są poprawnie rozwiązywane, adnotacje i pola formularzy rysują się tam, gdzie umieszcza je dokument, a strona, którą widzisz, odpowiada tej, którą widziałby użytkownik Chrome, ponieważ obie rysuje ten sam silnik
Wychodząc z tej bazy, dodatki są raczej stopniowe niż strukturalne. Wybór tekstu i wyszukiwanie czytają z tej samej warstwy tekstu, którą PDFium już buduje; metadane, takie jak Pdf.Title i Pdf.Author, to kwestia odczytania jednej właściwości; obrót i skala szarości to opcje renderowania, które przekazujesz, rysując stronę na mapie bitowej. Żadne z tych rozwiązań nie zmienia posiadanej tu struktury bazowej, którą stanowią: obiekt dokumentu, widok i przepływ procesu od załadowania do nawigacji, który je łączy. Wykonaj dobrze tę bazę, a cała reszta będzie już tylko ozdobą
Komponenty TPdf i TPdfView używane w tym artykule są częścią Komponentu PDFium dla Delphi i C++Builder, który zawiera pełną referencyjną implementację przeglądarki na stronie swojego produktu