PDFsharp 指南:使用 MigraDoc 和 PdfSharpCore 创建 PDF 文件

PDFsharp 指南:使用 MigraDoc 和 PdfSharpCore 创建 PDF 文件

PDFsharp 是一个开源的 .NET 库,用于通过 C# 代码创建和编辑 PDF 文档。您只需在页面上绘制文本、线条和图像,调用 Save 方法,即可获得一个 PDF 文件。无需浏览器引擎,也无需原生二进制文件。该项目始于 2005 年,如今许多 .NET 后端生成的发票、报告和证书仍然需要经过它。

我写这篇文章是为了帮助那些需要在服务器上使用 PDF 的开发者,让他们了解自己将要面对的问题。我们将介绍 PDFsharp 和 MigraDoc 如何协同工作、应该安装哪个版本、如何创建第一个可用的文件、Linux 系统上的字体问题、最近的许可变更,以及何时应该选择其他解决方案。

什么是PDFsharp?它能做什么?

PDFsharp库的幕后功臣是empira Software GmbH,一家德国小型软件公司,他们让这个项目得以延续近二十年。你的大部分时间都花在XGraphics类上。如果你曾经使用System.Drawing.Graphics进行过WinForms绘图,那么十分钟之内你就能上手:字体、画笔、x轴和y轴,开始吧!

开箱即用:

  • 使用文本、矢量形状、线条和图像从头开始创建 PDF 文档
  • 打开现有的 PDF 文件,并向其页面添加内容,例如图章、水印或页码。
  • 通过将页面从一个文件导入到另一个文件,合并和拆分文档。
  • 嵌入并筛选字体,使文件在每台机器上看起来都一样。
  • 添加书签、链接和文档元数据,例如标题、作者和关键词
  • 使用密码和权限标志保护文件
  • 自 6.2 版本起,支持电子签名。

部署过程很枯燥,我这么说其实是赞扬。所有代码都是托管的,所以运维人员再也不用在服务器上安装无头 Chrome 浏览器,也不用在凌晨两点到处找 Ghostscript DLL 文件了。你只需要引用一个 NuGet 包,推送一下,就搞定了。

现在问题来了。你得到的只是一个画布,仅此而已。长段落超出底部边距?那是你的问题。表格超出第一页?也是你的问题,除非你引入 MigraDoc。

PDFsharp 和 MigraDoc:你需要哪个层?

同一家供应商,同样的发布周期,但工作内容却截然不同。MigraDoc Foundation 的办公地点就在 PDFsharp 楼上。你无需告诉它字符串应该放在哪里,只需提供章节、段落、表格、页眉、页脚、样式以及一些图表,它就能自动计算出所有内容的位置。最终生成 PDF 文件。或者,如果你的同事坚持要用 Word 打开所有文件,它也会生成 RTF 文件。

我的简写是:PDFsharp 是“把这段文本放在 x=40,y=120 的位置”。MigraDoc 是“这里有 30 行和一个重复的页眉,你自己整理页面”。

任务最佳匹配为什么
单页证书或票据,位置固定PDFsharp完全控制每个坐标
包含表格和页眉的多页报告MigraDoc自动分页和样式
发票包含可变数量的行项目MigraDoc表格会跨页扩展和分割
在现有 PDF 文件上添加印章、水印或页码PDFsharp MigraDoc 仅创建新文档
合并或拆分现有文件PDFsharp页面导入是 PDFsharp 的一项功能。
报告内的业务图表MigraDoc内置图表支持
混合:生成的报告加上自定义绘制的封面两个都MigraDoc 可以渲染到 PDFsharp 页面上

实际项目中往往两者都会用到。正文通过 MigraDoc 生成,而手绘封面或供应商提供的 PDF 上的印章则通过底层实现。无论如何,你都无法避免安装 PDFsharp,因为 MigraDoc 软件包会将其作为依赖项引入。

PDFsharp 构建详解:核心构建、GDI+ 和 WPF

对于新手来说,在 NuGet 包列表中,PDFsharp 6 通常会排在下一个位置。它有三个版本,每个版本都对应一个 MigraDoc 版本。

