Odborný článok

Vytvorenie interaktívneho prehliadača PDF v Delphi pomocou TPDFlibViewer

Vloženie panelu na čítanie PDF do podnikovej aplikácie v Delphi nevyžaduje písanie vykresľovacej slučky, rolovacieho modulu ani modelu priblíženia. losLab PDF Library obsahuje TPDFlibViewer, VCL ovládací prvok, ktorý otvorí dokument jedným volaním a prináša výber textu, posúvanie, marquee zoom (priblíženie výberom), snímky, zvýraznenie oblastí, zvýraznenie výsledkov fulltextového vyhľadávania, interakciu s anotáciami a tlač ako vstavané správanie. Tento článok vás prevedie piatimi krokmi, ktoré premenia prázdny formulár na funkčný prehliadač: umiestnenie prvku, prepojenie pripravených akcií, pridanie zvýraznenia vyhľadávania, spracovanie kliknutí na anotácie a tlač

Prečo použiť ovládací prvok prehliadača namiesto písania vykresľovacej slučky?

Ako prvý prichádza na rad argument o vrstvení, pretože vysvetľuje, čo si v skutočnosti kupujete. Vykreslenie stránky do bitmapy predstavuje jednoduchých 20 percent; zvyšných 80 percent je všetko ostatné okolo: vyrovnávacia pamäť stránkových bitmap s rozpočtom na uvoľňovanie pamäte, rozloženie rolovania naprieč stránkami rôznych veľkostí, režimy priblíženia, ktoré sa prepočítavajú pri zmene veľkosti, testovanie zásahov (hit-testing), ktoré mapuje pixel myši späť na súradnice stránky pri akomkoľvek priblížení a otočení, a plánovanie prekresľovania, ktoré nebliká. TPDFlibViewer toto všetko spravuje a prácu s rastrom deleguje na rovnaké jadro popísané v článku o viacmotorovom vykresľovaní, takže vrstva ovládacieho prvku a vrstva vykresľovania zostávajú samostatne vymeniteľné. Kód vášho formulára komunikuje s rolovateľným ovládacím prvkom systému Windows, nie s vykresľovačom

Ako vložiť prehliadač PDF do aplikácie v Delphi?

TPDFlibViewer.LoadFromFile otvorí dokument prostredníctvom vlastnej internej inštancie TPDFlib ovládacieho prvku a v prípade úspechu vráti 1, takže minimálny prehliadač pozostáva z volania konštruktora, priradenia vlastnosti Parent a jedného načítania. Ak už inštanciu TPDFlib držíte niekde inde v aplikácii, metóda AttachLibrary namiesto toho zobrazí jej vybraný dokument, pričom vlastníctvo zostáva na vašej strane

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;

Vlastnosť ViewerMode vyberá, čo robí myš, z piatich hodnôt: vmSelect je predvolený nástroj na výber textu, vmHand posúva ťahaním s kurzorom ruky, pričom obyčajné kliknutie stále nasleduje odkazy, vmZoom is a marquee zoom, ktorý priblíži ťahanú oblasť tak, aby vyplnila zobrazovaciu plochu, vmSnapshot zachytí ťahanú oblasť do schránky (s udalosťou OnSnapshot a prepínačom SnapshotToClipboard) a vmHighlight vykresľuje trvalé polopriehľadné zvýraznenia oblastí. Nástroj na zvýraznenie stojí za zmienku kvôli súradniciam: oblasti sú uložené v používateľskom priestore stránky, nie v pixeloch klientskej oblasti, takže značky pridané cez AddHighlightRegion prežijú zmenu priblíženia, otočenie pohľadu aj navigáciu po stránkach bez akéhokoľvek účtovníctva na vašej strane. V otočenom pohľade môže smer ťahania dokonca bežať proti osiam stránky, čo po inverznom mapovaní rotácie vedie k zápornej šírke alebo výške — ovládací prvok obdĺžnik znormalizuje za vás

Ako prepojiť panel nástrojov PDF v Delphi bez spojovacieho kódu?

