Skip to content

Go PDF ライブラリ — PDF Oxide

PDF Oxide は Go 向けの最速 PDF ライブラリです。テキスト抽出はページ平均 0.8 ms、PyMuPDF の 5 倍、pypdf の 15 倍高速、3,830 件の PDF でパス率 100%。抽出・作成・編集をひとつのモジュールに。読み取りは sync.RWMutex により goroutine セーフ。ライセンスは MIT / Apache-2.0 です。

インストール

go get github.com/yfedoseev/pdf_oxide/go
go run github.com/yfedoseev/pdf_oxide/go/cmd/install@latest

前提条件: Go 1.21+ で CGo が有効(CGO_ENABLED=1、既定値)であること。

v0.3.31 からは、ネイティブ FFI アーカイブをモジュールにコミットせずオンデマンドでダウンロードします。install コマンドは対応プラットフォームの tarball(約 26 MB)を ~/.pdf_oxide/v<version>/ に取得し、公開済みの .sha256 で SHA-256 を検証したうえで、エクスポートすべき CGO_CFLAGSCGO_LDFLAGS を表示します。環境変数ではなくコード側に cgo_flags.go を出力したい場合は --write-flags=<dir> を指定します。マシンごとに一度実行するだけで済み、@latestruntime/debug.ReadBuildInfo から対応リリースを自動解決します。

バインディングは Rust コアをスタティックライブラリとしてリンクするため、ビルドされた Go バイナリは完全に自己完結しています。実行時に LD_LIBRARY_PATHDYLD_LIBRARY_PATHPATH を設定する必要はありません。go build したものをそのまま配布できます。

モノレポ/ソースツリーからのビルド: -tags pdf_oxide_dev を付けるとローカルの target/release/libpdf_oxide.a を CGo が参照します。インストーラーは不要です。

v0.3.30 以前からのアップグレード: コミットされていた go/lib/ 以下のネイティブアーカイブは削除されました。アップグレード後の最初の go build はリンク時に undefined reference to pdf_document_open ... で失敗します。一度 go run github.com/yfedoseev/pdf_oxide/go/cmd/install@latest を実行し、表示された CGO_* 変数をエクスポート(または --write-flags で書き出し)してください。

ビルド済みプラットフォーム:Linux x64/arm64、macOS x64/arm64(Apple Silicon)、Windows x64(x86_64-pc-windows-gnu 経由)。

PDF を開く

package main

import (
    "fmt"
    "log"

    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    doc, err := pdfoxide.Open("research-paper.pdf")
    if err != nil {
        log.Fatal(err)
    }
    defer doc.Close()

    count, _ := doc.PageCount()
    major, minor, _ := doc.Version()
    fmt.Printf("%d ページ, PDF %d.%d\n", count, major, minor)
}

Page API

v0.3.34 以降はページ単位の操作が可能です。doc.Page(i) は親ドキュメントに処理を委譲する軽量な *Page ハンドルを返します。

page, _ := doc.Page(0)
text, _ := page.Text()
md, _   := page.Markdown()

pages, _ := doc.Pages()
for _, p := range pages {
    t, _ := p.Text()
    fmt.Printf("--- ページ %d ---\n%s\n", p.Index+1, t)
}

PageText()Markdown()Html()PlainText()Chars()Words()Lines()Tables()Images()Paths()Fonts()Annotations()Info()Search()NeedsOcr()TextWithOcr() を提供します。

テキスト抽出

単一ページ

text, err := doc.ExtractText(0)
if err != nil {
    log.Fatal(err)
}
fmt.Println(text)

全ページ

allText, err := doc.ExtractAllText()
if err != nil {
    log.Fatal(err)
}
fmt.Println(allText)

ページを手動で走査

pages, _ := doc.Pages()
for _, p := range pages {
    text, err := p.Text()
    if err != nil {
        log.Printf("ページ %d: %v", p.Index, err)
        continue
    }
    fmt.Printf("--- ページ %d ---\n%s\n", p.Index+1, text)
}

構造化抽出

words, _  := doc.ExtractWords(0)        // []Word
lines, _  := doc.ExtractTextLines(0)    // []TextLine
chars, _  := doc.ExtractChars(0)        // []Char
tables, _ := doc.ExtractTables(0)       // []Table — 行・セルと bbox 付き(v0.3.34)
paths, _  := doc.ExtractPaths(0)        // []Path

for _, w := range words {
    fmt.Printf("%q @ (%.1f, %.1f)\n", w.Text, w.X, w.Y)
}

for _, t := range tables {
    fmt.Printf("%dx%d (ヘッダー=%v)\n", t.RowCount, t.ColCount, t.HasHeader)
    for r := 0; r < t.RowCount; r++ {
        for c := 0; c < t.ColCount; c++ {
            fmt.Printf("%s\t", t.CellText(r, c))
        }
        fmt.Println()
    }
}

矩形領域による抽出:

region, _ := doc.ExtractTextInRect(0, 50, 700, 200, 50) // x, y, 幅, 高さ
words, _  := doc.ExtractWordsInRect(0, 50, 700, 200, 50)

Markdown 変換

md, err := doc.ToMarkdown(0)
if err != nil {
    log.Fatal(err)
}
fmt.Println(md)

// 全ページ
allMd, _ := doc.ToMarkdownAll()

HTML 変換

html, _  := doc.ToHtml(0)
allHtml, _ := doc.ToHtmlAll()

画像抽出

import "os"

images, err := doc.Images(0)
if err != nil {
    log.Fatal(err)
}

for i, img := range images {
    fmt.Printf("画像 %d: %dx%d %s %s %dbpc (%d バイト)\n",
        i, img.Width, img.Height, img.Format, img.Colorspace, img.BitsPerComponent, len(img.Data))
    os.WriteFile(fmt.Sprintf("image_%d.%s", i, img.Format), img.Data, 0644)
}

