Technický článek

Vytvoření interaktivního prohlížeče PDF v Delphi pomocí TPDFlibViewer

Vložení panelu pro čtení PDF do podnikové aplikace v Delphi nevyžaduje psaní vykreslovací smyčky, posuvného mechanismu ani modelu přiblížení. losLab PDF Library obsahuje TPDFlibViewer, VCL komponentu, která otevře dokument jediným voláním a přináší výběr textu, posouvání (pan), výběrové přiblížení (marquee zoom), snímky obrazovky, zvýraznění oblastí, zvýraznění fulltextového vyhledávání, interakci s anotacemi a tisk jako vestavěné funkce. Tento článek vás provede pěti kroky, které promění prázdný formulář ve funkční prohlížeč: vložení komponenty, propojení předpřipravených akcí, přidání zvýraznění vyhledávání, zpracování kliknutí na anotace a tisk

Proč použít komponentu prohlížeče namísto psaní vykreslovací smyčky?

Na prvním místě je argument vrstvení, protože vysvětluje, co vlastně získáváte. Vykreslení stránky do bitmapy představuje snadných 20 procent; zbývajících 80 procent je vše ostatní kolem toho: mezipaměť bitmap stránek s limitem uvolňování, rozvržení posouvání napříč stránkami různých velikostí, režimy přiblížení, které se přepočítávají při změně velikosti, testování zásahů (hit-testing), které mapuje pixel myši zpět na souřadnice stránky při jakémkoli přiblížení a otočení, a plánování překreslování bez blikání. Komponenta TPDFlibViewer toto vše zajišťuje a rastrovou práci deleguje na stejné jádro popsané v článku o vykreslování s více jádry, takže vrstva ovládacího prvku a vykreslovací vrstva zůstávají samostatně vyměnitelné. Kód vašeho formuláře komunikuje s posuvným prvkem Windows, nikoli s vykreslovačem

Jak vložit prohlížeč PDF do aplikace v Delphi?

Metoda TPDFlibViewer.LoadFromFile otevře dokument prostřednictvím vlastní interní instance TPDFlib tohoto ovládacího prvku a při úspěchu vrátí 1, takže minimální prohlížeč představuje volání konstruktoru, přiřazení Parent a jedno načtení. Pokud již máte instanci TPDFlib na jiném místě v aplikaci, metoda AttachLibrary místo toho zobrazí její vybraný dokument, přičemž vlastnictví zůstává na vaší straně

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;  // pan by dragging; a plain click still follows links
end;

Vlastnost ViewerMode vybírá činnost myši z pěti hodnot: vmSelect je výchozí nástroj pro výběr textu, vmHand posouvá tažením s kurzorem ruky, zatímco prosté kliknutí stále sleduje odkazy, vmZoom je výběrové přiblížení, které zvětší taženou oblast tak, aby vyplnila zobrazení, vmSnapshot zachytí taženou oblast do schránky (s událostí OnSnapshot a přepínačem SnapshotToClipboard) a vmHighlight vykresluje trvalé průsvitné zvýraznění oblastí. Nástroj pro zvýraznění stojí za zmínku ohledně souřadnic: oblasti se ukládají v uživatelském prostoru stránky, nikoli v klientských pixelech, takže značky vytvořené pomocí AddHighlightRegion přečkají přibližování, otáčení pohledu i navigaci po stránkách bez jakékoli evidence z vaší strany. V otočeném pohledu může směr tažení dokonce běžet proti osám stránky, což po inverzním mapování rotace vede k záporné šířce nebo výšce — ovládací prvek za vás obdélník normalizuje

Jak propojit panel nástrojů PDF v Delphi bez propojovacího kódu?

