做 .NET 开发这些年,PDF 生成是个绕不开的痛。
发票要 PDF、报表要 PDF、合同要 PDF、导出要 PDF。每次遇到这个需求,大多数人的第一反应是找库——iTextSharp、PDFsharp、Rotativa……绕了一圈下来,要么 API 难用得要命,要么生成的 PDF 在不同浏览器里显示不一致,要么性能差到生成一份 10 页的报告要等好几秒。
直到我遇到了 QuestPDF。
一句话概括它的核心思路:用写代码的方式设计 PDF,而不是用 HTML 拼页面再转 PDF。 这个转变听起来简单,但它解决了我在 PDF 生成这件事上遇到过的几乎所有痛点。
一个真实的场景
假设你要生成一份订单发票,包含:
公司 Logo 和标题 客户信息和订单号 一个表格,列出每个商品的单价、数量、小计 底部有页码和合计金额 有的订单商品多,要自动分页
用传统方案,你会怎么写?
可能先拼 HTML 字符串,然后用 Rotativa 或者 wkhtmltopdf 转 PDF。结果是:CSS 兼容问题一堆,表格跨页断行控制不了,页码位置调半天,部署到 Linux 还可能直接罢工。
用 QuestPDF,同样的需求,代码是这样的:
Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header()
.Text("Invoice")
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
// 表格:RelativeColumn 按比例分配列宽
x.Item().Table(table =>
{
table.Columns(columns =>
{
columns.RelativeColumn(3); // 商品名
columns.RelativeColumn(1); // 数量
columns.RelativeColumn(1); // 单价
columns.RelativeColumn(1); // 小计
});
// ... 填充行数据
});
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.GeneratePdf("invoice.pdf");
这段代码的结构,就是 PDF 的结构。Header、内容区、Footer,每一块在哪里、怎么排列,一目了然。
为什么它和别家不一样
PDF 生成这个领域,最常见的技术路线是先把页面画成 HTML,再用工具转成 PDF。这条路线的最大问题是:HTML 是为浏览器设计的,PDF 是为打印设计的,两者的渲染模型根本不同。
CSS 里的 flexbox、grid,在 PDF 转化的过程中会有各种奇怪的边距问题;浏览器版本不同渲染结果不同;中文换行在不同系统上表现不一致;部署环境缺少字体文件整个 PDF 就乱码。
QuestPDF 的思路是从底层重新设计一套 PDF 布局引擎,而不是借用 HTML 的渲染能力。它的 API 直接对应 PDF 的页面模型:你可以精确控制每一个元素的位置、尺寸、间距,可以直接操作表格的单元格合并和跨页行为,可以给页面加背景、水印、页眉页脚。
这不是在 PDF 上面糊一层 API,而是真正把 PDF 的能力封装成了 C# 的 Fluent 接口。
你能用的能力清单
这是我在 QuestPDF 文档里看到的完整功能列表,每一项都对应一个实际需求:
页面元素:页眉、页脚、背景、水印、页边距
文本:字体样式、多级标题、页码、段落排版
容器:背景色、边框、圆角、渐变色、阴影
布局组件:表格(含单元格合并和跨页控制)、列表、图层、行列布局、内联元素
其他:图片(PNG/JPG/WebP/SVG)、条形码、二维码、链接、Z-index 层级控制
还有一个特别实用的东西:QR 码和条形码原生支持。发票要放二维码?QuestPDF 直接提供了集成能力,不需要再引入第三方库。
表格跨页,终于能控制了
这是我最想单独拿出来说的一点。
几乎所有用 PDF 做报表的人,都被表格跨页折磨过:一行数据被截断到两页,金额在上一页而商品名在下一页,看起来完全是 bug。
QuestPDF 的表格 API 支持 ColumnSpan、RowSpan,更重要的是可以让表头在每一页都重复出现。你可以用相对比例来分配列宽,让表格在任意页面宽度下都保持合理的排布:
table.Columns(columns =>
{
columns.RelativeColumn(3); // 商品名,占 3 份
columns.RelativeColumn(1); // 数量
columns.RelativeColumn(1); // 单价
columns.RelativeColumn(1); // 小计
});
你甚至可以为每一列设置宽度比例,让表格自动填满可用宽度——这是 HTML 转 PDF 方案很难优雅实现的东西。
Companion App:开发阶段的杀手级功能
QuestPDF 还提供了一个 Companion 桌面应用,在开发阶段可以实时预览 PDF 的效果,支持热重载。
你改一行代码,PDF 预览窗口直接刷新,不需要每次都运行程序再打开 PDF 文件检查效果。对于复杂报表的开发,这个功能能节省大量时间。
Companion 应用还能:展开 PDF 的节点树查看层次结构、放大缩小查看细节、显示布局异常信息并定位到代码行。
这对于调试"为什么这一行跑到下一页去了"这类问题特别有用。
性能,不是一句空话
官方的测试数据:每秒可生成数千页 PDF,保持极低的 CPU 和内存占用。这个数字对应的场景是大批量文档生成,比如一次性导出几千份发票。
支撑这个性能的基础是:QuestPDF 内置了一套专为 PDF 设计的布局引擎,渲染过程不需要浏览器介入;字体子集化(subsetting)自动进行,只嵌入用到的字形;图片压缩是自适应的,根据内容选择最优压缩比。
标准合规:发票系统集成的入场券
如果你做的是企业级应用,特别是要和发票系统对接,PDF 标准合规是硬需求。
QuestPDF 支持:
PDF/A-2/3
(归档标准,保证文档 10-20 年后可读) PDF/UA-1
(无障碍标准,支持屏幕阅读器) ZUGFeRD / Factur-X
(欧洲电子发票标准,内嵌 XML 结构数据)
最后这个对于欧洲市场的发票系统集成非常关键——Factur-X 要求 PDF 里必须包含机器可读的 XML 元数据,不只是视觉上的发票格式。QuestPDF 直接把这件事做进了库里面。
License:不恶心人
QuestPDF 的 License 政策在国内开源圈算是清流:
免费
个人使用、非营利组织、年度营收低于 100 万美元的组织、所有开源项目(MIT 协议) 付费
商业使用,企业用途,年费制,无席位或服务器费用
对于大多数中小型项目和 SaaS 产品,Community License 已经完全够用。没有"每天只能生成 1000 份"的限制,没有水印,没有功能阉割。
怎么跑起来
dotnet add package QuestPDF
是的,就这一行。
官方提供了完整的发票教程,250 行 C# 代码,包含从 Logo 加载、表格绘制到页码对齐的全部细节。认真读一遍,就能掌握 QuestPDF 的核心用法。
文档地址:questpdf.com
写在最后
PDF 生成不是一个性感的技术话题,但它是一个一旦方案选错就会持续坑你的领域。
QuestPDF 解决的不是 PDF 生成从 0 到 1 的问题——你能用 iTextSharp 也写出来。它解决的是从 1 到 100 的问题:当你的报表越来越复杂,当你的用户开始在不同环境下打开 PDF,当你要对接发票系统标准,当你需要在大批量生成时保证性能——QuestPDF 的优势才会完全显现出来。
换个角度说:能用 C# 代码直接设计 PDF 文档这件事本身,就已经是效率的巨大提升。代码即设计稿,代码即版本控制,代码即跨平台保障。
值得一试。