rasgapdf 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rasgapdf/__init__.py +156 -0
- rasgapdf/librasgapdf.so +0 -0
- rasgapdf-0.1.0.dist-info/METADATA +203 -0
- rasgapdf-0.1.0.dist-info/RECORD +6 -0
- rasgapdf-0.1.0.dist-info/WHEEL +5 -0
- rasgapdf-0.1.0.dist-info/top_level.txt +1 -0
rasgapdf/__init__.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import ctypes
|
|
5
|
+
from typing import Dict, Any, List, Optional
|
|
6
|
+
|
|
7
|
+
def _find_library() -> str:
|
|
8
|
+
lib_name = "librasgapdf.so" if sys.platform != "win32" else "rasgapdf.dll"
|
|
9
|
+
if sys.platform == "darwin":
|
|
10
|
+
lib_name = "librasgapdf.dylib"
|
|
11
|
+
|
|
12
|
+
search_paths = [
|
|
13
|
+
# Relative to installed package
|
|
14
|
+
os.path.join(os.path.dirname(__file__), lib_name),
|
|
15
|
+
os.path.join(os.path.dirname(__file__), "..", "..", "..", "target", "release", lib_name),
|
|
16
|
+
os.path.join(os.path.dirname(__file__), "..", "..", "target", "release", lib_name),
|
|
17
|
+
os.path.join(os.path.expanduser("~"), "rasgapdf", "target", "release", lib_name),
|
|
18
|
+
"/usr/local/lib/" + lib_name,
|
|
19
|
+
"/usr/lib/" + lib_name,
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
for p in search_paths:
|
|
23
|
+
abs_p = os.path.abspath(p)
|
|
24
|
+
if os.path.exists(abs_p):
|
|
25
|
+
return abs_p
|
|
26
|
+
|
|
27
|
+
# Fallback to system loader
|
|
28
|
+
return lib_name
|
|
29
|
+
|
|
30
|
+
_lib_path = _find_library()
|
|
31
|
+
try:
|
|
32
|
+
_lib = ctypes.CDLL(_lib_path)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
_lib = None
|
|
35
|
+
|
|
36
|
+
if _lib:
|
|
37
|
+
_lib.rasgapdf_parse_raw.argtypes = [ctypes.c_char_p]
|
|
38
|
+
_lib.rasgapdf_parse_raw.restype = ctypes.c_void_p
|
|
39
|
+
|
|
40
|
+
_lib.rasgapdf_parse_file.argtypes = [ctypes.c_char_p]
|
|
41
|
+
_lib.rasgapdf_parse_file.restype = ctypes.c_void_p
|
|
42
|
+
|
|
43
|
+
_lib.rasgapdf_parse_file_options.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
|
44
|
+
_lib.rasgapdf_parse_file_options.restype = ctypes.c_void_p
|
|
45
|
+
|
|
46
|
+
_lib.rasgapdf_free_string.argtypes = [ctypes.c_void_p]
|
|
47
|
+
_lib.rasgapdf_free_string.restype = None
|
|
48
|
+
|
|
49
|
+
_lib.rasgapdf_version.argtypes = []
|
|
50
|
+
_lib.rasgapdf_version.restype = ctypes.c_char_p
|
|
51
|
+
|
|
52
|
+
def version() -> str:
|
|
53
|
+
if not _lib:
|
|
54
|
+
return "0.1.0 (unlinked)"
|
|
55
|
+
return _lib.rasgapdf_version().decode("utf-8")
|
|
56
|
+
|
|
57
|
+
def parse_raw(file_path: str) -> Dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
⚡ Modo 1 (Bruto): Retorna exclusivamente a lista plana de coordenadas X/Y
|
|
60
|
+
sem overhead de blocos hierárquicos ou detecção de tabelas.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
file_path: Caminho do arquivo PDF.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
dict: {
|
|
67
|
+
"file_name": str,
|
|
68
|
+
"total_pages": int,
|
|
69
|
+
"total_spans": int,
|
|
70
|
+
"parse_time_ms": float,
|
|
71
|
+
"spans": [
|
|
72
|
+
{
|
|
73
|
+
"page": int,
|
|
74
|
+
"x0": float,
|
|
75
|
+
"y0": float,
|
|
76
|
+
"x1": float,
|
|
77
|
+
"y1": float,
|
|
78
|
+
"font_size": float,
|
|
79
|
+
"font_name": str,
|
|
80
|
+
"text": str
|
|
81
|
+
}, ...
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
"""
|
|
85
|
+
if not _lib:
|
|
86
|
+
raise RuntimeError(f"RasgaPDF native library ({_lib_path}) not loaded.")
|
|
87
|
+
|
|
88
|
+
if not os.path.exists(file_path):
|
|
89
|
+
raise FileNotFoundError(f"PDF file not found: {file_path}")
|
|
90
|
+
|
|
91
|
+
path_bytes = os.path.abspath(file_path).encode("utf-8")
|
|
92
|
+
res_ptr = _lib.rasgapdf_parse_raw(path_bytes)
|
|
93
|
+
if not res_ptr:
|
|
94
|
+
raise RuntimeError("Null pointer returned from native rasgapdf library.")
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
raw_json = ctypes.string_at(res_ptr).decode("utf-8")
|
|
98
|
+
parsed = json.loads(raw_json)
|
|
99
|
+
if isinstance(parsed, dict) and parsed.get("error"):
|
|
100
|
+
raise RuntimeError(f"RasgaPDF Error: {parsed.get('message')}")
|
|
101
|
+
return parsed
|
|
102
|
+
finally:
|
|
103
|
+
_lib.rasgapdf_free_string(res_ptr)
|
|
104
|
+
|
|
105
|
+
def parse_pdf(
|
|
106
|
+
file_path: str,
|
|
107
|
+
detect_tables: bool = True,
|
|
108
|
+
min_table_cols: int = 3,
|
|
109
|
+
col_tolerance: float = 16.0,
|
|
110
|
+
row_tolerance: float = 6.0,
|
|
111
|
+
) -> Dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
🎯 Modo 2 (Estruturado): Processa o PDF e retorna o layout espacial hierárquico
|
|
114
|
+
com blocos de texto, linhas, spans e tabelas detectadas.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
file_path: Caminho do arquivo PDF.
|
|
118
|
+
detect_tables: Se deve detectar matrizes de tabelas automaticamente.
|
|
119
|
+
min_table_cols: Quantidade mínima de colunas para agrupar em tabela.
|
|
120
|
+
col_tolerance: Tolerância horizontal de alinhamento em pontos.
|
|
121
|
+
row_tolerance: Tolerância vertical de linha em pontos.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
dict: SpatialDocument contendo páginas, blocos, linhas e tabelas.
|
|
125
|
+
"""
|
|
126
|
+
if not _lib:
|
|
127
|
+
raise RuntimeError(f"RasgaPDF native library ({_lib_path}) not loaded.")
|
|
128
|
+
|
|
129
|
+
if not os.path.exists(file_path):
|
|
130
|
+
raise FileNotFoundError(f"PDF file not found: {file_path}")
|
|
131
|
+
|
|
132
|
+
opts = {
|
|
133
|
+
"mode": "structured",
|
|
134
|
+
"detect_tables": detect_tables,
|
|
135
|
+
"min_table_columns": min_table_cols,
|
|
136
|
+
"column_tolerance": col_tolerance,
|
|
137
|
+
"row_tolerance": row_tolerance,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
path_bytes = os.path.abspath(file_path).encode("utf-8")
|
|
141
|
+
opts_bytes = json.dumps(opts).encode("utf-8")
|
|
142
|
+
|
|
143
|
+
res_ptr = _lib.rasgapdf_parse_file_options(path_bytes, opts_bytes)
|
|
144
|
+
if not res_ptr:
|
|
145
|
+
raise RuntimeError("Null pointer returned from native rasgapdf library.")
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
raw_json = ctypes.string_at(res_ptr).decode("utf-8")
|
|
149
|
+
parsed = json.loads(raw_json)
|
|
150
|
+
if isinstance(parsed, dict) and parsed.get("error"):
|
|
151
|
+
raise RuntimeError(f"RasgaPDF Error: {parsed.get('message')}")
|
|
152
|
+
return parsed
|
|
153
|
+
finally:
|
|
154
|
+
_lib.rasgapdf_free_string(res_ptr)
|
|
155
|
+
|
|
156
|
+
__all__ = ["parse_raw", "parse_pdf", "version"]
|
rasgapdf/librasgapdf.so
ADDED
|
Binary file
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rasgapdf
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ultra-fast 2D spatial PDF parser powered by Rust
|
|
5
|
+
Author: Messias Martins
|
|
6
|
+
Project-URL: Homepage, https://github.com/messiasmartins/rasgapdf
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/messiasmartins/rasgapdf/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Rust
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Topic :: Text Processing :: General
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# ⚡ RasgaPDF
|
|
17
|
+
|
|
18
|
+
<div align="center">
|
|
19
|
+
|
|
20
|
+
**The Blazing-Fast 2D Spatial PDF Parser powered by Pure Rust.**
|
|
21
|
+
*Extract exact physical coordinates (Raw X/Y) or structured blocks & tables at native hardware speeds.*
|
|
22
|
+
|
|
23
|
+
[](LICENSE)
|
|
24
|
+
[](Cargo.toml)
|
|
25
|
+
[](bindings/python)
|
|
26
|
+
[](bindings/nodejs)
|
|
27
|
+
[](bindings/go)
|
|
28
|
+
[](bindings/csharp)
|
|
29
|
+
[](https://huggingface.co/martinsmessias/rasgapdf)
|
|
30
|
+
|
|
31
|
+
[**Live Gradio Demo**](#-interactive-gradio-demo) • [**Quickstart**](#-quickstart) • [**Two Modes**](#-two-parsing-modes) • [**Architecture**](docs/ARCHITECTURE.md) • [**Spatial Schema**](docs/SCHEMA.md) • [**Contributing**](CONTRIBUTING.md)
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🎯 The Philosophy: Pure Spatial Parsing
|
|
38
|
+
|
|
39
|
+
Most PDF libraries make fragile guesses: they convert multi-column documents into flat, corrupted text streams, mangle tables, and strip away spatial geometry.
|
|
40
|
+
|
|
41
|
+
**RasgaPDF does one thing with uncompromising precision:**
|
|
42
|
+
It returns the **exact 2D physical spatial geometry** of the PDF.
|
|
43
|
+
|
|
44
|
+
- **Bounding Boxes Everywhere:** Every character, word span, line, block, and table has exact coordinates `[x0, y0, x1, y1]`.
|
|
45
|
+
- **Zero Lossy Guesswork:** No broken heuristics pretending to be Markdown. Your downstream LLMs, RAG pipelines, or OCR validators receive the true physical layout.
|
|
46
|
+
- **Microsecond Rayon Parallelism:** Multi-threaded page processing in pure Rust utilizing AVX2/AVX-512 SIMD.
|
|
47
|
+
- **Polyglot First:** Native C-ABI FFI bindings for **Python**, **Node.js**, **Go**, and **C# (.NET)**.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## ⚡ Two Parsing Modes
|
|
52
|
+
|
|
53
|
+
RasgaPDF offers two distinct modes for different engineering needs:
|
|
54
|
+
|
|
55
|
+
### ⚡ Modo 1: Coordenadas Brutas (Raw X/Y Puro)
|
|
56
|
+
O retorno mais direto, bruto e rápido possível. Retorna exclusivamente a lista plana de coordenadas físicas de cada elemento desenhado na página, sem criar hierarquias de blocos ou overhead de tabelas:
|
|
57
|
+
- **Latência:** ~3 a 8 ms por página
|
|
58
|
+
- **Saídas suportadas:** JSON plano, Text formatado, TSV ou CSV
|
|
59
|
+
- **Uso ideal:** RAG espacial, visualizadores 2D, indexação vetorial, pipelines Unix com `awk`/`grep`.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
[Pág. 1] (X0: 50.00, Y0: 67.00, X1: 244.00, Y1: 83.00) [12.0pt] A Revolução da Inteligência Artificial
|
|
63
|
+
[Pág. 1] (X0: 50.00, Y0: 103.00, X1: 478.00, Y1: 119.00) [12.0pt] A Inteligência Artificial (IA) tem transformado...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 🎯 Modo 2: Estruturado Hierárquico
|
|
67
|
+
Processa a geometria e agrupa elementos em parágrafos visuais (`blocks`), linhas (`lines`), spans de fonte e detecta matrizes de tabelas automaticamente via projeção horizontal de colunas:
|
|
68
|
+
- **Latência:** ~8 a 15 ms por página
|
|
69
|
+
- **Saída:** Árvore hierárquica `SpatialDocument` -> `pages` -> `text_blocks` -> `lines` -> `tables`.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## ⚡ Performance Benchmark
|
|
74
|
+
|
|
75
|
+
Measured on Intel Core i7-1365U (10 Cores / 12 Threads, AVX2):
|
|
76
|
+
|
|
77
|
+
| Library / Tool | Language | Single Page | 50 Pages | Memory Footprint | Output Type |
|
|
78
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
79
|
+
| **⚡ RasgaPDF (Raw X/Y)** | **Rust (Native)** | **7.7 ms** | **95 ms** | **~14 MB** | **Flat Raw Coordinates** |
|
|
80
|
+
| **🚀 RasgaPDF (Structured)**| **Rust (Native)** | **8.5 ms** | **142 ms** | **~18 MB** | **Spatial Blocks & Tables** |
|
|
81
|
+
| PyMuPDF (`fitz`) | C / Python | 28.0 ms | 620 ms | ~45 MB | Text / Pixmap |
|
|
82
|
+
| `pdfplumber` | Python | 410.0 ms | 9,800 ms | ~140 MB | Python dicts |
|
|
83
|
+
| `pypdf` | Python | 95.0 ms | 2,100 ms | ~85 MB | Flat text string |
|
|
84
|
+
| Apache Tika | Java | 650.0 ms | 12,400 ms | ~320 MB | XHTML |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 💻 Standalone CLI Tool
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cargo build --release
|
|
92
|
+
./target/release/rasgapdf --help
|
|
93
|
+
|
|
94
|
+
# ⚡ MODO 1: Coordenadas Brutas (Raw X/Y)
|
|
95
|
+
./target/release/rasgapdf sample.pdf --raw
|
|
96
|
+
./target/release/rasgapdf sample.pdf --raw --format text
|
|
97
|
+
./target/release/rasgapdf sample.pdf --raw --format csv
|
|
98
|
+
./target/release/rasgapdf sample.pdf --raw --format tsv
|
|
99
|
+
|
|
100
|
+
# 🎯 MODO 2: Estruturado (Hierárquico com Blocos e Tabelas)
|
|
101
|
+
./target/release/rasgapdf sample.pdf --pretty
|
|
102
|
+
./target/release/rasgapdf sample.pdf -o output.json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 🚀 Polyglot Quickstart
|
|
108
|
+
|
|
109
|
+
### 🐍 Python
|
|
110
|
+
```python
|
|
111
|
+
import rasgapdf
|
|
112
|
+
|
|
113
|
+
# ⚡ Modo 1: Coordenadas Brutas (Raw X/Y)
|
|
114
|
+
raw = rasgapdf.parse_raw("document.pdf")
|
|
115
|
+
print(f"Extraídos {raw['total_spans']} spans em {raw['parse_time_ms']:.2f} ms")
|
|
116
|
+
for s in raw["spans"]:
|
|
117
|
+
print(f"Pág {s['page']} | ({s['x0']}, {s['y0']}) -> {s['text']}")
|
|
118
|
+
|
|
119
|
+
# 🎯 Modo 2: Estruturado (Hierárquico)
|
|
120
|
+
doc = rasgapdf.parse_pdf("document.pdf", detect_tables=True)
|
|
121
|
+
for page in doc["pages"]:
|
|
122
|
+
for table in page["tables"]:
|
|
123
|
+
print("Tabela detectada:", table["rows"])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 🟢 Node.js
|
|
127
|
+
```javascript
|
|
128
|
+
const rasgapdf = require('rasgapdf');
|
|
129
|
+
|
|
130
|
+
// ⚡ Modo 1: Coordenadas Brutas
|
|
131
|
+
const raw = rasgapdf.parseRaw('document.pdf');
|
|
132
|
+
console.log(`Raw: ${raw.total_spans} spans em ${raw.parse_time_ms} ms`);
|
|
133
|
+
|
|
134
|
+
// 🎯 Modo 2: Estruturado
|
|
135
|
+
const doc = rasgapdf.parsePdf('document.pdf');
|
|
136
|
+
console.log(`Blocos de texto: ${doc.pages[0].text_blocks.length}`);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 🔵 Go
|
|
140
|
+
```go
|
|
141
|
+
package main
|
|
142
|
+
|
|
143
|
+
import (
|
|
144
|
+
"fmt"
|
|
145
|
+
"github.com/messiasmartins/rasgapdf/bindings/go"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
func main() {
|
|
149
|
+
// ⚡ Modo 1: Coordenadas Brutas
|
|
150
|
+
raw, _ := rasgapdf.ParseRaw("document.pdf")
|
|
151
|
+
fmt.Printf("Raw: %d spans em %.2f ms\n", raw.TotalSpans, raw.ParseTimeMs)
|
|
152
|
+
|
|
153
|
+
// 🎯 Modo 2: Estruturado
|
|
154
|
+
opts := rasgapdf.DefaultOptions()
|
|
155
|
+
doc, _ := rasgapdf.ParseFile("document.pdf", &opts)
|
|
156
|
+
fmt.Printf("Estruturado: %d páginas\n", doc.TotalPages)
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 🟣 C# / .NET 8
|
|
161
|
+
```csharp
|
|
162
|
+
using RasgaPdf;
|
|
163
|
+
|
|
164
|
+
// ⚡ Modo 1: Coordenadas Brutas
|
|
165
|
+
var raw = SpatialPdf.ParseRaw("document.pdf");
|
|
166
|
+
Console.WriteLine($"Raw: {raw.TotalSpans} spans em {raw.ParseTimeMs} ms");
|
|
167
|
+
|
|
168
|
+
// 🎯 Modo 2: Estruturado
|
|
169
|
+
var doc = SpatialPdf.Parse("document.pdf");
|
|
170
|
+
Console.WriteLine($"Estruturado: {doc.TotalPages} páginas");
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 🎨 Interactive Gradio Demo
|
|
176
|
+
|
|
177
|
+
We provide an interactive **visual spatial explorer** built with Gradio:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
cd demo_gradio
|
|
181
|
+
pip install -r requirements.txt
|
|
182
|
+
python app.py
|
|
183
|
+
```
|
|
184
|
+
Acesse `http://localhost:7860` para:
|
|
185
|
+
- Inspecionar a **tabela interativa de Coordenadas Físicas (Raw X/Y)**.
|
|
186
|
+
- Visualizar os **bounding boxes translúcidos** sobrepostos à página do PDF.
|
|
187
|
+
- Analisar **tabelas detectadas** renderizadas em DataFrames.
|
|
188
|
+
- Exportar JSONs brutos ou estruturados.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 🤝 Contributing & Community
|
|
193
|
+
|
|
194
|
+
Contributions are what make the open-source community thrive!
|
|
195
|
+
- Read our [Contributing Guide](CONTRIBUTING.md) to get started.
|
|
196
|
+
- Adhere to our [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 📄 License
|
|
201
|
+
|
|
202
|
+
RasgaPDF is licensed under the [MIT License](LICENSE).
|
|
203
|
+
Copyright (c) 2026 Messias Martins.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
rasgapdf/__init__.py,sha256=1PCpr7JigOnNxYBR0d6L98_LM_whJQ358F56uy4ItJM,5132
|
|
2
|
+
rasgapdf/librasgapdf.so,sha256=pKotSfi5thIU6kl3glawsJXvCuknsWueJtz-7B-8wbM,8745688
|
|
3
|
+
rasgapdf-0.1.0.dist-info/METADATA,sha256=ZBYSPut-3clnJJ7BY8clLUovFlaEhBy28inak9x7K7k,7587
|
|
4
|
+
rasgapdf-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
5
|
+
rasgapdf-0.1.0.dist-info/top_level.txt,sha256=3DOdLinXRup1qeRyF1_qxtGgDWzf_LUMAw51ycM8qts,9
|
|
6
|
+
rasgapdf-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rasgapdf
|