PDF-библиотека для C# / .NET — PDF Oxide
PDF Oxide — самая быстрая PDF-библиотека для .NET: 0,8 мс в среднем на извлечение текста, в 5 раз быстрее PyMuPDF, в 15 раз быстрее pypdf, 100 % успеха на 3 830 PDF. Один пакет для извлечения, создания и редактирования — с поддержкой NativeAOT, trim-safe, с идиоматичным using, async Task<T>, CancellationToken и LINQ-коллекциями. Лицензия MIT / Apache-2.0.
Установка
dotnet add package PdfOxide
Целевые фреймворки: net8.0 и net10.0. Флаги IsAotCompatible=true и IsTrimmable=true включены.
В NuGet-пакете поставляются готовые нативные библиотеки для Windows, macOS (Intel + Apple Silicon) и Linux (x64 + ARM64). Системные зависимости и тулчейн Rust не требуются.
Открытие PDF
using PdfOxide.Core;
using var doc = PdfDocument.Open("research-paper.pdf");
Console.WriteLine($"Страниц: {doc.PageCount}");
Console.WriteLine($"Версия PDF: {doc.Version.Major}.{doc.Version.Minor}");
Из потока:
using var stream = File.OpenRead("report.pdf");
using var doc = PdfDocument.Open(stream);
С паролем:
using var doc = PdfDocument.OpenWithPassword("secure.pdf", "user-password");
PDF c AES-256 (V=5, R=6) поддерживаются полностью.
API страниц
С версии v0.3.34 PdfDocument предоставляет свойство Pages (IReadOnlyList<PdfPage>) и индексатор int, так что можно использовать foreach и LINQ.
using PdfOxide.Core;
using var doc = PdfDocument.Open("paper.pdf");
foreach (var page in doc.Pages)
{
Console.WriteLine($"--- Страница {page.Index + 1} ---");
Console.WriteLine(page.ExtractText());
}
// Либо обращение по индексу
PdfPage first = doc[0];
string md = await first.ToMarkdownAsync();
У каждой PdfPage есть полный синхронный и асинхронный набор: ExtractText() / ExtractTextAsync(), ToMarkdown(), ToHtml(), ToPlainText(), ExtractWords(), ExtractTextLines(), ExtractTables(), ExtractChars(), ExtractImages(), Search().
Извлечение текста
Одна страница
using var doc = PdfDocument.Open("report.pdf");
string text = doc.ExtractText(0);
Console.WriteLine(text);
Все страницы
string allText = doc.ExtractAllText();
Ручной обход страниц
for (int i = 0; i < doc.PageCount; i++)
{
Console.WriteLine($"--- Страница {i + 1} ---");
Console.WriteLine(doc.ExtractText(i));
}
Асинхронное извлечение
У каждого метода извлечения есть *Async-вариант, возвращающий Task<T> и принимающий необязательный CancellationToken.
using PdfOxide.Core;
using var doc = PdfDocument.Open("large.pdf");
string text = await doc.ExtractTextAsync(0);
// Fan-out с отменой
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);
Полные шаблоны — в руководстве по async.
Структурированное извлечение
var words = doc.ExtractWords(0);
foreach (var (text, x, y, w, h) in words)
{
Console.WriteLine($"\"{text}\" в ({x:F1}, {y:F1})");
}
// По области
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($"таблица {rows}x{cols}");
}
Конвертация в Markdown
string markdown = doc.ToMarkdown(0);
string allMarkdown = doc.ToMarkdownAll();
Конвертация в HTML
string html = doc.ToHtml(0);
string allHtml = doc.ToHtmlAll();
Извлечение изображений
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} байт)");
File.WriteAllBytes($"image_{Array.IndexOf(images.ToArray(), img)}.{img.Format}", img.Data);
}
PDF с индексированным цветом автоматически разворачиваются в RGB (1/2/4/8 bpc с базовыми цветовыми пространствами RGB, градации серого или CMYK).
Поиск
var results = doc.SearchAll("квартальная выручка");
foreach (var (page, text, x, y, w, h) in results)
{
Console.WriteLine($"Страница {page}: \"{text}\" в ({x}, {y})");
}
// Одна страница с учётом регистра
var pageResults = doc.SearchPage(0, "точная фраза", caseSensitive: true);
LINQ подключается естественно:
var hitsByPage = doc.SearchAll("keyword")
.GroupBy(r => r.Page)
.OrderBy(g => g.Key);
foreach (var group in hitsByPage)
{
Console.WriteLine($"Страница {group.Key}: совпадений — {group.Count()}");
}
Создание PDF
using PdfOxide.Core;
// Из Markdown
using (var pdf = Pdf.FromMarkdown("# Счёт\n\nИтого: **$42,00**"))
{
pdf.Save("invoice.pdf");
}
// Из HTML
using (var pdf = Pdf.FromHtml("<h1>Отчёт</h1><p>Сформирован 2026-04-09</p>"))
{
pdf.Save("report.pdf");
}
// Из простого текста
using (var pdf = Pdf.FromText("Простой текстовый документ.\n\nВторой абзац."))
{
pdf.Save("notes.pdf");
}
// Из изображения
using (var pdf = Pdf.FromImage("scan.jpg"))
{
pdf.Save("scan.pdf");
}
Редактирование — метаданные и формы
using PdfOxide.Core;
using var editor = DocumentEditor.Open("form.pdf");
// Чтение метаданных
Console.WriteLine($"Заголовок: {editor.Title}");
Console.WriteLine($"Страниц: {editor.PageCount}");
// Обновление метаданных
editor.Title = "Квартальный отчёт";
editor.Author = "Финансовый отдел";
editor.Subject = "Результаты Q1 2026";
// Заполнение и флаттенинг полей формы
editor.SetFormFieldValue("employee.name", "Jane Doe");
editor.SetFormFieldValue("employee.email", "jane@example.com");
editor.FlattenForms();
editor.Save("edited.pdf");
// или: await editor.SaveAsync("edited.pdf");
Чтение полей формы без редактирования:
using var doc = PdfDocument.Open("form.pdf");
foreach (var f in doc.GetFormFields())
{
Console.WriteLine($"{f.Name} ({f.FieldType}) = \"{f.Value}\"");
}
Примечание. В текущем .NET-биндинге доступны открытие, чтение, конвертация и создание документов, извлечение изображений, чтение, заполнение и флаттенинг полей формы, редактирование метаданных. Операции со страницами, аннотации, рендеринг и подписи доступны в ядре Rust и других биндингах; аналогичный .NET-API появится в одной из будущих версий.
Публикация с NativeAOT
.NET-биндинг PDF Oxide готов к публикации через NativeAOT:
dotnet publish -c Release -r linux-x64 --self-contained -p:PublishAot=true
Все 881 объявление P/Invoke используют LibraryImport (P/Invoke с генерацией исходников), IsAotCompatible=true, IsTrimmable=true. AOT-бинарник линкует только то, что действительно используется, а нативное Rust-ядро статически связано с поставляемой платформенной библиотекой.
Плагины и расширения
Пакет PdfOxide.Plugins (поставляется вместе с PdfOxide) предоставляет точки расширения для процессоров, которые преобразуют извлечённый контент: классификаторы, постобработчики, валидаторы. Как писать расширения — в руководстве по плагинам.
Обработка ошибок
Все методы бросают типизированные исключения при ошибке:
using PdfOxide.Core;
try
{
using var doc = PdfDocument.Open("document.pdf");
string text = doc.ExtractText(0);
}
catch (PdfOxideException ex)
{
Console.Error.WriteLine($"Ошибка PDF Oxide: {ex.Message}");
}
catch (FileNotFoundException)
{
Console.Error.WriteLine("Файл не найден");
}
Дальше
- Быстрый старт Python — PDF Oxide из Python
- Справочник C# API — полная документация API
- Руководство по async — шаблоны с
Task<T>+CancellationToken - Руководство по параллелизму — шаблоны с
ReaderWriterLockSlim - Извлечение текста — подробные параметры извлечения
- Пакет в NuGet — заметки о релизах и статистика скачиваний