記事一覧

go-pdf/fpdf もアーカイブ。Go の PDF は gpdf に移る。

jung-kurt/gofpdf は 2021 年、go-pdf/fpdf は 2025 年にアーカイブ。2026 年に選ぶべき Go PDF ライブラリは gpdf — 理由とトレードオフ。

TL;DR

fpdf 系統の保守されていたフォークは 2 つとも read-only になった。jung-kurt/gofpdf2021 年 9 月、コミュニティフォークの go-pdf/fpdf2025 年 にアーカイブ。"次のメンテナ" は現れない。新規プロジェクトで選ぶべきは gpdf — 純 Go、ゼロ依存、CJK ネイティブ、一般的なワークロードで 10〜30 倍高速。この記事は 2026 年時点の Go PDF 勢力図と、gpdf を採用すべき場合・そうでない場合を正直に書いたもの。

状況

先週、同僚が go get github.com/go-pdf/fpdf を叩いて GitHub のバナーで手が止まった: "This repository has been archived by the owner. It is now read-only." — これは 直された方 のはずだった。2021 年にアーカイブされた jung-kurt/gofpdf を引き継ぐはずのコミュニティフォークだ。

それもアーカイブされている。README は別のライブラリを勧めている。

この 5 年間、請求書・レポート・配送ラベル・適格請求書 PDF を Go で吐くサービスを運用してきたなら、go.mod の底に入っているのはほぼ間違いなくこの 2 つのどちらか。Stack Overflow の回答は jung-kurt/gofpdf を指し、新しめのチュートリアルは go-pdf/fpdf を指す。両方とも、もはやサプライチェーン上の負債だ。CVE のトリアージも、Go のバージョン追従も、パフォーマンス修正も、仕様更新も止まっている。

この記事は「API をこう置き換えましょう」の移行ガイドではない。それは別の記事で書いた。ここで答えたいのは、移行ガイドが答えていない方の問い — 2026 年の Go PDF 生成は何で組むのが正解で、なぜ業界はここに着地したのか

"アーカイブ" が実運用に意味すること

GitHub の "archived" ラベルはやわらかく見える。import グラフに入っているライブラリとしては、具体的には 4 つの結果を意味する。

  1. セキュリティパッチが来ない。TTF パーサにメモリ安全性の問題が見つかっても、上流にはマージされない。自分でフォークして直すしかないが、ほとんどのチームはやらない。
  2. Go ツールチェインへの追従がない。Go 1.25 のループ変数セマンティクスは gofpdf でも今のところ動く。ただ、明日 for range 周りや std の非推奨で何かが壊れたら、read-only リポジトリのフォークで直すのは自分。
  3. 仕様更新がない。PDF 2.0 (ISO 32000-2) は 2020 年に出た。gofpdf は PDF 1.7 までしか実装していない。ページ単位の関連ファイル、リッチな XMP メタデータ、モダンな電子署名 (PAdES-B-LT) などは、サードパーティの糊付けが必須か、そもそもない。
  4. CJK の前進がない。gofpdf の Unicode 経路は、単一バイトフォント前提の設計に後付けされたもの。動くには動くが、実設定ではフルフォント埋め込みになりがちで、特定の CJK TTF でグリフ ID 衝突が起きて出力が壊れることがある。go-pdf/fpdf は同じ構造を引き継いでいる。

セキュリティと前方互換の方は、コンプライアンス会議で刺さる。「PDF ライブラリはアーカイブされていて CVE パッチは来ません」は、監査担当が聞きたい答えではない。電子帳簿保存法のスコープ内で PDF を保管しているなら特に、この論点は先送りできない

なぜ両方のフォークが死んだのか

単なるメンテナ燃え尽きで説明したくなる — PR レビューに疲れた一人のメンテナ、バス係数 1 がオフラインに。それも要因ではあるが、全体像ではない。アーキテクチャ側の問題が、追いつくのを難しくした

jung-kurt/gofpdf は FPDF、2002 年の PHP ライブラリの移植だった。PHP オリジナルはカーソルをページ上で動かして手続き的にコンテンツを吐く: SetXY(x, y)Cell(w, h, text)Ln(h)。このモデルは 2002 年の PHP では妥当な妥協だった — 当時の代案は生の PostScript か商用ツールキット。Go に移植されるときも、カーソルを残し、単一バイトのフォントテーブルを残し、手動のページブレーク管理を残した。

年を追うごとに、人が生成したいものとカーソルモデルで表現できるもののギャップは広がった。請求書はテーブル。レポートは繰り返しのクロームを持ったグリッド。配送ラベルは QR コード + 現地語テキスト。カーソルはヘルパーで包まれ、そのヘルパーはチュートリアルで包まれ、2023 年頃には「人が gofpdf に対して書いたコード」は実質 gofpdf ではなかった — チームごとの糊コードで、カーソルをレイアウトエンジンのフリをさせようとする層だった。