Unita PDFlibViewerActions zcela odstraňuje nutnost propojovacího kódu pro panel nástrojů. Poskytuje dvacet předpřipravených potomků TAction pokrývajících navigaci po stránkách (TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), přiblížení (TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), otočení pohledu (TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), tisk, dialogy pro otevření a uložení souboru, kopírování vybraného textu a jednu přepínací akci pro každý nástroj myši (TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode). Každá akce ukazuje na ovládací prvek prostřednictvím své publikované vlastnosti Viewer a udržuje si vlastní stav: každá akce se deaktivuje, pokud není otevřen žádný dokument, navigační akce sledují pozici stránky, kopírování sleduje, zda existuje výběr, a akce režimu a přizpůsobení udržují svůj zaškrtnutý stav v souladu s aktivním nástrojem a režimem přiblížení. V době návrhu se všechny registrují v kategorii losLab PDF editoru Action List; za běhu aplikace představují stejné třídy pouhé tři řádky kódu každá

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;

Pokud stavíte vlastní registraci akcí na tomto vzoru, je dobré vědět o jedné pasti v rozsahu unit. Metoda RegisterActions je deklarována v System.Actions, nikoli ve Vcl.ActnList, a chyba kompilátoru, kterou obdržíte, pokud máte v klauzuli uses pouze Vcl.ActnList, je silně zavádějící: literál třídního pole [TAction1, TAction2, ...] se interpretuje jako množina, což generuje řadu chyb E2010 Incompatible types: 'Integer' and 'class of ...', které vypadají jako nesoulad typů ve vašich třídách akcí. Nápravou je přidání jediné položky do uses, avšak diagnostika ukazuje všude jinde než na ni. U panelů nástrojů pro přiblížení hlásí TPDFlibViewer.EffectiveZoomPercent skutečně účinné přiblížení — včetně vypočítaného faktoru v režimech přizpůsobení šířce a stránce — takže krok přiblížení může vycházet ze skutečného aktuálního zvětšení, nikoli z poslední hodnoty, kterou jste přiřadili

Zvýraznění výsledků vyhledávání a otočení souřadnic v pozadí

Metoda HighlightSearchHits prohledá celý dokument na zadaný dotaz, vykreslí průsvitný pruh nad každou shodou a vrátí počet nalezených výskytů; metody ClearSearchHighlights, SearchHighlightCount a vlastnost SearchHighlightColor doplňují toto rozhraní. Vzhledem k tomu, že obdélníky výskytů jsou uloženy v prostoru stránky a překreslovány při každém vykreslení, zůstávají pruhy spojené s textem, i když uživatel posouvá, přibližuje nebo otáčí pohled. Pokud použijete doprovodný panel TPDFlibSearchPanel, jeho metoda RunSearch automaticky vykreslí stejné pruhy v připojeném prohlížeči pomocí vlastnosti HighlightInView, která je ve výchozím nastavení zapnutá

procedure TMainForm.btnSearchClick(Sender: TObject);
var
  Hits: Integer;
begin
  FViewer.SearchHighlightColor := clYellow;
  Hits := FViewer.HighlightSearchHits(edtQuery.Text, False);  // case-insensitive
  StatusBar1.SimpleText := Format('%d matches', [Hits]);
end;

Nejjemnější částí a důvodem, proč dát přednost vestavěné cestě před vytvářením vlastní z čistých výsledků vyhledávání, je převod souřadnicového systému. Textové jádro hlásí obdélníky shod v souřadnicích, které závisí na nastavení knihovny Origin: s Origin 0 nebo 3 osy Y běží zdola nahoru jako v nativním uživatelském prostoru PDF a s Origin 2 nebo 3 se osa X měří od pravého okraje. Překryvná vrstva prohlížeče pracuje v prostoru stránky shora dolů a zleva doprava, takže každý obdélník výskytu musí být před uložením převrácen (Y = PageHeight - Bottom, a případně X = PageWidth - Right) — braní nahlášeného horního okraje za nominální hodnotu pod výchozím počátkem vykreslí zvýraznění na vertikálně zrcadlené pozici na stránce. TPDFlibViewer provádí tento převod podle aktivního počátku při záznamu každého výskytu a poté při vykreslování aplikuje dopředné mapování rotace, což je důvod, proč pruhy dopadnou správně v každém úhlu. Samotná průsvitnost využívá rozhraní Windows AlphaBlend API s konstantní hodnotou source alpha, protože plátno VCL nemá nativní alfa kompozici. Podrobně se vyhledáváním textu a geometrickým rozhraním API zabývá článek o vyhledávání textu a výčtu prvků stránky

