rasgapdf 0.2.3__tar.gz → 0.2.4__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rasgapdf
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Ultra-fast 2D spatial PDF parser powered by pure Rust — extract exact [x0, y0, x1, y1] bounding boxes, text blocks, tables, images & vector paths in milliseconds for RAG & LLMs
5
5
  Author: Messias Martins
6
6
  Project-URL: Homepage, https://martinsmessias.github.io/rasgapdf/
@@ -191,6 +191,33 @@ print(manifest["written"], "files written")
191
191
 
192
192
  ---
193
193
 
194
+ ---
195
+
196
+ ## 🦜️🔗 AI Framework Integrations (LangChain & LlamaIndex)
197
+
198
+ ### 🦜️ LangChain Document Loader
199
+ `python
200
+ from rasgapdf import RasgaPDFLoader
201
+
202
+ # 1 Document per page with markdown tables:
203
+ loader = RasgaPDFLoader('document.pdf', mode='pages')
204
+ docs = loader.load()
205
+
206
+ # Spatial RAG: 1 Document per block/table with exact [x0, y0, x1, y1] coordinates:
207
+ spatial_loader = RasgaPDFLoader('document.pdf', mode='elements')
208
+ chunks = spatial_loader.load()
209
+ for chunk in chunks:
210
+ print(chunk.metadata['bbox'], chunk.page_content[:40])
211
+ `
212
+
213
+ ### 🦙 LlamaIndex Reader
214
+ `python
215
+ from rasgapdf import RasgaPDFReader
216
+
217
+ reader = RasgaPDFReader(mode='pages')
218
+ documents = reader.load_data('financial_report.pdf')
219
+ `
220
+
194
221
  ## 📄 License
195
222
 
196
223
  GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). Copyright (c) 2026 Messias Martins.
