firecrawl/pdf-inspector · 라이선스 MIT · 언어 Rust(edition 2021) · PDF 파싱은 lopdf 단일 의존, 네이티브 pdfium/ML 없음 · src/ 약 7.5만 줄 · 추적 파일 281개 · 분류 4종 TextBased/Scanned/ImageBased/Mixed · 벤치 Overall 0.875(1위) · 최신 커밋 2026-07-31 · TrendShift Daily #1)
pdf-inspector는 "OCR을 돌리기 전에, 이 PDF가 OCR이 필요한 스캔본인지 아니면 이미 텍스트가 들어있는 문서인지를 밀리초 단위로 판별해 주고, 텍스트 문서라면 위치까지 아는 추출과 Markdown 변환을 로컬에서 끝내 주는 순수 Rust 라이브러리"다. 한마디로 PDF 처리 파이프라인의 "문지기(gatekeeper)"다. 값비싼 방(OCR)에 아무나 들여보내지 않고, 표를 먼저 검사해서 정말 필요한 문서만 통과시킨다.
큰 병원 응급실에는 트리아지 간호사가 입구에 앉아 있다. 도착한 환자를 30초 안에 훑어보고 "이 사람은 붕대만, 저 사람은 즉시 수술"을 가른다. 모두를 값비싼 수술실(=OCR)로 보내면 병원이 마비되기 때문이다.
pdf-inspector가 딱 그 간호사다. PDF가 도착하면 본문 전체를 열지 않고 몇 페이지만 빠르게 훑어서 — "글자 명령이 충분히 있네? → 텍스트 기반, 로컬에서 바로 처리" 또는 "온통 커다란 이미지뿐이네? → 스캔본, OCR로 보내" 를 20ms에 판정한다. Firecrawl 말로는 실제 PDF의 약 54%가 OCR이 필요 없는데, 이걸 걸러내지 못하면 그 절반에까지 초 단위 OCR 비용을 태우게 된다.
사용자(또는 상위 파이프라인)가 PDF 한 개를 던졌을 때 pdf-inspector 안에서 벌어지는 일을 압축하면 이렇다:
여기서 결정적인 건 ⑥에서 반환하는 정보의 정밀도다. 단순히 "스캔이다/아니다"가 아니라, "문서 전체 중 3, 7, 12페이지만 OCR이 필요하다"는 페이지 단위 라우팅(pages_needing_ocr)까지 준다. 300페이지 문서에서 스캔된 3페이지만 골라 OCR하면 되니, 비용이 100배 차이 난다.
첫째, 발상의 전환이다. PDF에서 텍스트를 뽑는 도구(pymupdf4llm, markitdown, marker 등)는 이미 많다. 그런데 그 도구들은 대부분 "일단 다 처리하고 본다". pdf-inspector는 그 앞단에 "이 문서를 처리할 가치가 있나(=OCR이 필요한가)"를 먼저 묻는 얇고 빠른 판별기를 끼운다. 대량 문서 파이프라인을 굴리는 회사(Firecrawl 자신, RAG 서비스, 문서 검색 스타트업)에게 이건 곧바로 비용 절감으로 이어져서 실용 가치가 즉시 와닿는다.
둘째, 속도 숫자가 압도적이다. README의 2026-07-31 벤치마크(Apple M4 Pro, opendataloader-bench 200개 PDF, OCR 끄고 로컬 엔진만 비교):
| 도구 | 종합 점수 | 표(TEDS) | 200문서 처리 시간 |
|---|---|---|---|
| pdf-inspector | 0.875 (1위) | 0.814 (1위) | 0.470초 (1위) |
| pymupdf4llm | 그 아래 | — | 17.1초 |
| markitdown | 그 아래 | — | 16.2초 |
같은 코퍼스를 35배 이상 빠르게 처리하면서 정확도(읽기 순서 0.915, 표 0.814)까지 1위다. "빠른데 부정확"이 아니라 "빠르면서 정확"이라, 벤치마크를 본 개발자들이 곧장 관심을 보였다.
공항 보안검색과 같다. 모든 승객의 가방을 일일이 손으로 열어보면(=모든 PDF에 OCR) 줄이 끝없이 길어진다. 그래서 먼저 X-ray 벨트에 통과시켜 대부분을 빠르게 흘려보내고, 수상한 가방만 골라 정밀 검사한다. pdf-inspector가 그 X-ray다 — 초저비용으로 대량을 훑어, 진짜 정밀 처리(OCR)가 필요한 소수만 집어낸다.
셋째, 순수 Rust + 단일 의존이라는 공학적 매력이다. PDF 파싱을 lopdf 크레이트 하나로만 하고 네이티브 pdfium/mupdf FFI도, 머신러닝 모델도 쓰지 않는다. 덕분에 빌드가 가볍고, 크로스 컴파일이 쉽고, 브라우저 WASM으로도 나간다. "무거운 C++ 라이브러리에 링크하지 않는 PDF 도구"라는 점이 Rust 커뮤니티에 특히 어필했다.
요즘은 뭐든 "AI로 분류"하려 들지만, pdf-inspector는 PDF 내부 구조를 직접 세는 규칙(휴리스틱)만으로 판별한다. ML 추론이 없으니 결정적(같은 입력 → 같은 출력)이고, GPU가 필요 없고, 밀리초에 끝난다. "문제를 잘 이해하면 무거운 모델 없이도 풀린다"는 걸 보여 주는 좋은 사례다.
본체는 pdf_inspector라는 이름의 Rust 라이브러리 크레이트다. Cargo.toml의 crate-type은 ["lib", "cdylib"] — Rust에서 쓸 lib과, 다른 언어(Python/Node)에서 링크할 C 호환 동적 라이브러리(cdylib)를 동시에 뽑는다. PDF를 실제로 뜯어보는 유일한 의존이 lopdf다.
features=["rayon"](병렬 파싱)을 켜고, WASM 빌드에선 default-features=false, features=["wasm_js"]로 싱글스레드·JS 랜덤을 쓴다 — 같은 크레이트를 타깃에 맞춰 다르게 조립하는 좋은 예.| 크레이트 | 버전 | 용도 |
|---|---|---|
| lopdf | 0.41 | 유일한 PDF 파서. 네이티브=rayon 병렬, WASM=wasm_js 싱글스레드 |
| thiserror | 2.0 | 에러 타입(PdfError) 정의 — 보일러플레이트 없이 enum으로 |
| regex | 1.10 | 텍스트 후처리(페이지 번호·URL·하이픈 재결합 등) |
| ttf-parser | 0.25 | TrueType 폰트 파싱 — Identity-H CID 폰트의 cmap을 읽어 글자 복원 |
| unicode-normalization | 0.1 | 추출 텍스트 NFKC 정규화(합자·전각 문자 정리) |
| once_cell | 1.19 | 지연 초기화 static(글리프 이름 표 등 큰 상수 데이터) |
| log | 0.4 | 로깅 파사드(RUST_LOG으로 모듈별 디버그) |
| pyo3 | 0.25 (옵션) | Python 바인딩. features=["python"]일 때만 |
| rayon / env_logger | 1.10 / 0.11 | 네이티브 전용 — 병렬화, CLI 로깅 |
| include_dir | 0.7 (WASM 전용) | CJK CMap을 바이너리에 임베드(WASM엔 파일시스템 없음) |
이 저장소의 가장 배울 만한 설계는 Rust 코어 하나를 4가지 배포 형태로 내보내는 구조다. 로직은 한 곳에 있고, 각 언어의 얇은 포장만 따로 있다.
Rust crate는 0.1.7, Python은 0.2.6, Node는 1.11.2, WASM은 0.1.3. 하나로 착각하면 안 된다. README 벤치마크가 말하는 "pdf-inspector 0.2.6"은 Python 패키지 버전 기준이다. 배포 채널마다 릴리스 주기가 독립적이라 이렇게 갈린다.
각 바인딩의 진입점은 이렇게 대응된다:
src/bin/) — pdf2md(→Markdown), detect-pdf(분류만), dump_ops(연산자 덤프 디버그). Cargo.toml에 autobins=false로 명시 등록.src/python.rs, ~642줄) — pyo3 + maturin 빌드. 타입 스텁 pdf_inspector.pyi 제공. 여기선 PdfType이 문자열 "text_based"|"scanned"|"image_based"|"mixed"로 나온다.napi/) — napi-rs. linux-x64, darwin-arm64, win32-x64 프리빌트 바이너리 3종을 npm에 올려서, 사용자는 Rust 툴체인 없이 설치만 하면 된다.wasm/) — wasm-bindgen + serde-wasm-bindgen. 릴리스 프로파일이 lto=true, opt-level="s", strip=true로 바이너리 크기 최적화. 파일 없이 브라우저 안에서 PDF 판별이 돈다..github/workflows/에 6개 워크플로가 있다: ci(테스트+fmt+clippy), pages(랜딩 사이트), 그리고 채널별 배포 4종(publish-crate·publish-pypi·publish-wasm·publish[npm]). 특히 clippy를 -D warnings로 강제해서 경고 하나만 있어도 CI가 깨진다 — Rust 프로젝트의 품질 규율을 볼 수 있는 대목이다.
pdf-inspector의 심장은 src/detector.rs(약 3,645줄)다. 핵심 아이디어는 "PDF 전체를 렌더링하지 말고, content stream의 연산자만 세라"는 것. PDF 페이지는 결국 "글자 그려라(Tj/TJ), 이미지 붙여라(Do), 선 그어라(path)" 같은 명령의 목록이다. 이 명령의 비율만 보면 렌더링 없이도 문서의 정체가 드러난다.
BT ... Tj ... ET는 텍스트 블록, Do는 외부 객체(주로 이미지) 그리기, path 연산자(m·l·re)는 선/도형. pdf-inspector는 이 대본에서 Tj/TJ(글자) 개수와 Do(이미지) 개수의 균형을 읽어 판별한다 — 사람이 그림을 안 보고 대본만 읽고 "이건 연극이다/영화다"를 맞히는 셈.표본으로 뽑힌 각 페이지에서 PageAnalysis 구조체에 십수 가지 신호를 채운다. 그중 판별에 쓰이는 핵심만:
| 신호 | 뜻 |
|---|---|
| text_operator_count | Tj/TJ(글자 그리기) 연산자 개수 — 많을수록 텍스트 문서 |
| image_count / has_images | Do(이미지) 연산자 개수 / 이미지 존재 여부 |
| has_template_image | 페이지 절반 이상을 덮는 대형 배경 이미지(=스캔 의심) |
| unique_text_chars | 서로 다른 글자 종류 수 — 너무 적으면 진짜 텍스트가 아님 |
| path_op_count / has_vector_text | 선 그리기 명령 수 / 글자를 도형으로 그린 "벡터 아웃라인 텍스트" 여부 |
| has_identity_h_no_tounicode | CID 폰트인데 유니코드 매핑표가 없음(→글자 복원 불가 의심) |
| font_change_count | 폰트 전환 횟수 — 신문 다단 레이아웃 감지에 사용 |
단순히 "글자 명령이 있으면 텍스트"가 아니다. 이미지가 있으면 기준을 높이는 등, 실전 함정을 피하는 조건이 겹쳐 있다(실제 코드):
// 이미지가 있는 페이지는 글자 명령을 더 많이(10개↑) 요구 —
// 로고 옆 캡션 몇 글자만 있는 스캔본을 '텍스트'로 오판하지 않기 위해
let effective_min_ops = if analysis.has_images || analysis.image_count > 0 {
config.min_text_ops_per_page.max(10) // 이미지 有 → 최소 10
} else {
config.min_text_ops_per_page // 이미지 無 → 최소 3 (기본값)
};
if analysis.text_operator_count >= effective_min_ops
&& !is_image_dominated // image_count>10 && image>text*3 이면 이미지 지배
&& analysis.unique_text_chars >= 5 // 글자 종류가 5개는 넘어야
&& !analysis.has_vector_text // 도형으로 그린 가짜 글자 아니고
&& !analysis.has_only_type3_fonts // 비트맵 글꼴만 있는 것도 아니면
{
pages_with_text += 1; // → 진짜 '텍스트 페이지'로 카운트
}
"글자가 있다"와 "읽을 수 있다"는 다르다. 간판에 로고와 함께 "Since 1998" 같은 장식 글자 몇 개가 박혀 있다고 그 가게가 "책"은 아니다. pdf-inspector는 그래서 이미지가 끼어 있으면 "진짜 본문이려면 글자 명령이 최소 10개는 돼야 한다"고 기준을 올린다. 스캔 표지 한 장에 저작권 문구 몇 줄 얹힌 문서를 텍스트로 착각하지 않으려는 방어다.
페이지별 카운트가 모이면, 아래 순서대로 흘러가며 최종 PdfType과 신뢰도(confidence, 0~1)를 정한다(실제 코드):
// 위에서부터 순서대로 검사, 처음 걸리는 가지가 결정
let (pdf_type, confidence) = if has_template_images && pages_with_text > 0 {
ocr_recommended = true; // 큰 배경이미지 + 약간의 글자
(PdfType::Mixed, 0.5 + (0.3 * (1.0 - template_ratio)))
} else if text_ratio >= config.text_page_ratio_threshold { // 텍스트 페이지 비율 ≥ 0.6
ocr_recommended = false;
(PdfType::TextBased, text_ratio) // ★ 로컬 추출로 라우팅
} else if pages_with_text == 0 && (pages_with_images > 0 || pages_with_vector_text > 0) {
ocr_recommended = true;
if total_text_ops == 0 && pages_with_vector_text == 0 {
(PdfType::Scanned, 0.95) // 글자 0 + 이미지뿐 → 확실한 스캔
} else {
(PdfType::ImageBased, 0.8)
}
} else if pages_with_text > 0 && (pages_with_images > 0 || pages_with_vector_text > 0) {
ocr_recommended = true;
(PdfType::Mixed, 0.7) // 글자도 이미지도 섞임
} else if total_text_ops == 0 {
ocr_recommended = true;
(PdfType::Scanned, 0.9)
} else {
ocr_recommended = false;
(PdfType::TextBased, text_ratio.max(0.5))
};
읽는 법: 위에서 아래로 순서대로 검사해서 처음 참이 되는 가지가 결과다. 가장 중요한 지름길은 두 번째 가지 — "텍스트 페이지 비율이 60% 이상이면 무조건 TextBased, OCR 불필요". 반대로 "글자 명령이 아예 0이고 이미지만 있으면" 신뢰도 0.95로 Scanned. 애매한 중간(글자+이미지 혼재)은 전부 ocr_recommended=true로 안전하게 OCR을 권한다 — "확실할 때만 OCR을 건너뛴다"는 보수적 설계다.
기본 결정 트리 위에, "겉보기론 텍스트인데 실은 못 읽는" 위장 케이스를 잡는 규칙이 얹혀 있다. 이게 정확도의 비결이다.
단일 이미지가 TEMPLATE_IMAGE_THRESHOLD = 500,000 픽셀(반 페이지 at 150DPI)을 넘으면 "배경 스캔"으로 본다. 교묘하게 스캔본을 여러 조각(타일)으로 쪼갠 경우(JBIG2 strip 스캔)엔 개별 타일은 작아도, total_area ≥ 임계 × 4(=200만 픽셀)면 template로 승격시켜 잡아낸다.
일부 PDF는 글자를 폰트가 아니라 선(path)으로 직접 그린다. 겉보기엔 글자지만 복사·검색이 안 된다. 이걸 잡는 조건:
let has_vector_text =
path_ops >= 1000
&& path_ops > text_ops.saturating_mul(200) // 글자 대비 선이 200배↑
&& unique_alphanum_chars < 30; // 실제 글자 종류는 30 미만
아웃라인 글자 하나엔 path 명령이 10~30개 붙으니, 페이지 전체가 수천 개 path인데 진짜 글자 종류는 적으면 "도형으로 위장한 텍스트"로 판정 → OCR 권고.
추출은 되지만 다단이 뒤섞여 품질이 나쁜 케이스. text_operator_count ≥ 1500 && font_change_count ≥ 50 && (폰트전환/글자) < 0.15인 페이지가 절반 이상이면 OCR 권고. 소스 주석엔 실제 캘리브레이션 데이터(WSJ 비율 0.02~0.06 vs 일반 계약서 0.25~0.35)까지 적혀 있다.
Identity-H/V CID 폰트인데 ToUnicode 매핑이 없거나(CID→유니코드 불가), Type3(비트맵 글꼴)만 있으면 suspected_garbled_text로 플래그. 단, ToUnicode/W-array가 유니코드처럼 보이거나 임베드된 TrueType cmap이 있으면 fallback 디코딩 가능으로 처리해 무리하게 OCR로 보내지 않는다.
초대형 PDF에서 모든 페이지를 검사하면 느리다. 그래서 표본 전략을 고른다(기본은 Sample(8)):
| 전략 | 동작 | 용도 |
|---|---|---|
| EarlyExit | 앞에서부터 스캔, 첫 비텍스트 페이지에서 중단 | TextBased를 빠르게 확정 |
| Full | 전 페이지, 조기 종료 없음 | Mixed vs Scanned 정확 판정 |
| Sample(n) | 앞/중간/뒤 균등 n페이지만(기본 8) | 초대형 PDF (기본값) |
| Pages(vec) | 지정한 1-index 페이지만 | 호출자가 대상 페이지를 알 때 |
소스 주석 왈: "EarlyExit is too aggressive for PDFs with an image-only cover followed by text-heavy pages (e.g., annual reports)." — 표지만 이미지이고 본문은 텍스트인 연차보고서 같은 문서에서, 첫 페이지(이미지)만 보고 조기 종료하면 "스캔본"으로 오판한다. 그래서 앞·중간·뒤를 골고루 표본하는 Sample을 기본으로 둔다. "엣지 케이스가 기본값을 정한다"는 실전 감각.
판별(detection)과 추출(extraction)이 각각 PDF를 열면 I/O가 두 배다. pdf-inspector는 load_document_from_path/load_document_from_mem로 한 번 로드한 문서를 둘이 공유한다. 추출 파이프라인은 이렇게 이어진다:
표 감지(tables/)는 3가지 전략을 우선순위대로 시도해 처음 유효한 결과가 이긴다: ① rect-based(셀 사각형을 union-find로 클러스터링) → ② line-based(수평·수직 선으로 격자 복원) → ③ heuristic(선이 없는 표를 간격·정렬로 추론). Markdown 변환은 폰트 크기를 0.5pt 단위로 클러스터링해 본문 대비 크기로 H1~H4 heading을 판정하고, 태그드 PDF면 structure tree의 역할(H1~H6·P·L·Code)을 우선 사용한다.
구조에서 읽히는 힌트 두 가지. 첫째, 가장 큰 파일이 adobe_korea1.rs(1.7만 줄)인데 이건 로직이 아니라 CJK(한중일) 문자 코드맵 데이터다 — 한국어·일본어 PDF의 CID 폰트를 유니코드로 복원하려면 이 방대한 표가 필요하다. 즉 이 라이브러리는 처음부터 동아시아 문서를 진지하게 다룬다. 둘째, detector.rs는 3,645줄로 크지 않다 — 판별 로직 자체는 "얇고 빠른" 설계 의도가 파일 크기에도 드러난다. 무게는 오히려 추출·표·폰트 쪽에 실려 있다.
대부분의 개발자는 PDF를 "그냥 열리는 파일"로만 안다. 이 레포는 PDF가 사실은 객체 그래프 + content stream(그리기 대본)이라는 진실을 코드로 가르쳐 준다. Tj/TJ(텍스트), Do(XObject/이미지), path 연산자, CID 폰트와 ToUnicode 매핑, Identity-H 인코딩 — 이 개념들을 실전 판별 로직 안에서 만나니 교과서보다 훨씬 잘 붙는다.
ML 없이 신호를 세고 임계값과 결정 트리로 분류하는 법. 특히 "엣지 케이스가 기본값과 임계값을 정한다"는 실전 감각(연차보고서 표지, 타일드 스캔, 신문 다단)을 소스 주석의 캘리브레이션 데이터와 함께 볼 수 있다. 데이터 없이도 견고한 분류기를 짜는 사고법의 표본.
cdylib + pyo3(Python) + napi-rs(Node) + wasm-bindgen(브라우저)로 하나의 로직을 네 채널에 내보내는 전체 그림. cfg(target_arch="wasm32")로 같은 코드를 타깃별로 다르게 조립(rayon 병렬 vs 싱글스레드, 파일 로드 vs include_dir 임베드)하는 조건부 컴파일도 좋은 교재다.
"전체를 렌더링하지 않고 표본 연산자만 센다", "detection과 extraction이 문서를 한 번만 로드해 공유한다", "ScanStrategy로 검사량을 조절한다" — 일을 덜 하도록 설계해서 속도를 얻는 사고를 구체적 코드로 배운다. 35배 속도차의 정체가 마법이 아니라 이 절약들의 합이라는 걸 확인할 수 있다.
clippy -D warnings 강제, thiserror 에러 설계, 손으로 조립한 최소 PDF 픽스처로 하는 결정적 테스트(858개+), 스냅샷 회귀 테스트, 비밀번호를 Debug에서 [REDACTED]로 가리는 보안 습관 — 프로덕션 Rust 크레이트의 위생을 통째로 훑을 수 있다.
내 컴퓨터의 잡다한 PDF(영수증·논문·스캔한 서류·워드 출력본)를 모아 detect-pdf로 돌려 보면, 어떤 게 TextBased고 어떤 게 Scanned인지, confidence가 어떻게 갈리는지 내 데이터로 체감된다. 오판이 나오면 그 PDF의 dump_ops 출력을 보며 "왜 그렇게 판정했나"를 역추적하는 게 최고의 공부다.
| 쓰는 방식 | 필요한 것 |
|---|---|
| Rust 라이브러리/CLI | Rust 툴체인(1.7x+, edition 2021). cargo build --release 또는 cargo run --bin pdf2md -- 파일.pdf |
| Python 패키지 | Python 3.8+ (abi3-py38). pip install 시 프리빌트 휠, 소스 빌드 땐 maturin + Rust |
| Node/Bun | Node.js. 프리빌트 바이너리(linux-x64-gnu / darwin-arm64 / win32-x64-msvc) 제공 → Rust 툴체인 불필요 |
| 브라우저 WASM | 모던 브라우저. init() 후 동기 실행. 대형 문서는 Web Worker 권장(싱글스레드) |
external/bcmaps/는 런타임 의존 — CJK CMap을 실행 중 상대 경로(CARGO_MANIFEST_DIR 기준)로 읽는다. crates.io 10MiB 제한 때문에 테스트 픽스처는 크레이트에서 빼지만 이 CMap은 포함시킨다. WASM은 파일시스템이 없어 include_dir로 바이너리에 임베드.워드 출력본, 스캔한 서류, 논문 PDF를 하나씩 준비해 detect-pdf 파일.pdf --json을 돌려 보자. pdf_type·confidence·pages_needing_ocr가 어떻게 다른지 비교하면, 이 도구가 반환하는 "라우팅 정보"의 실체가 잡힌다. 그다음 pdf2md 파일.pdf로 텍스트 PDF가 Markdown으로 얼마나 깔끔하게 나오는지 확인.
Python 바인딩으로 폴더 안 PDF를 전부 classify_pdf한 뒤, TextBased면 extract_pages_markdown으로 로컬 처리, 아니면 "OCR 대기열에 넣음"이라고 출력만 하는 라우터를 20줄로 짜 보자. Firecrawl이 실제로 하는 일의 축소판이다. 처리 시간을 로그로 찍어 로컬 경로가 얼마나 빠른지 체감할 것.
DetectionConfig의 text_page_ratio_threshold(기본 0.6)나 min_text_ops_per_page(기본 3)를 극단값으로 바꿔 빌드한 뒤, 같은 PDF 세트를 다시 분류해 보자. 어떤 문서가 어느 임계에서 분류가 뒤집히는지를 관찰하면, 저자가 왜 하필 0.6과 3을 골랐는지, 임계값 튜닝이 정확도에 어떻게 작동하는지 몸으로 이해된다.
tests/integration_tests.rs의 make_minimal_text_pdf() 헬퍼를 참고해, 바이트 단위로 "글자 명령 2개 + 커다란 이미지 1개"짜리 PDF를 직접 만들어 보자. 이게 Mixed로 잡히는지 Scanned로 잡히는지 예측하고 실제로 돌려 검증. PDF 포맷을 바닥부터 이해하게 되는, 이 레포에서 가장 남는 실습이다.
예: "가로 방향 스캔(landscape 스캔본)" 같은 미탐 케이스를 하나 정해, detector.rs에 신호와 조건을 추가한다. 그다음 tests/fixtures/에 해당 PDF를 넣고 스냅샷 테스트를 작성해, 기존 858개+ 테스트가 하나도 깨지지 않으면서 새 케이스가 통과하게 만든다. 기존 정확도를 해치지 않고 규칙을 늘리는 것이 얼마나 조심스러운 일인지 배우는 과제.
| 주차 | 주제 | 구체적으로 |
|---|---|---|
| 1주 | PDF 포맷의 진실 | 객체·xref·content stream·연산자, 폰트(Type1/TrueType/Type3/CID), Identity-H와 ToUnicode. detector.rs·extractor/content_stream.rs를 표본으로 |
| 2주 | 텍스트 레이아웃 복원 | 다단(column) 감지(투영 히스토그램·valley detection), 읽기 순서, 표 감지 3전략(rect/line/heuristic). extractor/layout.rs·tables/ |
| 3주 | Rust FFI & 크로스 빌드 | cdylib, pyo3(Python), napi-rs(Node), wasm-bindgen(브라우저). 조건부 컴파일 cfg(target_arch)로 타깃별 조립 |
| 4주 | 규칙 기반 분류 & 캘리브레이션 | 휴리스틱 설계, 임계값 튜닝, 엣지 케이스 주도 개발. 소스 주석의 실제 캘리브레이션 데이터 읽기 |
| 5주(선택) | OCR 파이프라인 통합 | 이 판별기 뒤에 붙일 OCR(Tesseract·PaddleOCR·클라우드 OCR API), 하이브리드 라우팅, RAG 문서 인제스트 파이프라인 설계 |
include_dir 임베드로 갈라진다.README.md(개요·벤치마크) · src/detector.rs(분류 두뇌) · src/lib.rs(공개 API) · AGENTS.md(개발 관례) · docs/rust-api.md·docs/python.md·docs/benchmarking.md