handlemyfile 0.1.0 → 0.2.0
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/LICENSE +21 -21
- package/README.md +68 -38
- package/handlemyfile.d.ts +8 -0
- package/handlemyfile.js +37 -0
- package/handlemyfile_bg.wasm +0 -0
- package/handlemyfile_bg.wasm.d.ts +15 -0
- package/index.d.ts +86 -0
- package/index.js +207 -0
- package/package.json +29 -10
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 HandleMyFile Contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HandleMyFile Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -101,17 +101,24 @@ pip install handlemyfile
|
|
|
101
101
|
```python
|
|
102
102
|
import handlemyfile
|
|
103
103
|
|
|
104
|
-
# Option 1: File-to-file conversion
|
|
105
|
-
|
|
104
|
+
# Option 1: File-to-file conversion (with optional progress callback)
|
|
105
|
+
def on_progress(current, total, stage):
|
|
106
|
+
print(f"Page {current}/{total} [{stage}]")
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
handlemyfile.convert_file("input.pdf", "output.docx", on_progress=on_progress)
|
|
109
|
+
|
|
110
|
+
# Option 2: In-memory byte conversion with typed errors
|
|
108
111
|
with open("document.pdf", "rb") as f:
|
|
109
112
|
pdf_bytes = f.read()
|
|
110
113
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
with open("converted.docx", "wb") as f:
|
|
114
|
-
|
|
114
|
+
try:
|
|
115
|
+
docx_bytes = handlemyfile.convert(pdf_bytes)
|
|
116
|
+
with open("converted.docx", "wb") as f:
|
|
117
|
+
f.write(docx_bytes)
|
|
118
|
+
except handlemyfile.PasswordProtectedError:
|
|
119
|
+
print("This PDF requires a password to open!")
|
|
120
|
+
except handlemyfile.InvalidPdfError:
|
|
121
|
+
print("Invalid PDF file header.")
|
|
115
122
|
```
|
|
116
123
|
|
|
117
124
|
### Python CLI
|
|
@@ -134,48 +141,71 @@ The package compiles to WebAssembly with zero external dependencies, making it i
|
|
|
134
141
|
npm install handlemyfile
|
|
135
142
|
```
|
|
136
143
|
|
|
137
|
-
###
|
|
144
|
+
### 1-Line Universal Usage (Browser, Vite, Next.js, Node.js)
|
|
138
145
|
|
|
139
|
-
|
|
140
|
-
import init, { convert_pdf_to_docx } from 'handlemyfile';
|
|
146
|
+
`handlemyfile` v0.2.0 introduces **zero-config auto-initialization**. You don't need to manually configure wasm asset URLs or call `init()`:
|
|
141
147
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
await init();
|
|
148
|
+
```typescript
|
|
149
|
+
import { convert } from 'handlemyfile';
|
|
145
150
|
|
|
146
|
-
|
|
147
|
-
|
|
151
|
+
// Auto-initializes WASM seamlessly in Browser or Node.js!
|
|
152
|
+
const docxBytes = await convert(pdfBytes);
|
|
153
|
+
```
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
const docxBytes = convert_pdf_to_docx(pdfBytes);
|
|
155
|
+
### Real-Time Progress Callback
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
157
|
+
For rich interactive progress bars (0% - 100%):
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { convert } from 'handlemyfile';
|
|
161
|
+
|
|
162
|
+
const docxBytes = await convert(pdfBytes, {
|
|
163
|
+
onProgress: ({ current, total, stage }) => {
|
|
164
|
+
console.log(`Page ${current}/${total} [${stage}]`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
162
167
|
```
|
|
163
168
|
|
|
164
|
-
###
|
|
169
|
+
### Non-Blocking Web Worker (Silky 60fps UI)
|
|
165
170
|
|
|
166
|
-
|
|
167
|
-
import fs from 'fs';
|
|
168
|
-
import init, { convert_pdf_to_docx } from 'handlemyfile';
|
|
171
|
+
To convert large (100+ page) PDFs without freezing the browser's main thread:
|
|
169
172
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
await init(wasmBuffer);
|
|
173
|
+
```typescript
|
|
174
|
+
import { convert } from 'handlemyfile';
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
+
// Automatically offloads computation to a background Web Worker!
|
|
177
|
+
const docxBytes = await convert(pdfBytes, {
|
|
178
|
+
worker: true,
|
|
179
|
+
onProgress: ({ current, total }) => {
|
|
180
|
+
updateProgressBar(current / total);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
```
|
|
176
184
|
|
|
177
|
-
|
|
178
|
-
|
|
185
|
+
### Granular Error Handling
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { convert, HandleMyFileError } from 'handlemyfile';
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const docxBytes = await convert(pdfBytes);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err instanceof HandleMyFileError) {
|
|
194
|
+
switch (err.code) {
|
|
195
|
+
case 'ERR_PASSWORD_PROTECTED':
|
|
196
|
+
alert('This PDF is password-protected.');
|
|
197
|
+
break;
|
|
198
|
+
case 'ERR_INVALID_HEADER':
|
|
199
|
+
alert('Invalid PDF format.');
|
|
200
|
+
break;
|
|
201
|
+
case 'ERR_CORRUPT_PDF':
|
|
202
|
+
alert('The PDF file is corrupted.');
|
|
203
|
+
break;
|
|
204
|
+
default:
|
|
205
|
+
console.error('Conversion failed:', err.message);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
179
209
|
```
|
|
180
210
|
|
|
181
211
|
---
|
package/handlemyfile.d.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export function convert_pdf_to_docx(pdf_data: Uint8Array | number[]): Uint8Array;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* WebAssembly entrypoint to convert PDF to DOCX with real-time page progress callback
|
|
11
|
+
*/
|
|
12
|
+
export function convert_pdf_to_docx_with_progress(pdf_data: Uint8Array | number[], progress_callback: Function): Uint8Array;
|
|
13
|
+
|
|
9
14
|
/**
|
|
10
15
|
* WebAssembly entrypoint to inspect parsed spatial elements & structured layout
|
|
11
16
|
*/
|
|
@@ -21,11 +26,14 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|
|
21
26
|
export interface InitOutput {
|
|
22
27
|
readonly memory: WebAssembly.Memory;
|
|
23
28
|
readonly convert_pdf_to_docx: (a: number, b: number) => [number, number, number, number];
|
|
29
|
+
readonly convert_pdf_to_docx_with_progress: (a: number, b: number, c: any) => [number, number, number, number];
|
|
24
30
|
readonly inspect_pdf_elements: (a: number, b: number) => [number, number, number];
|
|
25
31
|
readonly inspect_pdf_structure: (a: number, b: number) => [number, number, number];
|
|
26
32
|
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
27
33
|
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
28
34
|
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
35
|
+
readonly __wbindgen_exn_store: (a: number) => void;
|
|
36
|
+
readonly __externref_table_alloc: () => number;
|
|
29
37
|
readonly __wbindgen_externrefs: WebAssembly.Table;
|
|
30
38
|
readonly __externref_table_dealloc: (a: number) => void;
|
|
31
39
|
readonly __wbindgen_start: () => void;
|
package/handlemyfile.js
CHANGED
|
@@ -17,6 +17,24 @@ export function convert_pdf_to_docx(pdf_data) {
|
|
|
17
17
|
return v2;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* WebAssembly entrypoint to convert PDF to DOCX with real-time page progress callback
|
|
22
|
+
* @param {Uint8Array | number[]} pdf_data
|
|
23
|
+
* @param {Function} progress_callback
|
|
24
|
+
* @returns {Uint8Array}
|
|
25
|
+
*/
|
|
26
|
+
export function convert_pdf_to_docx_with_progress(pdf_data, progress_callback) {
|
|
27
|
+
const ptr0 = passArray8ToWasm0(pdf_data, wasm.__wbindgen_malloc);
|
|
28
|
+
const len0 = WASM_VECTOR_LEN;
|
|
29
|
+
const ret = wasm.convert_pdf_to_docx_with_progress(ptr0, len0, progress_callback);
|
|
30
|
+
if (ret[3]) {
|
|
31
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
32
|
+
}
|
|
33
|
+
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
34
|
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
35
|
+
return v2;
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
/**
|
|
21
39
|
* WebAssembly entrypoint to inspect parsed spatial elements & structured layout
|
|
22
40
|
* @param {Uint8Array | number[]} pdf_data
|
|
@@ -67,6 +85,10 @@ function __wbg_get_imports() {
|
|
|
67
85
|
__wbg___wbindgen_throw_4c88d8bf264fd984: function(arg0, arg1) {
|
|
68
86
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
69
87
|
},
|
|
88
|
+
__wbg_call_275b78b3b6520b92: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
|
89
|
+
const ret = arg0.call(arg1, arg2, arg3, arg4);
|
|
90
|
+
return ret;
|
|
91
|
+
}, arguments); },
|
|
70
92
|
__wbg_new_37eb45aaf5eeb820: function() {
|
|
71
93
|
const ret = new Array();
|
|
72
94
|
return ret;
|
|
@@ -119,6 +141,12 @@ function __wbg_get_imports() {
|
|
|
119
141
|
};
|
|
120
142
|
}
|
|
121
143
|
|
|
144
|
+
function addToExternrefTable0(obj) {
|
|
145
|
+
const idx = wasm.__externref_table_alloc();
|
|
146
|
+
wasm.__wbindgen_externrefs.set(idx, obj);
|
|
147
|
+
return idx;
|
|
148
|
+
}
|
|
149
|
+
|
|
122
150
|
function getArrayU8FromWasm0(ptr, len) {
|
|
123
151
|
ptr = ptr >>> 0;
|
|
124
152
|
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
|
|
@@ -145,6 +173,15 @@ function getUint8ArrayMemory0() {
|
|
|
145
173
|
return cachedUint8ArrayMemory0;
|
|
146
174
|
}
|
|
147
175
|
|
|
176
|
+
function handleError(f, args) {
|
|
177
|
+
try {
|
|
178
|
+
return f.apply(this, args);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
const idx = addToExternrefTable0(e);
|
|
181
|
+
wasm.__wbindgen_exn_store(idx);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
148
185
|
function passArray8ToWasm0(arg, malloc) {
|
|
149
186
|
const ptr = malloc(arg.length * 1, 1) >>> 0;
|
|
150
187
|
getUint8ArrayMemory0().set(arg, ptr / 1);
|
package/handlemyfile_bg.wasm
CHANGED
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
export const memory: WebAssembly.Memory;
|
|
4
|
+
export const convert_pdf_to_docx: (a: number, b: number) => [number, number, number, number];
|
|
5
|
+
export const convert_pdf_to_docx_with_progress: (a: number, b: number, c: any) => [number, number, number, number];
|
|
6
|
+
export const inspect_pdf_elements: (a: number, b: number) => [number, number, number];
|
|
7
|
+
export const inspect_pdf_structure: (a: number, b: number) => [number, number, number];
|
|
8
|
+
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
9
|
+
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
10
|
+
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
11
|
+
export const __wbindgen_exn_store: (a: number) => void;
|
|
12
|
+
export const __externref_table_alloc: () => number;
|
|
13
|
+
export const __wbindgen_externrefs: WebAssembly.Table;
|
|
14
|
+
export const __externref_table_dealloc: (a: number) => void;
|
|
15
|
+
export const __wbindgen_start: () => void;
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export interface ProgressEvent {
|
|
2
|
+
current: number;
|
|
3
|
+
total: number;
|
|
4
|
+
stage: 'parsing' | 'analyzing_layout' | 'generating_docx' | 'done' | string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ConvertOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Real-time progress callback with current page, total pages, and stage.
|
|
10
|
+
*/
|
|
11
|
+
onProgress?: (progress: ProgressEvent) => void;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Optional custom URL or BufferSource for handlemyfile_bg.wasm.
|
|
15
|
+
* If omitted, it will automatically resolve via import.meta.url or Node.js fs.
|
|
16
|
+
*/
|
|
17
|
+
wasmUrl?: string | URL | BufferSource;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* When true in a browser environment, automatically spawns a background Web Worker
|
|
21
|
+
* to keep the main thread and UI completely non-blocking (60fps).
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
worker?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ErrorCode =
|
|
28
|
+
| 'ERR_PASSWORD_PROTECTED'
|
|
29
|
+
| 'ERR_INVALID_HEADER'
|
|
30
|
+
| 'ERR_CORRUPT_PDF'
|
|
31
|
+
| 'ERR_EMPTY_DOCUMENT'
|
|
32
|
+
| 'ERR_CONVERSION_FAILED'
|
|
33
|
+
| 'ERR_WORKER';
|
|
34
|
+
|
|
35
|
+
export class HandleMyFileError extends Error {
|
|
36
|
+
code: ErrorCode;
|
|
37
|
+
constructor(message: string, code?: ErrorCode);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Universal auto-initializer for WASM runtime.
|
|
42
|
+
* Optional: Calling convert() automatically initializes if not already done.
|
|
43
|
+
*/
|
|
44
|
+
export function ensureInitialized(wasmUrlOrPath?: string | URL | BufferSource): Promise<void>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* High-Level, Zero-Config PDF to DOCX converter.
|
|
48
|
+
* Automatically initializes WebAssembly if not already loaded.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { convert } from 'handlemyfile';
|
|
53
|
+
*
|
|
54
|
+
* // 1-line conversion!
|
|
55
|
+
* const docxBytes = await convert(pdfBytes);
|
|
56
|
+
*
|
|
57
|
+
* // With real-time progress:
|
|
58
|
+
* const docxBytes = await convert(pdfBytes, {
|
|
59
|
+
* onProgress: ({ current, total, stage }) => {
|
|
60
|
+
* console.log(`Page ${current}/${total} [${stage}]`);
|
|
61
|
+
* }
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* // Off-main-thread in Web Worker:
|
|
65
|
+
* const docxBytes = await convert(pdfBytes, { worker: true });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function convert(pdfData: Uint8Array | ArrayBuffer, options?: ConvertOptions): Promise<Uint8Array>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Synchronous converter for performance-critical environments where wasm is already initialized.
|
|
72
|
+
*/
|
|
73
|
+
export function convertSync(pdfData: Uint8Array | ArrayBuffer): Uint8Array;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Run PDF conversion inside an isolated Web Worker to keep the browser UI completely responsive.
|
|
77
|
+
*/
|
|
78
|
+
export function convertInWorker(pdfBytes: Uint8Array | ArrayBuffer, options?: ConvertOptions): Promise<Uint8Array>;
|
|
79
|
+
|
|
80
|
+
export function init(moduleOrPath?: any): Promise<any>;
|
|
81
|
+
export function convert_pdf_to_docx(pdf_data: Uint8Array | number[]): Uint8Array;
|
|
82
|
+
export function convert_pdf_to_docx_with_progress(pdf_data: Uint8Array | number[], progress_callback: Function): Uint8Array;
|
|
83
|
+
export function inspect_pdf_elements(pdf_data: Uint8Array | number[]): any;
|
|
84
|
+
export function inspect_pdf_structure(pdf_data: Uint8Array | number[]): any;
|
|
85
|
+
|
|
86
|
+
export default convert;
|
package/index.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import initWasm, {
|
|
2
|
+
convert_pdf_to_docx,
|
|
3
|
+
convert_pdf_to_docx_with_progress,
|
|
4
|
+
inspect_pdf_elements,
|
|
5
|
+
inspect_pdf_structure
|
|
6
|
+
} from './handlemyfile.js';
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
initWasm as init,
|
|
10
|
+
convert_pdf_to_docx,
|
|
11
|
+
convert_pdf_to_docx_with_progress,
|
|
12
|
+
inspect_pdf_elements,
|
|
13
|
+
inspect_pdf_structure
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class HandleMyFileError extends Error {
|
|
17
|
+
constructor(message, code = 'ERR_CONVERSION_FAILED') {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'HandleMyFileError';
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let isInitialized = false;
|
|
25
|
+
let initPromise = null;
|
|
26
|
+
|
|
27
|
+
function parseErrorCode(rawMessage) {
|
|
28
|
+
const msg = String(rawMessage || '');
|
|
29
|
+
if (msg.includes('ERR_PASSWORD_PROTECTED')) {
|
|
30
|
+
return { code: 'ERR_PASSWORD_PROTECTED', message: 'The PDF document is encrypted or password-protected.' };
|
|
31
|
+
}
|
|
32
|
+
if (msg.includes('ERR_INVALID_HEADER')) {
|
|
33
|
+
return { code: 'ERR_INVALID_HEADER', message: 'The provided file is not a valid PDF document (missing %PDF- header).' };
|
|
34
|
+
}
|
|
35
|
+
if (msg.includes('ERR_EMPTY_DOCUMENT')) {
|
|
36
|
+
return { code: 'ERR_EMPTY_DOCUMENT', message: 'The PDF document contains zero readable pages.' };
|
|
37
|
+
}
|
|
38
|
+
if (msg.includes('ERR_CORRUPT_PDF')) {
|
|
39
|
+
return { code: 'ERR_CORRUPT_PDF', message: 'Failed to parse PDF binary or cross-reference streams (file may be corrupted).' };
|
|
40
|
+
}
|
|
41
|
+
return { code: 'ERR_CONVERSION_FAILED', message: msg || 'Document conversion failed.' };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Universal auto-initializer that seamlessly detects Node.js vs Browser environments.
|
|
46
|
+
*/
|
|
47
|
+
export async function ensureInitialized(wasmUrlOrPath) {
|
|
48
|
+
if (isInitialized) return;
|
|
49
|
+
if (initPromise) return initPromise;
|
|
50
|
+
|
|
51
|
+
initPromise = (async () => {
|
|
52
|
+
// 1. If explicit path or bytes provided
|
|
53
|
+
if (wasmUrlOrPath) {
|
|
54
|
+
await initWasm({ module_or_path: wasmUrlOrPath });
|
|
55
|
+
isInitialized = true;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 2. Node.js environment detection
|
|
60
|
+
const isNode = typeof process !== 'undefined' && process.versions && !!process.versions.node;
|
|
61
|
+
if (isNode) {
|
|
62
|
+
try {
|
|
63
|
+
const fs = await import('fs');
|
|
64
|
+
const path = await import('path');
|
|
65
|
+
const url = await import('url');
|
|
66
|
+
const dir = path.dirname(url.fileURLToPath(import.meta.url));
|
|
67
|
+
const wasmPath = path.join(dir, 'handlemyfile_bg.wasm');
|
|
68
|
+
const wasmBuffer = fs.readFileSync(wasmPath);
|
|
69
|
+
await initWasm({ module_or_path: wasmBuffer });
|
|
70
|
+
isInitialized = true;
|
|
71
|
+
return;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
// Fallback to default if dynamic import fails
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 3. Browser / Bundler environment (Vite, Webpack, Rollup)
|
|
78
|
+
const defaultWasmUrl = new URL('./handlemyfile_bg.wasm', import.meta.url);
|
|
79
|
+
await initWasm({ module_or_path: defaultWasmUrl });
|
|
80
|
+
isInitialized = true;
|
|
81
|
+
})();
|
|
82
|
+
|
|
83
|
+
return initPromise;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* High-Level, Zero-Config PDF to DOCX converter.
|
|
88
|
+
* Auto-initializes WASM if not already loaded.
|
|
89
|
+
*
|
|
90
|
+
* @param {Uint8Array | ArrayBuffer} pdfData - Input PDF bytes.
|
|
91
|
+
* @param {Object} [options] - Conversion options.
|
|
92
|
+
* @param {Function} [options.onProgress] - Progress callback: ({ current, total, stage }).
|
|
93
|
+
* @param {string | URL | BufferSource} [options.wasmUrl] - Custom URL or BufferSource for .wasm file.
|
|
94
|
+
* @param {boolean} [options.worker] - Run conversion off the main thread in a Web Worker.
|
|
95
|
+
* @returns {Promise<Uint8Array>} Generated DOCX bytes.
|
|
96
|
+
*/
|
|
97
|
+
export async function convert(pdfData, options = {}) {
|
|
98
|
+
const inputBytes = pdfData instanceof Uint8Array ? pdfData : new Uint8Array(pdfData);
|
|
99
|
+
|
|
100
|
+
if (options.worker) {
|
|
101
|
+
return convertInWorker(inputBytes, options);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await ensureInitialized(options.wasmUrl);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
if (typeof options.onProgress === 'function') {
|
|
108
|
+
return convert_pdf_to_docx_with_progress(inputBytes, (current, total, stage) => {
|
|
109
|
+
options.onProgress({ current, total, stage });
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return convert_pdf_to_docx(inputBytes);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
const { code, message } = parseErrorCode(err);
|
|
115
|
+
throw new HandleMyFileError(message, code);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Synchronous converter for performance-critical environments where wasm is already initialized.
|
|
121
|
+
*
|
|
122
|
+
* @param {Uint8Array | ArrayBuffer} pdfData
|
|
123
|
+
* @returns {Uint8Array}
|
|
124
|
+
*/
|
|
125
|
+
export function convertSync(pdfData) {
|
|
126
|
+
const inputBytes = pdfData instanceof Uint8Array ? pdfData : new Uint8Array(pdfData);
|
|
127
|
+
try {
|
|
128
|
+
return convert_pdf_to_docx(inputBytes);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const { code, message } = parseErrorCode(err);
|
|
131
|
+
throw new HandleMyFileError(message, code);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Run PDF conversion inside an isolated Web Worker to keep the browser UI 60fps responsive.
|
|
137
|
+
*
|
|
138
|
+
* @param {Uint8Array} pdfBytes
|
|
139
|
+
* @param {Object} [options]
|
|
140
|
+
* @returns {Promise<Uint8Array>}
|
|
141
|
+
*/
|
|
142
|
+
export async function convertInWorker(pdfBytes, options = {}) {
|
|
143
|
+
if (typeof Worker === 'undefined') {
|
|
144
|
+
// Fallback to main thread if Web Workers are not supported (e.g. Node.js without worker_threads)
|
|
145
|
+
return convert(pdfBytes, { ...options, worker: false });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const wasmUrl = options.wasmUrl
|
|
149
|
+
? String(options.wasmUrl)
|
|
150
|
+
: new URL('./handlemyfile_bg.wasm', import.meta.url).href;
|
|
151
|
+
|
|
152
|
+
// Inline worker script that imports this module
|
|
153
|
+
const workerCode = `
|
|
154
|
+
import { convert } from ${JSON.stringify(import.meta.url)};
|
|
155
|
+
|
|
156
|
+
self.onmessage = async (e) => {
|
|
157
|
+
const { id, pdfBytes, wasmUrl } = e.data;
|
|
158
|
+
try {
|
|
159
|
+
const docx = await convert(pdfBytes, {
|
|
160
|
+
wasmUrl,
|
|
161
|
+
onProgress: (p) => self.postMessage({ id, type: 'progress', progress: p })
|
|
162
|
+
});
|
|
163
|
+
self.postMessage({ id, type: 'success', docx }, [docx.buffer]);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
self.postMessage({ id, type: 'error', error: err.message, code: err.code || 'ERR_CONVERSION_FAILED' });
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
171
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
172
|
+
const worker = new Worker(blobUrl, { type: 'module' });
|
|
173
|
+
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
const id = Math.random().toString(36).substring(2);
|
|
176
|
+
|
|
177
|
+
worker.onmessage = (e) => {
|
|
178
|
+
if (e.data.id !== id) return;
|
|
179
|
+
if (e.data.type === 'progress') {
|
|
180
|
+
if (typeof options.onProgress === 'function') {
|
|
181
|
+
options.onProgress(e.data.progress);
|
|
182
|
+
}
|
|
183
|
+
} else if (e.data.type === 'success') {
|
|
184
|
+
cleanup();
|
|
185
|
+
resolve(e.data.docx);
|
|
186
|
+
} else if (e.data.type === 'error') {
|
|
187
|
+
cleanup();
|
|
188
|
+
reject(new HandleMyFileError(e.data.error, e.data.code));
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
worker.onerror = (err) => {
|
|
193
|
+
cleanup();
|
|
194
|
+
reject(new HandleMyFileError(err.message || 'Worker failure', 'ERR_WORKER'));
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
function cleanup() {
|
|
198
|
+
worker.terminate();
|
|
199
|
+
URL.revokeObjectURL(blobUrl);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Send binary copy to worker
|
|
203
|
+
worker.postMessage({ id, pdfBytes, wasmUrl });
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export default convert;
|
package/package.json
CHANGED
|
@@ -1,31 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handlemyfile",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"
|
|
5
|
-
"HandleMyFile Contributors"
|
|
6
|
-
],
|
|
4
|
+
"version": "0.2.0",
|
|
7
5
|
"description": "High-precision PDF to DOCX converter engine in Pure Rust, WebAssembly, and Python",
|
|
8
|
-
"version": "0.1.0",
|
|
9
6
|
"license": "MIT",
|
|
7
|
+
"author": "HandleMyFile Contributors",
|
|
10
8
|
"repository": {
|
|
11
9
|
"type": "git",
|
|
12
10
|
"url": "https://github.com/handlemyfile/handlemyfile"
|
|
13
11
|
},
|
|
14
12
|
"files": [
|
|
15
13
|
"handlemyfile_bg.wasm",
|
|
14
|
+
"handlemyfile_bg.wasm.d.ts",
|
|
16
15
|
"handlemyfile.js",
|
|
17
|
-
"handlemyfile.d.ts"
|
|
16
|
+
"handlemyfile.d.ts",
|
|
17
|
+
"index.js",
|
|
18
|
+
"index.d.ts",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
18
21
|
],
|
|
19
|
-
"main": "
|
|
20
|
-
"
|
|
22
|
+
"main": "index.js",
|
|
23
|
+
"module": "index.js",
|
|
24
|
+
"types": "index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./index.d.ts",
|
|
28
|
+
"import": "./index.js",
|
|
29
|
+
"default": "./index.js"
|
|
30
|
+
},
|
|
31
|
+
"./wasm": {
|
|
32
|
+
"types": "./handlemyfile.d.ts",
|
|
33
|
+
"import": "./handlemyfile.js",
|
|
34
|
+
"default": "./handlemyfile.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
21
38
|
"sideEffects": [
|
|
22
|
-
"./
|
|
39
|
+
"./handlemyfile_bg.wasm"
|
|
23
40
|
],
|
|
24
41
|
"keywords": [
|
|
25
42
|
"pdf",
|
|
26
43
|
"docx",
|
|
27
44
|
"converter",
|
|
28
45
|
"word",
|
|
29
|
-
"
|
|
46
|
+
"wasm",
|
|
47
|
+
"webassembly",
|
|
48
|
+
"rust"
|
|
30
49
|
]
|
|
31
|
-
}
|
|
50
|
+
}
|