NuGet 包建造平台字体和图像
PDFsharp / PDFsharp-MigraDoc核Windows、Linux、macOS需要字体解析器;支持 BMP、PNG、JPEG 图像
PDFsharp-GDI / PDFsharp-MigraDoc-GDI GDI+仅限 Windows 系统通过 System.Drawing 使用已安装的字体
PDFsharp-WPF / PDFsharp-MigraDoc-WPF WPF仅限 Windows 系统通过 WPF 使用已安装的字体

目前的软件包面向 .NET 8、9 和 10,以及 .NET Standard 2.0 和 .NET Framework 4.6.2。因此,几乎所有仍在开发中的项目都涵盖在内,从尘封已久的 WinForms 工具到全新的 ASP.NET Core API。

控制台应用、Web API、Docker、Azure Functions,以及任何基于 Linux 的应用?核心构建即可。GDI+ 或 WPF 仅适用于 Windows 桌面应用,例如需要使用系统字体或在原生控件中预览的应用。Stack Overflow 上有人问过他们的控制台应用是否需要 WPF 版本,答案是否定的。

PDFsharp 指南

如何在 Visual Studio 中通过 NuGet 安装 PDFsharp

你仍然会看到一些来自 WinForms 时代的论坛帖子,教你下载 ZIP 文件,找出其中的 DLL 文件,然后自己添加引用。忽略它们。通过 NuGet 安装 PDFsharp;包管理器会自动跟踪版本,所以你无需手动操作。

  1. 在 Visual Studio 中打开您的解决方案,然后在解决方案资源管理器中右键单击该项目。
  2. 选择“管理 NuGet 程序包” ,切换到“浏览”选项卡,然后搜索“PDFsharp”。
  3. 如果您还需要文档排版功能,请选择PDFsharp (核心版本)或PDFsharp-MigraDoc 。请确认发行商为 empira Software GmbH。
  4. 点击“安装”并接受许可提示。
  5. 请构建一次项目,以确认软件包已正确恢复。

终端用户可以跳过点击步骤:

bash dotnet add package PDFsharp dotnet add package PDFsharp-MigraDoc

在包管理器控制台中,同样的操作是运行 `Install-Package PDFsharp`。另外,请不要混用不同的构建版本。我亲眼见过一个团队因为类型冲突浪费了一整个下午的时间,原因就是一个项目引用了 PDFsharp,而它的邻近项目却引入了 PDFsharp-GDI。

使用 C 语言中的 PDFsharp 创建您的第一个 PDF 文档

下面介绍的是一个可以生成单页 PDF 文件的最小程序。只要设置了字体解析器(下一节),它就可以在任何操作系统上的 Core 版本上运行。

```csharp
使用 PdfSharp.Drawing;
使用 PdfSharp.Pdf;

// PDFsharp 6.5+ 首次使用前需要选择许可证
PdfSharpLicense.SetAgplLicense();

var document = new PdfDocument();
document.Info.Title = "Hello PDFsharp";

PdfPage page = document.AddPage();
XGraphics gfx = XGraphics.FromPdfPage(page);

var font = new XFont("Arial", 20, XFontStyleEx.Bold);
gfx.DrawString("你好,PDFsharp!", font, XBrushes.Black,
new XRect(0, 0, page.Width.Point, page.Height.Point),
XStringFormats.Center);

document.Save("hello.pdf");
```

所有 PDFsharp 程序都遵循以下结构:文档、页面、XGraphics、绘制、保存。单位为点(1/72 英寸),原点位于左上角。在 6.2.x 版本中,删除许可行。该 API 仅在 6.5.0 版本中出现。

MigraDoc 版本:

```csharp
使用 MigraDoc.DocumentObjectModel;
使用 MigraDoc.Rendering;

var doc = new Document();
Section section = doc.AddSection();
section.AddParagraph("你好,MigraDoc!");

var renderer = new PdfDocumentRenderer { Document = doc };
renderer.RenderDocument();
renderer.PdfDocument.Save("hello-migradoc.pdf");
```

