HTML 转 PDF 不用装浏览器了:等了这么多年,纯 C# 的渲染引擎可算来了
|
admin
2026年10月30日 15:11
本文热度 125
|
服务器内存又被 Chromium 吃掉了?
做过服务端 PDF 生成的同学,大概都经历过这样的崩溃时刻:凌晨三点告警——Docker 容器 OOM。排查半天,发现是某个报表服务跑了十几个 Puppeteer 实例,每个吃 300MB 内存,峰值直接把机器打穿。更恶心的是部署。你要在 Alpine 镜像里塞一个 300MB 的 Chromium,处理各种依赖冲突,CI 里要加 --no-sandbox,K8s 里要考虑共享内存……你只是想生成一张 PDF 发票,但运维同事看你的眼神像在看一个恐怖分子。Playwright、Puppeteer、wkhtmltopdf、WeasyPrint……工具不少,但它们有一个共同的问题:本质上都是「启动一个完整浏览器,让它帮你画一帧」。如果是为了渲染复杂交互页面,这没问题。但如果你只是想把一段 HTML 模板转成 PDF——发票、报表、合同、证书——杀鸡真的不用牛刀。
PaperSharp:不装浏览器,两行代码出 PDF
最近发现了PaperSharp,一个 .NET 生态的纯托管 HTML/CSS → PDF/图片渲染引擎。它的核心主张很简单:把 CSS 排版引擎直接编译进你的 .NET 进程,不调任何外部浏览器。NuGet 包名 PaperSharp,安装一行搞定:dotnet add package PaperSharp
using PaperSharp; byte[] pdf = Renderer.RenderPdf(html, css);byte[] png = Renderer.RenderPng(html, css, width: 794, scale: 2);
没有子进程,没有端口监听,没有内存黑洞。它就是你的类库里的一个方法调用。
https://papersharp.netlify.app
在线测试papersharp的渲染能力,浏览器使用WASM 实现前端浏览器直接运行c#代码,生成速度会额外增加,只用于演示。
杀手级特性一:原生中文排版
用过 iText 或者 PDFSharp 的人都知道,中文 PDF 生成的痛点不只是「能渲染」——而是排版语义。PaperSharp 在这方面的处理让我印象深刻:逐字级 CJK 断行:中文字符不会在行中间被截断成半字汉字页码:counter(page, cjk-ideographic) 直接输出「壹 / 贰 / 叁」字形级字体子集化:一页纯文本发票只有 50–120KB,而且文字可选中、可搜索举个实际例子。一个跨页长表格,表头自动重复,中文数字页码,表格行不被拆散——全是一个 CSS 搞定:@page { @bottom-right { content: counter(page, cjk-ideographic) ” / ” counter(pages) ” 页” }} thead { display: table-header-group }tr { break-inside: avoid }
这种打印级语义,大部分 PDF 库做不到,浏览器能但代价太大。PaperSharp 把它做到了原生。
杀手级特性二:30ms 一页,线程安全
对比一下:一个典型 Puppeteer 实例生成单页 PDF 大约 200–800ms,而且并发能力极差(每个实例一个 Chrome 进程)。而且因为是纯托管代码,在 .NET 进程内跑 GC、内存可控、不需要管理外部进程生命周期。部署到 K8s 里就是一个普通的 .NET Pod,不需要特殊 volume 挂载 /dev/shm。
杀手级特性三:确定性输出
同样的输入,永远产出 byte-identical 的结果。别小看这一点。在电子发票、合同、审计报表这类场景里,「可复现」意味着合规。浏览器渲染受 GPU 驱动、字体 hinting、OS 版本影响——同一份 HTML 在 Windows 和 Linux 上像素级不一致。PaperSharp 默认离线,不请求网络资源,渲染管线全在自己控制下。这意味着你可以做 snapshot 测试、可以存哈希做审计、可以让 QA 环境完美复现生产结果。
怎么用?一个完整例子
using PaperSharp; var html = """ 2026 Q3 财务报表 科目金额 营业收入¥12,345,678.00 <!-- 几百行... -->"""; var css = """@page { size: A4; margin: 25mm 20mm; @top-center { content: ”机密文件”; font-size: 8pt; color: #999 } @bottom-center { content: ”第 ” counter(page) ” 页 / 共 ” counter(pages) ” 页” } } table { border-collapse: collapse; width: 100% }thead { display: table-header-group }tr { break-inside: avoid }td, th { padding: 6px 14px; border: 1px solid #ddd }"""; byte[] pdf = Renderer.RenderPdf(html, css); // → 直接写响应流或存对象存储
几百行的财务表格自动跨页、表头重复、页码生成、水印页眉——全搞定。
它支持什么?
布局引擎:block flow、flexbox(含 wrap/reverse/gap/嵌套)、Grid 子集、表格、浮动 + 文字环绕、定位视觉效果:渐变(线性/径向/锥形)、圆角(含 squircle)、阴影、透明度、混合模式、clip-path、filter 链复杂文字:HarfBuzz 阿拉伯文整形、RTL/bidi、grapheme 安全断行(emoji ZWJ)伪元素:::before/::after + attr() + counter()/counters()PDF 增强:h1–h6 书签、可点击链接标注、文档元数据多格式输出:PDF / PNG / JPEG / WebP / ICO / SVGJS/WASM SDK:编译到 WebAssembly,在 Node.js/Bun 里调用(甚至可以给前端直接用)
谁该看看这个项目?
用 .NET 后端的业务系统——发票、合同、报表、证书,凡是需要 PDF 导出的被 Headless Chrome 折磨过的运维——容器瘦身、消除子进程管理做文档/打印类产品的独立开发者——HTML/CSS 模板 = 用户可编辑的 WYSIWYG如果你用的是 Python/Node/Go,目前还没直接受益(npm 包有 WASM 版但成熟度不同)。但如果你是 .NET 生态——这就是你等的那个东西。
局限性(说点不好听的)
它不是浏览器。不执行 JavaScript,不支持 ``、Web Components、Service Worker。复杂动态页面它搞不了。CSS 覆盖度是「子集」不是全部。Grid 只支持一部分,某些冷门属性会 fallback 或忽略。项目还在活跃开发中(MIT 协议),API 可能有 breaking change。但对于**「模板 → PDF」这个垂直场景**——发票、报告、合同——它的覆盖度已经非常实用了。
最后说一句
我其实一直在等 .NET 生态出现这样一个东西。不是「又一个封装 iText 的库」,而是真正理解了 CSS 打印语义、从零实现了排版管线、把浏览器能力裁剪到恰好够用的引擎。PaperSharp 目前做到这一步了。虽然还年轻,但方向是对的。
阅读原文:点击这里
该文章在 2026/10/30 15:12:13 编辑过