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.
Встановлення
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 МБ) у ~/.pdf_oxide/v<version>/, перевіряє SHA-256 проти опублікованого .sha256 та друкує значення CGO_CFLAGS і CGO_LDFLAGS, які треба експортувати. Передайте --write-flags=<dir>, щоб замість змінних оточення згенерувати файл cgo_flags.go поряд із кодом. Досить запустити раз на машину; @latest автоматично обирає відповідний реліз через runtime/debug.ReadBuildInfo.
Прив’язка лінкує Rust-ядро як статичну бібліотеку, тож готовий Go-бінарник цілком самодостатній — на етапі виконання не треба налаштовувати LD_LIBRARY_PATH, DYLD_LIBRARY_PATH чи PATH. Просто go build — і в продакшн.
Монорепо або складання з дерева вихідних кодів: додайте -tags pdf_oxide_dev, щоб CGo дивився на локальний target/release/libpdf_oxide.a. Інсталятор при цьому не потрібен.
Оновлення з 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)
}
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)
}
Відкриття з байтів та 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()
// Ескіз (ширина 200 px)
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: "Звіт за 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")
Штрихкоди
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