Primeros pasos con PDF Oxide (Elixir)
PDF Oxide es la forma más rápida de leer y escribir PDF desde Elixir: 0,8 ms de media en extracción de texto y 100 % de aciertos en 3.830 PDF. Es un NIF sobre el mismo núcleo Rust, que ejecuta el trabajo intensivo de CPU en dirty CPU schedulers (ERL_NIF_DIRTY_JOB_CPU_BOUND), de modo que nunca bloquea el scheduler de la BEAM.
Los handles de Document y Pdf son recursos NIF que libera el GC. Las funciones que pueden fallar devuelven {:ok, value} o {:error, code}, y los índices de página empiezan en 0.
Instalación
Añade pdf_oxide a las dependencias de tu mix.exs:
def deps do
[
{:pdf_oxide, "~> 0.3"}
]
end
Luego descarga y compila — el NIF se construye con elixir_make a partir de la cdylib nativa:
mix deps.get
mix compile
Guía rápida
Genera un PDF a partir de Markdown, serialízalo a bytes, ábrelo y vuelve a extraer el texto.
{:ok, pdf} = PdfOxide.from_markdown("# Hello pdf_oxide\n\nThis is an **Elixir** binding.\n")
{:ok, bytes} = PdfOxide.to_bytes(pdf)
{:ok, doc} = PdfOxide.open_from_bytes(bytes)
{:ok, pages} = PdfOxide.page_count(doc)
IO.puts("pages: #{pages}")
%{major: maj, minor: min} = PdfOxide.version(doc)
IO.puts("version: #{maj}.#{min}")
{:ok, text} = PdfOxide.extract_text(doc, 0)
IO.puts(text)
Abrir un PDF
Ábrelo desde una ruta de archivo o directamente desde bytes en memoria (útil cuando llegan en streaming desde S3, HTTP o una base de datos):
# Desde una ruta
{:ok, doc} = PdfOxide.open("report.pdf")
# Desde bytes ya en memoria
{:ok, doc} = PdfOxide.open_from_bytes(pdf_bytes)
# Documentos cifrados
{:ok, doc} = PdfOxide.open_with_password("confidential.pdf", "secret")
# Inspección
{:ok, count} = PdfOxide.page_count(doc)
encrypted? = PdfOxide.encrypted?(doc)
Cierra un documento explícitamente cuando termines (close/1 es idempotente) o deja que lo recupere el GC:
:ok = PdfOxide.close(doc)
Extracción de texto
Extrae texto plano de una sola página por su índice (que empieza en cero), o recupera el documento entero de una vez:
{:ok, doc} = PdfOxide.open("book.pdf")
# Una sola página
{:ok, text} = PdfOxide.extract_text(doc, 0)
# Texto plano, una página
{:ok, pt} = PdfOxide.to_plain_text(doc, 0)
# Todas las páginas, concatenadas
{:ok, all} = PdfOxide.to_plain_text_all(doc)
IO.puts(all)
Conversión a Markdown y HTML
Convierte una página —o el documento completo— a Markdown o HTML:
{:ok, doc} = PdfOxide.open("paper.pdf")
{:ok, md} = PdfOxide.to_markdown(doc, 0)
{:ok, mdall} = PdfOxide.to_markdown_all(doc)
{:ok, html} = PdfOxide.to_html(doc, 0)
{:ok, htmlall} = PdfOxide.to_html_all(doc)
Palabras y líneas
extract_words/2 devuelve structs PdfOxide.Word estructurados, con un cuadro delimitador y un flag bold; extract_text_lines/2 los agrupa en líneas.
{:ok, doc} = PdfOxide.open("paper.pdf")
{:ok, words} = PdfOxide.extract_words(doc, 0)
for w <- Enum.take(words, 10) do
%PdfOxide.Bbox{x: x, y: y, width: width} = w.bbox
IO.puts("#{w.text} at (#{x}, #{y}) w=#{width} bold=#{w.bold}")
end
{:ok, lines} = PdfOxide.extract_text_lines(doc, 0)
for line <- lines do
IO.puts("#{line.word_count} words: #{line.text}")
end
Búsqueda
Busca en una sola página o en todo el documento. El cuarto argumento es case_sensitive. Cada resultado incluye text, page y un PdfOxide.Bbox.
{:ok, doc} = PdfOxide.open("manual.pdf")
# Una página (índice 0), sin distinguir mayúsculas y minúsculas
{:ok, results} = PdfOxide.search(doc, 0, "configuration", false)
for r <- results do
%PdfOxide.Bbox{x: x, y: y} = r.bbox
IO.puts("page #{r.page}: '#{r.text}' at (#{x}, #{y})")
end
# Todas las páginas
{:ok, all} = PdfOxide.search_all(doc, "configuration", false)
IO.puts("#{length(all)} matches")
Creación de PDF
Las funciones fábrica del builder devuelven un handle Pdf que serializas con to_bytes/1 o escribes directamente en disco con save/2:
{:ok, pdf} = PdfOxide.from_markdown("# Hello World\n\nThis is a PDF.")
:ok = PdfOxide.save(pdf, "output.pdf")
{:ok, pdf} = PdfOxide.from_html("<h1>Invoice</h1><p>Amount: $42</p>")
{:ok, bytes} = PdfOxide.to_bytes(pdf)
{:ok, pdf} = PdfOxide.from_text("Plain text content.")
:ok = PdfOxide.save(pdf, "notes.pdf")
Renderizar páginas a imágenes
Con la funcionalidad de renderizado, rasteriza una página a un PdfOxide.RenderedImage y guárdala como PNG:
{:ok, doc} = PdfOxide.open("paper.pdf")
{:ok, img} = PdfOxide.render_page(doc, 0)
IO.puts("#{img.width}x#{img.height}, #{byte_size(img.data)} bytes")
:ok = PdfOxide.save(img, "page0.png")
# Factor de zoom, o una miniatura de tamaño fijo
{:ok, zoomed} = PdfOxide.render_page_zoom(doc, 0, 2.0)
{:ok, thumb} = PdfOxide.render_page_thumbnail(doc, 0, 128)
Manejo de errores
Las funciones que pueden fallar devuelven una tupla etiquetada — usa pattern matching para un control de flujo limpio:
case PdfOxide.open("/nonexistent/nope.pdf") do
{:ok, doc} ->
{:ok, text} = PdfOxide.extract_text(doc, 0)
IO.puts(text)
{:error, code} ->
IO.puts("could not open PDF: #{inspect(code)}")
end
Próximos pasos
- Primeros pasos con Rust — usar PDF Oxide desde Rust
- Primeros pasos con Python — usar PDF Oxide desde Python
- Extracción de texto — opciones de extracción y recetas en detalle
- Creación de PDF — creación avanzada con metadatos y cifrado
- Edición — modificar PDF existentes, anotaciones y campos de formulario