Jednotka PDFlibViewerActions úplne odstraňuje spojovací kód panela nástrojov. Poskytuje dvadsať pripravených potomkov triedy TAction pokrývajúcich navigáciu po stránkach (TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), priblíženie (TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), otáčanie zobrazenia (TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), tlač, dialógy otvorenia a uloženia súboru, kopírovanie vybraného textu a jednu prepínaciu akciu pre každý nástroj myši (TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode). Každá akcia ukazuje na ovládací prvok prostredníctvom svojej zverejnenej vlastnosti Viewer a udržiava si vlastný stav: každá akcia sa sama deaktivuje, keď nie je otvorený žiadny dokument, navigačné akcie sledujú pozíciu stránky, kopírovanie sleduje, či existuje výber, a akcie režimu a prispôsobenia udržiavajú svoj začiarknutý stav zosynchronizovaný s aktívnym nástrojom a režimom priblíženia. V čase návrhu sa všetky registrujú v kategórii losLab PDF v editore zoznamu akcií (Action List); za behu programu majú tie isté triedy iba tri riadky kódu

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;

Ak nad týmto vzorom budujete vlastnú registráciu akcií, mali by ste vedieť o jednej pasci na úrovni rozsahu jednotiek. Metóda RegisterActions je deklarovaná v System.Actions, nie vo Vcl.ActnList, a chyba kompilátora, ktorú dostanete, ak máte v klauzule uses iba Vcl.ActnList, je vyslovene zavádzajúca: literál poľa tried [TAction1, TAction2, ...] sa začne spracovávať ako množina, čo spôsobí reťazec chýb E2010 Incompatible types: 'Integer' and 'class of ...', ktoré vyzerajú ako nezhoda typov vo vašich triedach akcií. Riešením je pridanie jednej jednotky do uses, ale diagnostika ukazuje všade inde, len nie na ňu. Pre panely nástrojov priblíženia reportuje TPDFlibViewer.EffectiveZoomPercent skutočne použité priblíženie — vrátane vypočítaného faktora v režimoch prispôsobenia na šírku a na stránku — takže krok priblíženia môže stavať na reálnom aktuálnom zväčšení a nie na poslednej hodnote, ktorú ste priradili

Zvýraznenie výsledkov vyhľadávania a prevrátenie súradníc v pozadí

Metóda HighlightSearchHits vyhľadá hľadaný výraz v celom dokumente, vykreslí polopriehľadný pruh nad každým nálezom a vráti počet výsledkov; metóda ClearSearchHighlights, vlastnosti SearchHighlightCount a SearchHighlightColor dopĺňajú toto rozhranie. Keďže obdĺžniky nálezov sú uložené v priestore stránky a preprojektujú sa pri každom prekreslení, pruhy zostávajú prichytené k svojmu textu aj vtedy, keď používateľ posúva, približuje a otáča zobrazenie. Ak použijete sprievodný TPDFlibSearchPanel, jeho metóda RunSearch automaticky vykreslí rovnaké pruhy v pripojenom prehliadači prostredníctvom vlastnosti HighlightInView, ktorá je predvolene 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;

Zložitou časťou a dôvodom, prečo uprednostniť vstavanú cestu pred vlastným riešením zo surových výsledkov vyhľadávania, je konverzia súradnicového systému. Textový engine hlási obdĺžniky zhody v súradniciach, ktoré závisia od nastavenia vlastnosti Origin knižnice: pri hodnote Origin 0 alebo 3 smeruje os Y zdola nahor ako v natívnom používateľskom priestore PDF a pri Origin 2 alebo 3 sa os X meria od pravej hrany. Prekrytie prehliadača pracuje v priestore stránky zhora nadol a zľava doprava, takže každý obdĺžnik zásahu sa musí pred uložením prevrátiť (Y = PageHeight - Bottom a v príslušných prípadoch X = PageWidth - Right) — prevzatie nahlásenej hornej hrany za nominálnu hodnotu pri predvolenom počiatku vykreslí zvýraznenie na vertikálne zrkadlovo otočenej pozícii na stránke. Ovládací prvok TPDFlibViewer vykonáva túto konverziu podľa aktívneho počiatku súradníc (origin) pri zaznamenávaní každého zásahu a potom pri kreslení aplikuje dopredné mapovanie rotácie, vďaka čomu pruhy pristanú správne pod akýmkoľvek uhlom. Samotná polopriehľadnosť využíva rozhranie API systému Windows AlphaBlend s konštantnou hodnotou source alpha, keďže plátno VCL nemá natívne alfa skladanie (alpha compositing). Základné rozhrania API pre vyhľadávanie textu a geometriu sú podrobne popísané v článku o vyhľadávaní textu a enumerácii prvkov stránky

