Teknisk artikel

Bygg en interaktiv PDF-visare i Delphi med TPDFlibViewer

Att bädda in en panel för PDF-läsning i en Delphi-affärsapplikation kräver inte att du skriver en renderingsloop, en rullningsmotor eller en zoommodell. losLab PDF Library levereras med TPDFlibViewer, en VCL-kontroll som öppnar ett dokument med ett enda anrop och erbjuder textmarkering, panorering, markeringszoom, ögonblicksbilder, områdesmarkering, fulltextsökningsmarkering, interaktion med kommentarer och utskrift som inbyggt beteende. Denna artikel går igenom de fem stegen som förvandlar ett tomt formulär till en fungerande visare: dra och släpp kontrollen, koppla de färdiga åtgärderna, lägg till sökmarkering, hantera klick på kommentarer och skriv ut

Varför använda en visarkontroll istället för att skriva en renderingsloop?

Skiktargumentet kommer först, eftersom det förklarar vad du faktiskt köper. Att rendera en sida till en bitmapp är de enkla 20 procenten; de övriga 80 procenten är allt som omsluter det: en sidbitmappscache med en tömningsbudget, rullningslayout över sidor av olika storlekar, zoomlägen som räknas om vid storleksändring, träfftestning som mappar en muspixel tillbaka till sidkoordinater vid valfri zoom och rotation, samt schemaläggning av omritning som inte flimrar. TPDFlibViewer äger allt detta och delegerar rasterarbetet till samma motor som beskrivs i artikeln om rendering med flera motorer, så att kontrollskiktet och renderingsskiktet förblir separat utbytbara. Din formulärkod kommunicerar med en rullningsbar Windows-kontroll, inte med en renderare

Hur bäddar du in en PDF-visare i Delphi-applikationer?

TPDFlibViewer.LoadFromFile öppnar ett dokument genom kontrollens egna interna TPDFlib-instans och returnerar 1 vid framgång, så den minimala visaren är ett konstruktoranrop, en Parent-tilldelning och en laddning. Om du redan har en TPDFlib-instans någon annanstans i applikationen visar AttachLibrary dess valda dokument istället, medan äganderätten förblir på din sida

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;

Egenskapen ViewerMode väljer vad musen gör, utifrån fem värden: vmSelect är standardverktyget för textmarkering, vmHand panorerar genom att dra med en handpekare medan ett vanligt klick fortfarande följer länkar, vmZoom är en markeringszoom som förstorar det dragna området för att fylla visningsfönstret, vmSnapshot fångar ett draget område till urklipp (med en OnSnapshot-händelse och en SnapshotToClipboard-omkopplare) och vmHighlight ritar permanenta halvgenomskinliga områdesmarkeringar. Markeringsverktyget är värt en notering om koordinater: områden lagras i sidans användarutrymme, inte i klientpixlar, så markeringar med AddHighlightRegion överlever zoomning, vyrotation och sidnavigering utan någon bokföring från din sida. I en roterad vy kan dragriktningen till och med gå mot sidaxlarna, vilket ger en negativ bredd eller höjd efter den omvända rotationsmappningen – kontrollen normaliserar rektangeln åt dig

Hur kopplar du en PDF-verktygsrad i Delphi utan limkod?

Enheten PDFlibViewerActions tar helt bort limkoden för verktygsraden. Den tillhandahåller tjugo färdiga TAction-ättlingar som täcker sidnavigering (TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), zoom (TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), vyrotation (TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), utskrift, dialogrutor för att öppna och spara som, kopiera markerad text samt en omkopplingsåtgärd per musverktyg (TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode). Varje åtgärd pekar på en kontroll via sin publicerade egenskap Viewer och upprätthåller sitt eget tillstånd: varje åtgärd inaktiveras när inget dokument är öppet, navigeringsåtgärderna spårar sidpositionen, kopieringsåtgärden spårar om en markering finns och åtgärderna för läge och anpassning håller sitt markerade tillstånd synkroniserat med det aktiva verktyget och zoomläget. Vid designtid registreras alla i losLab PDF-kategorin i Action List-redigeraren; vid körtid är samma klasser tre rader vardera

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;

En fälla gällande enhetsomfång är värd att känna till om du bygger din egen åtgärdsregistrering ovanpå detta mönster. RegisterActions deklareras i System.Actions, inte i Vcl.ActnList, och kompilatorfelet du får med endast Vcl.ActnList i uses-klausulen är aktivt vilseledande: klassmatris-literalen [TAction1, TAction2, ...] faller tillbaka på att tolkas som en mängd, vilket ger en kedja av E2010 Incompatible types: 'Integer' and 'class of ...'-fel som ser ut som en typkonflikt i dina åtgärdsklasser. Lösningen är en enda post i uses-klausulen, men diagnostiken pekar överallt utom på den. För zoomverktygsrader rapporterar TPDFlibViewer.EffectiveZoomPercent den zoom som faktiskt används — inklusive den beräknade faktorn i lägena anpassa till bredd och anpassa till sida — så att ett zoomsteg kan bygga på den faktiska aktuella förstoringen snarare än på det senaste värdet du tilldelade