@@ -198,3 +225,19 @@ GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). Copyright (
198
225
  RasgaPDF embeds and links against [MuPDF](https://mupdf.com/) (Artifex Software),
199
226
  which is distributed under the AGPL-3.0. That is why this project is licensed
200
227
  under the same terms.
228
+
229
+ ---
230
+
231
+ ## 💼 Commercial & Enterprise Licensing (AGPL-3.0 Waiver)
232
+
233
+ Need to use **RasgaPDF** in closed-source commercial software, proprietary SaaS platforms, private cloud environments, or corporate applications without releasing your source code under the AGPL-3.0?
234
+
235
+ A **Commercial / Enterprise License** is available directly from the author:
236
+
237
+ - **Complete AGPL-3.0 Exemption:** Keep your intellectual property, backend code, and SaaS infrastructure 100% closed-source and proprietary.
238
+ - **Enterprise SLA & Support:** Direct architecture advisory, guaranteed response times, and priority bug resolution.
239
+ - **On-Premise & Air-Gapped Appliances:** Ready-to-deploy private Docker containers for confidential banking, healthcare, and legal compliance (LGPD / GDPR).
240
+ - **Custom Spatial Features:** Tailored table extractors for bank statements, invoices, and court proceedings.
241
+
242
+ 📬 **Inquiries & Licensing:**
243
+ [👉 **Click here to Request a Commercial License & Pricing**](mailto:messiasads2017@gmail.com?subject=RasgaPDF%20Commercial%20License%20Inquiry) with details about your organization, use case, and estimated volume.
@@ -163,6 +163,33 @@ print(manifest["written"], "files written")
163
163
 
164
164
  ---
165
165
 
166
+ ---
167
+
168
+ ## 🦜️🔗 AI Framework Integrations (LangChain & LlamaIndex)
169
+
170
+ ### 🦜️ LangChain Document Loader
171
+ `python
172
+ from rasgapdf import RasgaPDFLoader
173
+
174
+ # 1 Document per page with markdown tables:
175
+ loader = RasgaPDFLoader('document.pdf', mode='pages')
176
+ docs = loader.load()
177
+
178
+ # Spatial RAG: 1 Document per block/table with exact [x0, y0, x1, y1] coordinates:
179
+ spatial_loader = RasgaPDFLoader('document.pdf', mode='elements')
180
+ chunks = spatial_loader.load()
181
+ for chunk in chunks:
182
+ print(chunk.metadata['bbox'], chunk.page_content[:40])
183
+ `
184
+
185
+ ### 🦙 LlamaIndex Reader
186
+ `python
187
+ from rasgapdf import RasgaPDFReader
188
+
189
+ reader = RasgaPDFReader(mode='pages')
190
+ documents = reader.load_data('financial_report.pdf')
191
+ `
192
+
166
193
  ## 📄 License
167
194
 
168
195
  GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). Copyright (c) 2026 Messias Martins.
@@ -170,3 +197,19 @@ GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). Copyright (
170
197
  RasgaPDF embeds and links against [MuPDF](https://mupdf.com/) (Artifex Software),
171
198
  which is distributed under the AGPL-3.0. That is why this project is licensed
172
199
  under the same terms.
200
+
201
+ ---
202
+
203
+ ## 💼 Commercial & Enterprise Licensing (AGPL-3.0 Waiver)
204
+
205
+ Need to use **RasgaPDF** in closed-source commercial software, proprietary SaaS platforms, private cloud environments, or corporate applications without releasing your source code under the AGPL-3.0?
206
+
207
+ A **Commercial / Enterprise License** is available directly from the author:
208
+
209
+ - **Complete AGPL-3.0 Exemption:** Keep your intellectual property, backend code, and SaaS infrastructure 100% closed-source and proprietary.
210
+ - **Enterprise SLA & Support:** Direct architecture advisory, guaranteed response times, and priority bug resolution.
211
+ - **On-Premise & Air-Gapped Appliances:** Ready-to-deploy private Docker containers for confidential banking, healthcare, and legal compliance (LGPD / GDPR).
212
+ - **Custom Spatial Features:** Tailored table extractors for bank statements, invoices, and court proceedings.
213
+
214
+ 📬 **Inquiries & Licensing:**
215
+ [👉 **Click here to Request a Commercial License & Pricing**](mailto:messiasads2017@gmail.com?subject=RasgaPDF%20Commercial%20License%20Inquiry) with details about your organization, use case, and estimated volume.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rasgapdf"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  authors = [
9
9
  { name="Messias Martins" },
10
10
  ]
@@ -2,7 +2,8 @@ import os
2
2
  import sys
3
3
  import json
4
4
  import ctypes
5
- from typing import Dict, Any, List, Optional
5
+ from pathlib import Path
6
+ from typing import Dict, Any, List, Optional, Union
6
7
 
7
8
  def _find_library() -> str:
8
9
  lib_name = "librasgapdf.so" if sys.platform != "win32" else "rasgapdf.dll"
@@ -251,7 +252,7 @@ def parse_raw_bytes(data: bytes) -> Dict[str, Any]:
251
252
  _lib.rasgapdf_free_string(res_ptr)
252
253
 
253
254
 
254
- def extract_text(data_or_path: bytes | str) -> str:
255
+ def extract_text(data_or_path: Union[bytes, str]) -> str:
255
256
  """
256
257
  🚀 Extrai o texto completo do PDF em ordem natural de blocos de leitura
257
258
  diretamente de `bytes` ou caminho de arquivo.
@@ -339,4 +340,24 @@ def extract_images_bytes(data: bytes, output_dir: str) -> Dict[str, Any]:
339
340
  finally:
340
341
  _lib.rasgapdf_free_string(res_ptr)
341
342
 
342
- __all__ = ["parse_raw", "parse_raw_bytes", "parse_pdf", "extract_text", "extract_images", "extract_images_bytes", "version"]
343
+ def __getattr__(name: str):
344
+ if name == "RasgaPDFLoader":
345
+ from .integrations.langchain import RasgaPDFLoader
346
+ return RasgaPDFLoader
347
+ if name == "RasgaPDFReader":
348
+ from .integrations.llama_index import RasgaPDFReader
349
+ return RasgaPDFReader
350
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
351
+
352
+
353
+ __all__ = [
354
+ "parse_raw",
355
+ "parse_raw_bytes",
356
+ "parse_pdf",
357
+ "extract_text",
358
+ "extract_images",
359
+ "extract_images_bytes",
360
+ "version",
361
+ "RasgaPDFLoader",
362
+ "RasgaPDFReader",
363
+ ]
@@ -0,0 +1,6 @@
1
+ """Official AI & RAG Integrations for RasgaPDF (LangChain & LlamaIndex)."""
2
+
3
+ from .langchain import RasgaPDFLoader
4
+ from .llama_index import RasgaPDFReader
5
+
6
+ __all__ = ["RasgaPDFLoader", "RasgaPDFReader"]
@@ -0,0 +1,185 @@
1
+ """Official LangChain Document Loader for RasgaPDF.
2
+
3
+ Ultra-fast 2D spatial PDF loading with exact bounding boxes, structured tables,
4
+ and zero-loss layout preservation for RAG pipelines.
5
+ """
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any, Dict, Iterator, List, Optional, Union
9
+
10
+ try:
11
+ from langchain_core.document_loaders import BaseLoader
12
+ from langchain_core.documents import Document
13
+ except ImportError:
14
+ # Standalone fallback when langchain_core is not installed
15
+ class BaseLoader:
16
+ def load(self) -> List["Document"]:
17
+ return list(self.lazy_load())
18
+
19
+ @dataclass
20
+ class Document:
21
+ page_content: str
22
+ metadata: Dict[str, Any] = field(default_factory=dict)
23
+
24
+
25
+ class RasgaPDFLoader(BaseLoader):
26
+ """LangChain Document Loader powered by RasgaPDF.
27
+
28
+ Args:
29
+ file_path_or_bytes: Path to PDF file or raw bytes of a PDF.
30
+ mode: Extraction granularity:
31
+ - "pages" (default): One Document per page with layout-preserved text & markdown tables.
32
+ - "elements": One Document per spatial element (blocks and tables) with exact 2D bounding boxes.
33
+ - "text": A single Document containing the entire normalized text stream.
34
+ detect_tables: Whether to detect and reconstruct physical 2D tables. Default True.
35
+ markdown_tables: Format tables as Markdown matrices inside page content. Default True.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ file_path_or_bytes: Union[str, Path, bytes],
41
+ mode: str = "pages",
42
+ detect_tables: bool = True,
43
+ markdown_tables: bool = True,
44
+ ):
45
+ self.file_input = file_path_or_bytes
46
+ self.mode = mode
47
+ self.detect_tables = detect_tables
48
+ self.markdown_tables = markdown_tables
49
+ self.source = str(file_path_or_bytes) if not isinstance(file_path_or_bytes, (bytes, bytearray)) else "bytes_stream"
50
+
51
+ def lazy_load(self) -> Iterator[Document]:
52
+ import rasgapdf
53
+
54
+ if self.mode == "text":
55
+ text = rasgapdf.extract_text(self.file_input)
56
+ yield Document(
57
+ page_content=text,
58
+ metadata={"source": self.source, "mode": "text"},
59
+ )
60
+ return
61
+
62
+ doc = rasgapdf.parse_pdf(self.file_input, detect_tables=self.detect_tables)
63
+ pages = doc.get("pages", [])
64
+
65
+ if self.mode == "elements":
66
+ for page in pages:
67
+ page_num = page.get("page_number", 1)
68
+ width = page.get("width", 0.0)
69
+ height = page.get("height", 0.0)
70
+
71
+ # 1. Text blocks
72
+ for block in page.get("text_blocks", []):
73
+ lines_text = [line.get("text", "") for line in block.get("lines", []) if line.get("text")]
74
+ content = "\n".join(lines_text).strip()
75
+ if content:
76
+ yield Document(
77
+ page_content=content,
78
+ metadata={
79
+ "source": self.source,
80
+ "page": page_num,
81
+ "type": "text_block",
82
+ "bbox": block.get("bbox", {}),
83
+ "reading_order": block.get("reading_order", 0),
84
+ "page_width": width,
85
+ "page_height": height,
86
+ },
87
+ )
88
+
89
+ # 2. Tables
90
+ for t_idx, table in enumerate(page.get("tables", [])):
91
+ table_md = self._format_table_markdown(table) if self.markdown_tables else "\n".join(
92
+ ["\t".join([str(c) for c in row]) if isinstance(row, list) else str(row) for row in table.get("rows", [])]
93
+ )
94
+ if table_md.strip():
95
+ yield Document(
96
+ page_content=table_md,
97
+ metadata={
98
+ "source": self.source,
99
+ "page": page_num,
100
+ "type": "table",
101
+ "table_id": table.get("table_id", t_idx),
102
+ "bbox": table.get("bbox", {}),
103
+ "row_count": table.get("row_count", len(table.get("rows", []))),
104
+ "col_count": table.get("col_count", 0),
105
+ "page_width": width,
106
+ "page_height": height,
107
+ },
108
+ )
109
+ return
110
+
111
+ # Default mode: "pages"
112
+ for page in pages:
113
+ page_num = page.get("page_number", 1)
114
+ parts = []
115
+
116
+ # Add text blocks
117
+ for block in page.get("text_blocks", []):
118
+ lines_text = [line.get("text", "") for line in block.get("lines", []) if line.get("text")]
119
+ text = "\n".join(lines_text).strip()
120
+ if text:
121
+ parts.append(text)
122
+
123
+ # Add formatted tables
124
+ for table in page.get("tables", []):
125
+ if self.markdown_tables:
126
+ table_str = self._format_table_markdown(table)
127
+ else:
128
+ table_str = "\n".join(
129
+ ["\t".join([str(c) for c in row]) if isinstance(row, list) else str(row) for row in table.get("rows", [])]
130
+ )
131
+ if table_str:
132
+ parts.append(table_str)
133
+
134
+ page_content = "\n\n".join(parts).strip()
135
+ yield Document(
136
+ page_content=page_content,
137
+ metadata={
138
+ "source": self.source,
139
+ "page": page_num,
140
+ "total_pages": len(pages),
141
+ "width": page.get("width", 0.0),
142
+ "height": page.get("height", 0.0),
143
+ "block_count": len(page.get("text_blocks", [])),
144
+ "table_count": len(page.get("tables", [])),
145
+ },
146
+ )
147
+
148
+ def load(self) -> List[Document]:
149
+ return list(self.lazy_load())
150
+
151
+ @staticmethod
152
+ def _format_table_markdown(table: Dict[str, Any]) -> str:
153
+ rows = table.get("rows", [])
154
+ headers = table.get("headers", [])
155
+ if not rows and not headers:
156
+ return ""
157
+
158
+ matrix = []
159
+ if headers and isinstance(headers, list):
160
+ matrix.append([str(h).replace("\n", " ").strip() for h in headers])
161
+
162
+ for row in rows:
163
+ if isinstance(row, list):
164
+ row_cells = [str(c).replace("\n", " ").strip() for c in row]
165
+ elif isinstance(row, dict):
166
+ row_cells = [str(c.get("text", "")).replace("\n", " ").strip() for c in row.get("cells", [])]
167
+ else:
168
+ row_cells = [str(row)]
169
+ matrix.append(row_cells)
170
+
171
+ if not matrix:
172
+ return ""
173
+
174
+ max_cols = max(len(r) for r in matrix)
175
+ for r in matrix:
176
+ while len(r) < max_cols:
177
+ r.append("")
178
+
179
+ md_lines = []
180
+ header = matrix[0]
181
+ md_lines.append("| " + " | ".join(header) + " |")
182
+ md_lines.append("| " + " | ".join(["---"] * max_cols) + " |")
183
+ for r in matrix[1:]:
184
+ md_lines.append("| " + " | ".join(r) + " |")
185
+ return "\n".join(md_lines)
@@ -0,0 +1,73 @@
1
+ """Official LlamaIndex Reader for RasgaPDF.
2
+
3
+ High-throughput 2D spatial PDF reader for LlamaIndex indexers and agents.
4
+ """
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, Union
8
+
9
+ try:
10
+ from llama_index.core.readers.base import BaseReader
11
+ from llama_index.core.schema import Document
12
+ except ImportError:
13
+ class BaseReader:
14
+ pass
15
+
16
+ @dataclass
17
+ class Document:
18
+ text: str
19
+ extra_info: Dict[str, Any] = field(default_factory=dict)
20
+
21
+ @property
22
+ def metadata(self) -> Dict[str, Any]:
23
+ return self.extra_info
24
+
25
+
26
+ class RasgaPDFReader(BaseReader):
27
+ """LlamaIndex Reader powered by RasgaPDF.
28
+
29
+ Args:
30
+ mode: Granularity mode:
31
+ - "pages" (default): Returns one Document per PDF page.
32
+ - "elements": Returns one Document per text block or table with exact bounding boxes.
33
+ detect_tables: Detect and extract physical tables. Default True.
34
+ markdown_tables: Format tables as Markdown. Default True.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ mode: str = "pages",
40
+ detect_tables: bool = True,
41
+ markdown_tables: bool = True,
42
+ ):
43
+ self.mode = mode
44
+ self.detect_tables = detect_tables
45
+ self.markdown_tables = markdown_tables
46
+
47
+ def load_data(
48
+ self,
49
+ file: Union[str, Path, bytes],
50
+ extra_info: Optional[Dict[str, Any]] = None,
51
+ ) -> List[Document]:
52
+ from .langchain import RasgaPDFLoader
53
+
54
+ loader = RasgaPDFLoader(
55
+ file_path_or_bytes=file,
56
+ mode=self.mode,
57
+ detect_tables=self.detect_tables,
58
+ markdown_tables=self.markdown_tables,
59
+ )
60
+
61
+ base_meta = extra_info or {}
62
+ docs = []
63
+
64
+ for doc in loader.lazy_load():
65
+ meta = {**base_meta, **doc.metadata}
66
+ try:
67
+ # Use official llama_index Document if available
68
+ from llama_index.core.schema import Document as LlamaDoc
69
+ docs.append(LlamaDoc(text=doc.page_content, extra_info=meta))
70
+ except ImportError:
71
+ docs.append(Document(text=doc.page_content, extra_info=meta))
72
+
73
+ return docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rasgapdf
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Ultra-fast 2D spatial PDF parser powered by pure Rust — extract exact [x0, y0, x1, y1] bounding boxes, text blocks, tables, images & vector paths in milliseconds for RAG & LLMs
5
5
  Author: Messias Martins
6
6
  Project-URL: Homepage, https://martinsmessias.github.io/rasgapdf/
@@ -191,6 +191,33 @@ print(manifest["written"], "files written")
191
191
 
192
192
  ---
193
193
 
194
+ ---
195
+
196
+ ## 🦜️🔗 AI Framework Integrations (LangChain & LlamaIndex)
197
+
198
+ ### 🦜️ LangChain Document Loader
199
+ `python
200
+ from rasgapdf import RasgaPDFLoader
201
+
202
+ # 1 Document per page with markdown tables:
203
+ loader = RasgaPDFLoader('document.pdf', mode='pages')
204
+ docs = loader.load()
205
+
206
+ # Spatial RAG: 1 Document per block/table with exact [x0, y0, x1, y1] coordinates:
207
+ spatial_loader = RasgaPDFLoader('document.pdf', mode='elements')
208
+ chunks = spatial_loader.load()
209
+ for chunk in chunks:
210
+ print(chunk.metadata['bbox'], chunk.page_content[:40])
211
+ `
212
+
213
+ ### 🦙 LlamaIndex Reader
214
+ `python
215
+ from rasgapdf import RasgaPDFReader
216
+
217
+ reader = RasgaPDFReader(mode='pages')
218
+ documents = reader.load_data('financial_report.pdf')
219
+ `
220
+
194
221
  ## 📄 License
195
222
 
196
223
  GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). Copyright (c) 2026 Messias Martins.
@@ -198,3 +225,19 @@ GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). Copyright (
198
225
  RasgaPDF embeds and links against [MuPDF](https://mupdf.com/) (Artifex Software),
199
226
  which is distributed under the AGPL-3.0. That is why this project is licensed
200
227
  under the same terms.
228
+
229
+ ---
230
+
231
+ ## 💼 Commercial & Enterprise Licensing (AGPL-3.0 Waiver)
232
+
233
+ Need to use **RasgaPDF** in closed-source commercial software, proprietary SaaS platforms, private cloud environments, or corporate applications without releasing your source code under the AGPL-3.0?
234
+
235
+ A **Commercial / Enterprise License** is available directly from the author:
236
+
237
+ - **Complete AGPL-3.0 Exemption:** Keep your intellectual property, backend code, and SaaS infrastructure 100% closed-source and proprietary.
238
+ - **Enterprise SLA & Support:** Direct architecture advisory, guaranteed response times, and priority bug resolution.
239
+ - **On-Premise & Air-Gapped Appliances:** Ready-to-deploy private Docker containers for confidential banking, healthcare, and legal compliance (LGPD / GDPR).
240
+ - **Custom Spatial Features:** Tailored table extractors for bank statements, invoices, and court proceedings.
241
+
242
+ 📬 **Inquiries & Licensing:**
243
+ [👉 **Click here to Request a Commercial License & Pricing**](mailto:messiasads2017@gmail.com?subject=RasgaPDF%20Commercial%20License%20Inquiry) with details about your organization, use case, and estimated volume.
@@ -6,4 +6,7 @@ rasgapdf/rasgapdf.dll
6
6
  rasgapdf.egg-info/PKG-INFO
7
7
  rasgapdf.egg-info/SOURCES.txt
8
8
  rasgapdf.egg-info/dependency_links.txt
9
- rasgapdf.egg-info/top_level.txt
9
+ rasgapdf.egg-info/top_level.txt
10
+ rasgapdf/integrations/__init__.py
11
+ rasgapdf/integrations/langchain.py
12
+ rasgapdf/integrations/llama_index.py
File without changes