Kliknutí na anotace, nápovědy a export příloh

TPDFlibViewer přistupuje k anotacím jako k prvkům první kategorie. Kliknutí na neodkazovou anotaci vyvolá událost OnAnnotClick s číslem stránky, 1-indexovaným indexem anotace a řetězcem subtypu; metoda AnnotAtPagePoint programově zpřístupňuje stejný test zásahu a přeskakuje doprovodné vyskakovací (Popup) anotace, takže vždy získáte anotaci, kterou uživatel skutečně vidí. Najetí myší na anotaci zobrazí její text Contents jako nápovědu ovládacího prvku prostřednictvím přepínače AnnotHints, který je ve výchozím nastavení zapnutý a spolupracuje se standardní vlastností VCL ShowHint. U anotací typu FileAttachment zapíše metoda SaveAnnotAttachmentToFile vložený soubor na disk a při úspěchu vrátí 1, což završuje postup uložení kliknutím na několik řádků kódu

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;

Tisk a omezení, se kterými byste měli počítat

Tisk nevyžaduje žádnou samostatnou integraci. Metoda PrintDoc zobrazí standardní tiskový dialog Windows — výběr tiskárny, rozsah stránek, kopie, kompletování — a předá úlohu tiskovému jádru knihovny, zatímco PrintDoc(False) odešle celý dokument na výchozí tiskárnu bez jakéhokoli uživatelského rozhraní. Metoda PrintDocRange funguje plně programově s explicitním názvem tiskárny, počáteční a koncovou stránkou, počtem kopií a příznakem kompletování, což je správný přístupový bod pro dávkové tiskové úlohy nebo úlohy spouštěné serverem z téže instance prohlížeče

Dvě omezení zasluhují upřímné zmínění. Za prvé, TPDFlibViewer je ovládací prvek pro Windows VCL; vrstva prohlížeče je vyloučena z sestavení NOVCL, takže konzolové služby a cíle bez VCL by měly používat přímo základní vykreslovací rozhraní API TPDFlib. Za druhé, bezhlavé (headless) použití je záměrně podporováno, ale omezeno: metoda CaptureRegion vykreslí jakoukoli klientskou oblast — stránky plus zvýraznění — do bitmapy vlastněné volajícím a funguje na ovládacím prvku bez rodiče (parent) a bez popisovače okna (window handle), protože cesta vykreslování se vyhýbá voláním VCL, která by vynutila vytvoření popisovače. To umožňuje praktické ověřování na úrovni pixelů v automatizovaných testech, avšak cokoli, co skutečně vyžaduje smyčku zpráv, jako jsou interaktivní nástroje myši, stále patří do reálné relace uživatelského rozhraní. Co se týče výkonu, ovládací prvek udržuje mezipaměť stránek v paměti s konfigurovatelným limitem prostřednictvím SetCacheLimit a může ukládat vykreslené stránky i mezi relacemi; podrobně tuto vrstvu a její chování DPI pro jednotlivé monitory rozebírá článek o diskové mezipaměti stránek prohlížeče

Tedy pět kroků: umístěte komponentu a zavolejte LoadFromFile, připojte předpřipravené akce z PDFlibViewerActions, zavolejte HighlightSearchHits pro vyhledávání na stránce, zpracujte OnAnnotClick pro práci s anotacemi a zpřístupněte PrintDoc. Každá zde uvedená součást se dodává ve standardní losLab PDF Library pro Delphi, kde se společně instalují komponenta prohlížeče, třídy akcí i vykreslovací jádro