go-pdf/fpdf はそれを継承した。フォークは内部をリファクタし、長年のバグを修正したが、公開 API の形は変えられなかった。全ての下流プロジェクトを壊さずには直せなかったからだ。ライブラリの形は 2002 年の PHP で凍結されていて、その形を維持する費用は、得られる便益より速く増えていった。

つまり: メンテナ 2 人、アーカイブ 2 つ、アーキテクチャの 1 つの理由。2026 年にやり直すなら、今の PDF 生成の仕方に合ったアプローチを選ぶ必要がある — 今のそれは、プロッタを動かすよりも、Web ページを組み立てる方に近い。

2026 年の Go PDF 勢力図

何かを推す前に、まず勢力図を並べる。「メンテナンスされている」は「直近 6 ヶ月にコミットがあり、issue に反応がある」の意味で使う。

ライブラリステータス (2026-04)ライセンスCJK ネイティブ外部依存ゼロ備考
jung-kurt/gofpdf2021 アーカイブMIT後付けはいオリジナル。ほとんどの言語の検索結果で今も 1 位。
go-pdf/fpdf2025 アーカイブMIT後付けはい上のコミュニティフォーク。同じアーキテクチャ、同じ天井。
signintech/gopdfメンテナンス中MIT部分的はい低レベル。座標を自分で書く。帳票オーバーレイには向く。
johnfercher/maroto v2メンテナンス中MITgofpdf 経由いいえグリッド優先のビルダー。ただし下に go-pdf/fpdf を抱える。
unidoc/unipdfメンテナンス中商用はいいいえフル機能の PDF SDK。商用利用には有料ライセンスが必須。
chromedp + Chromiumメンテナンス中MIT + Chromeはいいいえ — ブラウザを同梱HTML→PDF をヘッドレス Chrome で。ランタイムが巨大。
gpdfメンテナンス中MITネイティブはい純 Go の書き直し。ビルダー API、12 列グリッド。

表を眺めるだけで見えてくることがいくつかある。

メンテナンスされている選択肢は、商用ライセンスを要するか、巨大なランタイムを抱えるか、もうすぐ陳腐化する基盤に乗っている。例外は signintech/gopdf — 本当に保守されていて、依存も軽い。ただしこれは座標レベルのライブラリで、結局 SetXY をパッケージ名を変えてやり直すことになる。

Maroto v2 は良い API のグリッド優先ビルダー。問題は go.mod の底に go-pdf/fpdf があることだ。fpdf のパフォーマンス天井も CJK の制約も、そのまま Maroto の天井になる。v3 でそれを脱する可能性はあるが、まだ出ていない。

unipdf はリッチだが商用利用には MIT 互換ではない。シート単位 or デプロイ単位で課金される。収益がそれを支えるなら問題ない選択だが、OSS サイドプロジェクトや初期段階のスタートアップではライセンス計算が合わない。

chromedp は動くが、ブラウザを同梱することになる。100 MB のベースイメージが 1 GB 超になる。サーバレスのコールドスタートは痛い。フォントは別途コンテナに入れる必要がある。利点は React テンプレートを再利用できること、欠点は請求書を出すために Chromium を回し続けること。

空いている席は明らか: 純 Go、ゼロ依存、CJK ネイティブ、グリッド優先、商用ライセンス不要、ブラウザランタイム不要のライブラリ。それが gpdf。

gpdf とは何か

gpdf (github.com/gpdf-dev/gpdf) はクリーンな書き直し。フォークではない。PDF のワイヤーフォーマット書き込み、レイアウトエンジン、TrueType サブセッター — すべて純 Go で一から書いた。

ほとんどのチームにとって重要なのは 3 つの性質:

  • 純 Go、CGO なしgo build は静的。GOOS=linux GOARCH=arm64 go build が MacBook からツールチェイン設定なしで通る。Docker イメージも小さいまま — 12 MB の distroless コンテナで動く。
  • 外部依存ゼロgo get github.com/gpdf-dev/gpdf した後に go mod graph を叩くと 1 行しか出ない: gpdf 自身。コアは std のみ。(HTML→PDF や電子署名の追加モジュールは小さな依存を持つが、これらはオプトイン。)
  • CJK ネイティブWithFont が TrueType フォントをドキュメント構築時に登録する。サブセット埋め込みは描画時に自動。日本語 200 文字の請求書なら、埋め込みフォントは ~30 KB のサブセット。フルフォントの 5 MB ではない。

