HotPDF 透過三種呼叫將載入的 PDF 列印到實體印表機、任何 Windows 裝置內容或剪貼簿:PrintLoadedDocument 驅動 VCL TPrinter、RenderLoadedPageToDC 將頁面直接繪製到任何 HDC 中,以及 RenderLoadedPageToClipboard 將頁面複製為點陣圖或向量關係圖。HotPDF 是適用於 Delphi 和 C++Builder 的原生 VCL PDF 元件,並在 v2.331.0 中新增了此一流的列印路徑,並在 v2.348.0 中進行了精煉,因此您不再需要自己圍繞點陣圖轉譯器手動編寫 TPrinter 迴圈。所有這三種呼叫都直接在關鍵位置繪製頁面的向量內容,因此列印輸出能保持與印表機所能轉譯的最高清晰度
為什麼要將 PDF 頁面直接繪製到印表機,而不是繪製到點陣圖?
PDF 頁面是由向量繪圖運算子(依據 ISO 32000-1 繪圖模型執行的路徑、文字和影像)組成的程式,而非像素網格。列印頁面最直觀的方法是將頁面點陣化為點陣圖,然後將該點陣圖傳送至印表機畫布,這確實可行,但它丟棄了印表機最擅長的優勢。現代雷射印表機的解析度可達 600 或 1200 DPI;若將在 150 或 300 DPI 下點陣化的點陣圖拉伸以適應頁面,列印出來的效果會很模糊,且帶有鋸齒狀的文字邊緣和明顯的插值痕跡。RenderLoadedPageToDC 採用了更好的路徑:它將頁面的內容資料流重現為直接輸出到目標裝置內容的 GDI 呼叫,因此向量文字和線條藝術能以向量形式到達印表機,並由印表機以其自身的解析度進行一次性點陣化
如何在 Delphi 中將載入的 PDF 列印到實體印表機?
HotPDF 使用 PrintLoadedDocument(Printer, Options) 列印載入的文件,該方法接收指定的 VCL TPrinter 和 THPDFPrintOptions 記錄,列印選定的頁面,並傳回傳送的頁面數量。首先設定 Printer.PrinterIndex 以選擇裝置,填寫選項記錄——THPDFPrintOptions.Default 類別函式會為您提供合理的預設值——然後呼叫它一次。程式庫會使用 Printer.BeginDoc 開啟工作,在頁面範圍之間走訪並在頁面之間輸出 Printer.NewPage,然後使用 Printer.EndDoc 關閉工作,因此您無需手動管理列印工作的生命週期
uses Printers;
var
Pdf: THotPDF;
Opts: THPDFPrintOptions;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('invoice.pdf') > 0 then
begin
Printer.PrinterIndex := -1; // -1 選擇預設印表機
Opts := THPDFPrintOptions.Default; // 所有頁面, 300 DPI, 符合頁面大小, 彩色
Opts.PageRange := '1,3-5'; // 從 1 開始;留空則列印每一頁
Opts.Copies := 2;
Pdf.PrintLoadedDocument(Printer, Opts);
end;
finally
Pdf.Free;
end;
end;
PageRange 字串是以 1 為起始的,且其讀取方式與列印對話方塊相同:'1,3-5' 會列印第 1 頁以及第 3 至第 5 頁,空字串則會列印整份文件。Copies 直接對應至 Printer.Copies。不過,請坦白評估 Collate:由於 VCL 在不同 Delphi 版本中公開逐份列印(collation)的方式不一致,HotPDF 會設定份數,並將逐份列印留給印表機驅動程式預設值,因此當您需要保證逐份列印順序時,請透過 TPrinterSetupDialog 在驅動程式上進行設定,而不是指望靠該旗標來傳遞
DPI 和 FitPage 如何決定列印比例?
在出貨前,FitPage 和 DPI 的互動方式值得深入瞭解。當 FitPage 為 True(預設值)時,HotPDF 會讀取每個頁面的 MediaBox,在保留外觀比例的情況下將其縮放到 Printer.PageWidth 和 Printer.PageHeight,並以使頁面填滿紙張的比例進行轉譯。在該模式下,Options.DPI 實際上會被符合大小的比例覆寫,編譯為輸出必須符合印表機的實體頁面尺寸。當 FitPage 為 False 時,HotPDF 會以完全符合 Options.DPI 的 1:1 比例轉譯每個頁面,將 72 個 PDF 單位對應至 DPI 裝置像素,當來源頁面與紙張尺寸相同且您介意精確解析度時,這正是您所需要的。當來源頁面尺寸與紙匣尺寸不同時,請使用 FitPage;當兩者相符且您希望有確定性的縮放時,請使用 1:1
v2.348.0 中新增的兩個選項修正了看起來差不多正確的列印輸出。每台實體印表機都有硬邊界(硬體無法觸及的非列印區域),預設情況下,頁面原點會落在該區域內,因此滿版頁面會在兩個邊緣丟失一部分內容。將 IgnoreHardMargin 設定為 True,HotPDF 會查詢印表機的 PHYSICALOFFSETX 和 PHYSICALOFFSETY,然後偏移裝置原點以取消該位移,使頁面原點對應至真實的紙張角落。第二個選項 AutoRotate 預設為開啟:它會讀取每個頁面的 MediaBox,當頁面寬度大於高度時,會將印表機切換至橫向,使寬頁面列印時不被裁剪,而不是被裁剪為縱向。一旦您明確設定了 Orientation 阻,AutoRotate 就會讓步,因此刻意選擇的列印方向始終優先
如何將 PDF 頁面轉譯至任何裝置內容?
RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) 是列印迴圈底層的基本方法,且其本身非常實用。PrintLoadedDocument 對於印表機畫布上的每一頁呼叫它一次,但目標可以是任何 Windows HDC:離線記憶體 DC、自訂檢視器中控制項的畫布,或列印預覽介面。PageIndex 是以 0 為起始的,W 和 H 是目標介面的裝置單位,且轉譯器會走訪頁面的 /Contents 並向您的 DC 輸出 GDI 呼叫。值得注意的一項特定行為是:它不繪製背景填滿。呼叫端擁有該介面,因此如果您希望頁面背後有白紙,您必須在轉譯前自行填滿 DC,這正是當您需要時可以將頁面複合在現有畫布之上的原因
// 在 150 DPI 下將第 1 頁繪製到離線 24 位元介面中
var
Surface: TBitmap;
begin
Surface := TBitmap.Create;
try
Surface.PixelFormat := pf24bit;
Surface.SetSize(1275, 1650); // 150 DPI 下為 8.5 x 11 英吋
Surface.Canvas.Brush.Color := clWhite;
Surface.Canvas.FillRect(Surface.Canvas.ClipRect); // 呼叫端繪製背景
Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
// Surface 現在包含複合後的頁面,準備進行傳送或儲存
finally
Surface.Free;
end;
end;
當您只需要一個完成的 TBitmap(例如縮圖、影像匯出、您要快取的預覽)時,請改用 RenderLoadedPageToBitmap,這已在隨附文章將 PDF 頁面轉譯至點陣圖中介紹。兩者的區別在於誰擁有該介面:點陣圖呼叫會配置並傳回像素緩衝區,而 RenderLoadedPageToDC 則繪製到您已經持有的裝置內容中,這正是印表機畫布或繪圖處理常式所傳遞給您的。兩者共用相同的內容資料流解譯器和內嵌字形引擎,因此無論哪種方式,文字輸出都具有精確的內嵌外框
如何將 PDF 頁面貼入 Word 或影像編輯器?
RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile) 將一個頁面置於 Windows 剪貼簿上,準備貼入 Word、PowerPoint、電子郵件或影像編輯器中。AsMetafile 旗標用於選擇格式,而此選擇比看起來更為重要。傳入 False,HotPDF 會放置在您的 DPI 下轉譯的 24 位元裝置點陣圖(CF_BITMAP)——一個固定像素網格,可以貼在任何地方,但當接收端放大它時會產生像素化。傳入 True,它會放置一個加強型關係圖(CF_ENHMETAFILE),其中頁面的文字和向量藝術以 GDI 繪圖指令的形式傳輸,因此貼上的結果在任何縮放比例下都能保持清晰,並可重新編排為目標文件所給予的任何尺寸
// 複製第 2 頁作為可縮放的向量關係圖,以便貼入 Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);
// 改為複製同一頁作為固定的 300-DPI 點陣圖
Pdf.RenderLoadedPageToClipboard(1, 300, False);
經驗法則非常簡單。如果使用者會將頁面貼入文件中並可能會調整其大小——例如報告中的圖表、拖入合約草稿中的條款——請使用關係圖,因為向量文字列印和縮放都很清晰。如果目標端僅支援點陣資料,或者您特別希望獲得已知解析度下的凍結快照,請使用點陣圖。至於從頁面中提取文字,而非提取其圖像,這是一項完全不同的工作,由文字擷取 API 處理,該 API 會傳回字元及其位置,而非轉譯後的影像
列印路徑的限制
此頁面上的所有內容均僅限 Windows,這點值得清楚說明。PrintLoadedDocument、RenderLoadedPageToDC 和 RenderLoadedPageToClipboard 是建置在 TPrinter、GDI 裝置內容模型、Windows 剪貼簿和加強型關係圖之上——所有這些都是 Win32 和 Win64 的建構。在其他平台上,HotPDF 的其餘部分仍可建立、編輯和讀取 PDF,但這種以 GDI 為後盾的列印和剪貼簿介面是不可用的。轉譯器也針對文字、路徑和影像的常見文件子集,因此富含漸層的設計打樣和受 ICC 管理的色彩將列印為忠實的預覽,而非色度計打樣,這與點陣圖轉译器具有相同的限制
對於跨多個檔案的批次列印,請依次載入並列印每個文件,並注意記憶體開銷:PrintLoadedDocument 一次轉譯一頁,但載入的文件圖本身在整個工作期間都會保留在記憶體中。當您在數百個大型檔案上驅動列印佇列時,用於大型 PDF 工作流程的直接檔案 API 資料串流能與列印路徑完美搭配——先在磁碟上分割、合併或選擇您需要的頁面,然後僅載入精簡後的結果進行列印。這就是 PrintLoadedDocument、RenderLoadedPageToDC 和 RenderLoadedPageToClipboard 隨附於適用於 Delphi 和 C++Builder 的 HotPDF 元件 的方式——這是一個無外部 DLL 相依性的原生 VCL 函式庫,涵蓋了 PDF 建立、編輯、文字擷取、轉譯,以及現在直接列印到 TPrinter、any device context, or the clipboard的功能