全部文章

2026 年 Go PDF 库终极对决

2026 年仍在维护的 Go PDF 库,在 4 种工作负载上基准测试,并对比许可证、依赖与维护状态。

作者: gpdf team

TL;DR

5 年前,搜索"Go PDF"几乎必然落在 jung-kurt/gofpdf。今天,它已归档。社区 fork go-pdf/fpdf 也归档了。真正的选择比搜索结果暗示的少得多:

  • 仍在积极维护:gpdf(本团队)、signintech/gopdf、johnfercher/maroto v2 —— 但 Maroto 仍依赖已归档的 gofpdf。
  • 已归档:jung-kurt/gofpdf(2021)、go-pdf/fpdf(2025)。
  • 商用 / AGPL:unidoc/unipdf。

本文在 4 种工作负载上对现役库做基准测试,列出许可证与依赖图,给出按用例的推荐。明年再跑一次。

偏见声明:我们是 gpdf 团队。基准测试代码公开(_benchmark/benchmark_test.go),欢迎自行克隆复测,若数字对不上请告知。

"Go PDF 库"其实是三种东西

"Go PDF 库"这个短语至少盖住 3 种不同的东西:

  1. 低层 PDF 写入器 —— 推字节、用图元绘制。jung-kurt/gofpdfsignintech/gopdf
  2. 包装写入器的布局库 —— 声明式的行列。johnfercher/maroto v2gpdf
  3. 完整文档套件 —— 解析、签名、PDF/A、OCR、涂黑。unidoc/unipdf

不明确类别就问"哪个 Go PDF 库最好",是 Reddit / 知乎上的讨论跑偏的主因。下文每节都尽量保持这个区分清晰。

未列入:任何调起 headless Chromium 的方案(go-rod、chromedp)。它们不是 PDF 库,是"会打印的浏览器"。对 CSS 重的设计还原度好,但冷启动慢、内存大、不适合 distroless。如果需求是"设计师给我 HTML/CSS,要像素级精确",那些工具存在,本文不参与那块比较。

记分板

最后发布归档许可证核心依赖CJK布局栅格2026 状态
gpdf(本团队)活跃MIT0原生12 栅格维护中
signintech/gopdf活跃MIT0手动 TTF维护中
johnfercher/maroto v2活跃MITgofpdf(已归档)经 gofpdf行/列地基已死
jung-kurt/gofpdf20212021-09-08MIT0AddUTF8Font已归档
go-pdf/fpdf20232025MIT0AddUTF8Font已归档
unidoc/unipdf活跃AGPL-3.0 / 商用商用

要注意 3 点。半数已归档。Maroto 自身在维护,但地基没人管 —— 即便今天能编译,仍是供应链问题。不接受 AGPL 的团队,unidoc 的选择不是技术问题而是 商业许可采购问题

基准测试

代码:gpdf 仓库里的 _benchmark/benchmark_test.go。环境:Apple M1(Max, 32 GB, macOS 14.5),Go 1.25,无 CGO。每个用例至少跑 5 秒实时,-benchmem 打开,记录 ns/op 和分配次数。

挑这 4 个场景是因为它们接近生产中实际生成的东西,不是合成玩具:

  1. 单页 hello world。1 页、1 行字、1 字体。每文档固定开销的下限。
  2. 4 列 10 行发票表。表头 + 10 行内容 + 列对齐 + 细边框。"生成发票"的形状。
  3. 100 页分页报表。重复页眉、页脚、页码,每页正文。度量分页成本。
  4. 复杂 CJK 发票。日文(平假名、片假名、汉字)混排,4 列 15 行明细、页眉、带页码的页脚,嵌入 NotoSansJP TrueType 子集。

未纳入:unidoc/unipdf。其二进制由许可证门控,在公开基准仓库里复现其发布方法论会产生误导。若在评估 unidoc,请跑其官方基准。

结果

工作负载gpdfsignintech/gopdfMaroto v2gofpdfgo-pdf/fpdf
单页 hello world13 µs423 µs237 µs132 µs135 µs
4×10 发票表108 µs835 µs8,600 µs241 µs243 µs
100 页报表683 µs8,600 µs19,800 µs11,700 µs11,900 µs
复杂 CJK 发票133 µs997 µs10,400 µs254 µsn/a

go-pdf/fpdf 的 CJK 栏 n/a:测试版本中 AddUTF8Font 在读取 NotoSansJP 的 cmap format 12 表时 panic。可修但 fork 已归档,没人发补丁。