没有任何坐标。随便塞500段文字进去,就能得到相应的页数,而且你这边完全不需要任何分页代码。

PDFsharp核心构建中的字体:解析器问题

随便问问就知道,字体问题是最常见的抱怨。GDI+ 和 WPF 构建只能读取 Windows 系统中已安装的 TrueType 字体。而 Core 构建则无法做任何假设,因为精简的 Linux 容器通常根本没有可用的字体。因此,首次 Docker 部署往往会因为找不到合适的字体而抛出异常。

解决方法是使用字体解析器,这是一个实现了 IFontResolver 接口的类。它有两个方法:

  • ResolveTypeface 将请求的字体系列和样式(例如,“Arial”,粗体)映射到字体名称
  • GetFont 返回该字体名称对应的 TTF 文件的原始字节。

在启动时通过 GlobalFontSettings.FontResolver 注册一次,在创建第一个 XFont 之前。

```csharp
public class AppFontResolver : IFontResolver
{
public FontResolverInfo? ResolveTypeface(string family, bool bold, bool italic)
=> new FontResolverInfo(bold ? "Inter-Bold" : "Inter-Regular");

public byte[]? GetFont(string faceName) => File.ReadAllBytes(Path.Combine(AppContext.BaseDirectory, "Fonts", faceName + ".ttf"));

}

GlobalFontSettings.FontResolver = new AppFontResolver();
```

在你亲身经历之前,有必要了解以下几点:

  • 将 TTF 文件保存在项目的 Fonts 文件夹中,并设置将其复制到输出目录,或者将其作为资源嵌入,以便容器镜像始终拥有它们。
  • 6.2.0 版本移除了旧的 CoreBuildFontResolver。在 Windows 或 WSL2 上,您可以使用 GlobalFontSettings.UseWindowsFontsUnderWindows 恢复类似功能,但这在 Linux 或 macOS 上无效。
  • 官方示例库中包含一个 SubstitutingFontResolver,您可以将其复制作为起点。
  • 在将商业字体嵌入生成的文件之前,请务必检查字体许可。像 Inter、Roboto 或 Noto 这样的开源字体则完全避免了这个问题。

有了解析器之后,在 Windows、Linux 和 macOS 上的输出看起来完全相同。

使用 PDFsharp 6 合并、签名和保护文件

很多团队从来不从头开始创建文档。他们使用库来粘贴、添加标记或锁定已有的文件。合并文档只需几行代码:以导入模式打开每个源文件,然后复制页面即可。

csharp var output = new PdfDocument(); foreach (var path in new[] { "part1.pdf", "part2.pdf" }) { using var input = PdfReader.Open(path, PdfDocumentOpenMode.Import); foreach (PdfPage p in input.Pages) output.AddPage(p); } output.Save("merged.pdf");

拆分操作与此循环相反,每个页面或范围生成一个输出文件。为了保护数据安全,`document.SecuritySettings` 接受用户密码、所有者密码和权限标志(例如打印、复制文本等)。

两个用户期待已久的功能终于在 6.2 版本中实现了:

  • 基于 CMS 的数字签名。默认签名器包含在一个单独的 PDFsharp.Cryptography 程序集中,您可以通过实现 IDigitalSigner 接口来插入您自己的签名器。时间戳服务器支持在现代 .NET 版本中有效,但在 .NET Framework 版本中无效。
  • 用于归档的PDF/A 输出。官方文档称其“存在一些局限性”且仍在开发中,因此在依赖其合规性之前,请使用 PDF/A 检测器验证结果。

该版本还引入了彩色表情符号字形和受管理的 MD5 实现,如果您在 FIPS 规则下或在 Blazor 中运行,这将非常重要。

PDFsharp 代码库、PdfSharpCore 分支和 1.x 版本

在 GitHub 上搜索 PDFsharp,你会发现一些名称几乎相同的项目。这都怪历史遗留问题。