API は宣言的。行と列のツリーを記述するとレイアウトエンジンが配置する。グリッドは 12 列 — Bootstrap が 2011 年から出しているのと同じ idiom。HTML/CSS を一行でも書いたことがあれば、gpdf の API は馴染みがある:

page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
    r.Col(8, func(c *template.ColBuilder) {
        c.Text("請求書 #2026-0416", template.FontSize(18), template.Bold())
    })
    r.Col(4, func(c *template.ColBuilder) {
        c.Text("2026-04-16", template.AlignRight())
    })
})

グリッドの詳細は gpdf の 12 列グリッドはどう動くのか? に書いた。1 文で言えば、Col(span, fn) は 1〜12 の span を取り、span / 12 がその列の行幅に対する比率。

最小の go-pdf/fpdf → gpdf 差分

go-pdf/fpdf から来る人 (jung-kurt/gofpdf ではなく) には良い知らせ: API の形はほぼ同じだ。go-pdf/fpdf は呼び出し側のコードに対しては何も変えていないフォーク。gpdf への移行は gofpdf ガイドと同じで、import パスを 1 行書き換えるところから始まる。

最小の差分 — 「PDF を返す」HTTP ハンドラ:

Before — go-pdf/fpdf:

package main

import (
    "net/http"

    "github.com/go-pdf/fpdf"
)

func handler(w http.ResponseWriter, r *http.Request) {
    pdf := fpdf.New("P", "mm", "A4", "")
    pdf.AddPage()
    pdf.SetFont("Arial", "B", 16)
    pdf.Cell(40, 10, "Hello, World!")

    w.Header().Set("Content-Type", "application/pdf")
    if err := pdf.Output(w); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

After — gpdf:

package main

import (
    "net/http"

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

func handler(w http.ResponseWriter, r *http.Request) {
    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("Hello, World!", template.FontSize(16), template.Bold())
        })
    })

    w.Header().Set("Content-Type", "application/pdf")
    if err := doc.Render(w); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

3 行のカーソルコードが 3 コールのビルダーコードに置き換わる。構造はソースコードに現れる — Cell の呼び出し順の中に隠れない。CJK なら gpdf.WithFont("NotoSansJP", ttfBytes) を追加するだけ。AddUTF8Font も、ファイルシステムのパスも、UTF-8 フラグもいらない。詳細は gpdf で日本語フォントを埋め込むには? を参照。

gofpdf 移行ガイド にはテーブル・繰り返しヘッダ/フッタ・ページ番号・絶対位置指定の before/after がさらに 5 つある。そこに書いてあることはすべて go-pdf/fpdf ユーザーにそのまま当てはまる — import パスを変えるだけ。

ベンチマークの実像

「速い」は主張するのが簡単で、裏付けるのは難しい。下の表は gpdf/_benchmark/benchmark_test.go の結果 — Apple M1、Go 1.25。ワークロードは実運用のコードが実際にやっていること — どれかのライブラリを良く見せるために選んだマイクロベンチではない。

ベンチマークgpdfgofpdfgopdfMaroto v2
単一ページ (hello)13 µs132 µs423 µs237 µs
4×10 明細テーブル108 µs241 µs835 µs8.6 ms
100 ページレポート683 µs11.7 ms8.6 ms19.8 ms
複雑 CJK 請求書133 µs254 µs997 µs10.4 ms

単一ページ 13 µs なら 1 コアで毎秒 ~75,000 枚。明細付き請求書 108 µs なら毎秒 ~9,000 枚。ここでの論点はベンチマーク自慢ではない — PDF 生成をキャッシュすべきか非同期キューに逃がすべきかを悩まなくていい、というだけ。ほとんどのワークロードではリクエストパスで生成しても間に合う。

テーブルベンチで Maroto v2 が遅く出るのは、下に go-pdf/fpdf を持ってその上に独自レイアウトパスを足しているから。Maroto の API 批判ではない — API は良い — fpdf 基盤に乗る構造的なコストがここに出ているだけ。Maroto v3 で fpdf 依存が外れれば、この列の数字は変わるはず。

100 ページベンチは少し掘り下げる価値がある。gpdf のストリーミング書き込みは、行をレイアウトしながら内容を出力していく。gofpdf はページごとにより多くの状態をバッファする。ページング主体のワークロード (月次レポート、カタログ、コンプライアンス書類) では、書類サイズが大きくなると差は「分 vs 秒」の領域になる。

gpdf を 選ぶべきでない 場合