バイト列・io.Reader から開く

// バイト列から
data, _ := os.ReadFile("document.pdf")
doc, err := pdfoxide.OpenFromBytes(data)

// 任意の io.Reader から
doc, err := pdfoxide.OpenReader(someReader)

// パスワード付き
doc, err := pdfoxide.OpenWithPassword("secure.pdf", "user-password")

PDF の作成

// Markdown から
pdf, _ := pdfoxide.FromMarkdown("# こんにちは\n\n本文です。")
defer pdf.Close()
pdf.Save("out.pdf")

// HTML から
htmlPdf, _ := pdfoxide.FromHtml("<h1>請求書</h1><p>金額: $42</p>")
defer htmlPdf.Close()
htmlPdf.Save("invoice.pdf")

// テキストから
txt, _ := pdfoxide.FromText("プレーンテキスト文書。")
defer txt.Close()

// 画像から
img, _ := pdfoxide.FromImage("photo.jpg")
defer img.Close()

// 複数 PDF を結合
merged, _ := pdfoxide.Merge([]string{"a.pdf", "b.pdf"})
os.WriteFile("merged.pdf", merged, 0644)

レンダリング

// フォーマット: 0 = PNG, 1 = JPEG
img, err := doc.RenderPage(0, 0)
if err != nil {
    log.Fatal(err)
}
defer img.Close()
img.SaveToFile("page.png")

// ズーム (2×)
zoomed, _ := doc.RenderPageZoom(0, 2.0, 0)
defer zoomed.Close()

// サムネイル (幅 200px)
thumb, _ := doc.RenderThumbnail(0, 200, 0)
defer thumb.Close()

検索

// 全ページを検索(大文字小文字を区別しない)
hits, _ := doc.SearchAll("configuration", false)
for _, r := range hits {
    fmt.Printf("ページ %d: %q @ (%.0f, %.0f)\n", r.Page, r.Text, r.X, r.Y)
}

// 単一ページを検索
pageHits, _ := doc.SearchPage(0, "configuration", false)

編集

メタデータ、ページ操作、注釈、フォームには DocumentEditor を使います。

editor, err := pdfoxide.OpenEditor("in.pdf")
if err != nil {
    log.Fatal(err)
}
defer editor.Close()

// メタデータ — 個別に設定
_ = editor.SetTitle("四半期レポート")
_ = editor.SetAuthor("財務チーム")

// まとめて設定
_ = editor.ApplyMetadata(pdfoxide.Metadata{
    Title:   "2026 Q1 レポート",
    Author:  "財務チーム",
    Subject: "業績",
})

// ページ操作
_ = editor.SetPageRotation(0, 90)
_ = editor.MovePage(2, 0)
_ = editor.DeletePage(5)

// フォーム
_ = editor.SetFormFieldValue("employee.name", "Jane Doe")
_ = editor.FlattenForms()

// 保存
_ = editor.Save("out.pdf")
_ = editor.SaveEncrypted("secret.pdf", "user", "owner")

バーコード

qr, _ := pdfoxide.GenerateQRCode("https://example.com", 0, 256)
defer qr.Close()
_ = os.WriteFile("qr.png", qr.PNGData(), 0644)

bc, _ := pdfoxide.GenerateBarcode("123456789", 0, 128)
defer bc.Close()

OCR

スキャンされたページで OCR を使うには ocr フィーチャーを有効にしてビルドします。

go build -tags ocr ./...
ocr, _ := pdfoxide.NewOcrEngine()
defer ocr.Close()

if ocr.NeedsOcr(doc, 0) {
    text, _ := ocr.ExtractTextWithOcr(doc, 0)
    fmt.Println(text)
}

詳しいレシピは OCR ガイド を参照してください。

並行性

PdfDocument の読み取りは goroutine セーフで、複数の goroutine で 1 つのドキュメントを共有して並列にページを抽出できます。

import "sync"

var wg sync.WaitGroup
count, _ := doc.PageCount()
out := make(chan string, count)

for i := 0; i < count; i++ {
    wg.Add(1)
    go func(page int) {
        defer wg.Done()
        text, err := doc.ExtractText(page)
        if err == nil {
            out <- text
        }
    }(i)
}

go func() { wg.Wait(); close(out) }()

for text := range out {
    _ = text
}

DocumentEditor は内部で書き込みを直列化しますが、独立した編集を複数の goroutine からパイプラインのように流し込まないでください。変更はひとつの goroutine にまとめてください。パターンは 並行性ガイド を参照してください。

エラー処理

import "errors"

text, err := doc.ExtractText(0)
if err != nil {
    switch {
    case errors.Is(err, pdfoxide.ErrDocumentClosed):
        log.Print("ドキュメントは閉じられています")
    case errors.Is(err, pdfoxide.ErrInvalidPageIndex):
        log.Print("無効なページインデックスです")
    case errors.Is(err, pdfoxide.ErrExtractionFailed):
        log.Print("抽出に失敗しました")
    default:
        log.Printf("想定外: %v", err)
    }
}

利用可能な番兵エラー:

ErrInvalidPath        ErrDocumentNotFound   ErrInvalidFormat
ErrExtractionFailed   ErrParseError         ErrInvalidPageIndex
ErrSearchFailed       ErrInternal           ErrDocumentClosed
ErrEditorClosed       ErrCreatorClosed      ErrIndexOutOfBounds
ErrEmptyContent

errors.As で数値の CodeMessage を取り出せます。

var e *pdfoxide.Error
if errors.As(err, &e) {
    fmt.Printf("code=%d message=%s\n", e.Code, e.Message)
}

次のステップ