旧版 1.x 系列(1.50、1.51)仅支持 .NET Framework。.NET Core 发布后,人们不愿等待,便自行移植。其中最广为人知的当属 Stefan Steiger 创建的 PdfSharpCore,它后来被其他分支继承和完善。PdfSharpCore 移除了 GDI+,并在官方发布前数年就实现了在 Linux 上的运行。

随后 PDFsharp 6 问世:它基于现代 .NET 进行了彻底重写,并提供了官方的跨平台核心版本。各种分支版本失去了存在的意义。

项目维护者平台地位
PDFsharp 6.x(empira/PDFsharp 存储库) empira Software GmbH .NET 8–10、.NET Standard 2.0、.NET Framework 4.6.2积极维护
PDFsharp 1.5x empira Software GmbH仅限 .NET Framework源代码保存在 GitHub 上,但已停止维护。
PdfSharpCore 及其分支社区.NET Core / .NET Standard 2.0因分支而异,更新速度较慢
PDFsharp-MigraDoc 6.x empira Software GmbH与 PDFsharp 6 相同积极维护

所以,Reddit 上反复出现的“PdfSharp vs PdfSharpCore”帖子呢?新项目,官方软件包。只有当旧代码已经依赖于它,并且现在迁移不值得投入大量时间时,才保留分支。如果觉得文档不够完善,可以去 empira 的独立示例仓库。它的文件夹和文件都是可运行的示例,比猜测要好得多。

PDFsharp是免费的吗?PDFsharp 6.5的许可变更

是的,在其大部分生命周期内都是如此。它采用的是 MIT 许可证,这意味着可以几乎不受任何限制地用于商业用途。包括 6.2.4 在内的所有版本仍然如此。

6.5.0 版本改变了这一点。该库现在采用双重许可:

  • AGPL(免费)。您需要在使用库之前调用 `PdfSharpLicense.SetAgplLicense()` 来启用它。AGPL 要求您将源代码提供给用户,包括通过网络与您的软件交互的用户,因此它不太适合闭源 SaaS。
  • 商业许可。您从供应商处购买许可密钥,并使用 PdfSharpLicense.LoadLicense(...) 加载它。这将解除 AGPL 协议的约束。

从 6.5.0 版本开始,你的代码必须明确地选择一个版本。7.0 版本的预览版也采用相同的方式。

实际操作中:

  • 与 AGPL 兼容的开源项目可以继续免费使用最新版本。
  • 闭源产品可以选择购买商业许可,也可以继续使用 6.2.x 版本,该版本仍遵循 MIT 许可协议。继续使用旧版本意味着会错过未来的修复,因此请认真权衡利弊。
  • 无论您选择哪种方案,请务必仔细阅读许可协议原文,并请您的法律团队签字确认。本指南不构成法律建议。

PDFsharp 指南

PDFsharp 的不足之处以及与之搭配使用的软件

这个库的功能很完善,只是功能范围比较窄,而且每个论坛帖子都会提到同样的问题。

  • PDFsharp 不支持HTML 转 PDF。它本身并不渲染 HTML 或 CSS。虽然有一个名为 HtmlRenderer.PdfSharp 的社区插件,但它仅支持 HTML 4.01 和 CSS Level 2,因此使用 flexbox 或 grid 等现代布局的 PDFsharp 将无法正常工作。需要 HTML 模板的团队通常会选择使用无头浏览器渲染器。
  • 不支持 JavaScript。无法捕获客户端构建的图表或内容。
  • 不支持图像渲染。PDFsharp无法将 PDF 页面转换为 PNG 格式以生成缩略图或预览图。您需要使用单独的栅格化工具才能实现此功能。
  • 文本提取功能有限。虽然可以遍历内容流,但没有高级 API 能够返回带有位置信息的干净文本。PdfPig 是一个独立的开源库,专门用于读取和提取文本。
  • 部分表单支持。简单的 AcroForm 表单填写功能正常;但包含脚本的复杂表单则另当别论。
  • 表格需要使用 MigraDoc。如果使用普通的 PDFsharp,则需要自行计算每一行的高度。

但这并不意味着它是个糟糕的选择。发票、账单、标签和证书等数据驱动型文档正是它的优势所在,而且它占用空间极小。但要完美复制网页则不然。