读数字

跨场景排名稳定。在所有用例里 gpdf 比第二名 快 10–30 倍,既不魔也不意外。3 个设计叠加:

单遍布局。gpdf 不构建中间 AST 再序列化。构建器在解析时直接写 PDF 内容流,分配数约为其他库的一半。100 页基准里 683 µs 对 19,800 µs 的差距,不是调参差异,是 架构不同

热路径无反射。布局引擎触碰的类型都是具体类型。单独看是微优化,但跨 100 页报表累积后,接口分派会在 profile 中显形。我们避开了。

TrueType 子集器不重读 cmap。gofpdf 每次字形查询都重读 cmap 表;gpdf 解析一次后缓存。纯 Latin 几乎无感;CJK 一段可能跨汉字、假名、标点用到 150 个字形,差距就是"同步生成够用"和"要排队"。

基准表看不到的告诫:对大部分 PDF 工作负载,绝对速度不如人想得那么重要。有意义的阈值是"能不能在请求路径上同步生成"。单页 hello world,本文所有库都过阈值;100 页报表,只有 gpdf 过。若最大文档就是一张收据,现役 4 库都行,按 API 与许可证选。

依赖

各库 go getgo mod graph 的结果:

外部模块传递性归档依赖
gpdf(核心)0
signintech/gopdf0
gofpdf0(自身归档)自身
go-pdf/fpdf0(自身归档)自身
johnfercher/maroto v2gofpdf(2021 归档)是 — gofpdf
unidoc/unipdf多(图像、加密、压缩)无归档

有"生产依赖禁止归档仓库"lint 规则的团队,今天的 Maroto v2 会被这条挡下。Maroto 维护者一年多来在替换 gofpdf,完成后此行会变。请在决策前确认 Maroto 仓库的当前状态。

许可证

许可证
gpdf(核心)MIT
signintech/gopdfMIT
johnfercher/maroto v2MIT
gofpdfMIT
go-pdf/fpdfMIT
unidoc/unipdfAGPL-3.0 或商用许可

unidoc 的 AGPL 条款严格:若用于用户通过网络交互的服务器,你的服务器端代码也必须以 AGPL 发布 —— 多数闭源 SaaS 无法接受。实际上商用许可成为唯一可行路径,价格不公开,需与销售沟通。

GitHub star 比较中最常被忽视的就是此处。unidoc 功能最多、星最多,但许可证对多数商用用例关上门(需购买)。并非贬低 unidoc —— 商业模式合理、产品优秀 —— 只是 go get 前请心里有数。

维护状况

  • gpdf —— 主维护者为本团队(gpdf-dev)。每 2–4 周发布一次;路线图在 repo 内;CI 覆盖 Go 1.22–1.26;主 repo 的 issue 数工作日内回应。
  • signintech/gopdf —— 活跃,提交节奏较慢。issue 会被看,PR 一般数周内合并。主用途仍是低层生成。
  • Maroto v2 —— 活跃。v2 在 2023 年落地后稳定。gofpdf 依赖已知、正在替换。决策前看 repo 当前状态。
  • gofpdf —— 2021-09-08 归档。仓库横幅:"This repository has been archived by the owner. It is now read-only." 不发安全补丁、不修 bug。
  • go-pdf/fpdf —— 2025 年归档。README 建议改用其他库。我们写了专门的迁移指南:gofpdf 已归档:迁移到 gpdf
  • unidoc/unipdf —— 活跃,商业团队,资源充足,提供企业支持。

怎么选

给决策树而不是功能矩阵。"功能最多"往往不是正确问题:

  • "Go 代码库要生成发票、报表、文档,希望 MIT、零依赖,文档中偶尔有 CJK。" → gpdf。
  • "低层 PDF 生成,带自定义几何,想要小、稳、手动控制的库。" → signintech/gopdf。
  • "已有 Maroto 风格的布局代码,能跑。" → 保持 Maroto v2 直到 gofpdf 替换落地后再评估。API 本身不是问题。
  • "需要 PDF/A、OCR、涂黑、电子签名,公司愿意付商用许可费。" → unidoc/unipdf,先谈许可证。
  • "仍在 gofpdf 上,跑得好好的。" → 今天没事。在下一个相关依赖 CVE 出现前规划迁移。迁移指南
  • "需要像素级 HTML/CSS 转 PDF。" → 以上都不行。用 go-rod / chromedp + headless Chromium,接受冷启动成本。