Sökmarkering och koordinatvändningen bakom den

HighlightSearchHits söker igenom hela dokumentet efter en söksträng och ritar ett halvgenomskinligt fält över varje matchning, samt returnerar antalet träffar; ClearSearchHighlights, SearchHighlightCount och egenskapen SearchHighlightColor kompletterar gränssnittet. Eftersom träffrektanglarna lagras i sidutrymmet och projiceras om vid varje ritning, förblir fälten fästa vid sin text medan användaren rullar, zoomar och roterar vyn. Om du använder den tillhörande TPDFlibSearchPanel, ritar dess RunSearch automatiskt samma fält i den anslutna visaren genom egenskapen HighlightInView, som är aktiverad som standard

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;

Den subtila delen, och anledningen till att föredra den inbyggda metoden framför att bygga en egen utifrån råa sökresultat, är konverteringen av koordinatsystem. Textmotorn rapporterar matchningsrektanglar i koordinater som beror på bibliotekets inställning för Origin: med Origin 0 eller 3 löper Y-axeln nerifrån och upp som i ursprungligt PDF-användarutrymme, och med Origin 2 och 3 mäts X-axeln från den högra kanten. Visarens överlägg fungerar i ett uppifrån-och-ned, vänsterbaserat sidutrymme, så varje träffrektangel måste vändas (Y = PageHeight - Bottom och i förekommande fall X = PageWidth - Right) innan den lagras — att acceptera den rapporterade överkanten som den är under standardursprunget ritar markeringen på den vertikalt speglade positionen på sidan. TPDFlibViewer utför denna konvertering enligt det aktiva ursprunget när den registrerar varje träff, och tillämpar sedan den framåtriktade rotationsmappningen vid ritningstid, vilket är anledningen till att fälten hamnar rätt i alla vinklar. Själva halvgenomskinligheten använder Windows AlphaBlend-API med en konstant käll-alfa, eftersom VCL-ritytan inte har någon inbyggd alfablandning. De underliggande API:erna för textsökning och geometri behandlas på djupet i artikeln om textsökning och uppräkning av sidelement

Klick på kommentarer, tips och export av bilagor

TPDFlibViewer behandlar kommentarer som förstklassiga interaktiva objekt. Om man klickar på en kommentar som inte är en länk utlöses OnAnnotClick med sidnumret, det 1-baserade kommentarsindexet och undertypssträngen; AnnotAtPagePoint exponerar samma träfftest programmatiskt och hoppar över tillhörande popup-kommentarer så att du alltid får den kommentar som användaren faktiskt ser. Att hovra över en kommentar visar dess innehållstext som kontrollens tips via omkopplaren AnnotHints, som är aktiverad som standard och samarbetar med standardegenskapen ShowHint i VCL. För FileAttachment-kommentarer skriver SaveAnnotAttachmentToFile den inbäddade filen till disken och returnerar 1 vid framgång, vilket slutför ett klicka-för-att-spara-arbetsflöde på bara några få rader

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;

Utskrift och de gränser du bör planera för

Utskrift kräver ingen separat integrering. PrintDoc visar standarddialogrutan för utskrift i Windows — val av skrivare, sidintervall, kopior, sortering — och överlämnar jobbet till bibliotekets utskriftsmotor, medan PrintDoc(False) skickar hela dokumentet till standardskrivaren utan något gränssnitt alls. PrintDocRange är helt programmatiskt med ett explicit skrivarnamn, start- och slutsida, antal kopior och sorteringsflagga, vilket är rätt ingångspunkt för batch- eller serverutlösta utskriftsjobb som drivs från samma visarinstans

Två begränsningar förtjänar att nämnas ärligt. För det första är TPDFlibViewer en Windows VCL-kontroll; visarskiktet är uteslutet från NOVCL-byggen, så konsoltjänster och mål utan VCL bör använda det underliggande TPDFlib-renderings-API:et direkt. För det andra stöds huvudlös (headless) användning avsiktligt men är begränsad: CaptureRegion renderar alla klientområden — sidor plus markeringar — till en bitmapp som ägs av anroparen och fungerar på en kontroll utan överordnad och utan ett fönsterhandtag, eftersom ritningsvägen undviker VCL-anrop som skulle tvinga fram skapandet av ett handtag. Detta gör verifiering på pixelnivå i automatiserade tester praktisk, men allt som verkligen behöver en meddelandeloop, såsom de interaktiva musverktygen, hör fortfarande hemma i en riktig UI-session. På prestandasidan har kontrollen en minnessidescache med en konfigurerbar budget via SetCacheLimit och kan bevara renderade sidor mellan sessioner; artikeln om visarens sidcache på disk täcker det skiktet och dess DPI-beteende per bildskärm i detalj

Fem steg, alltså: placera kontrollen och anropa LoadFromFile, anslut de färdiga åtgärderna från PDFlibViewerActions, anropa HighlightSearchHits för sökning på sidan, hantera OnAnnotClick för kommentarsarbetsflöden och exponera PrintDoc. Varje del som visas här ingår i standardversionen av losLab PDF Library för Delphi, där visarkontrollen, åtgärdsklasserna och renderingsmotorn installeras tillsammans