Osadzenie panelu odczytu PDF w aplikacji biznesowej w Delphi nie wymaga pisania pętli renderowania, silnika przewijania ani modelu skalowania. Biblioteka losLab PDF Library dostarcza komponent TPDFlibViewer — kontrolkę VCL, która otwiera dokument jednym wywołaniem i zapewnia wbudowaną obsługę zaznaczania tekstu, przesuwania (panning), powiększania obszaru (marquee zoom), tworzenia zrzutów ekranu, podświetlania regionów, podświetlania wyników wyszukiwania pełnotekstowego, interakcji z adnotacjami oraz drukowania. Ten artykuł opisuje pięć kroków, które zamieniają pusty formularz w działającą przeglądarkę: umieszczenie kontrolki, podłączenie gotowych akcji, dodanie podświetlania wyników wyszukiwania, obsługa kliknięć w adnotacje i drukowanie
Dlaczego warto użyć kontrolki przeglądarki zamiast pisać własną pętlę renderowania?
Argument podziału na warstwy jest najważniejszy, ponieważ wyjaśnia, co tak naprawdę zyskujesz. Renderowanie strony do mapy bitowej to zaledwie 20 procent pracy; pozostałe 80 procent to cała otoczka: pamięć podręczna stron z budżetem usuwania (eviction budget), układ przewijania stron o różnych rozmiarach, tryby powiększenia przeliczane przy zmianie rozmiaru okna, testy trafień (hit-testing) mapujące piksele myszy na współrzędne strony przy dowolnym powiększeniu i obrocie, a także harmonogram odświeżania eliminujący migotanie. Komponent TPDFlibViewer obsługuje to wszystko, przekazując zadania rastrowania do tego samego silnika, który opisano w artykule o renderowaniu wielosilnikowym, dzięki czemu warstwa kontrolna i warstwa renderowania pozostają niezależnie wymienialne. Kod Twojego formularza komunikuje się z przewijaną kontrolką Windows, a nie z rendererem
Jak osadzić przeglądarkę PDF w aplikacji w Delphi?
Metoda TPDFlibViewer.LoadFromFile otwiera dokument za pomocą wewnętrznej instancji TPDFlib kontrolki i zwraca wartość 1 w przypadku powodzenia, więc minimalna przeglądarka to wywołanie konstruktora, przypisanie Parent i jedno wczytanie. Jeśli posiadasz już instancję TPDFlib w innym miejscu aplikacji, metoda AttachLibrary wyświetli wybrany dokument w przeglądarce, przy czym prawo własności do obiektu pozostanie po Twojej stronie
uses PDFlibViewer;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FViewer := TPDFlibViewer.Create(Self);
FViewer.Parent := Self;
FViewer.Align := alClient;
if FViewer.LoadFromFile('quarterly-report.pdf', '') = 1 then
FViewer.ViewerMode := vmHand; // przesuwanie poprzez przeciąganie; pojedyncze kliknięcie nadal podąża za linkami
end;
Właściwość ViewerMode określa działanie myszy i może przybierać jedną z pięciu wartości: vmSelect to domyślne narzędzie zaznaczania tekstu, vmHand umożliwia przesuwanie poprzez przeciąganie z kursorem dłoni (przy czym kliknięcie nadal podąża za linkami), vmZoom to powiększanie obszaru powiększające zaznaczony pas do rozmiaru okna, vmSnapshot przechwytuje przeciągnięty obszar do schowka (z obsługą zdarzenia OnSnapshot i przełącznika SnapshotToClipboard), a vmHighlight rysuje trwałe, półprzezroczyste wyróżnienia obszarów. Narzędzie wyróżniania wymaga uwagi w kwestii współrzędnych: obszary są zapisywane w przestrzeni użytkownika strony, a nie w pikselach klienta, dzięki czemu wyróżnienia utworzone za pomocą AddHighlightRegion zachowują swoje położenie przy powiększaniu, obrocie widoku i zmianie stron bez konieczności dodatkowych obliczeń z Twojej strony. W obróconym widoku kierunek przeciągania może być nawet przeciwny do osi strony, co po odwróceniu obrotu daje ujemną szerokość lub wysokość — kontrolka automatycznie normalizuje ten prostokąt
Jak podłączyć pasek narzędzi PDF w Delphi bez pisania kodu pośredniczącego?
Moduł PDFlibViewerActions eliminuje potrzebę ręcznego pisania kodu pośredniczącego dla paska narzędzi. Dostarcza on dwadzieścia gotowych klas pochodnych TAction, obejmujących nawigację po stronach (TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), powiększenie (TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), obrót widoku (TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), drukowanie, okna dialogowe otwierania i zapisu pliku, kopiowanie zaznaczonego tekstu oraz po jednej akcji przełączania na każde narzędzie myszy (TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode). Każda akcja wskazuje na kontrolkę za pomocą opublikowanej właściwości Viewer i zarządza własnym stanem: wyłącza się automatycznie, gdy żaden dokument nie jest otwarty, akcje nawigacyjne śledzą pozycję strony, akcja kopiowania sprawdza obecność zaznaczenia, a akcje trybu i dopasowania synchronizują swój stan zaznaczenia (checked) z aktywnym narzędziem i trybem powiększenia. W czasie projektowania wszystkie są widoczne w kategorii losLab PDF edytora listy akcji (Action List); w kodzie te same klasy to zaledwie po trzy linie kodu każda
uses System.Actions, Vcl.ActnList, PDFlibViewer, PDFlibViewerActions;
procedure TMainForm.BuildToolbar;
var
NextPage: TPDFlibViewerNextPage;
FitWidth: TPDFlibViewerFitWidth;
HandMode: TPDFlibViewerHandMode;
begin
NextPage := TPDFlibViewerNextPage.Create(Self);
NextPage.ActionList := ActionList1;
NextPage.Viewer := FViewer;
btnNext.Action := NextPage;
FitWidth := TPDFlibViewerFitWidth.Create(Self);
FitWidth.ActionList := ActionList1;
FitWidth.Viewer := FViewer;
btnFitWidth.Action := FitWidth;
HandMode := TPDFlibViewerHandMode.Create(Self);
HandMode.ActionList := ActionList1;
HandMode.Viewer := FViewer;
btnHand.Action := HandMode;
end;
Warto wiedzieć o jednej pułapce związanej z zasięgiem modułów, jeśli budujesz własną rejestrację akcji na tym wzorcu. Procedura RegisterActions jest zadeklarowana w System.Actions, a nie w Vcl.ActnList, a błąd kompilatora, który pojawia się przy umieszczeniu w sekcji uses wyłącznie Vcl.ActnList, jest bardzo mylący: tablica klas [TAction1, TAction2, ...] jest wówczas interpretowana jako zbiór, co generuje serię błędów E2010 Incompatible types: 'Integer' and 'class of ...' sugerujących niezgodność typów w klasach akcji. Rozwiązaniem jest dodanie modułu do sekcji uses, lecz sam komunikat błędu wskazuje na wszystko inne. W przypadku pasków powiększenia, właściwość TPDFlibViewer.EffectiveZoomPercent zwraca powiększenie rzeczywiste — włączając w to obliczony współczynnik w trybach dopasowania do szerokości i strony — dzięki czemu krok przybliżenia może opierać się na faktycznym powiększeniu, a nie na ostatnio przypisanej wartości
Podświetlanie wyników wyszukiwania i związane z nim odwrócenie współrzędnych
Metoda HighlightSearchHits przeszukuje cały dokument pod kątem podanego zapytania i nakłada półprzezroczysty pasek na każde dopasowanie, zwracając liczbę trafień. Uzupełniają ją metoda ClearSearchHighlights, właściwość SearchHighlightCount oraz właściwość SearchHighlightColor. Ponieważ prostokąty dopasowań są przechowywane w przestrzeni strony i rzutowane na nowo przy każdym rysowaniu, paski pozostają powiązane z tekstem podczas przewijania, powiększania i obracania widoku przez użytkownika. W przypadku korzystania z towarzyszącego panelu TPDFlibSearchPanel, jego metoda RunSearch automatycznie nanosi te same paski na dołączoną przeglądarkę dzięki właściwości HighlightInView, która jest domyślnie włączona
procedure TMainForm.btnSearchClick(Sender: TObject);
var
Hits: Integer;
begin
FViewer.SearchHighlightColor := clYellow;
Hits := FViewer.HighlightSearchHits(edtQuery.Text, False); // wielkość liter bez znaczenia
StatusBar1.SimpleText := Format('%d matches', [Hits]);
end;
Subtelną kwestią, dla której warto wybrać wbudowaną ścieżkę zamiast samodzielnego mapowania surowych wyników wyszukiwania, jest konwersja układu współrzędnych. Silnik tekstowy raportuje prostokąty dopasowań we współrzędnych zależnych od ustawienia Origin biblioteki: przy Origin o wartości 0 lub 3 oś Y biegnie od dołu do góry (jak w natywnej przestrzeni użytkownika PDF), a przy Origin o wartości 2 lub 3 oś X jest mierzona od prawej krawędzi. Nakładka przeglądarki działa w układzie zorientowanym od góry do dołu i od lewej, więc każdy prostokąt dopasowania musi zostać odwrócony (Y = PageHeight - Bottom oraz w odpowiednich przypadkach X = PageWidth - Right) przed zapisaniem. Przyjęcie zgłoszonej górnej krawędzi bez przeliczenia przy domyślnym punkcie odniesienia spowodowałoby narysowanie wyróżnienia w pionowym odbiciu lustrzanym na stronie. Komponent TPDFlibViewer wykonuje tę konwersję zgodnie z aktywnym punktem odniesienia (origin) przy rejestrowaniu każdego trafienia, a następnie stosuje obrót w czasie rysowania, dlatego paski układają się poprawnie pod każdym kątem. Sama półprzezroczystość wykorzystuje systemowe API AlphaBlend ze stałą wartością alfa, jako że płótno VCL nie obsługuje natywnego składania kanału alfa. Podstawowe API wyszukiwania tekstu i geometrii opisano szczegółowo w artykule o wyszukiwaniu tekstu i enumeracji elementów strony
Kliknięcia w adnotacje, podpowiedzi i eksport załączników
Komponent TPDFlibViewer traktuje adnotacje jako w pełni interaktywne obiekty. Kliknięcie adnotacji niebędącej linkiem wyzwala zdarzenie OnAnnotClick z numerem strony, indeksem adnotacji (liczonym od 1) oraz typem adnotacji (subtype). Funkcja AnnotAtPagePoint udostępnia ten sam test trafień programowo i pomija adnotacje pomocnicze typu Popup, dzięki czemu zawsze otrzymujesz adnotację faktycznie widzianą przez użytkownika. Najechanie myszą na adnotację wyświetla jej zawartość (tekst Contents) jako podpowiedź (hint) kontrolki za pomocą przełącznika AnnotHints, który jest domyślnie włączony i współdziała ze standardową właściwością VCL ShowHint. W przypadku adnotacji typu FileAttachment, metoda SaveAnnotAttachmentToFile zapisuje osadzony plik na dysku i zwraca wartość 1 w przypadku powodzenia, co pozwala zrealizować schemat kliknij-aby-zapisać w zaledwie kilku liniach kodu
procedure TMainForm.ViewerAnnotClick(Sender: TObject; APage, AnnotIndex: Integer;
const Subtype: WideString);
begin
if Subtype = 'FileAttachment' then
if FViewer.SaveAnnotAttachmentToFile(APage, AnnotIndex,
'C:\Temp\attached-invoice.xml') = 1 then
StatusBar1.SimpleText := 'Attachment saved';
end;
Drukowanie oraz ograniczenia, które należy uwzględnić
Drukowanie nie wymaga dodatkowej integracji. Metoda PrintDoc wyświetla standardowe okno drukowania Windows — wybór drukarki, zakres stron, kopie, sortowanie — i przekazuje zadanie do silnika druku biblioteki, podczas gdy PrintDoc(False) wysyła cały dokument do domyślnej drukarki bez wyświetlania interfejsu użytkownika. Metoda PrintDocRange działa w pełni programowo, przyjmując jawną nazwę drukarki, stronę początkową i końcową, liczbę kopii oraz flagę sortowania, co stanowi właściwy punkt wejścia dla zadań drukowania wsadowego lub sterowanego z serwera za pomocą tej samej instancji przeglądarki
Dwa ograniczenia zasługują na szczere omówienie. Po pierwsze, TPDFlibViewer to kontrolka Windows VCL; warstwa przeglądarki jest wykluczona z kompilacji NOVCL, stąd usługi konsolowe i cele inne niż VCL powinny bezpośrednio korzystać z bazowego API renderowania TPDFlib. Po drugie, użycie w trybie bezgłowym (headless) jest wspierane, lecz ma swoje granice: metoda CaptureRegion renderuje dowolny obszar klienta — strony wraz z wyróżnieniami — do mapy bitowej przekazanej przez wywołującego i działa na kontrolce pozbawionej rodzica (parent) i uchwytu okna, jako że ścieżka rysowania unika wywołań VCL wymuszających utworzenie uchwytu. Ułatwia to weryfikację pikseli w testach automatycznych, lecz operacje wymagające pętli komunikatów, takie jak interaktywne narzędzia myszy, nadal wymagają rzeczywistej sesji interfejsu graficznego. Pod kątem wydajności, kontrolka utrzymuje pamięć podręczną stron w pamięci RAM z konfigurowalnym limitem za pomocą SetCacheLimit oraz może zapisywać wyrenderowane strony między sesjami; artykuł o dyskowej pamięci podręcznej przeglądarki opisuje szczegółowo tę warstwę i jej zachowanie przy DPI na monitor
Podsumowując, pięć kroków: umieść kontrolkę i wywołaj LoadFromFile, dołącz gotowe akcje z PDFlibViewerActions, wywołaj HighlightSearchHits dla wyszukiwania na stronie, obsłuż zdarzenie OnAnnotClick dla przepływów pracy z adnotacjami i udostępnij PrintDoc. Każdy opisany element jest częścią standardowej wersji losLab PDF Library dla Delphi, gdzie kontrolka przeglądarki, klasy akcji oraz silnik renderowania instalują się wspólnie