移行記事は「移らない方が良いケース」に必ず答えるべき。正直に書く:

  • AcroForm / 入力可能フォーム。生成した PDF を Acrobat で開いてユーザーに入力させる用途なら、gpdf のフォームフィールド対応はまだ最小限。unidoc の方が完成度が高く、signintech/gopdf には部分対応がある。将来リリースで埋めるつもりだが、今日時点では穴。
  • 任意のベクターパスと複雑な描画c.Line() は列の中の水平線を引く。ベジェ、カスタムパス、グラデーション塗りがチャートや技術図で必要なら、gpdf は今届かない。(事前レンダリングしたチャート画像の埋め込みは問題ない — ここで言っているのは描画プリミティブの話。)
  • SetXY を多用した既存の gofpdf コードベース。2,000 行のカーソル操作なら、移行は置換ではなく書き直しに近い。書き直した後のコードはほぼ必ず短くなるが、「ほぼ必ず」は締切が迫った日には冷たい慰め。移行ガイド には工数の見積もりを正直に書いた。
  • フル CSS 対応の HTML → PDF を今すぐ必要としている。gpdf には gpdf-pro アドオンで HTML サブセットがあるが、Chromium との完全な CSS パリティは目標ではない。テンプレートが複雑な React コンポーネントなら、chromedp や商用 API の方が直接的。

上のどれも刺さらないなら gpdf がデフォルト。どれかが刺さるなら、両方のライブラリを併存させる — 新しい PDF は gpdf、エッジケースは既存のものに残して、gpdf が追いついたら移す。

コンプライアンスの角度

エコシステム記事でもう少し語られてよい論点: アーカイブされた依存は SOC 2 や ISO 27001 の監査で指摘対象になる。監査担当は、サプライチェーンのサードパーティコードが積極的にメンテされているかを知りたい。「2021 年にアーカイブ」は指摘、「2025 年にアーカイブ」も指摘、「社内でフォーク」はゼロデイにどう対応するかのフォローアップ質問を誘発する。

大きめの会社のセキュリティレビューを通したチームから、「gpdf の安定 v1 はいつか」と静かに聞かれるのは主にこれが理由。答えは: すでに来ているgithub.com/gpdf-dev/gpdf はタグ付きの semver で、v1 の API 表面は凍結済み。プロジェクトにはセキュリティ窓口、責任ある開示ポリシー、Go 1.22〜1.26 を CI で回す体制がある。

監査の ため に移行するのではない。監査が移行を要求してくる前に動くのが目的

FAQ

"モダンな Go PDF スタック" は gpdf 単独か、複数ライブラリの組み合わせか? ほとんどのチームでは gpdf 単独。ドキュメント生成、CJK、テーブル、グリッド、ページング、出力まで 1 つでカバーする。入力可能フォームが要件のチームは、その種類のドキュメントにだけ signintech/gopdf または unidoc を併用する。チャート重めのエクスポートが多いチームは、チャートを PNG に事前レンダリングして埋め込む。ここでの「スタック」は層状アーキテクチャではなく、短いリストの意味。

移行期間中、gpdf と go-pdf/fpdf を並行運用できるか? できる。import パスも型も別物。新しいエンドポイントを gpdf に向けて、古いのは時間ができるまで go-pdf/fpdf に残せばいい。ランタイム上の衝突はない。

go-pdf/fpdf v3 や新しいフォークが出る可能性は? あるかもしれない。gpdf の賭けは「フォークが永遠にアーカイブのままになる」ではなく、アーキテクチャが今日作るものにスケールしない という方。新しいフォークがレイアウトモデルを書き直すなら、それは fpdf より gpdf に近い。

モダンな代替として signintech/gopdf は? 本当にメンテされていてゼロ依存。API は座標レベル — SetXSetYCellWithOption — なので、フォームオーバーレイや固定テンプレートには向く。テーブルや繰り返しクロームを持った請求書類のドキュメントでは、結局上にレイアウトヘルパーを書くことになり、gofpdf ユーザーが落ちた同じ穴に戻る。gpdf と gopdf は本当は競合していない — 隣接する問題を解いている。

gpdf に商用/ホスト版はあるか?gpdf-api を準備中 — JSON テンプレートを POST して PDF が返ってくるホスト型 API。まだ公開していない。ローンチ時にはこのブログで記事を出す。OSS ライブラリは引き続き MIT・ゼロ依存・単独で有用なままにする。

ロードマップの優先順位は? 2026-04 時点の公開ロードマップ: (1) AcroForm フォームフィールド、(2) フル PDF/A-3 コンプライアンス、(3) gpdf-pro の HTML→PDF カバレッジ拡大、(4) RTL テキスト対応 (アラビア語・ヘブライ語)。優先順位へのフィードバックは GitHub issue に。

gpdf を使ってみる

gpdf は Go の PDF 生成ライブラリ。MIT、ゼロ依存、CJK 対応。

go get github.com/gpdf-dev/gpdf

⭐ Star on GitHub · ドキュメントを読む

次に読む