File size: 5,647 Bytes
98e73d0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# ⚡ RasgaPDF (Node.js)

<div align="center">

**Ultra-Fast 2D Spatial PDF Parser powered by Pure Rust.**  
*Extract exact physical coordinates (Raw X/Y) or structured blocks & tables with sub-millisecond latency.*

[![npm version](https://img.shields.io/npm/v/rasgapdf.svg?color=green&logo=npm)](https://www.npmjs.com/package/rasgapdf)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)

</div>

---

## 🚀 Installation

```bash
npm install rasgapdf
```

*Note: Bundles pre-compiled native Rust C-ABI binaries. Works out-of-the-box with optional `koffi` fast FFI and automatic CLI fallback.*

---

## ⚡ Two Extraction Modes

### ⚡ Mode 1: Flat Physical Coordinates (Raw X/Y)
Zero-overhead extraction returning exact physical 2D coordinates `[x0, y0, x1, y1]` for every text span (~3-8 ms):

```javascript
const { parseRaw } = require('rasgapdf');

const raw = parseRaw('sample.pdf');
console.log(`Parsed ${raw.total_spans} spans in ${raw.parse_time_ms.toFixed(2)} ms`);

// Iterate through raw physical coordinates
for (const span of raw.spans) {
  console.log(`[Page ${span.page}] (${span.x0}, ${span.y0}, ${span.x1}, ${span.y1}) -> "${span.text}"`);
}
```

### 🎯 Mode 2: Hierarchical Structured Document
Extracts visual text blocks, lines, font sizes, and automatically detects matrix tables via column alignment:

```javascript
const { parsePdf } = require('rasgapdf');

const doc = parsePdf('invoice.pdf', {
  detect_tables: true,
  min_table_columns: 3
});

console.log(`Total Pages: ${doc.total_pages}`);
for (const page of doc.pages) {
  // Access text blocks
  for (const block of page.text_blocks) {
    console.log(`Block #${block.block_id}: ${block.lines[0].text}`);
  }

  // Access detected tables
  for (const table of page.tables) {
    console.log('Table Headers:', table.headers);
    console.log('Table Rows:', table.rows);
  }
}
```

---

### 🖼️ Embedded Images & Vector Graphics (opt-in)

Both are **off by default**, so the JSON shape is unchanged unless you ask for them. Image bytes are never inlined: the tree carries metadata (dimensions, colour space, filter, SHA-256, placement `bbox`) and `extractImages()` writes the original streams to disk.

```javascript
const { parsePdf, extractImages } = require('rasgapdf');

const doc = parsePdf('report.pdf', {
  extract_images: true,
  extract_drawings: true
});

const page = doc.pages[0];

for (const image of page.images) {
  console.log(image.name, image.pixel_width, 'x', image.pixel_height, image.filter, image.bbox);
}

for (const drawing of page.drawings) {
  console.log(drawing.drawing_id, drawing.drawing_type, drawing.stroke_color);
}

// Writes page_001_img_001_xref7.jpg (etc.) plus manifest.json
const manifest = extractImages('report.pdf', './out');
console.log(`${manifest.written} of ${manifest.total_images} images written`);
```

---

## 📐 Schema

Returns a deterministic JSON structure representing the exact 2D canvas geometry of the PDF.

### `RawSpatialDocument`
```typescript
interface RawSpatialDocument {
  file_name: string;
  total_pages: number;
  total_spans: number;
  parse_time_ms: number;
  spans: Array<{
    page: number;
    x0: number;
    y0: number;
    x1: number;
    y1: number;
    font_size: number;
    font_name: string;
    text: string;
  }>;
}
```

### `SpatialDocument`
```typescript
interface SpatialDocument {
  file_name: string;
  total_pages: number;
  parse_time_ms: number;
  pages: Array<{
    page_number: number;
    width: number;
    height: number;
    rotation: number;          // real page /Rotate (0, 90, 180, 270)
    text_blocks: SpatialBlock[];
    tables: SpatialTable[];
    raw_span_count: number;
    images: SpatialImage[];    // empty unless extract_images: true
    drawings: SpatialDrawing[];// empty unless extract_drawings: true
  }>;
}

interface SpatialImage {
  image_id: number;
  name: string;
  xref: number;
  bbox: { x0: number; y0: number; x1: number; y1: number; width: number; height: number } | null;
  pixel_width: number;
  pixel_height: number;
  bits_per_component: number | null;
  color_space: string | null;
  filter: string | null;       // e.g. "DCTDecode"
  mime_type: string | null;    // e.g. "image/jpeg"
  byte_length: number | null;
  sha256: string | null;
  file: string | null;         // filled only by extractImages()
}

interface SpatialDrawing {
  drawing_id: number;
  bbox: { x0: number; y0: number; x1: number; y1: number; width: number; height: number };
  drawing_type: 'fill' | 'stroke' | 'fill_stroke';
  item_count: number;
  items: Array<{               // discriminated by `kind`
    kind: 'line' | 'curve' | 'rect' | 'quad';
    p1?: [number, number]; p2?: [number, number];
    p3?: [number, number]; p4?: [number, number];
    bbox?: { x0: number; y0: number; x1: number; y1: number; width: number; height: number };
    orientation?: number;      // 1 anti-clockwise, -1 clockwise
    points?: Array<[number, number]>;
  }>;
  stroke_color: [number, number, number] | null;
  fill_color: [number, number, number] | null;
  width: number | null;
  fill_opacity: number | null;
  stroke_opacity: number | null;
  even_odd: boolean | null;
  dash_pattern: number[] | null;
  dash_phase: number | null;
  line_join: string | null;
  line_cap: string | null;
  layer: string | null;
  seqno: number;
}
```

---

## 📄 License
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). Copyright (c) 2026 Messias Martins.

RasgaPDF links against [MuPDF](https://mupdf.com/) (Artifex Software), licensed
under the AGPL-3.0 — hence the same license here.