PDF-библиотека для Go — PDF Oxide
PDF Oxide — самая быстрая PDF-библиотека для Go: 0,8 мс на страницу в среднем, в 5 раз быстрее PyMuPDF, в 15 раз быстрее pypdf, 100 % успеха на 3 830 PDF. Один модуль для извлечения, создания и редактирования. Чтение безопасно между goroutine через sync.RWMutex. Лицензия MIT / Apache-2.0.
Установка
Начиная с v0.3.38 доступны два бэкенда — выберите подходящий:
Вариант A — CGo (статическая линковка, по умолчанию)
go get github.com/yfedoseev/pdf_oxide/go
go run github.com/yfedoseev/pdf_oxide/go/cmd/install@latest
Требуется Go 1.21+ с CGO_ENABLED=1 (по умолчанию) и C-тулчейн в PATH. Полный API. Инсталлятор скачивает статический архив pdf_oxide-go-ffi-<platform>.tar.gz, проверяет SHA-256 и печатает значения CGO_CFLAGS / CGO_LDFLAGS, которые нужно экспортировать. Rust-ядро связывается статически, поэтому итоговый Go-бинарник полностью самодостаточен — настраивать LD_LIBRARY_PATH / DYLD_LIBRARY_PATH / PATH в рантайме не нужно. Просто go build и отдавайте.
Вариант B — purego (без C-тулчейна, CGO_ENABLED=0)
go get github.com/yfedoseev/pdf_oxide/go
go run github.com/yfedoseev/pdf_oxide/go/cmd/install@latest -shared
Добавлен в v0.3.38 через ebitengine/purego. Инсталлятор скачивает cdylib pdf_oxide-go-ffi-shared-<platform>.tar.gz (libpdf_oxide.so / .dylib / .dll) и печатает переменные окружения, которые нужно экспортировать:
export CGO_ENABLED=0
export PDF_OXIDE_LIB_PATH="$HOME/.cache/pdf_oxide/v0.3.38/lib/linux_amd64/libpdf_oxide.so"
Бэкенд выбирается автоматически через встроенный в Go build-тег cgo: //go:build cgo → API на CGo, //go:build !cgo → purego.
Что доступно в purego (компилируется при !cgo): PdfDocument с открытием (путь / байты / пароль), количество страниц, версия, извлечение текста / Markdown / HTML / plain-text, Page API, шрифты, аннотации, элементы страницы, поиск, размеры страницы, логирование и PdfCreator.FromMarkdown / .FromHtml / .FromText для тестовых фикстур.
Только CGo (ошибка компиляции при !cgo): DocumentEditor, DocumentBuilder + FluentPageBuilder + EmbeddedFont, рендеринг (RenderPage, RenderPageZoom, RenderThumbnail, RenderPageRegion, RenderPageFit), штрихкоды (GenerateQRCode, GenerateBarcode), подписи (Signatures, Signature.Verify), TSA (TsaClient), OCR (OcrEngine) и SetFormFieldValue / FlattenForms.
Флаги инсталлятора
| Флаг | По умолчанию | Назначение |
|---|---|---|
-version |
версия, зашитая в модуль | Закрепить конкретный релиз |
-dir |
os.UserCacheDir()/pdf_oxide/v<ver> |
Переопределить каталог установки |
-shared |
выключен | Скачать cdylib (бэкенд purego) вместо staticlib |
-write-flags |
пусто (только печать env) | Каталог для сгенерированного cgo_flags.go |
-env-only |
выключен | Пропустить загрузку; только напечатать env для уже установленного архива |
-skip-checksum |
выключен | Пропустить проверку SHA-256 (не рекомендуется) |
Каталоги кэша (v0.3.38+)
Корень установки перенесён в os.UserCacheDir(), чтобы совпадать с Go-шным соглашением GOCACHE:
| ОС | Путь |
|---|---|
| Linux | $XDG_CACHE_HOME/pdf_oxide или ~/.cache/pdf_oxide |
| macOS | ~/Library/Caches/pdf_oxide |
| Windows | %LocalAppData%\pdf_oxide |
Обновление с v0.3.30 – v0.3.37: первая сборка go build упадёт на линковке (undefined reference to pdf_document_open ...), пока инсталлятор не отработает один раз в новый путь. Старый каталог ~/.pdf_oxide/ автоматически не мигрирует — удалите его вручную, если хотите освободить место.
Монорепозиторий или сборка из исходников: добавьте -tags pdf_oxide_dev, чтобы CGo ссылался на локальный target/release/libpdf_oxide.a. Инсталлятор при этом не нужен.
Платформы с готовыми бинарниками: 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)
}
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)
}
Каждый Page предоставляет Text(), 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)
}
Открытие из байтов и 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 (работает под purego)
pdf, _ := pdfoxide.FromMarkdown("# Привет\n\nОсновной текст.")
defer pdf.Close()
pdf.Save("out.pdf")
// Из HTML (работает под purego)
htmlPdf, _ := pdfoxide.FromHtml("<h1>Счёт</h1><p>Сумма: $42</p>")
defer htmlPdf.Close()
htmlPdf.Save("invoice.pdf")
// Из текста (работает под purego)
txt, _ := pdfoxide.FromText("Простой текстовый документ.")
defer txt.Close()
// Начиная отсюда — только CGo:
// Из изображения
img, _ := pdfoxide.FromImage("photo.jpg")
defer img.Close()
// Объединение нескольких PDF
merged, _ := pdfoxide.Merge([]string{"a.pdf", "b.pdf"})
os.WriteFile("merged.pdf", merged, 0644)
DocumentBuilder (только CGo, v0.3.38)
Fluent-API DocumentBuilder появляется в Go в v0.3.38. Здесь же идут аннотации, виджеты AcroForm (TextField, Checkbox, ComboBox, RadioGroup, PushButton), графические примитивы (Rect, FilledRect, Line), встраиваемые шрифты (CJK / кириллица / греческий) и шифрование AES-256:
font, _ := pdfoxide.EmbeddedFontFromFile("DejaVuSans.ttf")
defer font.Close()
builder := pdfoxide.NewDocumentBuilder()
builder.RegisterEmbeddedFont("DejaVu", font)
builder.A4Page().
Font("DejaVu", 12).At(72, 720).Text("Привет, мир!").
Highlight(1.0, 1.0, 0.0).
TextField("name", 150, 680, 200, 20, "Jane Doe").
Checkbox("subscribe", 72, 650, 15, 15, true).
Done()
_ = builder.SaveEncrypted("out.pdf", "user-pw", "owner-pw")
Полный перечень методов (одинаковый во всех биндингах) — в Fluent API DocumentBuilder.
Рендеринг
Все API рендеринга доступны только в CGo (под CGO_ENABLED=0 — ошибка компиляции).
// Формат: 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()
// Превью (ширина 200 px)
thumb, _ := doc.RenderThumbnail(0, 200, 0)
defer thumb.Close()
// Вырезанная область (v0.3.38)
region, _ := doc.RenderPageRegion(0, 72, 200, 468, 300, 0)
defer region.Close()
// Вписывание в заданный прямоугольник (v0.3.38)
fitted, _ := doc.RenderPageFit(0, 1024, 768, 0)
defer fitted.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 доступен только в CGo. Используйте его для метаданных, операций со страницами, аннотаций и форм:
editor, err := pdfoxide.OpenEditor("in.pdf")
if err != nil {
log.Fatal(err)
}
defer editor.Close()
// Метаданные — по одному полю
_ = editor.SetTitle("Квартальный отчёт")
_ = editor.SetAuthor("Финансовый отдел")
// Или несколько полей сразу
_ = editor.ApplyMetadata(pdfoxide.Metadata{
Title: "Отчёт за Q1 2026",
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")
Штрихкоды
Генерация штрихкодов доступна только в CGo.
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 могут использовать один документ, чтобы извлекать страницы параллельно.
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)
}
}
Доступные sentinel-ошибки:
ErrInvalidPath ErrDocumentNotFound ErrInvalidFormat
ErrExtractionFailed ErrParseError ErrInvalidPageIndex
ErrSearchFailed ErrInternal ErrDocumentClosed
ErrEditorClosed ErrCreatorClosed ErrIndexOutOfBounds
ErrEmptyContent
Получить числовой Code и Message можно через errors.As:
var e *pdfoxide.Error
if errors.As(err, &e) {
fmt.Printf("code=%d message=%s\n", e.Code, e.Message)
}
Дальше
- Быстрый старт Python — PDF Oxide из Python
- Справочник Go API — полная документация API
- Руководство по параллелизму — паттерны с goroutine
- Извлечение текста — подробные параметры извлечения
- Создание PDF — продвинутое создание
- Пакет на pkg.go.dev — сгенерированная документация API