Kliknutia na anotácie, tipy a export príloh

Ovládací prvok TPDFlibViewer považuje anotácie za interaktívne objekty prvej triedy. Kliknutie na anotáciu, ktorá nie je odkazom, vyvolá udalosť OnAnnotClick s číslom stránky, 1-indexovaným indexom anotácie a reťazcom subtypu; metóda AnnotAtPagePoint programovo sprístupňuje rovnaký test zásahu a vynecháva sprievodné anotácie typu Popup, takže vždy dostanete anotáciu, ktorú používateľ skutočne vidí. Prejdenie myšou nad anotáciou zobrazí text jej obsahu (Contents) ako tip (hint) ovládacieho prvku prostredníctvom prepínača AnnotHints, ktorý je predvolene zapnutý a spolupracuje so štandardnou vlastnosťou VCL ShowHint. Pri anotáciách typu FileAttachment zapíše metóda SaveAnnotAttachmentToFile vložený súbor na disk a v prípade úspechu vráti 1, čo dokončí pracovný postup uloženia na kliknutie v niekoľkých riadkoch

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;

Tlač a obmedzenia, s ktorými by ste mali počítať

Tlač nevyžaduje žiadnu samostatnú integráciu. Metóda PrintDoc zobrazí štandardný dialóg tlače systému Windows — výber tlačiarne, rozsah strán, počet kópií, triedenie — a odovzdá úlohu tlačovému enginu knižnice, zatiaľ čo volanie PrintDoc(False) odošle celý dokument na predvolenú tlačiareň úplne bez používateľského rozhrania. Metóda PrintDocRange funguje plne programovo s explicitným názvom tlačiarne, začiatočnou a koncovou stránkou, počtom kópií a príznakom triedenia, čo je správny vstupný bod pre dávkové alebo serverom spúšťané tlačové úlohy riadené z tej istej inštancie prehliadača

Dve obmedzenia si zaslúžia úprimnú zmienku. Po prvé, TPDFlibViewer je ovládací prvok VCL systému Windows; vrstva prehliadača je vylúčená z zostáv NOVCL, takže konzolové služby a ciele mimo VCL by mali priamo používať základné rozhranie API na vykresľovanie TPDFlib. Po druhé, bezhlavé (headless) použitie je zámerne podporované, ale obmedzené: metóda CaptureRegion vykreslí akúkoľvek klientsku oblasť — stránky plus zvýraznenia — do bitmapy vlastnenej volajúcim a funguje na ovládacom prvku bez priradeného rodiča (unparented) a bez rukoväte okna (window handle), pretože cesta prekresľovania sa vyhýba volaniam VCL, ktoré by vynútili vytvorenie rukoväte. Vďaka tomu je overenie na úrovni pixelov v automatizovaných testoch praktické, ale čokoľvek, čo skutočne vyžaduje slučku správ (message loop), ako sú interaktívne nástroje myši, stále patrí do reálnej UI relácie. Pokiaľ ide o výkon, ovládací prvok si udržiava vyrovnávaciu pamäť stránok s konfigurovateľným limitom prostredníctvom SetCacheLimit a dokáže uchovať vykreslené stránky naprieč reláciami; článok o diskovom cache prehliadača stránok podrobne pokrýva túto vrstvu a jej správanie pri DPI na jednotlivých monitoroch

Päť krokov teda znie: umiestniť ovládací prvok a zavolať LoadFromFile, pripojiť pripravené akcie z PDFlibViewerActions, zavolať HighlightSearchHits pre vyhľadávanie priamo na stránke, spracovať udalosť OnAnnotClick pre pracovné postupy s anotáciami a sprístupniť metódu PrintDoc. Každá tu zobrazená časť sa dodáva v štandardnej knižnici losLab PDF Library pre Delphi, kde sa ovládací prvok prehliadača, triedy akcií a vykresľovací engine inštalujú spoločne