|
2 | 2 |
|
3 | 3 | Fast Rust library for PDF inspection, classification, and text extraction. Intelligently detects scanned vs text-based PDFs to enable smart routing decisions. |
4 | 4 |
|
5 | | -## Features |
6 | | - |
7 | | -- **Smart Detection** - Detects scanned vs text-based PDFs in ~10-50ms by sampling content streams for text operators (`Tj`/`TJ`) without loading the full document |
8 | | -- **Direct Extraction** - Text extraction using [lopdf](https://github.com/J-F-Liu/lopdf) with no external dependencies |
9 | | -- **Structure Detection** - Headers (by font size), lists, code blocks (monospace fonts) |
10 | | -- **CLI Tools** - `detect-pdf` and `pdf2md` binaries included |
11 | | - |
12 | | -## Installation |
13 | | - |
14 | | -Add to your `Cargo.toml`: |
15 | | - |
16 | | -```toml |
17 | | -[dependencies] |
18 | | -pdf-inspector = { git = "https://github.com/firecrawl/pdf-inspector" } |
19 | | -``` |
20 | | - |
21 | | -## Usage |
22 | | - |
23 | | -### Quick Start |
24 | | - |
25 | | -The simplest way to convert a PDF to Markdown: |
26 | | - |
27 | | -```rust |
28 | | -use pdf_inspector::process_pdf; |
29 | | - |
30 | | -fn main() -> Result<(), Box<dyn std::error::Error>> { |
31 | | - let result = process_pdf("document.pdf")?; |
32 | | - |
33 | | - match result.pdf_type { |
34 | | - pdf_inspector::PdfType::TextBased => { |
35 | | - println!("Markdown:\n{}", result.markdown.unwrap()); |
36 | | - } |
37 | | - pdf_inspector::PdfType::Scanned => { |
38 | | - println!("PDF is scanned - OCR required"); |
39 | | - } |
40 | | - _ => {} |
41 | | - } |
42 | | - |
43 | | - Ok(()) |
44 | | -} |
45 | | -``` |
46 | | - |
47 | | -### PDF Type Detection |
48 | | - |
49 | | -Quickly detect if a PDF is text-based or scanned without full extraction: |
50 | | - |
51 | | -```rust |
52 | | -use pdf_inspector::{detect_pdf_type, PdfType}; |
53 | | - |
54 | | -fn main() -> Result<(), Box<dyn std::error::Error>> { |
55 | | - let result = detect_pdf_type("document.pdf")?; |
56 | | - |
57 | | - println!("Type: {:?}", result.pdf_type); |
58 | | - println!("Pages: {}", result.page_count); |
59 | | - println!("Confidence: {:.0}%", result.confidence * 100.0); |
60 | | - |
61 | | - if let Some(title) = result.title { |
62 | | - println!("Title: {}", title); |
63 | | - } |
64 | | - |
65 | | - match result.pdf_type { |
66 | | - PdfType::TextBased => println!("Ready for text extraction"), |
67 | | - PdfType::Scanned => println!("Needs OCR"), |
68 | | - PdfType::ImageBased => println!("Mostly images"), |
69 | | - PdfType::Mixed => println!("Mix of text and images"), |
70 | | - } |
71 | | - |
72 | | - Ok(()) |
73 | | -} |
74 | | -``` |
75 | | - |
76 | | -### Text Extraction |
77 | | - |
78 | | -Extract plain text from a PDF: |
79 | | - |
80 | | -```rust |
81 | | -use pdf_inspector::extract_text; |
82 | | - |
83 | | -fn main() -> Result<(), Box<dyn std::error::Error>> { |
84 | | - let text = extract_text("document.pdf")?; |
85 | | - println!("{}", text); |
86 | | - Ok(()) |
87 | | -} |
88 | | -``` |
89 | | - |
90 | | -### Extract Text with Position Information |
91 | | - |
92 | | -Get text items with position data for advanced processing: |
93 | | - |
94 | | -```rust |
95 | | -use pdf_inspector::{extract_text_with_positions, TextItem}; |
96 | | -use pdf_inspector::extractor::group_into_lines; |
97 | | - |
98 | | -fn main() -> Result<(), Box<dyn std::error::Error>> { |
99 | | - let items = extract_text_with_positions("document.pdf")?; |
100 | | - |
101 | | - for item in &items { |
102 | | - println!("'{}' at ({}, {}) size={}", |
103 | | - item.text, item.x, item.y, item.font_size); |
104 | | - } |
105 | | - |
106 | | - // Group items into lines |
107 | | - let lines = group_into_lines(items); |
108 | | - for line in lines { |
109 | | - println!("Line: {}", line.text()); |
110 | | - } |
111 | | - |
112 | | - Ok(()) |
113 | | -} |
114 | | -``` |
115 | | - |
116 | | -### Custom Markdown Conversion |
117 | | - |
118 | | -Convert text to Markdown with custom options: |
119 | | - |
120 | | -```rust |
121 | | -use pdf_inspector::{to_markdown, MarkdownOptions}; |
122 | | - |
123 | | -fn main() { |
124 | | - let text = "• First item\n• Second item\n\nconst x = 5;"; |
125 | | - |
126 | | - // With all detection enabled (default) |
127 | | - let md = to_markdown(text, MarkdownOptions::default()); |
128 | | - println!("{}", md); |
129 | | - |
130 | | - // Disable code detection |
131 | | - let opts = MarkdownOptions { |
132 | | - detect_headers: true, |
133 | | - detect_lists: true, |
134 | | - detect_code: false, |
135 | | - base_font_size: None, |
136 | | - }; |
137 | | - let md = to_markdown(text, opts); |
138 | | - println!("{}", md); |
139 | | -} |
140 | | -``` |
141 | | - |
142 | | -### Processing from Memory |
143 | | - |
144 | | -All functions have memory buffer variants for processing PDFs already in memory: |
145 | | - |
146 | | -```rust |
147 | | -use pdf_inspector::{process_pdf_mem, detector::detect_pdf_type_mem}; |
148 | | -use pdf_inspector::extractor::{extract_text_mem, extract_text_with_positions_mem}; |
149 | | - |
150 | | -fn main() -> Result<(), Box<dyn std::error::Error>> { |
151 | | - let buffer = std::fs::read("document.pdf")?; |
152 | | - |
153 | | - // Process from memory |
154 | | - let result = process_pdf_mem(&buffer)?; |
155 | | - |
156 | | - // Or detect only |
157 | | - let detection = detect_pdf_type_mem(&buffer)?; |
158 | | - |
159 | | - // Or extract text |
160 | | - let text = extract_text_mem(&buffer)?; |
161 | | - |
162 | | - Ok(()) |
163 | | -} |
164 | | -``` |
165 | | - |
166 | | -### Custom Detection Configuration |
167 | | - |
168 | | -Fine-tune the detection algorithm: |
169 | | - |
170 | | -```rust |
171 | | -use pdf_inspector::detector::{detect_pdf_type_with_config, DetectionConfig}; |
172 | | - |
173 | | -fn main() -> Result<(), Box<dyn std::error::Error>> { |
174 | | - let config = DetectionConfig { |
175 | | - max_pages_to_sample: 10, // Sample more pages |
176 | | - min_text_ops_per_page: 5, // Require more text operators |
177 | | - text_page_ratio_threshold: 0.8, // Stricter text classification |
178 | | - }; |
179 | | - |
180 | | - let result = detect_pdf_type_with_config("document.pdf", config)?; |
181 | | - println!("{:?}", result.pdf_type); |
182 | | - |
183 | | - Ok(()) |
184 | | -} |
185 | | -``` |
| 5 | +## Supported Features |
| 6 | + |
| 7 | +| Category | Feature | Description | |
| 8 | +|----------|---------|-------------| |
| 9 | +| **Detection** | Fast Classification | ~10-50ms by sampling content streams | |
| 10 | +| | PDF Types | TextBased, Scanned, ImageBased, Mixed | |
| 11 | +| | Confidence Scoring | 0.0-1.0 scale for classification certainty | |
| 12 | +| | Configurable Thresholds | Tune sampling depth and detection sensitivity | |
| 13 | +| | Metadata Extraction | Document title from PDF Info dictionary | |
| 14 | +| **Text Extraction** | Plain Text | Direct extraction from text-based PDFs | |
| 15 | +| | Position-Aware | Text with X/Y coordinates, font info, page numbers | |
| 16 | +| | Multi-Column Support | Automatic detection and proper reading order | |
| 17 | +| | Text Encoding | UTF-16BE, UTF-8, and Latin-1 | |
| 18 | +| **Headers** | Auto Detection | H1-H4 based on font size ratios | |
| 19 | +| **Lists** | Bullet Points | `•`, `-`, `*`, `○`, `●`, `◦` | |
| 20 | +| | Numbered Lists | `1.`, `1)`, `(1)` | |
| 21 | +| | Letter Lists | `a.`, `a)`, `(a)` | |
| 22 | +| **Code Blocks** | Monospace Fonts | Courier, Consolas, Monaco, Menlo, Fira Code, JetBrains Mono | |
| 23 | +| | Keyword Detection | Language keywords and syntax patterns | |
| 24 | +| **Tables** | Region Detection | Automatic table boundary identification | |
| 25 | +| | Column/Row Detection | Position clustering for structure | |
| 26 | +| | Markdown Output | Proper alignment and formatting | |
| 27 | +| | Footnotes | Extraction and formatting | |
| 28 | +| **Text Processing** | Subscript/Superscript | Font size and Y-offset detection | |
| 29 | +| | Hyphenation Fixing | Rejoins words broken across lines | |
| 30 | +| | Page Number Filtering | Removes isolated page numbers | |
| 31 | +| | URL Formatting | Converts URLs to markdown links | |
| 32 | +| | Drop Cap Merging | Handles large initial letters | |
| 33 | + |
| 34 | +## Output Formats |
| 35 | + |
| 36 | +| Format | Description | |
| 37 | +|--------|-------------| |
| 38 | +| Markdown | Headers, lists, code blocks, tables, page breaks | |
| 39 | +| Plain Text | Basic text extraction | |
| 40 | +| JSON | Metadata with type, confidence, page count, timing | |
| 41 | +| Positioned Items | Low-level text with coordinates and font info | |
186 | 42 |
|
187 | 43 | ## CLI Tools |
188 | 44 |
|
189 | | -### pdf2md |
190 | | - |
191 | | -Convert a PDF to Markdown: |
192 | | - |
193 | | -```bash |
194 | | -# Output to stdout |
195 | | -pdf2md document.pdf |
196 | | - |
197 | | -# Output to file |
198 | | -pdf2md document.pdf output.md |
| 45 | +| Tool | Description | |
| 46 | +|------|-------------| |
| 47 | +| `pdf2md` | Convert PDF to Markdown (supports `--json` output) | |
| 48 | +| `detect-pdf` | Detect PDF type without conversion (supports `--json` output) | |
199 | 49 |
|
200 | | -# JSON output with metadata |
201 | | -pdf2md document.pdf --json |
202 | | -``` |
| 50 | +## API Overview |
203 | 51 |
|
204 | | -### detect-pdf |
| 52 | +### Functions |
205 | 53 |
|
206 | | -Detect PDF type without conversion: |
| 54 | +| Function | Description | |
| 55 | +|----------|-------------| |
| 56 | +| `process_pdf` / `process_pdf_mem` | Detect, extract, and convert to markdown | |
| 57 | +| `detect_pdf_type` / `detect_pdf_type_mem` | Fast type detection only | |
| 58 | +| `extract_text` / `extract_text_mem` | Plain text extraction | |
| 59 | +| `extract_text_with_positions` | Text with coordinates | |
| 60 | +| `to_markdown` | Convert text to markdown | |
207 | 61 |
|
208 | | -```bash |
209 | | -# Human-readable output |
210 | | -detect-pdf document.pdf |
| 62 | +### Types |
211 | 63 |
|
212 | | -# JSON output |
213 | | -detect-pdf document.pdf --json |
214 | | -``` |
| 64 | +| Type | Description | |
| 65 | +|------|-------------| |
| 66 | +| `PdfType` | `TextBased`, `Scanned`, `ImageBased`, `Mixed` | |
| 67 | +| `PdfProcessResult` | Full result with text, markdown, and metadata | |
| 68 | +| `PdfTypeResult` | Detection result with type, confidence, page count | |
| 69 | +| `TextItem` | Text with position, font info, and page number | |
| 70 | +| `TextLine` | Grouped items on the same line | |
| 71 | +| `MarkdownOptions` | Configuration for markdown conversion | |
| 72 | +| `DetectionConfig` | Configuration for PDF type detection | |
| 73 | +| `PdfError` | `Io`, `Parse`, `Encrypted`, `InvalidStructure` | |
215 | 74 |
|
216 | 75 | ## How Detection Works |
217 | 76 |
|
218 | | -Instead of loading the entire PDF, we: |
219 | | - |
220 | 77 | 1. Load only metadata (xref table, trailer, page count) |
221 | 78 | 2. Sample first ~5 pages' content streams |
222 | 79 | 3. Scan raw bytes for `Tj`/`TJ` (text) and `Do` (image) operators |
223 | 80 | 4. Classify based on text operator presence |
224 | 81 |
|
225 | 82 | This allows detecting 300+ page PDFs in milliseconds. |
226 | 83 |
|
227 | | -## API Reference |
228 | | - |
229 | | -### Types |
230 | | - |
231 | | -| Type | Description | |
232 | | -|------|-------------| |
233 | | -| `PdfType` | Enum: `TextBased`, `Scanned`, `ImageBased`, `Mixed` | |
234 | | -| `PdfProcessResult` | Full processing result with text, markdown, and metadata | |
235 | | -| `PdfTypeResult` | Detection result with type, confidence, and page count | |
236 | | -| `TextItem` | Text with position (x, y), font info, and page number | |
237 | | -| `TextLine` | Group of `TextItem`s on the same line | |
238 | | -| `MarkdownOptions` | Configuration for markdown conversion | |
239 | | -| `DetectionConfig` | Configuration for PDF type detection | |
240 | | -| `PdfError` | Error type: `Io`, `Parse`, `Encrypted`, `InvalidStructure` | |
241 | | - |
242 | | -### Functions |
243 | | - |
244 | | -| Function | Description | |
245 | | -|----------|-------------| |
246 | | -| `process_pdf(path)` | High-level: detect, extract, and convert | |
247 | | -| `process_pdf_mem(buffer)` | Same as above, from memory | |
248 | | -| `detect_pdf_type(path)` | Fast type detection | |
249 | | -| `detect_pdf_type_mem(buffer)` | Type detection from memory | |
250 | | -| `extract_text(path)` | Extract plain text | |
251 | | -| `extract_text_mem(buffer)` | Extract text from memory | |
252 | | -| `extract_text_with_positions(path)` | Extract text with coordinates | |
253 | | -| `to_markdown(text, options)` | Convert text to markdown | |
254 | | - |
255 | 84 | ## License |
256 | 85 |
|
257 | 86 | MIT |
0 commit comments