PDFsharp 加密货币发票和付款收据

收据可能是人们使用 PDFsharp 最常创建的内容。加密货币收据比银行卡收据稍微复杂一些。好消息是:一旦支付网关收到你的 webhook(支付状态变更时发送的 HTTP 回调),你就拥有了所有数据。无需额外查找。

我会在上面写些什么:

  • 订单或发票编号以及商家详情
  • 支付时的加密货币金额及其等值法币金额
  • 币种和网络(例如,TRON 上的 USDT 与以太坊上的 USDT)
  • 交易哈希值,以便客户可以在区块浏览器上进行验证。
  • 缩短的接收钱包地址和确认时间
  • 指向交易或发票页面的二维码

哈希值和地址使用等宽字体。顾客会眯着眼睛一个个地看,而 Arial 字体中,数字 0 和大写字母 O 看起来就像双胞胎。如果哈希值太宽,请换行;6 磅的字体对顾客没有任何帮助。嵌入字体,将文件与订单一起保存,并在文件上签名,以防会计需要收据。

以上内容并未涵盖实际收款环节。要实现这一功能,你需要接入像Plisio这样的支付网关。它可以接收 BTC、USDT 以及其他多种加密货币,生成发票,并在状态每次变更时调用你的端点。你的处理程序会将有效负载传递给 MigraDoc,买家在感谢页面上就能收到带有你公司 logo 的收据。

结论

我一直选择 PDFsharp 的原因只有一个:它体积小巧,不会占用太多空间。Core 版本可以在任何支持 .NET 的平台上运行,而当画布不足以满足需求时,MigraDoc 可以接管布局工作。有三件事需要尽早确定:选择哪个版本、字体加载方式以及你使用的许可证(因为 6.5 版本现在采用的是 AGPL 许可证)。它用于打印数据,而不是网页,因此可以稳定运行多年。

任何问题?

主要用于从 .NET 代码生成 PDF 文件,例如发票、报告、货运标签和证书。它也便于合并和拆分文件,或者在别人创建的 PDF 文件上添加页码和水印。有了 MigraDoc,处理包含表格和图表的长文档也变得轻松无比。

在 6.2.4 版本之前,是的,它是 MIT 许可,可以用于付费产品。从 6.5.0 版本开始,情况就有所不同了。AGPL 许可是免费的,但需要你共享源代码;商业许可则需要付费,但不需要共享源代码。你的启动代码必须明确说明你选择哪种许可。

不,它内部没有 HTML 或 CSS 引擎。一个名为 HtmlRenderer.PdfSharp 的社区插件尝试过,但它只支持 HTML 4.01 和 CSS 2,所以任何使用 flexbox 或 grid 布局构建的内容都会失效。对于实际的网页,人们会使用基于无头浏览器的转换器。

通过 NuGet。在 Visual Studio 中右键单击项目,选择“管理 NuGet 程序包”,搜索“PDFsharp”。在终端中,运行 `dotnet add package PDFsharp` 即可。还需要表格和分页功能?添加 PDFsharp-MigraDoc 即可。省去手动复制 DLL 的麻烦。

固定布局和编辑现有文件:PDFsharp。任何需要文本自动流动、表格自动扩展和页面自动换行的情况:MigraDoc。说实话,你最终可能两者都会用到,而且由于 MigraDoc 运行在 PDFsharp 之上,它们可以很好地协同工作。

PDFsharp 6 支持 .NET 8、9 和 10,以及 .NET Standard 2.0 和 .NET Framework 4.6.2。Core 版本可在 Windows、Linux 和 macOS 上运行。GDI+ 和 WPF 版本仅适用于 Windows。如果要在 Linux 上运行,请先设置字体解析器,否则可能会出现字体错误。

Ready to Get Started?

Create an account and start accepting payments – no contracts or KYC required. Or, contact us to design a custom package for your business.

Make first step

Always know what you pay

Integrated per-transaction pricing with no hidden fees

Start your integration

Set up Plisio swiftly in just 10 minutes.