pdf-inspector

firecrawlMIT活跃维护

用 Rust 写的高性能 PDF 解析库,能快速判断 PDF 是扫描件还是文本型并抽取文字,方便后续决定走 OCR 还是直接解析。

Fast Rust library for PDF inspection, classification, and text extraction. Intelligently detects scanned vs text-based PDFs to enable smart routing decisions.

v1.25.2发布于 2026-09-28 07:14 UTC19,979star +47 / 7 天1,358fork同步于 2026-11-10 11:24 UTC

怎么上手

由 README 提炼,执行前请确认
pip install pdf-inspector
cargo add pdf-inspector
硬件要求按 README 说明

README 未说明显存或 GPU 要求;基准测试在 Apple M4 Pro 上完成。OCR 需另行安装 PDFium 与 ONNX Runtime 库,仅在页面被路由到 OCR 时加载。

支持 Apple Silicon
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

Rust 写的 PDF 检测与文本提取库,快速判断扫描件还是文本件,省掉不必要的 OCR

用 Rust 写的高性能 PDF 解析库,能快速判断 PDF 是扫描件还是文本型并抽取文字,方便后续决定走 OCR 还是直接解析。

核心能力

  • 采样内容流,10-50ms 内分类 TextBased/Scanned/ImageBased/Mixed 并给出置信度
  • 按位置提取文本,保留字体、坐标、旋转角度与多栏阅读顺序
  • 转 Markdown 支持标题分级、列表、代码块、表格、粗斜体与链接
  • 双模式表格检测:绘图矩形加文本对齐启发式
  • 可按页路由选择性 OCR,仅渲染缺文本的页面

容易踩的坑

