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,FitPage,彩色
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 and 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 并将 GDI 调用发射到您的 DC 中。有一个特意设计的行为值得去了解:它不绘制任何背景填充。调用者拥有该表面,因此如果您想要页面背后有白纸,您在渲染前自行填充 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);
// 表面现在保存了复合后的页面,准备好传送或保存
finally
Surface.Free;
end;
end;
当您只需要一个加工好的 TBitmap(缩略图、图像导出、您将缓存的预览)时,请转而使用 在 Delphi 中将 PDF 页面渲染到位图 中涵盖的 RenderLoadedPageToBitmap。区别在于谁拥有表面:位图调用分配并返回像素缓冲区,而 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 处理,它返回字符及其位置而不是渲染的图像
打印路径的终点
本页上的所有内容都仅限 Windows,这值得明确说明。PrintLoadedDocument、RenderLoadedPageToDC 和 RenderLoadedPageToClipboard 是构建在 TPrinter、GDI 设备上下文模型、Windows 剪贴板和增强型元文件之上的——这些全都是 Win32 和 Win64 结构。在其他目标平台上,HotPDF 的其余部分仍可创建、编辑和读取 PDF,但这种受 GDI 支持的打印和剪贴板界面是不可用的。渲染器还针对文本、路径和图像的常见文档子集,因此渐变较多的设计样张和 ICC 管理的彩色打印输出的是忠实的预览,而不是比色样张,这与位图渲染器具有相同的局限
对于跨许多文件的批量打印,依次加载并打印每个文档,并关注内存账单:PrintLoadedDocument 一次渲染一页,但已加载的文档图本身在整个作业期间都驻留在内存中。当您驱动针对数百个大型文件的打印队列时,用于大型 PDF 工作流的流式 Direct File API 可以与打印路径很好地结合——首先在磁盘上分割、合并或选择您需要的页面,然后仅加载纤薄的结果进行打印。这就是 PrintLoadedDocument、RenderLoadedPageToDC 和 RenderLoadedPageToClipboard 随适用于 Delphi 和 C++Builder 的 HotPDF Component 一起提供的方式——这是一个没有外部 DLL 依赖的原生 VCL 库,涵盖了 PDF 创建、编辑、文本提取、渲染,以及现在直接打印到 TPrinter、任何设备上下文或剪贴板