Skip to content

C# / .NET PDF 라이브러리 — PDF Oxide

PDF Oxide는 .NET에서 가장 빠른 PDF 라이브러리입니다. 텍스트 추출 평균 0.8ms, PyMuPDF보다 5배, pypdf보다 15배 빠르며 3,830개 PDF에서 100% 통과율. 하나의 패키지로 추출·생성·편집을 제공합니다. NativeAOT 지원, trim-safe, using, async Task<T>, CancellationToken, LINQ 친화적 컬렉션 등 관용적인 API. MIT / Apache-2.0 라이선스.

설치

dotnet add package PdfOxide

타깃 프레임워크: net8.0net10.0. IsAotCompatible=trueIsTrimmable=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");

AES-256 (V=5, R=6) PDF도 완전히 지원합니다.

페이지 API

v0.3.34부터 PdfDocumentPages 속성(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는 동기·비동기의 전체 API를 제공합니다: 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));
}

비동기 추출

모든 추출 메서드에는 Task<T>를 반환하고 선택적으로 CancellationToken을 받는 *Async 버전이 있습니다.

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);

자세한 패턴은 비동기 가이드를 참고하세요.

구조화된 추출

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로 확장됩니다(RGB, 그레이스케일, CMYK 기반 색공간에서 1/2/4/8 bpc).

검색

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, "exact phrase", 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 = "2026년 Q1 실적";

// 폼 필드 입력 및 평탄화
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 배포

PDF Oxide의 .NET 바인딩은 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("파일을 찾을 수 없습니다");
}

다음 단계