技术文章

在 Delphi 中无需 Office 将 Excel 工作表导出为 PDF

要在没有安装 Office 的情况下从 Delphi 或 C++Builder 将 Excel 工作表导出为 PDF,请在 HotXLS 工作表或工作簿上调用 SaveAsPDF。HotXLS 是一个原生的 VCL Excel 组件,其工作表到 PDF 的导出将工作表的已用范围渲染为一个自包含的 PDF 1.4 文件,将 Excel 列宽和单元格显示文本映射到 A4 页面上并自动多页分页。无需 Excel 副本、无需打印机驱动,也无需外部 DLL 参与渲染

HotXLS 将 PDF 视为数据呈现格式,它是 CSV、TSV 和 HTML 导出路径 的同胞,而不是像素精确的渲染引擎。该导出再现表格内容和布局:您在网格中看到的值、列比例以及单元格周围的网格线。这种定位很重要,因为它预先设定了坦诚的预期,而本文的其余部分正是关于这一边界的准确位置

如何在没有 Office 的情况下在 Delphi 中将 Excel 转换为 PDF?

最短的路径是单次调用。TXLSWorksheet 暴露了 SaveAsPDF(FileName)SaveAsPDF(Stream),并且 TXLSWorkbook 暴露了转发到活动工作表的相同两个重载。每个方法都返回一个 Integer(成功时为 1,失败时为 -1),因此您可以在结果上进行分支,而无需将调用封装在您自己的异常处理中

uses
  lxHandle;

var
  Book: TXLSWorkbook;
begin
  Book := TXLSWorkbook.Create;
  try
    Book.Open('sales-june.xlsx');
    // 将活动工作表的已用范围直接渲染到 report.pdf
    if Book.SaveAsPDF('report.pdf') = 1 then
      Writeln('PDF written');
  finally
    Book.Free;
  end;
end;

当您在工作表上调用 SaveAsPDF 时,HotXLS 会导出该工作表的 UsedRange(即每个已填充单元格的边界框)。工作簿重载是一个便捷方式,它委托给活动工作表,因此多工作表报告要么循环遍历它需要的工作表,要么在调用前选择活动工作表。没有将每个标签页连接在一起的整个工作簿、一次性完成的模式:每次调用仅渲染一个工作表或一个范围

HotXLS 如何将已用范围转换为 PDF 页面?

HotXLS 逐个单元格遍历已用范围,并将其布局为分页网格。列几何形状直接来自 Excel:每个列宽都从 Excel 宽度单位转换为印刷点数,并带有一个最小下限,以防任何列坍塌为细条。当所有列加起来比可用页面宽时,HotXLS 会将它们均匀缩放以适应页面;当它们已经适应时,它会保持其自然宽度,而不是将它们拉伸到整个页面

接着行向下流动,直到下一行会越过底部边距,此时 HotXLS 会开启一个新页面并继续,因此一个包含千行的工作表会自动分页,无需您进行任何页断点记账。每个单元格都打印其 FormattedText(应用数字格式后生成的显示字符串),因此日期显示为工作表显示的日期,货币单元格保留其千位分隔符和符号。超出其列宽的文本将被裁剪为单元格宽度,而不是溢出到相邻单元格中

控制方向、边距和字号

对于超出默认布局的任何要求,请直接驱动导出器。TXLSPDFExport 是单行辅助方法背后的类,它发布了三个属性:OrientationxlsPdfPortraitxlsPdfLandscape,默认纵向)、Margin(点数,默认 40)和 FontSize(点数,默认 9)。在调用 SaveAsPDF 之前设置它们,整个文档都会采用它们

uses
  lxHandle, lxPDF;

var
  Book: TXLSWorkbook;
  Exporter: TXLSPDFExport;
begin
  Book := TXLSWorkbook.Create;
  try
    Book.Open('wide-ledger.xlsx');
    Exporter := TXLSPDFExport.Create;
    try
      Exporter.Orientation := xlsPdfLandscape;  // 适应页面上的宽工作表
      Exporter.Margin := 28;                     // 更紧凑的页面边距,单位为点数
      Exporter.FontSize := 8;                    // 更紧密的行
      Exporter.SaveAsPDF(Book.ActiveSheet, 'ledger.pdf');
    finally
      Exporter.Free;
    end;
  finally
    Book.Free;
  end;