OCR 功能需单独安装 PDFium 和 ONNX Runtime 库及模型文件,默认构建不含;Rust 与 CLI 需用 --features ocr 编译才启用 OCR;Python 和 Node 包虽内置 OCR 集成,但外部运行时仍需自行准备。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v1.25.2
  1. v1.25.21.25.2 — Script sizes and ToUnicode repair checks最新正式版2026-09-28 · 2026-09-28 07:14 UTC

    Changes since 1.25.1:

    • Size a super- or subscript run by its letters and digits: an exponent whose minus sign a symbol font sets a design size above its digits (as TeX does) keeps its <sup> markup; a run of signs alone, or with signs more than a quarter larger than its letters and digits, is still sized by its largest glyph (#603).
    • Skip a subset font's ToUnicode repairs where the font itself shows the CMap is right: the repair through a /CIDToGIDMap for a CMap keyed by code, and the renumbering of a subset that kept its glyph indexes (#605).

    No API changes.

    Release preparation: #606.

    Full changelog

  2. v1.25.11.25.1 — Font decoding fixes2026-09-28 · 2026-09-28 07:14 UTC
    查看更新说明

    Changes since 1.25.0:

    • Read simple fonts (Type1, TrueType, Type3) one byte per code when their ToUnicode CMap declares a two-byte codespace: text shown as a kerned run of short strings no longer loses every two-byte string (Income Statement read as Iometatent). The stale-CMap check for Type1 fonts judges such a CMap too, and the page detector counts such a font's text byte by byte (#595).
    • Read a Type1 font whose /Encoding names no base encoding through the built-in encoding of its embedded program (PDF 32000-1:2008, Table 114): TeX's fonts read their ligatures (efficiency, was eciency), curly quotes, dashes and math symbols. A code the program leaves at .notdef reads as nothing, the word space excepted, and the base-14 width fallback measures such fonts as they read (#596).
    • Take a repaired ToUnicode CMap for a string of one or two glyphs only on evidence beyond short common words: engagement no longer reads as engageaent, AND as ANa or Three as ThaTe (#597).

    No API changes.

    Release preparation: #604.

    Full changelog

  3. v1.25.01.25.0 — Async positions and underline detection speed2026-09-28 · 2026-09-28 07:14 UTC
    查看更新说明

    Changes since 1.24.0:

    • Node: extractTextWithPositionsAsync, the async variant of extractTextWithPositions: the same arguments and items, run on the libuv thread pool so a slow page no longer blocks the caller's event loop (#592).
    • Underline and strikeout detection no longer takes quadratic time on pages drawn from many thin filled rects or short strokes: a page of about 200,000 thin rects went from about 40 s to under 0.5 s, with the same marks (#592).
    • The linux-x64-gnu Node binary is cross-built for a GLIBC_2.17 floor again, so it loads on Amazon Linux 2023 (the managed AWS Lambda Node runtimes), RHEL 9 and Debian 11 (#586).

    API: Node extractTextWithPositionsAsync.

    Release preparation: #593.

    Full changelog

  4. v1.24.01.24.0 — Text paint and document information2026-09-23 · 2026-09-23 04:42 UTC
    查看更新说明

    Changes since 1.23.0:

    • Report the paint each text run was shown with: TextItem gains fill_color and stroke_color (8-bit sRGB, read from DeviceRGB, DeviceGray, DeviceCMYK, ICCBased by component count and Indexed palettes; None for Separation, DeviceN, Pattern and CIE-based spaces) and render_mode (the Tr mode, so invisible text and clipping-only text can be told apart). Extraction and markdown are unchanged (#579).
    • Read the document information dictionary's /Author, /Subject, /Keywords, /Creator, /Producer, /CreationDate and /ModDate beside /Title, each decoded as a PDF text string (#579).
    • Decode the document /Title as a PDF text string: titles in PDFDocEncoding no longer read with U+FFFD, titles in UTF-16LE no longer read as mojibake, and titles given by reference are read (#579).
    • Extract text a page shows with the " operator, which the page parser skipped together with the spacing and line move it makes (#579).
    • Take an ActualText span's weight from the paint of its first painted glyph rather than from the paint in force at the span's end (#579).
    • Keep two lines of small type apart when their baselines lie less than 5 pt apart: a stacked table header at 4.7 pt on a 4.5 pt pitch no longer comes out as words zipped from both lines, and type of 8⅓ pt and above groups exactly as before (#580).

    API: TextItem::fill_color, stroke_color and render_mode (Node fillColor, strokeColor, renderMode; Python fill_color, stroke_color, render_mode; the pdf2md --items-json fields), and the PdfProcessResult / PdfTypeResult fields author, subject, keywords, creator, producer, creation_date and mod_date (Node and WebAssembly in camelCase, Python as attributes, and the pdf2md --json / detect-pdf --json members). Rust literals of these types must include the new fields.

    Release preparation: #582.

    Full changelog

  5. v1.23.01.23.0 — Text-layer repairs2026-09-22 · 2026-09-22 01:26 UTC
    查看更新说明

    Changes since 1.22.1:

    • Read a Form XObject whose /BBox numerals are too large for any parser: such numerals are saturated in place before the document is read, so the form's text is extracted and the OCR pipeline renders it instead of an empty page (#560).

    • Read right-to-left runs shown in reading order forwards: a visible run of right-to-left letters painted forwards now votes for visual storage, so pages that came out word-mirrored read correctly; the logical-order reading is kept for invisible text layers (#561).

    • Read a re-encoded simple font by its /Differences names when it kept a stale ToUnicode CMap, and keep one glyph's characters together through the visual-order read-back (#562).

    • Compose a detached spacing accent with the letter it is painted over instead of leaving a stray accent a word on (#563).

    • Treat a return from a zero-advance dependent sign placed behind the pen as no word gap, so scripts that attach signs by stepping the pen back keep their words whole (#564).

    • Classify a page whose text layer is drawn invisibly under a covering image as a scan with the OCR reason invisible_text_layer, following the text rendering mode, the transformation, the graphics state stack and image draws through form content within a per-page budget (#566).

    • Interpolate single-glyph gaps in a ToUnicode CMap when the neighbouring codes make the reading unambiguous, mark the rest as U+FFFD instead of dropping them, and report each font's coverage (#568).

    • A ToUnicode entry whose destination is a control character is a miss, not text: the marker replaces it and the font program, the declared encoding or a blank glyph's advance recovers the letter where it can (#567).

    • pdf2md and detect-pdf take the PDF path as the first non-option argument, so flags may come before or after it, and a missing input names the path that could not be opened; the built-in CMaps are compiled into the binary instead of being read from the crate checkout at run time, so cargo install binaries and published wheels find them (PDF_INSPECTOR_BCMAPS_DIR still overrides) (#570).

    API: PdfProcessResult::cmap_gaps (Node cmapGaps, Python cmap_gaps, WebAssembly cmapGaps, and the pdf2md --json / detect-pdf --analyze --json field) lists, per font with gaps among the two-byte codes shown, the codes shown, interpolated and left unmapped (#568). The detector's invisible_text_layer OCR reason is new (#566).

    Release preparation: #571.

    Full changelog

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    pdf-inspector

    Crates.io npm PyPI License: MIT

    Fast Rust library for PDF classification and text extraction. By default it detects whether a PDF is text-based or scanned, extracts text with position awareness, and converts to clean Markdown without OCR. Native Rust and CLI consumers can opt into selective OCR. Includes bindings for Python, Node.js, and browser WebAssembly.

    Built by Firecrawl to handle text-based PDFs locally in under 200ms, skipping expensive OCR services for the ~54% of PDFs that don't need them.

    Features

    • Smart classification — Detect TextBased, Scanned, ImageBased, or Mixed PDFs in ~10-50ms by sampling content streams. Returns a confidence score (0.0-1.0) and per-page OCR routing.
    • Text extraction — Position-aware extraction with font info, X/Y coordinates, and automatic multi-column reading order. Rotated runs (margin stamps, chart axis titles) keep a true axis-aligned box and report their rotation angle instead of collapsing to zero width.
    • Markdown conversion — Headings (H1-H4 via font size ratios), bullet/numbered/letter lists, code blocks (monospace font detection), tables (rectangle-based and heuristic), bold/italic formatting, URL linking, and page breaks.
    • Table detection — Dual-mode: rectangle-based detection from PDF drawing ops, plus heuristic detection from text alignment. Handles financial tables, footnotes, and continuation tables across pages.
    • CID font support — ToUnicode CMap decoding for Type0/Identity-H fonts, UTF-16BE, UTF-8, and Latin-1 encodings.
    • Multi-column layout — Automatic detection of newspaper-style columns, sequential reading order, and RTL text support.
    • Encoding issue detection — Automatically flags broken font encodings so callers can fall back to OCR.
    • Selective OCR — Rust, CLI, Python, and Node can render only pages that need OCR, run PP-OCRv6 Small locally, and preserve per-page provenance and hosted-fallback recommendations.
    • Single document load — The document is parsed once and shared between detection and extraction, avoiding redundant I/O.
    • Browser WebAssembly — Run the same Rust parser locally in browsers and Web Workers, with embedded CMaps and no server round trip.
    • Lightweight by default — The default Rust and browser builds remain pure extraction. Native Python and Node packages include the OCR integration, but PDFium, ONNX Runtime, and model files remain external and are touched only when a page is routed to OCR.

    Benchmark

    Evaluated on the opendataloader-bench corpus (200 PDFs). Only local engines without model-based PDF parsing are shown; OCR was disabled. Scores are 0-1, higher is better.

    EngineOverallReading Order (NID)Tables (TEDS)Headings (MHS)Speed (200 docs)
    pdf-inspector0.8750.9150.8140.7880.470s
    liteparse0.8730.9130.6930.8110.750s
    opendataloader0.8310.9020.4890.7392.569s
    pymupdf4llm0.7350.8860.4010.42417.117s
    markitdown0.5890.8440.2730.00016.165s

    Results were refreshed on July 31, 2026, on an Apple M4 Pro. Engine versions were pdf-inspector 0.2.6, LiteParse 2.10.1, OpenDataLoader 2.2.1, PyMuPDF4LLM 0.2.0, and MarkItDown 0.1.5. Speed is the median of five alternating or rotating complete corpus runs after an excluded warm-up run, with each parser processing documents sequentially in a single process.

    The complete parser configuration, per-document predictions, evaluator output, and generated charts are available in the reproducible results branch.

    Best fit: Native-text PDFs where speed, reading order, and table structure matter. In this comparison, pdf-inspector delivered the higher overall, reading-order, and table scores, along with the fastest complete run. That makes it a strong local default for reports, research papers, financial documents, invoices, and legal PDFs that need clean, structured Markdown without adding OCR latency or infrastructure.

    Use the paired benchmark harness to compare two local builds against the exact same corpus and evaluator revision.

    Quick start

    Python

    pip install pdf-inspector
    
    import pdf_inspector
    
    result = pdf_inspector.process_pdf("document.pdf")
    print(result.pdf_type)   # "text_based", "scanned", "image_based", "mixed"
    print(result.markdown)   # Markdown string or None
    
    # Selective OCR; clean text PDFs do not load the external OCR runtime.
    ocr = pdf_inspector.process_pdf_with_ocr("document.pdf")
    print(ocr.pages_routed_to_ocr)
    

    Full API reference: docs/python.md

    Node.js

    npm install @firecrawl/pdf-inspector
    
    import { readFileSync } from 'fs';
    import { processPdf, processPdfWithOcr } from '@firecrawl/pdf-inspector';
    
    const pdf = readFileSync('document.pdf');
    const result = processPdf(pdf);
    console.log(result.pdfType);   // "TextBased", "Scanned", "ImageBased", "Mixed"
    console.log(result.markdown);  // Markdown string or null
    
    const ocr = await processPdfWithOcr(pdf); // selective OCR, off the event loop
    console.log(ocr.pagesRoutedToOcr);
    

    Full API reference: napi/README.md

    Browser WebAssembly

    npm install @firecrawl/pdf-inspector-wasm
    
    import init, { processPdf } from '@firecrawl/pdf-inspector-wasm';
    
    await init();
    const response = await fetch('/document.pdf');
    const pdf = new Uint8Array(await response.arrayBuffer());
    const result = processPdf(pdf);
    
    console.log(result.pdfType);
    console.log(result.markdown);
    

    Full API reference: wasm/README.md

    Rust

    Install from crates.io:

    cargo add pdf-inspector
    

    Or add it manually:

    [dependencies]
    pdf-inspector = "1"
    
    use pdf_inspector::process_pdf;
    
    let result = process_pdf("document.pdf")?;
    println!("Type: {:?}", result.pdf_type);
    if let Some(markdown) = &result.markdown {
        println!("{}", markdown);
    }
    

    Full API reference: docs/rust-api.md

    CLI

    # Install the CLI tools
    cargo install pdf-inspector
    
    # Convert PDF to Markdown
    pdf2md document.pdf
    
    # JSON output (for piping), with the document information (title, author, producer, dates, ...)
    pdf2md document.pdf --json
    
    # Positioned TextItem JSON (coordinates relative to the visible page box): axis-aligned box, rotation, font, paint (fill and stroke colour, render mode), underline metadata
    pdf2md document.pdf --items-json
    
    # Raw markdown only (no headers)
    pdf2md document.pdf --raw
    
    # Token-efficient output (collapses long dot leaders and similar source padding)
    pdf2md document.pdf --compact
    
    # Insert page break markers (<!-- Page N -->)
    pdf2md document.pdf --pages
    
    # Process only specific pages
    pdf2md document.pdf --select-pages 1,3,5-10
    
    # Detection only (no extraction)
    detect-pdf document.pdf
    detect-pdf document.pdf --json
    
    # Detection + layout analysis (tables, columns)
    detect-pdf document.pdf --analyze --json
    

    Rust and CLI consumers opt into OCR at build time:

    cargo install pdf-inspector --features ocr --bin pdf2md
    PDFIUM_LIB_PATH=/path/to/libpdfium ORT_DYLIB_PATH=/path/to/libonnxruntime \
      pdf2md scan.pdf --ocr auto --json
    

    The OCR JSON envelope is versioned and reports routed pages, per-page source and confidence, warnings, and pages recommended for the hosted document pipeline. Native Python and Node packages expose the same pipeline without a source-build feature. All native entry points still require separately installed PDFium and ONNX Runtime libraries only when OCR is routed. See the OCR runtime setup guide for pinned downloads, platform support, model-cache behavior, and hosted-fallback integration. See the Rust API guide for lower-level controls.

    From a source checkout, use cargo run --bin pdf2md -- document.pdf or cargo run --bin detect-pdf -- document.pdf instead.

    Architecture

    PDF bytes
      │
      ├─► detector         → PdfType (TextBased / Scanned / ImageBased / Mixed)
      │
      └─► extractor
            ├─ fonts        → font widths, encodings
            ├─ content_stream → walk PDF operators → TextItems + PdfRects
            ├─ xobjects     → Form XObject text, image placeholders
            ├─ links        → hyperlinks, AcroForm fields
            └─ layout       → column detection → line grouping → reading order
                  │
                  ├─► tables
                  │     ├─ detect_rects      → rectangle-based tables (union-find)
                  │     ├─ detect_heuristic  → alignment-based tables
                  │     ├─ grid              → column/row assignment → cells
                  │     └─ format            → cells → Markdown table
                  │
                  └─► markdown
                        ├─ analysis     → font stats, heading tiers
                        ├─ preprocess   → merge headings, drop caps
                        ├─ convert      → line loop + table/image insertion
                        ├─ classify     → captions, lists, code
                        └─ postprocess  → cleanup → final Markdown
    

    The document is loaded once via load_document_from_path / load_document_from_mem and shared between the detection and extraction stages, so there's no redundant parsing.

    Project structure

    src/
      lib.rs                — Public API, PdfOptions builder, convenience functions
      python.rs             — PyO3 Python bindings
      types.rs              — Shared types: TextItem, TextLine, PdfRect, ItemType
      text_utils.rs         — Character/text helpers (CJK, RTL, ligatures, bold/italic)
      process_mode.rs       — ProcessMode enum (DetectOnly, Analyze, Full)
      detector.rs           — Fast PDF type detection without full document load
      glyph_names.rs        — Adobe Glyph List → Unicode mapping
      tounicode.rs          — ToUnicode CMap parsing for CID-encoded text
      extractor/            — Text extraction pipeline
      tables/               — Table detection and formatting
      markdown/             — Markdown conversion and structure detection
      bin/                  — CLI tools (pdf2md, detect_pdf)
    napi/                   — Node.js/Bun bindings (napi-rs)
    wasm/                   — Browser bindings (wasm-bindgen)
    

    How classification works

    1. Parse the xref table and page tree (no full object load)
    2. Select pages based on ScanStrategy (default: a sample of 8 pages)
    3. Look for Tj/TJ (text operators) and Do (image operators) in content streams
    4. Classify based on text operator presence across sampled pages

    This detects 300+ page PDFs in milliseconds. The result includes pages_needing_ocr — a list of specific page numbers that lack text, enabling per-page OCR routing instead of all-or-nothing.

    Scan strategies

    StrategyBehaviorBest for
    EarlyExitScan all pages, stop on first non-text pagePipelines routing TextBased PDFs to fast extraction
    FullScan all pages, no early exitAccurate Mixed vs Scanned classification
    Sample(n) (default: Sample(8))Sample n evenly distributed pages (first, last, middle)Very large PDFs where speed matters more than precision
    Pages(vec)Only scan specific 1-indexed page numbersWhen the caller knows which pages to check

    Markdown output

    The converter handles:

    ElementHow it's detected
    Headings (H1-H4)Font size tiers relative to body text, with 0.5pt clustering
    Bold/italicFont name patterns (Bold, Italic, Oblique)
    Bullet lists•, -, *, ○, ●, ◦ prefixes
    Numbered lists1., 1), (1) patterns
    Letter listsa., a), (a) patterns
    Code blocksMonospace fonts (Courier, Consolas, Monaco, Menlo, Fira Code, JetBrains Mono) and keyword detection
    TablesRectangle-based detection from PDF drawing ops + heuristic detection from text alignment
    Financial tablesToken splitting for consolidated numeric values
    Captions"Figure", "Table", "Source:" prefix detection
    Sub/superscriptFont size and Y-offset relative to baseline
    URLsConverted to Markdown links
    HyphenationRejoins words broken across lines
    Page numbersFiltered from output
    Drop capsLarge initial letters merged with following text
    Dot leadersTOC-style dots collapsed to " ... "

    Use case: smart PDF routing

    pdf-inspector was built for pipelines that process PDFs at scale. Instead of sending every PDF through OCR:

    PDF arrives
      → pdf-inspector classifies it (~20ms)
      → TextBased + high confidence?
          YES → extract locally (~150ms), done
          NO  → send to OCR service (2-10s)
    

    This saves cost and latency for the majority of PDFs that are already text-based (reports, papers, invoices, legal docs).

    Debugging

    See docs/debugging.md for RUST_LOG environment variable usage.

    License

    MIT