2026년 최고의 Rust PDF 크레이트
PDF Oxide를 대표적인 Rust PDF 크레이트인 lopdf, printpdf, pdf-rs, pdf_extract와 정면 비교합니다. 각 크레이트는 추상화 수준과 타깃 유스케이스가 다릅니다 — 이 페이지는 프로젝트에 맞는 크레이트를 고르는 기준을 제공합니다.
요약
| PDF Oxide | lopdf | printpdf | pdf-rs | pdf_extract | |
|---|---|---|---|---|---|
| API 수준 | 고수준 | 저수준 | 중수준 (생성) | 저수준 (읽기) | 중수준 (읽기) |
| PDF 읽기 | 예 | 예 | 아니오 | 예 | 예 |
| PDF 쓰기 | 예 | 예 | 예 | 아니오 | 아니오 |
| 텍스트 추출 | 예 (고수준) | 수동 | 아니오 | 수동 | 예 (기본) |
| 이미지 추출 | 예 (고수준) | 수동 | 아니오 | 수동 | 아니오 |
| 폼 필드 | 읽기 + 쓰기 | 수동 | 아니오 | 읽기 전용 | 아니오 |
| PDF 생성 | 예 | 예 | 예 | 아니오 | 아니오 |
| Markdown/HTML 입력 | 예 | 아니오 | 아니오 | 아니오 | 아니오 |
| 기존 PDF 편집 | 예 | 예 (저수준) | 아니오 | 아니오 | 아니오 |
| 주석 | 읽기 + 쓰기 | 수동 | 아니오 | 읽기 전용 | 아니오 |
| 암호화 | 읽기 + 쓰기 | 아니오 | 아니오 | 아니오 | 아니오 |
| PDF/A 검증 | 예 | 아니오 | 아니오 | 아니오 | 아니오 |
| 렌더링 | 예 (tiny-skia) | 아니오 | 아니오 | 부분적 | 아니오 |
| Python 바인딩 | 예 | 아니오 | 아니오 | 아니오 | 아니오 |
| 라이선스 | MIT | MIT | MIT | MIT | Apache-2.0 |
모든 라이브러리는 허용적 라이선스입니다. 차이점은 범위와 추상화 수준에 있습니다.
성능 비교
전체 코퍼스 벤치마크 (3,830개 PDF)
전체 3,830개 PDF 코퍼스에서 테스트했습니다 — PDF 사양 호환성(veraPDF, 2,907개 파일), 실제 브라우저 렌더링 엣지 케이스(Mozilla pdf.js, 897개 파일), 비정상 구조 및 퍼즈 생성 손상을 포함한 보안/안정성 스트레스 테스트(DARPA SafeDocs, 26개 파일)의 세 가지 독립적이고 공개적으로 이용 가능한 테스트 스위트를 포함합니다. 전체 코퍼스 세부 정보를 참조하세요.
| 라이브러리 | 평균 | p99 | 통과율 | 텍스트 추출 | 비고 |
|---|---|---|---|---|---|
| PDF Oxide | 0.8ms | 9ms | 100% | 내장, 프로덕션급 | Unicode, CJK, 읽기 순서 |
| oxidize_pdf | 13.5ms | 11ms | 99.1% | 기본 | 48초 최대 이상치 |
| unpdf | 2.8ms | 10ms | 95.1% | 기본 | 전체 코퍼스에서 185개 실패 |
| pdf_extract | 4.08ms | 37ms | 91.5% | 기본 | 복잡한 레이아웃 누락 |
| lopdf | 0.3ms | 2ms | 80.2% | 내장 추출 없음 | PDF의 20% 실패 |
lopdf는 파싱 가능한 PDF에서 더 빠르지만, 코퍼스의 20%에서 실패하며 텍스트 추출을 제공하지 않습니다. 폰트 디코딩, CMap 해석, 간격 분석을 직접 구축해야 합니다.
pdf_extract는 기본적인 텍스트 추출을 제공하지만 91.5% 통과율이며, 복잡한 레이아웃, CJK 텍스트, 태그된 PDF에서 어려움을 겪습니다. oxidize_pdf는 적절한 신뢰성(99.1%)을 보이지만 평균 추출 시간이 pdf_oxide보다 17배 느리며, 최악의 경우 48초의 이상치가 있습니다. unpdf는 전체 코퍼스를 처리하지만 185개 PDF에서 실패합니다.
PDF Oxide는 100% 신뢰성과 프로덕션급 텍스트 추출을 결합한 유일한 Rust 크레이트입니다.
API 설계 비교
PDF Oxide: 고수준, 작업 지향
PDF Oxide는 일반적인 작업을 위한 전용 메서드를 제공합니다. 텍스트, 이미지, 폼 필드를 다루며 — PDF 오브젝트나 딕셔너리가 아닙니다.
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("report.pdf")?;
// 텍스트 추출 -- 한 번의 호출
let text = doc.extract_text(0)?;
println!("{}", text);
// 폰트 메타데이터가 포함된 스타일 스팬
let spans = doc.extract_spans(0)?;
for span in &spans {
println!("'{}' font={} size={:.1}pt", span.text, span.font_name, span.font_size);
}
// 이미지 추출
let images = doc.extract_images(0)?;
for img in &images {
println!("{}x{} {:?}", img.width, img.height, img.format);
}
// 폼 필드
let fields = doc.extract_form_fields()?;
for field in &fields {
println!("{}: {:?}", field.name, field.value);
}
PDF 생성도 마찬가지로 간단합니다:
use pdf_oxide::api::Pdf;
// Markdown에서 생성
let pdf = Pdf::from_markdown("# Report\n\n| A | B |\n|---|---|\n| 1 | 2 |")?;
pdf.save("report.pdf")?;
// HTML에서 생성
let pdf = Pdf::from_html("<h1>Report</h1><p>Content here.</p>")?;
pdf.save("report.pdf")?;
lopdf: 저수준 오브젝트 조작
lopdf는 PDF 오브젝트, 스트림, 크로스 레퍼런스 테이블에 직접 접근할 수 있습니다. 효과적으로 사용하려면 PDF 사양을 이해해야 합니다. 내장 텍스트 추출이 없으므로 — 딕셔너리를 탐색하고 스트림을 직접 디코딩해야 합니다.
use lopdf::Document;
let doc = Document::load("report.pdf")?;
// 페이지 딕셔너리 가져오기
let page_id = doc.page_iter().next().unwrap();
let page = doc.get_dictionary(page_id)?;
// 콘텐츠 스트림 가져오기 -- 수동 작업
let contents = page.get("Contents")?;
let stream = doc.get_object(contents.as_reference()?)?;
// 텍스트를 추출하려면 다음을 수행해야 합니다:
// 1. 콘텐츠 스트림 연산자 파싱
// 2. /Resources에서 폰트 참조 해석
// 3. CMap/ToUnicode 매핑 디코딩
// 4. 텍스트 매트릭스 변환 적용
// 5. 인코딩 차이 처리
//
// lopdf는 이 중 어떤 것도 제공하지 않습니다 -- 원시 오브젝트 접근만 가능합니다
println!("Page has {} objects", doc.objects.len());
lopdf는 PDF 구조를 직접 조작해야 할 때 적합한 도구입니다: 문서 병합, 오브젝트 스트림 재작성, 또는 특수 PDF 프로세서 구축 등에 사용합니다.
printpdf: PDF 생성 전용
printpdf는 생성 전용 라이브러리입니다. 기존 PDF를 읽거나 파싱할 수 없습니다. 텍스트, 이미지, 벡터 그래픽으로 PDF 문서를 처음부터 구축하기 위한 타입 API를 제공합니다.
use printpdf::*;
let (doc, page1, layer1) = PdfDocument::new(
"Report", Mm(210.0), Mm(297.0), "Layer 1"
);
let current_layer = doc.get_page(page1).get_layer(layer1);
// 텍스트 추가 -- 수동 폰트 로딩 필요
let font = doc.add_builtin_font(BuiltinFont::Helvetica)?;
current_layer.use_text("Hello World", 24.0, Mm(10.0), Mm(280.0), &font);
// 저장
doc.save(&mut std::io::BufWriter::new(
std::fs::File::create("output.pdf")?,
))?;
// 기존 PDF를 읽을 수 없음
// 텍스트, 이미지, 폼 필드 추출 불가
printpdf는 새 PDF만 생성하면 되고 깔끔하고 집중된 생성 API를 원할 때 적합한 도구입니다.
pdf-rs: 저수준 PDF 읽기
pdf-rs는 PDF 구조를 Rust 타입으로 파싱하지만 최소한의 고수준 기능을 제공합니다. PDF 오브젝트에 대한 타입 접근은 가능하지만 텍스트 디코딩, 폰트 해석, 콘텐츠 스트림 파싱은 직접 처리해야 합니다.
use pdf::file::FileOptions;
let file = FileOptions::cached().open("report.pdf")?;
// 페이지 오브젝트 접근
let page = file.get_page(0)?;
let media_box = page.media_box()?;
println!("Page size: {:?}", media_box);
// 콘텐츠 스트림 접근 -- 저수준
if let Some(ref contents) = page.contents {
// 원시 연산을 반환합니다 -- 직접 해석해야 합니다
// 내장 텍스트 조립, 폰트 디코딩, 레이아웃 분석 없음
}
// PDF를 쓰거나 수정할 수 없음
pdf-rs는 분석, 검증, 또는 커스텀 렌더러 구축을 위해 사양 호환 파서가 필요할 때 적합한 도구입니다.
작업별 기능 비교
텍스트 추출
| 라이브러리 | 내장 지원 | 품질 | 필요한 작업 |
|---|---|---|---|
| PDF Oxide | 예 | 프로덕션급 (Unicode, CJK, 읽기 순서) | 메서드 한 번 호출 |
| pdf_extract | 예 | 기본 (복잡한 레이아웃 누락) | 메서드 한 번 호출 |
| lopdf | 아니오 | N/A | 수백 줄의 커스텀 코드 |
| printpdf | 아니오 | N/A | 불가능 (쓰기 전용) |
| pdf-rs | 아니오 | N/A | 상당한 커스텀 코드 필요 |
PDF Oxide는 CMap/ToUnicode 디코딩, 폰트 메트릭 기반 간격, 구조 트리 읽기 순서, 합자 재구성을 처리합니다. lopdf나 pdf-rs 위에 동등한 기능을 구현하려면 수천 줄의 코드와 깊은 PDF 사양 지식이 필요합니다.
PDF 생성
| 라이브러리 | 접근 방식 | Markdown/HTML 입력 | 표 | 바코드 |
|---|---|---|---|---|
| PDF Oxide | 고수준 + 저수준 | 예 | 예 | 예 |
| lopdf | 원시 오브젝트 구성 | 아니오 | 아니오 | 아니오 |
| printpdf | 타입 레이어 API | 아니오 | 아니오 | 아니오 |
| pdf-rs | N/A (읽기 전용) | N/A | N/A | N/A |
암호화
| 라이브러리 | 암호화 읽기 | 암호화 쓰기 | 알고리즘 |
|---|---|---|---|
| PDF Oxide | 예 | 예 | RC4-40, RC4-128, AES-128, AES-256 |
| lopdf | 아니오 | 아니오 | – |
| printpdf | 아니오 | 아니오 | – |
| pdf-rs | 부분적 | 아니오 | RC4만 |
적합성 검증
| 라이브러리 | PDF/A | PDF/X | PDF/UA |
|---|---|---|---|
| PDF Oxide | 검증 + 변환 | 검증 | 검증 |
| lopdf | 아니오 | 아니오 | 아니오 |
| printpdf | 부분적 (PDF/A-1b 출력) | 아니오 | 아니오 |
| pdf-rs | 아니오 | 아니오 | 아니오 |
의존성 풋프린트
| 라이브러리 | 의존성 | 컴파일 시간 | 바이너리 크기 |
|---|---|---|---|
| PDF Oxide | ~40 (코어) | ~30초 | ~4 MB |
| lopdf | ~15 | ~10초 | ~1 MB |
| printpdf | ~20 | ~15초 | ~2 MB |
| pdf-rs | ~25 | ~20초 | ~2 MB |
PDF Oxide는 폰트 파싱, 이미지 디코딩, 콘텐츠 스트림 해석, 암호화를 포함하기 때문에 더 많은 의존성이 있습니다 — 다른 라이브러리가 사용자에게 맡기거나 아예 생략하는 기능입니다. 모든 선택적 기능(rendering, barcodes, office)을 포함하면 ~100개로 늘어납니다.
라이브러리 조합
모든 라이브러리가 허용적 라이선스이므로, 하나의 프로젝트에서 조합할 수 있습니다:
[dependencies]
pdf_oxide = "0.3"
lopdf = "0.32" # 선택 사항: 엣지 케이스를 위한 원시 오브젝트 접근
일반적인 패턴:
- PDF Oxide + lopdf: PDF Oxide를 추출과 생성에 사용하고, 원시 오브젝트 조작이 필요한 엣지 케이스에서 lopdf로 폴백합니다.
- PDF Oxide + printpdf: PDF Oxide를 읽기에, printpdf를 특수 생성 워크플로에 사용합니다.
사용 사례 매트릭스
“PDF에서 텍스트를 추출해야 합니다”
| 크레이트 | 적합? | 비고 |
|---|---|---|
| PDF Oxide | 예 | 최고의 추출 품질, 100% 통과율, 읽기 순서, 폰트 메타데이터 |
| pdf_extract | 부분적 | 기본 추출, 91.5% 통과율 |
| lopdf | 아니오 | 텍스트 추출 없음 |
| printpdf | 아니오 | PDF를 읽을 수 없음 |
| pdf-rs | 부분적 | 기본 파싱, 고수준 텍스트 추출 없음 |
“PDF를 생성해야 합니다”
| 크레이트 | 적합? | 비고 |
|---|---|---|
| PDF Oxide | 예 | 고수준 (Markdown/HTML) 및 저수준 API |
| lopdf | 부분적 | 저수준 오브젝트 구성 |
| printpdf | 예 | 깔끔한 생성 API, 읽기 불가 |
| pdf-rs | 아니오 | 읽기 전용 |
“기존 PDF를 편집해야 합니다”
| 크레이트 | 적합? | 비고 |
|---|---|---|
| PDF Oxide | 예 | DOM 스타일 편집, 주석, 폼 |
| lopdf | 부분적 | 저수준 오브젝트 조작 |
| printpdf | 아니오 | PDF를 읽을 수 없음 |
| pdf-rs | 아니오 | 읽기 전용 |
“전체 라이프사이클이 필요합니다 (추출 + 생성 + 편집)”
| 크레이트 | 적합? | 비고 |
|---|---|---|
| PDF Oxide | 예 | 세 가지 모두를 지원하는 유일한 크레이트 |
| lopdf + printpdf | 부분적 | 두 크레이트, 텍스트 추출 없음 |
| pdf-rs + printpdf | 부분적 | 두 크레이트, 편집 없음 |
언제 어떤 것을 사용할까
PDF Oxide를 선택하세요: 하나 이상의 PDF 기능(추출 + 생성, 또는 추출 + 편집)이 필요하고 100% 신뢰성을 갖춘 단일의 잘 테스트된 의존성을 원하는 경우.
lopdf를 선택하세요: 저수준 PDF 구조 조작이 필요하고 PDF 사양을 직접 다루는 것이 편한 경우. 병합, 분할, 배치 PDF 처리에 적합합니다.
printpdf를 선택하세요: PDF만 생성하고 읽을 필요가 없는 경우. 보고서 및 문서 생성을 위한 가장 깔끔한 API입니다.
pdf-rs를 선택하세요: PDF 분석을 위한 사양 호환 파서가 필요하거나 자체 렌더링 파이프라인을 구축하는 경우.
pdf_extract를 선택하세요: 기본적인 텍스트 추출이 필요하고 높은 신뢰성이나 복잡한 레이아웃 지원이 필요하지 않은 경우.
관련 페이지
- 성능 벤치마크 – 전체 코퍼스 벤치마크 결과
- Rust 시작하기 – 설치 및 첫 번째 추출
- Rust API 레퍼런스 – 전체 Rust API
- Python PDF 라이브러리 비교 – Python 생태계 비교