end;

TXLSPDFExport 还可以通过 SaveAsPDF(Range: IXLSRange; ...) 接受一个范围而不是整个工作表,这就是您从同样持有草稿计算的工作表中导出汇总块的方式。该范围重载与工作表辅助方法在内部所依赖的重载相同,因此无论您向其传递完整的已用范围还是精心挑选的选区,其布局、缩放和分页行为都完全相同

var
  Book: TXLSWorkbook;
  Exporter: TXLSPDFExport;
  Summary: IXLSRange;
begin
  Book := TXLSWorkbook.Create;
  try
    Book.Open('quarter.xlsx');
    // 仅导出汇总块,而不导出旁边的草稿列
    Summary := Book.Sheets[1].Range['A1', 'F20'];
    Exporter := TXLSPDFExport.Create;
    try
      Exporter.SaveAsPDF(Summary, 'summary.pdf');
    finally
      Exporter.Free;
    end;
  finally
    Book.Free;
  end;
end;

为什么 HotXLS 不嵌入字体:Helvetica 和 WinAnsi 权衡

HotXLS 使用 PDF 内置的 Helvetica 和 WinAnsiEncoding 写入文本,这是一个特意的零依赖选择,但带有一个实际局限。Because Helvetica 是每个合规 PDF 阅读器都已经携带的标准 14 种字体之一,所以导出器根本不嵌入任何字体程序:文件保持很小且可以在任何地方打开,无需许可或随之发运任何内容。该选择的代价是页面能显示的字符集

WinAnsiEncoding 涵盖了 Windows-1252 字符集,因此拉丁文本、常用标点和常见的西欧重音符号都能正确渲染,并且 HotXLS 将少数排版字符、弯引号、短破折号和长破折号以及欧元符号折叠到它们的 CP1252 插槽中。该范围之外的字符(中日韩意音文字、西里尔字母、希腊字母和阿拉伯字母)在页面上会坍塌为问号。如果您的工作表携带非拉丁文本,该导出器是错误的工具,适合的任务是全样式 PDF 渲染器

工作表到 PDF 导出的终点

HotXLS 渲染内容和结构,而不是工作表完整的视觉样式。单元格填充、各个边缘的边框样式、每个单元格的字体和颜色、图表以及嵌入图像都不在导出器绘制的范围内;您得到的是带有均匀网格线的数据网格。这与 HTML 和 RTF 导出器所遵循的约定相同,它适合报告或数据移交(其中数字承载含义,而主题不承载)

当目标是带样式的、与品牌一致的文档时,通常的模式是先在电子表格模板中组装数字,这在 模板驱动的报表生成工作流 中有深入涵盖。并且因为这完全不触及 COM 或运行中的 Excel 实例,所以该管道适合 HotXLS 全程使用的相同 无需 Office 的自动化模型:纯 Pascal,可部署在无头服务器上,无需许可或监视任何互操作

自包含的 PDF,按字节对应

HotXLS 手动构建 PDF 文件结构,并从精确的字节偏移量计算其交叉引用表,这就是输出能在每个阅读器中打开的原因。当序列化每个对象时,HotXLS 在追加其字节前记录该对象的起始偏移量,接着将运行位置增加刚刚写入的长度,因此 xref 表能精确落到每个对象上,且 startxref 标记了该表开始的位置。只要发现这些偏移量即使偏差一个字节,阅读器就会拒绝整个文件,这使得自计算偏移量成为导出器承重的约定

该约定带来了一个值得了解的严格后果。Pages 节点首先作为对象 1 写入,但在每个页面都发射出来前,其子页面列表是未知的,因此 HotXLS 将其保留为占位符,并在偏移量轮次运行前修补入最终的子项和数量,决不能在运行后修补。一旦对象的字节被测量用于 xref,它们就会被冻结:此后改变单个字节都会使后面的每个偏移量滑动并损坏该表。这是一个小小的纪律,换来的是没有底层库的标准有效的 PDF 1.4

这里展示的 SaveAsPDF 方法和 TXLSPDFExport 类随适用于 Delphi 和 C++Builder 的 HotXLS Delphi Excel Component 一起提供,此外还有 CSV、HTML 和 RTF 导出器以及完整的工作表 API