@jackgreen2018/pdf-engine 1.0.7 → 1.0.8
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.
- package/package.json +1 -1
- package/pkg/package.json +1 -1
- package/pkg/pdf_engine_bg.wasm +0 -0
- package/src/lib.rs +53 -29
package/package.json
CHANGED
package/pkg/package.json
CHANGED
package/pkg/pdf_engine_bg.wasm
CHANGED
|
Binary file
|
package/src/lib.rs
CHANGED
|
@@ -15,35 +15,67 @@ pub fn get_page_count(buffer: &[u8]) -> Result<u32, JsValue> {
|
|
|
15
15
|
Ok(doc.get_pages().len().max(1) as u32)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/// Decode PDF hex strings (<HHHH...>) in content stream operators.
|
|
19
|
+
fn decode_hex_strings(content: &str) -> String {
|
|
20
|
+
let mut result = String::new();
|
|
21
|
+
let bytes = content.as_bytes();
|
|
22
|
+
let mut i = 0;
|
|
23
|
+
while i < bytes.len() {
|
|
24
|
+
if bytes[i] == b'<' {
|
|
25
|
+
let mut j = i + 1;
|
|
26
|
+
while j < bytes.len() && bytes[j] != b'>' {
|
|
27
|
+
j += 1;
|
|
28
|
+
}
|
|
29
|
+
if j > i + 1 && j < bytes.len() {
|
|
30
|
+
let hex = &content[i+1..j];
|
|
31
|
+
if hex.len() % 2 == 0 && hex.chars().all(|c| c.is_ascii_hexdigit()) {
|
|
32
|
+
let mut buf = Vec::with_capacity(hex.len() / 2);
|
|
33
|
+
for k in (0..hex.len()).step_by(2) {
|
|
34
|
+
if let Ok(byte) = u8::from_str_radix(&hex[k..k+2], 16) {
|
|
35
|
+
buf.push(byte);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if let Ok(s) = String::from_utf8(buf) {
|
|
39
|
+
result.push_str(&s);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
i = j + 1;
|
|
44
|
+
} else {
|
|
45
|
+
i += 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
result
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Extract text by iterating ALL objects and processing content streams.
|
|
52
|
+
fn collect_page_text(doc: &Document) -> String {
|
|
53
|
+
let mut text = String::new();
|
|
54
|
+
for (_id, obj) in doc.objects.iter() {
|
|
55
|
+
let Object::Stream(ref stream) = obj else { continue };
|
|
56
|
+
let mut s = stream.clone();
|
|
57
|
+
let _ = s.decompress();
|
|
58
|
+
let content = String::from_utf8_lossy(&s.content);
|
|
59
|
+
// ponytail: only process streams that look like page content (have BT/ET text blocks)
|
|
60
|
+
if content.contains("BT") && content.contains("ET") {
|
|
61
|
+
text.push_str(&decode_hex_strings(&content));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
text
|
|
65
|
+
}
|
|
66
|
+
|
|
18
67
|
#[wasm_bindgen]
|
|
19
68
|
pub fn extract_text(buffer: &[u8]) -> Result<String, JsValue> {
|
|
20
69
|
let doc = Document::load_mem(buffer)
|
|
21
70
|
.map_err(|e| JsValue::from_str(&format!("PDF error: {}", e)))?;
|
|
22
71
|
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
for obj in doc.objects.values() {
|
|
26
|
-
if let Object::Dictionary(ref dict) = *obj {
|
|
27
|
-
let is_page = match dict.get(b"/Type") {
|
|
28
|
-
Ok(Object::String(s, _)) => s == b"Page",
|
|
29
|
-
_ => false,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
if is_page {
|
|
33
|
-
match dict.get(b"/Contents") {
|
|
34
|
-
Ok(Object::Stream(stream)) => {
|
|
35
|
-
text.push_str(&String::from_utf8_lossy(&stream.content));
|
|
36
|
-
},
|
|
37
|
-
_ => {}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
72
|
+
let text = collect_page_text(&doc);
|
|
42
73
|
|
|
43
74
|
if text.is_empty() {
|
|
44
|
-
|
|
75
|
+
Ok("No text extracted.".to_string())
|
|
76
|
+
} else {
|
|
77
|
+
Ok(text)
|
|
45
78
|
}
|
|
46
|
-
Ok(text)
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
/// Remap all ObjectId references in `obj` using `id_map`.
|
|
@@ -109,11 +141,9 @@ fn walk_refs(obj: &Object, doc: &Document, visited: &mut BTreeMap<ObjectId, Obje
|
|
|
109
141
|
|
|
110
142
|
/// Copy page at `page_obj_id` from `src` into `dst`, updating all internal references.
|
|
111
143
|
fn copy_page_into(src: &Document, dst: &mut Document, page_obj_id: ObjectId) -> ObjectId {
|
|
112
|
-
// Collect all reachable objects from this page
|
|
113
144
|
let mut to_copy: BTreeMap<ObjectId, Object> = BTreeMap::new();
|
|
114
145
|
collect_reachable(page_obj_id, src, &mut to_copy);
|
|
115
146
|
|
|
116
|
-
// Assign new IDs in dst's address space
|
|
117
147
|
let mut id_map: BTreeMap<ObjectId, ObjectId> = BTreeMap::new();
|
|
118
148
|
let mut next_id = dst.max_id + 1;
|
|
119
149
|
|
|
@@ -156,12 +186,10 @@ pub fn merge(pdf_buffers: Array) -> Result<Uint8Array, JsValue> {
|
|
|
156
186
|
let other = Document::load_mem(&buf.to_vec())
|
|
157
187
|
.map_err(|e| JsValue::from_str(&format!("PDF load error: {}", e)))?;
|
|
158
188
|
|
|
159
|
-
// Pre-compute page count before any mutable borrow
|
|
160
189
|
let page_count = doc.get_pages().len() as i64;
|
|
161
190
|
|
|
162
191
|
for &page_obj_id in other.get_pages().values() {
|
|
163
192
|
let new_page_id = copy_page_into(&other, &mut doc, page_obj_id);
|
|
164
|
-
// Append new page to the Pages catalog Kids array
|
|
165
193
|
if let Ok(root_ref) = doc.trailer.get(b"Root").and_then(|r| r.as_reference()) {
|
|
166
194
|
if let Some(Object::Dictionary(ref mut root_dict)) = doc.objects.get_mut(&root_ref) {
|
|
167
195
|
if let Ok(pages_ref) = root_dict.get(b"Pages").and_then(|p| p.as_reference()) {
|
|
@@ -192,20 +220,16 @@ pub fn split(buffer: &[u8], pages: Array) -> Result<Array, JsValue> {
|
|
|
192
220
|
.map_err(|e| JsValue::from_str(&format!("PDF error: {}", e)))?;
|
|
193
221
|
let page_len = pages.length();
|
|
194
222
|
|
|
195
|
-
// get_pages returns BTreeMap<u32, ObjectId>
|
|
196
223
|
let page_map: BTreeMap<u32, ObjectId> = doc.get_pages();
|
|
197
|
-
|
|
198
224
|
let results = Array::new();
|
|
199
225
|
|
|
200
226
|
for i in 0..page_len {
|
|
201
227
|
let page_num = pages.get(i).as_f64().unwrap_or(0.0) as u32;
|
|
202
228
|
let Some(&page_obj_id) = page_map.get(&page_num) else { continue };
|
|
203
229
|
|
|
204
|
-
// Build a new document containing just this page
|
|
205
230
|
let mut new_doc = Document::with_version("1.5");
|
|
206
231
|
let _ = copy_page_into(&doc, &mut new_doc, page_obj_id);
|
|
207
232
|
|
|
208
|
-
// Update the Pages catalog (first Pages dictionary in new_doc)
|
|
209
233
|
if let Some((_, &pages_obj_id)) = new_doc.get_pages().iter().next() {
|
|
210
234
|
let new_count = new_doc.get_pages().len() as i64;
|
|
211
235
|
if let Some(Object::Dictionary(ref mut pages_dict)) = new_doc.objects.get_mut(&pages_obj_id) {
|