在 Delphi 商務應用程式中嵌入 PDF 閱讀面板,不需要撰寫轉譯迴圈、捲動引擎或縮放模型。losLab PDF 函式庫提供了 TPDFlibViewer,這是一個 VCL 控制項,只需一次呼叫即可開啟文件,並帶來文字選取、平移、區域縮放、快照、區域醒目提示、全文搜尋醒目提示、註解互動和列印等內建行為。本文將逐步說明將空白表單轉換為可用檢視器的五個步驟:置放控制項、連接隨插即用動作、加入搜尋醒目提示、處理註解點擊以及列印。
為什麼使用檢視器控制項而不是自己撰寫轉譯迴圈?
分層論點是最重要的,因為它解釋了您實際買到了什麼。將頁面轉譯為點陣圖只是簡單的 20%;其他 80% 是圍繞在其周圍的所有內容:具有淘汰機制的頁面點陣圖快取、跨不同大小頁面的捲動版面配置、在調整大小後重新計算的縮放模式、在任何縮放和旋轉下將滑鼠像素對應回頁面座標的命中測試,以及不會閃爍的重繪排程。TPDFlibViewer 擁有所有這些功能,並將點陣處理工作委託給多引擎轉譯文章中描述的相同引擎,因此控制項層 and 轉譯層可以保持獨立替換。您的表單程式碼是與可捲動的 Windows 控制項進行溝通,而不是與轉譯器溝通。
如何在 Delphi 應用程式中嵌入 PDF 檢視器?
TPDFlibViewer.LoadFromFile 透過控制項自身的內部 TPDFlib 實例開啟文件,並在成功時傳回 1,因此最簡單的檢視器只需呼叫建構函式、指派 Parent 並執行一次載入。如果您已經在應用程式的其他地方持有 TPDFlib 實例,AttachLibrary 則會改為顯示其選取的文件,而所有權仍保留在您這邊。
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;
ViewerMode 屬性可從五個值中選擇滑鼠的行為:vmSelect 是預設的文字選取工具;vmHand 透過使用抓取游標拖曳來平移,而單純的點擊仍會跟隨連結;vmZoom 是區塊縮放,可放大拖曳的區域以填滿檢視區;vmSnapshot 會將拖曳的區域擷取到剪貼簿(具有 OnSnapshot 事件和 SnapshotToClipboard 切換開關);而 vmHighlight 則會繪製常駐的半透明區域醒目提示。醒目提示工具在座標方面值得注意:區域是儲存在頁面使用者空間中,而不是用戶端像素中,因此 AddHighlightRegion 標記在縮放、檢視旋轉和頁面導覽後仍能保留,您無需進行任何記錄工作。在旋轉的檢視中,拖曳方向甚至可以與頁面軸向相反,這在反向旋轉對應後會產生負的寬度或高度 —— 控制項會自動為您將矩形規格化。
如何在 Delphi 中連接 PDF 工具列而不需要膠水程式碼?
PDFlibViewerActions 單元完全移除了工具列的膠水程式碼。它提供了 20 個隨插即用的 TAction 衍生類別,涵蓋頁面導覽(TPDFlibViewerFirstPage、TPDFlibViewerPriorPage、TPDFlibViewerNextPage、TPDFlibViewerLastPage)、縮放(TPDFlibViewerZoomIn、TPDFlibViewerZoomOut、TPDFlibViewerFitWidth、TPDFlibViewerFitPage、TPDFlibViewerActualSize)、檢視旋轉(TPDFlibViewerRotateClockwise、TPDFlibViewerRotateAntiClockwise)、列印、檔案開啟與另存新檔對話方塊、複製選取文字,以及每個滑鼠工具對應的切換動作(TPDFlibViewerSelectMode、TPDFlibViewerHandMode、TPDFlibViewerMarqueeZoomMode、TPDFlibViewerSnapshotMode、TPDFlibViewerHighlightMode)。每個動作都透過其發布的 Viewer 屬性指向一個控制項並維護其自身的狀態:在未開啟任何文件時,每個動作都會自動停用;導覽動作會追蹤頁面位置;複製動作會追蹤是否存在選取內容;而模式與符合動作則會與作用中的工具和縮放模式保持選取狀態同步。在設計時,它們都會註冊在 Action List 編輯器的 losLab PDF 類別中;在執行階段,相同的類別程式碼只需三行。
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;
如果您在此模式之上建置自己的動作註冊,有一個單元範圍的陷阱值得注意。RegisterActions 是宣告在 System.Actions 中,而不是在 Vcl.ActnList 中,如果在 uses 子句中只包含 Vcl.ActnList,您會得到一個非常具有誤導性的編譯器錯誤:[TAction1, TAction2, ...] 類別陣列字面值會退化為被解析為集合,從而產生一連串的 E2010 類型不相容:'Integer' 與 'class of ...' 錯誤,看起來像是動作類別中的類型不相容。解決方法是加入一個 uses 項目,但診斷資訊卻指向了除它以外的所有地方。對於縮放工具列,TPDFlibViewer.EffectiveZoomPercent 會報告實際生效的縮放比例 —— 包括符合寬度和符合頁面模式中計算出的係數 —— 因此放大步驟可以基於真實的目前放大倍率,而不是您最後指派的值。
搜尋結果醒目提示及其背後的座標翻轉
HighlightSearchHits 會在整個文件中搜尋查詢字串,並在每個相符項上繪製一個半透明列,並傳回符合次數;ClearSearchHighlights、SearchHighlightCount 和 SearchHighlightColor 屬性則使該功能更完整。因為符合的矩形是儲存在頁面空間中,並在每次繪製时重新投影,因此在使用者捲動、縮放和旋轉檢視時,醒目提示列會始終與其文字保持貼合。如果您使用隨附的 TPDFlibSearchPanel,其 RunSearch 會透過預設啟用的 HighlightInView 屬性,自動在附加的檢視器中繪製相同的醒目提示列。
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;
最微妙的部分(也是為什麼選擇內建途徑而不是從原始搜尋結果中自行設計的原因)是座標系統的轉換。文字引擎報告的符合矩形座標取決於函式庫的 Origin 設定:當 Origin 為 0 或 3 時,Y 軸是如原生 PDF 使用者空間中那樣自底向上延伸;當 Origin 為 2 或 3 時,X 軸是從右邊緣開始測量。檢視器的重疊層是在自頂向下、靠左的頁面空間中運作,因此在儲存每個符合矩形之前,必須對其進行翻轉(Y = PageHeight - Bottom,在適用處 X = PageWidth - Right) —— 在預設原點下直接採用報告的頂部邊緣值,會在頁面垂直鏡像的位置繪製醒目提示。TPDFlibViewer 在記錄每次符合時會根據作用中的原點進行此轉換,然後在繪製時套用正向旋轉對應,這就是為什麼醒目提示列能在每個角度都正確呈現的原因。由於 VCL 畫布沒有原生的 Alpha 混合功能,半透明效果本身是使用具有常數來源 Alpha 值的 Windows AlphaBlend API 實現。底層的文字搜尋和幾何 API 在文字搜尋與頁面元素列舉文章中有深入介紹。
註解點擊、提示與附件匯出
TPDFlibViewer 將註解視為第一類互動式物件。點擊非連結註解會觸發 OnAnnotClick,並帶有頁碼、以 1 為基準的註解索引以及子類型字串;AnnotAtPagePoint 透過程式設計方式公開相同的命中測試,並跳過快顯(Popup)隨附註解,因此您始終可以獲得使用者實際看到的註解。將滑鼠懸停在註解上時,會透過預設啟用的 AnnotHints 切換開關,將其 Contents 文字顯示為控制項的提示,並與標準 VCL ShowHint 屬性協同運作。對於 FileAttachment(檔案附件)註解,SaveAnnotAttachmentToFile 會將內嵌檔案寫入磁碟並在成功時傳回 1,從而只需幾行程式碼即可完成點擊儲存的工作流程。
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;
列印及其您應該規劃的界限
列印不需要個別的整合。PrintDoc 會顯示標準的 Windows 列印對話方塊 —— 印表機選擇、頁面範圍、份數、自動分頁 —— 並將工作交給函式庫的列印引擎;而 PrintDoc(False) 則會將整個文件傳送到預設印表機,完全不顯示 UI。PrintDocRange 則以完全程式設計的方式運作,包含明確的印表機名稱、起始與結束頁面、複製份數和自動分頁旗標,這是從同一個檢視器實例驅動批次或伺服器觸發列印工作的正確進入點。
有兩個界限值得坦白說明。首先,TPDFlibViewer 是 Windows VCL 控制項;檢視器層已排除在 NOVCL 建置之外,因此主控台服務和非 VCL 目標應該直接使用底層的 TPDFlib 轉譯 API。其次,雖然特意支援無介面(headless)使用,但也有其限制:CaptureRegion 可將任何用戶端區域(頁面加上醒目提示)轉譯為呼叫端擁有的點陣圖,且可在沒有視窗控制代碼的無父控制項上運作,因為繪製路徑避免了會強制建立控制代碼的 VCL 呼叫。這使得自動化測試中的像素級驗證變得可行,但任何真正需要訊息迴圈的功能(例如互動式滑鼠工具),仍屬於真實的 UI 工作階段。在效能方面,控制項透過 SetCacheLimit 保留一個具有可設定預算的記憶體頁面快取,並可跨工作階段保留轉譯的頁面;檢視器磁碟頁面快取文章詳細介紹了該層及其每監視器 DPI 行為。
總結五個步驟:放置控制項並呼叫 LoadFromFile、附加來自 PDFlibViewerActions 的隨插即用動作、呼叫 HighlightSearchHits 進行頁面內搜尋、處理 OnAnnotClick 以進行註解工作流程,以及公開 PrintDoc。這裡展示的每個部分都隨附在標準的 Delphi losLab PDF 函式庫中,其中檢視器控制項、動作類別和轉譯引擎會一起安裝。