Empezar con PDF Oxide (Go)
PDF Oxide es la biblioteca PDF más rápida para Go: 0,8 ms promedio en extracción de texto, 5× más rápida que PyMuPDF, 15× más rápida que pypdf y 100 % de éxito en 3 830 PDFs. Un solo módulo para extraer, crear y editar PDFs. Lecturas seguras entre goroutines vía sync.RWMutex. Licencia MIT / Apache-2.0.
Instalación
A partir de la v0.3.38 se envían dos backends. Elige uno:
Opción A — CGo (enlace estático, predeterminado)
go get github.com/yfedoseev/pdf_oxide/go
go run github.com/yfedoseev/pdf_oxide/go/cmd/install@latest
Requiere Go 1.21+ con CGO_ENABLED=1 (valor por defecto) y una toolchain de C en el PATH. Expone la API completa. El instalador descarga un archivo estático pdf_oxide-go-ffi-<platform>.tar.gz, verifica su SHA-256 e imprime los valores de CGO_CFLAGS y CGO_LDFLAGS que debes exportar. El núcleo Rust queda enlazado estáticamente, de modo que el binario resultante es autocontenido: no hace falta configurar LD_LIBRARY_PATH, DYLD_LIBRARY_PATH ni PATH en tiempo de ejecución. Basta con go build y listo.
Opción B — purego (sin toolchain de C, CGO_ENABLED=0)
go get github.com/yfedoseev/pdf_oxide/go
go run github.com/yfedoseev/pdf_oxide/go/cmd/install@latest -shared
Añadido en la v0.3.38 mediante ebitengine/purego. El instalador descarga un cdylib pdf_oxide-go-ffi-shared-<platform>.tar.gz (libpdf_oxide.so / .dylib / .dll) e imprime las variables de entorno que debes exportar:
export CGO_ENABLED=0
export PDF_OXIDE_LIB_PATH="$HOME/.cache/pdf_oxide/v0.3.38/lib/linux_amd64/libpdf_oxide.so"
La selección del backend es automática vía la etiqueta cgo integrada en Go: //go:build cgo → API CGo, //go:build !cgo → purego.
Superficie de purego (lo que compila bajo !cgo): apertura de PdfDocument (ruta / bytes / contraseña), recuento de páginas, versión, extracción de texto / Markdown / HTML / texto plano, API de Page, fuentes, anotaciones, elementos de página, búsqueda, dimensiones de página, logging, más PdfCreator.FromMarkdown / .FromHtml / .FromText para fixtures de prueba.
Solo CGo (error de compilación bajo !cgo): DocumentEditor, DocumentBuilder + FluentPageBuilder + EmbeddedFont, renderizado (RenderPage, RenderPageZoom, RenderThumbnail, RenderPageRegion, RenderPageFit), códigos de barras (GenerateQRCode, GenerateBarcode), firmas (Signatures, Signature.Verify), TSA (TsaClient), OCR (OcrEngine), y SetFormFieldValue / FlattenForms.
Flags del instalador
| Flag | Valor por defecto | Propósito |
|---|---|---|
-version |
versión compilada en el módulo | Fijar una release concreta |
-dir |
os.UserCacheDir()/pdf_oxide/v<ver> |
Cambiar el directorio de instalación |
-shared |
off | Descargar el cdylib (backend purego) en lugar del staticlib |
-write-flags |
vacío (solo imprime variables) | Directorio donde generar un cgo_flags.go |
-env-only |
off | Omite la descarga; solo imprime variables para una instalación existente |
-skip-checksum |
off | Omite la verificación SHA-256 (no recomendado) |
Ubicaciones de caché (v0.3.38+)
La raíz de instalación pasó a os.UserCacheDir() para alinearse con la convención GOCACHE del propio Go:
| SO | Ruta |
|---|---|
| Linux | $XDG_CACHE_HOME/pdf_oxide o ~/.cache/pdf_oxide |
| macOS | ~/Library/Caches/pdf_oxide |
| Windows | %LocalAppData%\pdf_oxide |
Actualización desde v0.3.30 – v0.3.37: el primer go build fallará al enlazar (undefined reference to pdf_document_open ...) hasta que ejecutes el instalador una vez contra la nueva ruta. El antiguo directorio ~/.pdf_oxide/ no se migra automáticamente; bórralo a mano si quieres recuperar espacio en disco.
Monorepo o builds desde el árbol de fuentes: añade -tags pdf_oxide_dev para que CGo apunte a un target/release/libpdf_oxide.a local — sin necesidad de instalador.
Plataformas con binarios preconstruidos: Linux x64/arm64, macOS x64/arm64 (Apple Silicon) y Windows x64 (vía x86_64-pc-windows-gnu).
Abrir un 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 páginas, PDF %d.%d\n", count, major, minor)
}
API de páginas
Desde la v0.3.34 puedes trabajar primero a nivel de página. doc.Page(i) devuelve un handle liviano *Page que delega en el documento padre.
page, _ := doc.Page(0)
text, _ := page.Text()
md, _ := page.Markdown()
pages, _ := doc.Pages()
for _, p := range pages {
t, _ := p.Text()
fmt.Printf("--- Página %d ---\n%s\n", p.Index+1, t)
}
Cada Page expone Text(), Markdown(), Html(), PlainText(), Chars(), Words(), Lines(), Tables(), Images(), Paths(), Fonts(), Annotations(), Info(), Search(), NeedsOcr() y TextWithOcr().
Extracción de texto
Una sola página
text, err := doc.ExtractText(0)
if err != nil {
log.Fatal(err)
}
fmt.Println(text)
Todas las páginas
allText, err := doc.ExtractAllText()
if err != nil {
log.Fatal(err)
}
fmt.Println(allText)
Recorrer páginas manualmente
pages, _ := doc.Pages()
for _, p := range pages {
text, err := p.Text()
if err != nil {
log.Printf("página %d: %v", p.Index, err)
continue
}
fmt.Printf("--- Página %d ---\n%s\n", p.Index+1, text)
}
Extracción estructurada
words, _ := doc.ExtractWords(0) // []Word
lines, _ := doc.ExtractTextLines(0) // []TextLine
chars, _ := doc.ExtractChars(0) // []Char
tables, _ := doc.ExtractTables(0) // []Table — filas + celdas con bboxes (v0.3.34)
paths, _ := doc.ExtractPaths(0) // []Path
for _, w := range words {
fmt.Printf("%q en (%.1f, %.1f)\n", w.Text, w.X, w.Y)
}
for _, t := range tables {
fmt.Printf("%dx%d (encabezado=%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()
}
}
Extracción por región:
region, _ := doc.ExtractTextInRect(0, 50, 700, 200, 50) // x, y, ancho, alto
words, _ := doc.ExtractWordsInRect(0, 50, 700, 200, 50)
Conversión a Markdown
md, err := doc.ToMarkdown(0)
if err != nil {
log.Fatal(err)
}
fmt.Println(md)
// Todas las páginas
allMd, _ := doc.ToMarkdownAll()
Conversión a HTML
html, _ := doc.ToHtml(0)
allHtml, _ := doc.ToHtmlAll()
Extracción de imágenes
import "os"
images, err := doc.Images(0)
if err != nil {
log.Fatal(err)
}
for i, img := range images {
fmt.Printf("Imagen %d: %dx%d %s %s %dbpc (%d bytes)\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)
}
Abrir desde bytes y readers
// Desde bytes
data, _ := os.ReadFile("document.pdf")
doc, err := pdfoxide.OpenFromBytes(data)
// Desde cualquier io.Reader
doc, err := pdfoxide.OpenReader(someReader)
// Con contraseña
doc, err := pdfoxide.OpenWithPassword("secure.pdf", "user-password")
Creación de PDFs
// Desde Markdown (funciona bajo purego)
pdf, _ := pdfoxide.FromMarkdown("# Hola\n\nCuerpo del texto.")
defer pdf.Close()
pdf.Save("out.pdf")
// Desde HTML (funciona bajo purego)
htmlPdf, _ := pdfoxide.FromHtml("<h1>Factura</h1><p>Importe: $42</p>")
defer htmlPdf.Close()
htmlPdf.Save("invoice.pdf")
// Desde texto (funciona bajo purego)
txt, _ := pdfoxide.FromText("Documento de texto plano.")
defer txt.Close()
// Solo CGo a partir de aquí:
// Desde una imagen
img, _ := pdfoxide.FromImage("photo.jpg")
defer img.Close()
// Combinar varios PDFs
merged, _ := pdfoxide.Merge([]string{"a.pdf", "b.pdf"})
os.WriteFile("merged.pdf", merged, 0644)
DocumentBuilder (solo CGo, v0.3.38)
La API fluida DocumentBuilder llega a Go en la v0.3.38. Aquí se incluyen anotaciones, widgets AcroForm (TextField, Checkbox, ComboBox, RadioGroup, PushButton), primitivas gráficas (Rect, FilledRect, Line), fuentes embebidas (CJK / cirílico / griego) y cifrado 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("Privet, mir!").
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")
Consulta API fluida de DocumentBuilder para la superficie de métodos completa (la misma forma en todos los bindings).
Renderizado
Todas las APIs de renderizado son solo CGo (error de compilación bajo CGO_ENABLED=0).
// Formato: 0 = PNG, 1 = JPEG
img, err := doc.RenderPage(0, 0)
if err != nil {
log.Fatal(err)
}
defer img.Close()
img.SaveToFile("page.png")
// Zoom (2×)
zoomed, _ := doc.RenderPageZoom(0, 2.0, 0)
defer zoomed.Close()
// Miniatura (200 px de ancho)
thumb, _ := doc.RenderThumbnail(0, 200, 0)
defer thumb.Close()
// Región recortada (v0.3.38)
region, _ := doc.RenderPageRegion(0, 72, 200, 468, 300, 0)
defer region.Close()
// Ajustar a un cuadro objetivo (v0.3.38)
fitted, _ := doc.RenderPageFit(0, 1024, 768, 0)
defer fitted.Close()
Búsqueda
// Buscar en todas las páginas (sin distinguir mayúsculas)
hits, _ := doc.SearchAll("configuration", false)
for _, r := range hits {
fmt.Printf("página %d: %q en (%.0f, %.0f)\n", r.Page, r.Text, r.X, r.Y)
}
// Buscar en una sola página
pageHits, _ := doc.SearchPage(0, "configuration", false)
Edición
DocumentEditor es solo CGo. Úsalo para metadatos, operaciones sobre páginas, anotaciones y formularios:
editor, err := pdfoxide.OpenEditor("in.pdf")
if err != nil {
log.Fatal(err)
}
defer editor.Close()
// Metadatos — un campo a la vez
_ = editor.SetTitle("Informe trimestral")
_ = editor.SetAuthor("Equipo de Finanzas")
// O varios campos a la vez
_ = editor.ApplyMetadata(pdfoxide.Metadata{
Title: "Informe Q1 2026",
Author: "Equipo de Finanzas",
Subject: "Resultados",
})
// Operaciones de página
_ = editor.SetPageRotation(0, 90)
_ = editor.MovePage(2, 0)
_ = editor.DeletePage(5)
// Formularios
_ = editor.SetFormFieldValue("employee.name", "Jane Doe")
_ = editor.FlattenForms()
// Guardar
_ = editor.Save("out.pdf")
_ = editor.SaveEncrypted("secret.pdf", "user", "owner")
Códigos de barras
La generación de códigos de barras es solo 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
Compila con la feature ocr para habilitar OCR sobre páginas escaneadas:
go build -tags ocr ./...
ocr, _ := pdfoxide.NewOcrEngine()
defer ocr.Close()
if ocr.NeedsOcr(doc, 0) {
text, _ := ocr.ExtractTextWithOcr(doc, 0)
fmt.Println(text)
}
Consulta la guía de OCR para recetas completas.
Concurrencia
Las lecturas sobre PdfDocument son seguras entre goroutines: varias goroutines pueden compartir un mismo documento para extraer páginas en paralelo.
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 serializa las escrituras de forma interna, pero no canalices ediciones independientes desde varias goroutines: junta los cambios en una sola goroutine. Consulta los patrones en la guía de concurrencia.
Manejo de errores
import "errors"
text, err := doc.ExtractText(0)
if err != nil {
switch {
case errors.Is(err, pdfoxide.ErrDocumentClosed):
log.Print("el documento está cerrado")
case errors.Is(err, pdfoxide.ErrInvalidPageIndex):
log.Print("índice de página inválido")
case errors.Is(err, pdfoxide.ErrExtractionFailed):
log.Print("falló la extracción")
default:
log.Printf("inesperado: %v", err)
}
}
Errores centinela disponibles:
ErrInvalidPath ErrDocumentNotFound ErrInvalidFormat
ErrExtractionFailed ErrParseError ErrInvalidPageIndex
ErrSearchFailed ErrInternal ErrDocumentClosed
ErrEditorClosed ErrCreatorClosed ErrIndexOutOfBounds
ErrEmptyContent
Obtén el Code numérico y el Message con errors.As:
var e *pdfoxide.Error
if errors.As(err, &e) {
fmt.Printf("code=%d message=%s\n", e.Code, e.Message)
}
Próximos pasos
- Inicio con Python — usar PDF Oxide desde Python
- Referencia de la API en Go — documentación completa de la API
- Guía de concurrencia — patrones con goroutines
- Extracción de texto — opciones detalladas de extracción
- Creación de PDFs — creación avanzada
- Paquete en pkg.go.dev — documentación de la API generada