Biblioteca PDF para C# / .NET — PDF Oxide
PDF Oxide es la biblioteca PDF más rápida para .NET: 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 paquete para extraer, crear y editar — NativeAOT-ready, trim-safe, con using, async Task<T>, CancellationToken y colecciones compatibles con LINQ. Licencia MIT / Apache-2.0.
Instalación
dotnet add package PdfOxide
Target frameworks: net8.0 y net10.0. Vienen activados IsAotCompatible=true e IsTrimmable=true.
El paquete NuGet incluye bibliotecas nativas precompiladas para Windows, macOS (Intel + Apple Silicon) y Linux (x64 + ARM64). Sin dependencias del sistema y sin necesidad del toolchain de Rust.
Abrir un PDF
using PdfOxide.Core;
using var doc = PdfDocument.Open("research-paper.pdf");
Console.WriteLine($"Páginas: {doc.PageCount}");
Console.WriteLine($"Versión de PDF: {doc.Version.Major}.{doc.Version.Minor}");
Desde un stream:
using var stream = File.OpenRead("report.pdf");
using var doc = PdfDocument.Open(stream);
Con contraseña:
using var doc = PdfDocument.OpenWithPassword("secure.pdf", "user-password");
Los PDFs con cifrado AES-256 (V=5, R=6) son totalmente compatibles.
API de páginas
Desde la v0.3.34, PdfDocument expone la propiedad Pages (un IReadOnlyList<PdfPage>) y un indexador int, así que puedes iterar con foreach y aprovechar LINQ.
using PdfOxide.Core;
using var doc = PdfDocument.Open("paper.pdf");
foreach (var page in doc.Pages)
{
Console.WriteLine($"--- Página {page.Index + 1} ---");
Console.WriteLine(page.ExtractText());
}
// O por índice directo
PdfPage first = doc[0];
string md = await first.ToMarkdownAsync();
Cada PdfPage tiene una superficie completa síncrona y asíncrona: ExtractText() / ExtractTextAsync(), ToMarkdown(), ToHtml(), ToPlainText(), ExtractWords(), ExtractTextLines(), ExtractTables(), ExtractChars(), ExtractImages(), Search().
Extracción de texto
Una sola página
using var doc = PdfDocument.Open("report.pdf");
string text = doc.ExtractText(0);
Console.WriteLine(text);
Todas las páginas
string allText = doc.ExtractAllText();
Recorrer páginas manualmente
for (int i = 0; i < doc.PageCount; i++)
{
Console.WriteLine($"--- Página {i + 1} ---");
Console.WriteLine(doc.ExtractText(i));
}
Extracción asíncrona
Cada método de extracción tiene su contraparte *Async que devuelve Task<T> y admite un CancellationToken opcional.
using PdfOxide.Core;
using var doc = PdfDocument.Open("large.pdf");
string text = await doc.ExtractTextAsync(0);
// Fan-out con cancelación
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var tasks = Enumerable.Range(0, doc.PageCount)
.Select(i => doc.ExtractTextAsync(i, cts.Token));
string[] pages = await Task.WhenAll(tasks);
Consulta los patrones completos en la guía async.
Extracción estructurada
var words = doc.ExtractWords(0);
foreach (var (text, x, y, w, h) in words)
{
Console.WriteLine($"\"{text}\" en ({x:F1}, {y:F1})");
}
// Por región
string regionText = doc.ExtractTextInRect(0, x: 50, y: 700, width: 200, height: 50);
var tables = doc.ExtractTables(0);
foreach (var (rows, cols) in tables)
{
Console.WriteLine($"tabla de {rows}x{cols}");
}
Conversión a Markdown
string markdown = doc.ToMarkdown(0);
string allMarkdown = doc.ToMarkdownAll();
Conversión a HTML
string html = doc.ToHtml(0);
string allHtml = doc.ToHtmlAll();
Extracción de imágenes
using PdfOxide.Core;
using var doc = PdfDocument.Open("brochure.pdf");
var images = doc.ExtractImages(0);
foreach (var img in images)
{
Console.WriteLine($"{img.Width}x{img.Height} {img.Format} ({img.Colorspace}, {img.BitsPerComponent} bpc, {img.Data.Length} bytes)");
File.WriteAllBytes($"image_{Array.IndexOf(images.ToArray(), img)}.{img.Format}", img.Data);
}
Los PDFs con color indexado se expanden automáticamente a RGB (1/2/4/8 bpc con RGB, escala de grises o CMYK como espacio de color base).
Búsqueda
var results = doc.SearchAll("ingresos trimestrales");
foreach (var (page, text, x, y, w, h) in results)
{
Console.WriteLine($"Página {page}: \"{text}\" en ({x}, {y})");
}
// Una página, distinguiendo mayúsculas
var pageResults = doc.SearchPage(0, "frase exacta", caseSensitive: true);
LINQ se integra de forma natural:
var hitsByPage = doc.SearchAll("keyword")
.GroupBy(r => r.Page)
.OrderBy(g => g.Key);
foreach (var group in hitsByPage)
{
Console.WriteLine($"Página {group.Key}: {group.Count()} coincidencias");
}
Creación de PDFs
using PdfOxide.Core;
// Desde Markdown
using (var pdf = Pdf.FromMarkdown("# Factura\n\nTotal: **$42,00**"))
{
pdf.Save("invoice.pdf");
}
// Desde HTML
using (var pdf = Pdf.FromHtml("<h1>Informe</h1><p>Generado el 2026-04-09</p>"))
{
pdf.Save("report.pdf");
}
// Desde texto plano
using (var pdf = Pdf.FromText("Documento de texto plano.\n\nSegundo párrafo."))
{
pdf.Save("notes.pdf");
}
// Desde una imagen
using (var pdf = Pdf.FromImage("scan.jpg"))
{
pdf.Save("scan.pdf");
}
Edición — Metadatos y formularios
using PdfOxide.Core;
using var editor = DocumentEditor.Open("form.pdf");
// Leer metadatos
Console.WriteLine($"Título: {editor.Title}");
Console.WriteLine($"Páginas: {editor.PageCount}");
// Actualizar metadatos
editor.Title = "Informe trimestral";
editor.Author = "Equipo de Finanzas";
editor.Subject = "Resultados Q1 2026";
// Rellenar y aplanar campos de formulario
editor.SetFormFieldValue("employee.name", "Jane Doe");
editor.SetFormFieldValue("employee.email", "jane@example.com");
editor.FlattenForms();
editor.Save("edited.pdf");
// o: await editor.SaveAsync("edited.pdf");
Leer campos de formulario sin editar:
using var doc = PdfDocument.Open("form.pdf");
foreach (var f in doc.GetFormFields())
{
Console.WriteLine($"{f.Name} ({f.FieldType}) = \"{f.Value}\"");
}
Nota: el binding de .NET expone actualmente apertura, lectura, conversión y creación de documentos, extracción de imágenes, lectura, llenado y aplanado de campos de formulario, y edición de metadatos. Las operaciones de página, anotaciones, renderizado y firmas están disponibles en el núcleo Rust y en otros bindings; se añadirá una superficie equivalente para .NET en una versión futura.
Publicación con NativeAOT
El binding .NET de PDF Oxide está listo para publicación con NativeAOT:
dotnet publish -c Release -r linux-x64 --self-contained -p:PublishAot=true
Las 881 declaraciones P/Invoke usan LibraryImport (P/Invoke generado por fuente), con IsAotCompatible=true e IsTrimmable=true. Tu binario AOT enlaza solo lo que utiliza, y el núcleo Rust nativo queda enlazado estáticamente en la biblioteca específica de la plataforma incluida.
Plugins y extensiones
El paquete PdfOxide.Plugins (que se publica junto con PdfOxide) expone puntos de extensión para procesadores que transforman el contenido extraído: clasificadores, postprocesadores, validadores. Consulta la guía de plugins para autorar extensiones.
Manejo de errores
Todos los métodos lanzan excepciones tipadas ante un fallo:
using PdfOxide.Core;
try
{
using var doc = PdfDocument.Open("document.pdf");
string text = doc.ExtractText(0);
}
catch (PdfOxideException ex)
{
Console.Error.WriteLine($"Error de PDF Oxide: {ex.Message}");
}
catch (FileNotFoundException)
{
Console.Error.WriteLine("Archivo no encontrado");
}
Próximos pasos
- Inicio con Python — usar PDF Oxide desde Python
- Referencia de la API en C# — documentación completa de la API
- Guía async — patrones con
Task<T>+CancellationToken - Guía de concurrencia — patrones con
ReaderWriterLockSlim - Extracción de texto — opciones detalladas de extracción
- Paquete NuGet — notas de versión y estadísticas de descarga