我们是 gpdf 团队,自然认为第 1 和第 5 类的大多数场景 gpdf 是合理默认 —— 偏见在所难免。请读基准代码、在本地跑一跑,别照单全收这张表。

30 行的 gpdf 示例

"最快"与"依赖图最小"只有代码能读时才有意义。完整可运行的发票一页,无伪代码、无省略 import:

package main

import (
    "log"
    "os"

    "github.com/gpdf-dev/gpdf"
    "github.com/gpdf-dev/gpdf/document"
    "github.com/gpdf-dev/gpdf/pdf"
    "github.com/gpdf-dev/gpdf/template"
)

func main() {
    doc := gpdf.NewDocument(
        gpdf.WithPageSize(document.A4),
        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
    )

    page := doc.AddPage()
    page.AutoRow(func(r *template.RowBuilder) {
        r.Col(12, func(c *template.ColBuilder) {
            c.Text("发票 #2026-0042", template.Bold(), template.FontSize(20))
            c.Spacer(document.Mm(6))
            c.Table(
                []string{"项目", "数量", "单价", "金额"},
                [][]string{
                    {"前端开发", "40 小时", "¥1,050", "¥42,000"},
                    {"后端开发", "60 小时", "¥1,050", "¥63,000"},
                    {"UI 设计", "20 小时", "¥840",   "¥16,800"},
                },
                template.ColumnWidths(50, 15, 15, 20),
                template.TableHeaderStyle(
                    template.Bold(),
                    template.TextColor(pdf.White),
                    template.BgColor(pdf.RGBHex(0x1A237E)),
                ),
            )
        })
    })

    data, err := doc.Generate()
    if err != nil {
        log.Fatal(err)
    }
    if err := os.WriteFile("invoice.pdf", data, 0o644); err != nil {
        log.Fatal(err)
    }
}

SetXY。零手动列宽计算。给文档选项加 gpdf.WithFont("NotoSansCJK", ttfBytes),上面的中文就能正常渲染,不会变豆腐。

未纳入的部分

每篇比较都有"因为 X 而未纳入"的小节。我们的:

  • 私有 gofpdf fork。确实存在在生产里的内部 fork。看不到的代码无法基准。
  • pdfcpu。常在"Go PDF 库"列表里出现,但主用途是 PDF 处理器(合并、拆分、加密、盖章),不是生成。超出本文范围,会单写一篇处理向的文章。
  • 任何包装 gotenberg 或 headless 浏览器服务的。不是库,比较不公平。
  • 我们自家的 gpdf 基准。本次比较的焦点是核心数字。

FAQ

gpdf 为什么比 gofpdf 快 10 倍?是不是有魔法? 没有单一魔法。3 个设计叠加:单遍布局(构建器到写入器之间无 AST)、热路径用具体类型、cmap 缓存的 TrueType 子集器。任一单独提供 2× 收益,叠起来是数量级差异。

能自己复现这个基准吗? 能。git clone https://github.com/gpdf-dev/gpdf && cd gpdf/_benchmark && go test -bench=. -benchmem。相同 CPU 架构、相同 Go 版本下数字对不上,请开 issue。基准漂移会发生,我们想知道。

gofpdf 会回来吗? 现实地讲,不会。最后一次提交是 2021 年。issue tracker 已关闭。即使有人重新打开,游标 + 单字节字体 + 无栅格的架构不是 2026 年合适的起点。当作历史文物、直接迁移更实际。

Java iText / Python ReportLab / Node pdfkit 呢? 跨语言基准是另一篇。简短版:Go 在吞吐和冷启动上通常赢,在功能广度(尤其 HTML→PDF 保真度)上输。已在 Go 的团队用 gpdf 更快、更小;Python / Node 团队迁移成本高,只有在大流量场景才划算。

如果 gpdf 的竞争对手变强,这份对比会保持公平吗? 会。每年跑一次。若 signintech/gopdf 推出表格 API 把时间减半,2027 版会写进去。若 Maroto v2 替换完 gofpdf,那一行会改。基准代码故意公开,不用任何人相信我们。

试试 gpdf

gpdf 是 Go 的 PDF 生成库。MIT、零依赖、原生 CJK。

go get github.com/gpdf-dev/gpdf

⭐ 在 GitHub 点星 · 阅读文档

下一步阅读