@ohah/hwpjs 0.1.0-rc.1 → 0.1.0-rc.3
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 +1 -1
- package/README.md +181 -48
- package/android/src/main/jni/include/ffi.rs.h +5 -3
- package/android/src/main/jni/libs/arm64-v8a/libhwpjs-prebuilt.a +0 -0
- package/android/src/main/jni/libs/armeabi-v7a/libhwpjs-prebuilt.a +0 -0
- package/android/src/main/jni/libs/x86/libhwpjs-prebuilt.a +0 -0
- package/android/src/main/jni/libs/x86_64/libhwpjs-prebuilt.a +0 -0
- package/android/src/main/jni/src/ffi.rs.cc +17 -9
- package/bin/hwpjs.js +3 -0
- package/cpp/CxxHwpjsModule.cpp +3 -3
- package/cpp/bridging-generated.hpp +53 -0
- package/dist/cli/commands/batch.js +97 -0
- package/dist/cli/commands/extract-images.js +59 -0
- package/dist/cli/commands/info.js +59 -0
- package/dist/cli/commands/to-json.js +46 -0
- package/dist/cli/commands/to-markdown.js +46 -0
- package/dist/cli/index.js +21 -0
- package/dist/index.js +52 -52
- package/dist/react-native/index.cjs +1 -1
- package/dist/react-native/index.cjs.map +1 -1
- package/dist/react-native/index.d.cts +3 -3
- package/dist/react-native/index.d.mts +3 -3
- package/dist/react-native/index.mjs +1 -1
- package/dist/react-native/index.mjs.map +1 -1
- package/ios/framework/libhwpjs.xcframework/ios-arm64/libhwpjs-prebuilt.a +0 -0
- package/ios/framework/libhwpjs.xcframework/ios-arm64_x86_64-simulator/libhwpjs-prebuilt.a +0 -0
- package/ios/include/ffi.rs.h +5 -3
- package/ios/src/ffi.rs.cc +17 -9
- package/package.json +20 -11
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,87 +1,220 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ohah/hwpjs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
HWP parser for Node.js, Web, and React Native
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
한글과컴퓨터의 한/글 문서 파일(.hwp)을 읽고 파싱하는 라이브러리입니다. Rust로 구현된 핵심 로직을 Node.js, Web, React Native 환경에서 사용할 수 있도록 제공합니다.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## 설치
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm install @ohah/hwpjs
|
|
11
|
+
# 또는
|
|
12
|
+
yarn add @ohah/hwpjs
|
|
13
|
+
# 또는
|
|
14
|
+
pnpm add @ohah/hwpjs
|
|
15
|
+
# 또는
|
|
16
|
+
bun add @ohah/hwpjs
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 사용법
|
|
20
|
+
|
|
21
|
+
### CLI (Command Line Interface)
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
명령줄에서 직접 HWP 파일을 변환할 수 있습니다:
|
|
15
24
|
|
|
16
25
|
```bash
|
|
17
|
-
|
|
26
|
+
# 전역 설치
|
|
27
|
+
npm install -g @ohah/hwpjs
|
|
28
|
+
|
|
29
|
+
# JSON 변환
|
|
30
|
+
hwpjs to-json document.hwp -o output.json --pretty
|
|
31
|
+
|
|
32
|
+
# Markdown 변환
|
|
33
|
+
hwpjs to-markdown document.hwp -o output.md --include-images
|
|
34
|
+
|
|
35
|
+
# 파일 정보 확인
|
|
36
|
+
hwpjs info document.hwp
|
|
37
|
+
|
|
38
|
+
# 이미지 추출
|
|
39
|
+
hwpjs extract-images document.hwp -o ./images
|
|
40
|
+
|
|
41
|
+
# 배치 변환
|
|
42
|
+
hwpjs batch ./documents -o ./output --format json --recursive
|
|
18
43
|
```
|
|
19
44
|
|
|
20
|
-
|
|
45
|
+
더 자세한 내용은 [CLI 가이드](https://ohah.github.io/hwpjs/guide/cli)를 참고하세요.
|
|
21
46
|
|
|
22
|
-
###
|
|
47
|
+
### Node.js
|
|
23
48
|
|
|
24
|
-
|
|
49
|
+
```typescript
|
|
50
|
+
import { readFileSync } from 'fs';
|
|
51
|
+
import { toJson, toMarkdown, fileHeader } from '@ohah/hwpjs';
|
|
25
52
|
|
|
26
|
-
|
|
53
|
+
// HWP 파일 읽기
|
|
54
|
+
const fileBuffer = readFileSync('./document.hwp');
|
|
27
55
|
|
|
28
|
-
|
|
56
|
+
// JSON으로 변환
|
|
57
|
+
const jsonString = toJson(fileBuffer);
|
|
58
|
+
const document = JSON.parse(jsonString);
|
|
59
|
+
console.log(document);
|
|
29
60
|
|
|
30
|
-
|
|
61
|
+
// Markdown으로 변환
|
|
62
|
+
const { markdown, images } = toMarkdown(fileBuffer, {
|
|
63
|
+
image: 'blob', // 또는 'base64'
|
|
64
|
+
use_html: true,
|
|
65
|
+
include_version: true,
|
|
66
|
+
include_page_info: true,
|
|
67
|
+
});
|
|
31
68
|
|
|
32
|
-
|
|
69
|
+
// FileHeader만 추출
|
|
70
|
+
const headerString = fileHeader(fileBuffer);
|
|
71
|
+
const header = JSON.parse(headerString);
|
|
72
|
+
console.log(header);
|
|
73
|
+
```
|
|
33
74
|
|
|
34
|
-
###
|
|
75
|
+
### Web (Browser)
|
|
35
76
|
|
|
36
|
-
|
|
77
|
+
```typescript
|
|
78
|
+
import { toJson, toMarkdown } from '@ohah/hwpjs';
|
|
37
79
|
|
|
38
|
-
|
|
80
|
+
// File input에서 HWP 파일 읽기
|
|
81
|
+
const fileInput = document.querySelector('input[type="file"]');
|
|
82
|
+
fileInput.addEventListener('change', async (e) => {
|
|
83
|
+
const file = e.target.files[0];
|
|
84
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
85
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
39
86
|
|
|
40
|
-
|
|
87
|
+
// JSON으로 변환
|
|
88
|
+
const jsonString = toJson(uint8Array);
|
|
89
|
+
const document = JSON.parse(jsonString);
|
|
41
90
|
|
|
42
|
-
|
|
91
|
+
// Markdown으로 변환 (base64 이미지 포함)
|
|
92
|
+
const { markdown } = toMarkdown(uint8Array, {
|
|
93
|
+
image: 'base64',
|
|
94
|
+
});
|
|
43
95
|
|
|
44
|
-
|
|
96
|
+
// 결과 표시
|
|
97
|
+
document.getElementById('output').innerHTML = markdown;
|
|
98
|
+
});
|
|
99
|
+
```
|
|
45
100
|
|
|
46
|
-
|
|
101
|
+
### React Native
|
|
47
102
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
103
|
+
```typescript
|
|
104
|
+
import { toJson, toMarkdown } from '@ohah/hwpjs';
|
|
105
|
+
import * as FileSystem from 'expo-file-system';
|
|
51
106
|
|
|
52
|
-
|
|
107
|
+
// HWP 파일 읽기
|
|
108
|
+
const fileUri = 'file:///path/to/document.hwp';
|
|
109
|
+
const base64 = await FileSystem.readAsStringAsync(fileUri, {
|
|
110
|
+
encoding: FileSystem.EncodingType.Base64,
|
|
111
|
+
});
|
|
112
|
+
const uint8Array = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
|
53
113
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
114
|
+
// JSON으로 변환
|
|
115
|
+
const jsonString = toJson(uint8Array);
|
|
116
|
+
const document = JSON.parse(jsonString);
|
|
57
117
|
|
|
58
|
-
|
|
118
|
+
// Markdown으로 변환
|
|
119
|
+
const { markdown, images } = toMarkdown(uint8Array, {
|
|
120
|
+
image: 'blob',
|
|
121
|
+
});
|
|
122
|
+
```
|
|
59
123
|
|
|
60
|
-
|
|
61
|
-
$ ava --verbose
|
|
124
|
+
## API
|
|
62
125
|
|
|
63
|
-
|
|
64
|
-
✔ sleep function from native code (201ms)
|
|
65
|
-
─
|
|
126
|
+
### `toJson(data: Buffer | Uint8Array): string`
|
|
66
127
|
|
|
67
|
-
|
|
68
|
-
|
|
128
|
+
HWP 파일을 JSON 문자열로 변환합니다.
|
|
129
|
+
|
|
130
|
+
**Parameters:**
|
|
131
|
+
- `data`: HWP 파일의 바이트 배열 (Buffer 또는 Uint8Array)
|
|
132
|
+
|
|
133
|
+
**Returns:**
|
|
134
|
+
- JSON 문자열 (파싱된 HWP 문서)
|
|
135
|
+
|
|
136
|
+
**Example:**
|
|
137
|
+
```typescript
|
|
138
|
+
const fileBuffer = readFileSync('./document.hwp');
|
|
139
|
+
const jsonString = toJson(fileBuffer);
|
|
140
|
+
const document = JSON.parse(jsonString);
|
|
69
141
|
```
|
|
70
142
|
|
|
71
|
-
|
|
143
|
+
### `toMarkdown(data: Buffer | Uint8Array, options?: ToMarkdownOptions): ToMarkdownResult`
|
|
144
|
+
|
|
145
|
+
HWP 파일을 Markdown 형식으로 변환합니다.
|
|
146
|
+
|
|
147
|
+
**Parameters:**
|
|
148
|
+
- `data`: HWP 파일의 바이트 배열 (Buffer 또는 Uint8Array)
|
|
149
|
+
- `options`: 변환 옵션 (선택)
|
|
150
|
+
- `image`: 이미지 형식 (`'base64'` 또는 `'blob'`, 기본값: `'blob'`)
|
|
151
|
+
- `use_html`: HTML 태그 사용 여부 (기본값: `false`)
|
|
152
|
+
- `include_version`: 버전 정보 포함 여부 (기본값: `false`)
|
|
153
|
+
- `include_page_info`: 페이지 정보 포함 여부 (기본값: `false`)
|
|
154
|
+
|
|
155
|
+
**Returns:**
|
|
156
|
+
- `ToMarkdownResult` 객체:
|
|
157
|
+
- `markdown`: Markdown 문자열
|
|
158
|
+
- `images`: 이미지 데이터 배열 (blob 형식인 경우)
|
|
159
|
+
|
|
160
|
+
**Example:**
|
|
161
|
+
```typescript
|
|
162
|
+
// Base64 이미지 포함
|
|
163
|
+
const { markdown } = toMarkdown(fileBuffer, {
|
|
164
|
+
image: 'base64',
|
|
165
|
+
use_html: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Blob 이미지 (별도 배열로 반환)
|
|
169
|
+
const { markdown, images } = toMarkdown(fileBuffer, {
|
|
170
|
+
image: 'blob',
|
|
171
|
+
});
|
|
172
|
+
// images 배열에서 이미지 데이터 사용
|
|
173
|
+
images.forEach(img => {
|
|
174
|
+
console.log(`Image ${img.id}: ${img.format}, ${img.data.length} bytes`);
|
|
175
|
+
});
|
|
176
|
+
```
|
|
72
177
|
|
|
73
|
-
|
|
178
|
+
### `fileHeader(data: Buffer | Uint8Array): string`
|
|
74
179
|
|
|
75
|
-
|
|
180
|
+
HWP 파일의 FileHeader만 추출하여 JSON 문자열로 반환합니다.
|
|
76
181
|
|
|
77
|
-
|
|
182
|
+
**Parameters:**
|
|
183
|
+
- `data`: HWP 파일의 바이트 배열 (Buffer 또는 Uint8Array)
|
|
78
184
|
|
|
79
|
-
|
|
80
|
-
|
|
185
|
+
**Returns:**
|
|
186
|
+
- JSON 문자열 (FileHeader 정보)
|
|
81
187
|
|
|
82
|
-
|
|
188
|
+
**Example:**
|
|
189
|
+
```typescript
|
|
190
|
+
const fileBuffer = readFileSync('./document.hwp');
|
|
191
|
+
const headerString = fileHeader(fileBuffer);
|
|
192
|
+
const header = JSON.parse(headerString);
|
|
193
|
+
console.log(header.version);
|
|
83
194
|
```
|
|
84
195
|
|
|
85
|
-
|
|
196
|
+
## 예제
|
|
197
|
+
|
|
198
|
+
더 자세한 예제는 [예제 디렉토리](../../examples)를 참고하세요.
|
|
199
|
+
|
|
200
|
+
- [Node.js 예제](../../examples/node)
|
|
201
|
+
- [Web 예제](../../examples/web)
|
|
202
|
+
- [React Native 예제](../../examples/react-native)
|
|
203
|
+
|
|
204
|
+
## 지원 플랫폼
|
|
205
|
+
|
|
206
|
+
### Node.js
|
|
207
|
+
- Windows (x64, x86, arm64)
|
|
208
|
+
- macOS (x64, arm64)
|
|
209
|
+
- Linux (x64)
|
|
210
|
+
|
|
211
|
+
### Web
|
|
212
|
+
- WASM (WebAssembly)
|
|
213
|
+
|
|
214
|
+
### React Native
|
|
215
|
+
- iOS
|
|
216
|
+
- Android
|
|
217
|
+
|
|
218
|
+
## 라이선스
|
|
86
219
|
|
|
87
|
-
|
|
220
|
+
MIT
|
|
@@ -1027,11 +1027,13 @@ private:
|
|
|
1027
1027
|
|
|
1028
1028
|
::rust::Box<::craby::hwpjs::bridging::Hwpjs> createHwpjs(::std::size_t id, ::rust::Str data_path) noexcept;
|
|
1029
1029
|
|
|
1030
|
-
::rust::
|
|
1030
|
+
::rust::Vec<::std::uint8_t> createVecFromSlice(::rust::Slice<::std::uint8_t const> data) noexcept;
|
|
1031
1031
|
|
|
1032
|
-
::rust::String
|
|
1032
|
+
::rust::String fileHeader(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> data);
|
|
1033
1033
|
|
|
1034
|
-
::
|
|
1034
|
+
::rust::String toJson(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> data);
|
|
1035
|
+
|
|
1036
|
+
::craby::hwpjs::bridging::ToMarkdownResult toMarkdown(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> data, ::craby::hwpjs::bridging::ToMarkdownOptions options);
|
|
1035
1037
|
} // namespace bridging
|
|
1036
1038
|
} // namespace hwpjs
|
|
1037
1039
|
} // namespace craby
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1102,11 +1102,13 @@ extern "C" {
|
|
|
1102
1102
|
|
|
1103
1103
|
::craby::hwpjs::bridging::Hwpjs *craby$hwpjs$bridging$cxxbridge1$190$create_hwpjs(::std::size_t id, ::rust::Str data_path) noexcept;
|
|
1104
1104
|
|
|
1105
|
-
|
|
1105
|
+
void craby$hwpjs$bridging$cxxbridge1$190$create_vec_from_slice(::rust::Slice<::std::uint8_t const> data, ::rust::Vec<::std::uint8_t> *return$) noexcept;
|
|
1106
1106
|
|
|
1107
|
-
::rust::repr::PtrLen craby$hwpjs$bridging$cxxbridge1$190$
|
|
1107
|
+
::rust::repr::PtrLen craby$hwpjs$bridging$cxxbridge1$190$hwpjs_file_header(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> *data, ::rust::String *return$) noexcept;
|
|
1108
1108
|
|
|
1109
|
-
::rust::repr::PtrLen craby$hwpjs$bridging$cxxbridge1$190$
|
|
1109
|
+
::rust::repr::PtrLen craby$hwpjs$bridging$cxxbridge1$190$hwpjs_to_json(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> *data, ::rust::String *return$) noexcept;
|
|
1110
|
+
|
|
1111
|
+
::rust::repr::PtrLen craby$hwpjs$bridging$cxxbridge1$190$hwpjs_to_markdown(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> *data, ::craby::hwpjs::bridging::ToMarkdownOptions *options, ::craby::hwpjs::bridging::ToMarkdownResult *return$) noexcept;
|
|
1110
1112
|
} // extern "C"
|
|
1111
1113
|
|
|
1112
1114
|
::std::size_t Hwpjs::layout::size() noexcept {
|
|
@@ -1121,8 +1123,14 @@ extern "C" {
|
|
|
1121
1123
|
return ::rust::Box<::craby::hwpjs::bridging::Hwpjs>::from_raw(craby$hwpjs$bridging$cxxbridge1$190$create_hwpjs(id, data_path));
|
|
1122
1124
|
}
|
|
1123
1125
|
|
|
1124
|
-
::rust::
|
|
1125
|
-
::rust::
|
|
1126
|
+
::rust::Vec<::std::uint8_t> createVecFromSlice(::rust::Slice<::std::uint8_t const> data) noexcept {
|
|
1127
|
+
::rust::MaybeUninit<::rust::Vec<::std::uint8_t>> return$;
|
|
1128
|
+
craby$hwpjs$bridging$cxxbridge1$190$create_vec_from_slice(data, &return$.value);
|
|
1129
|
+
return ::std::move(return$.value);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
::rust::String fileHeader(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> data) {
|
|
1133
|
+
::rust::ManuallyDrop<::rust::Vec<::std::uint8_t>> data$(::std::move(data));
|
|
1126
1134
|
::rust::MaybeUninit<::rust::String> return$;
|
|
1127
1135
|
::rust::repr::PtrLen error$ = craby$hwpjs$bridging$cxxbridge1$190$hwpjs_file_header(it_, &data$.value, &return$.value);
|
|
1128
1136
|
if (error$.ptr) {
|
|
@@ -1131,8 +1139,8 @@ extern "C" {
|
|
|
1131
1139
|
return ::std::move(return$.value);
|
|
1132
1140
|
}
|
|
1133
1141
|
|
|
1134
|
-
::rust::String toJson(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec
|
|
1135
|
-
::rust::ManuallyDrop<::rust::Vec
|
|
1142
|
+
::rust::String toJson(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> data) {
|
|
1143
|
+
::rust::ManuallyDrop<::rust::Vec<::std::uint8_t>> data$(::std::move(data));
|
|
1136
1144
|
::rust::MaybeUninit<::rust::String> return$;
|
|
1137
1145
|
::rust::repr::PtrLen error$ = craby$hwpjs$bridging$cxxbridge1$190$hwpjs_to_json(it_, &data$.value, &return$.value);
|
|
1138
1146
|
if (error$.ptr) {
|
|
@@ -1141,8 +1149,8 @@ extern "C" {
|
|
|
1141
1149
|
return ::std::move(return$.value);
|
|
1142
1150
|
}
|
|
1143
1151
|
|
|
1144
|
-
::craby::hwpjs::bridging::ToMarkdownResult toMarkdown(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec
|
|
1145
|
-
::rust::ManuallyDrop<::rust::Vec
|
|
1152
|
+
::craby::hwpjs::bridging::ToMarkdownResult toMarkdown(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> data, ::craby::hwpjs::bridging::ToMarkdownOptions options) {
|
|
1153
|
+
::rust::ManuallyDrop<::rust::Vec<::std::uint8_t>> data$(::std::move(data));
|
|
1146
1154
|
::rust::ManuallyDrop<::craby::hwpjs::bridging::ToMarkdownOptions> options$(::std::move(options));
|
|
1147
1155
|
::rust::MaybeUninit<::craby::hwpjs::bridging::ToMarkdownResult> return$;
|
|
1148
1156
|
::rust::repr::PtrLen error$ = craby$hwpjs$bridging$cxxbridge1$190$hwpjs_to_markdown(it_, &data$.value, &options$.value, &return$.value);
|
package/bin/hwpjs.js
ADDED
package/cpp/CxxHwpjsModule.cpp
CHANGED
|
@@ -60,7 +60,7 @@ jsi::Value CxxHwpjsModule::fileHeader(jsi::Runtime &rt,
|
|
|
60
60
|
throw jsi::JSError(rt, "Expected 1 argument");
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
auto arg0 = react::bridging::fromJs<rust::Vec<
|
|
63
|
+
auto arg0 = react::bridging::fromJs<rust::Vec<uint8_t>>(rt, args[0], callInvoker);
|
|
64
64
|
auto ret = craby::hwpjs::bridging::fileHeader(*it_, arg0);
|
|
65
65
|
|
|
66
66
|
return react::bridging::toJs(rt, ret);
|
|
@@ -84,7 +84,7 @@ jsi::Value CxxHwpjsModule::toJson(jsi::Runtime &rt,
|
|
|
84
84
|
throw jsi::JSError(rt, "Expected 1 argument");
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
auto arg0 = react::bridging::fromJs<rust::Vec<
|
|
87
|
+
auto arg0 = react::bridging::fromJs<rust::Vec<uint8_t>>(rt, args[0], callInvoker);
|
|
88
88
|
auto ret = craby::hwpjs::bridging::toJson(*it_, arg0);
|
|
89
89
|
|
|
90
90
|
return react::bridging::toJs(rt, ret);
|
|
@@ -108,7 +108,7 @@ jsi::Value CxxHwpjsModule::toMarkdown(jsi::Runtime &rt,
|
|
|
108
108
|
throw jsi::JSError(rt, "Expected 2 arguments");
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
auto arg0 = react::bridging::fromJs<rust::Vec<
|
|
111
|
+
auto arg0 = react::bridging::fromJs<rust::Vec<uint8_t>>(rt, args[0], callInvoker);
|
|
112
112
|
auto arg1 = react::bridging::fromJs<craby::hwpjs::bridging::ToMarkdownOptions>(rt, args[1], callInvoker);
|
|
113
113
|
auto ret = craby::hwpjs::bridging::toMarkdown(*it_, arg0, arg1);
|
|
114
114
|
|
|
@@ -4,12 +4,47 @@
|
|
|
4
4
|
#include "cxx.h"
|
|
5
5
|
#include "ffi.rs.h"
|
|
6
6
|
#include <react/bridging/Bridging.h>
|
|
7
|
+
#include <variant>
|
|
7
8
|
|
|
8
9
|
using namespace facebook;
|
|
9
10
|
|
|
11
|
+
namespace hwpjs {
|
|
12
|
+
|
|
13
|
+
class RustVecBuffer : public jsi::MutableBuffer {
|
|
14
|
+
public:
|
|
15
|
+
explicit RustVecBuffer(rust::Vec<uint8_t> vec)
|
|
16
|
+
: vec_(std::move(vec)) {}
|
|
17
|
+
|
|
18
|
+
~RustVecBuffer() override = default;
|
|
19
|
+
|
|
20
|
+
size_t size() const override {
|
|
21
|
+
return vec_.size();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
uint8_t* data() override {
|
|
25
|
+
return const_cast<uint8_t*>(vec_.data());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private:
|
|
29
|
+
rust::Vec<uint8_t> vec_;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
} // namespace hwpjs
|
|
33
|
+
|
|
10
34
|
namespace facebook {
|
|
11
35
|
namespace react {
|
|
12
36
|
|
|
37
|
+
template <>
|
|
38
|
+
struct Bridging<std::monostate> {
|
|
39
|
+
static std::monostate fromJs(jsi::Runtime& rt, const jsi::Value &value, std::shared_ptr<CallInvoker> callInvoker) {
|
|
40
|
+
return std::monostate{};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static jsi::Value toJs(jsi::Runtime& rt, const std::monostate& value) {
|
|
44
|
+
return jsi::Value::undefined();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
13
48
|
template <>
|
|
14
49
|
struct Bridging<rust::Str> {
|
|
15
50
|
static rust::Str fromJs(jsi::Runtime& rt, const jsi::Value &value, std::shared_ptr<CallInvoker> callInvoker) {
|
|
@@ -34,6 +69,24 @@ struct Bridging<rust::String> {
|
|
|
34
69
|
}
|
|
35
70
|
};
|
|
36
71
|
|
|
72
|
+
template <>
|
|
73
|
+
struct Bridging<rust::Vec<uint8_t>> {
|
|
74
|
+
static rust::Vec<uint8_t> fromJs(jsi::Runtime& rt, const jsi::Value &value, std::shared_ptr<CallInvoker> callInvoker) {
|
|
75
|
+
auto arrayBuffer = value.asObject(rt).getArrayBuffer(rt);
|
|
76
|
+
uint8_t* data = arrayBuffer.data(rt);
|
|
77
|
+
size_t size = arrayBuffer.size(rt);
|
|
78
|
+
rust::Slice<const uint8_t> slice{data, size};
|
|
79
|
+
rust::Vec<uint8_t> vec = craby::hwpjs::bridging::createVecFromSlice(slice);
|
|
80
|
+
|
|
81
|
+
return vec;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static jsi::Value toJs(jsi::Runtime& rt, const rust::Vec<uint8_t>& vec) {
|
|
85
|
+
auto buffer = std::make_shared<hwpjs::RustVecBuffer>(std::move(vec));
|
|
86
|
+
return jsi::ArrayBuffer(rt, buffer);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
37
90
|
template <typename T>
|
|
38
91
|
struct Bridging<rust::Vec<T>> {
|
|
39
92
|
static rust::Vec<T> fromJs(jsi::Runtime& rt, const jsi::Value &value, std::shared_ptr<CallInvoker> callInvoker) {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.batchCommand = batchCommand;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
// CLI는 빌드된 NAPI 모듈을 사용합니다
|
|
7
|
+
// @ts-ignore - 런타임에 dist/index.js에서 로드됨 (빌드 후 경로: ../../index)
|
|
8
|
+
const { toJson, toMarkdown } = require('../../index');
|
|
9
|
+
function batchCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('batch')
|
|
12
|
+
.description('Batch convert HWP files in a directory')
|
|
13
|
+
.argument('<input-dir>', 'Input directory containing HWP files')
|
|
14
|
+
.option('-o, --output-dir <dir>', 'Output directory (default: ./output)', './output')
|
|
15
|
+
.option('--format <format>', 'Output format (json or markdown)', 'json')
|
|
16
|
+
.option('-r, --recursive', 'Process subdirectories recursively')
|
|
17
|
+
.option('--pretty', 'Pretty print JSON (only for json format)')
|
|
18
|
+
.option('--include-images', 'Include images as base64 (only for markdown format)')
|
|
19
|
+
.action((inputDir, options) => {
|
|
20
|
+
try {
|
|
21
|
+
const outputDir = options.outputDir || './output';
|
|
22
|
+
const format = options.format || 'json';
|
|
23
|
+
// Create output directory
|
|
24
|
+
if (!require('fs').existsSync(outputDir)) {
|
|
25
|
+
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
// Find all HWP files
|
|
28
|
+
const hwpFiles = [];
|
|
29
|
+
function findHwpFiles(dir, basePath = '') {
|
|
30
|
+
const entries = (0, fs_1.readdirSync)(dir);
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const fullPath = (0, path_1.join)(dir, entry);
|
|
33
|
+
const stat = (0, fs_1.statSync)(fullPath);
|
|
34
|
+
if (stat.isDirectory() && options.recursive) {
|
|
35
|
+
findHwpFiles(fullPath, (0, path_1.join)(basePath, entry));
|
|
36
|
+
}
|
|
37
|
+
else if (stat.isFile() && (0, path_1.extname)(entry).toLowerCase() === '.hwp') {
|
|
38
|
+
hwpFiles.push(fullPath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
findHwpFiles(inputDir);
|
|
43
|
+
if (hwpFiles.length === 0) {
|
|
44
|
+
console.log('No HWP files found in the specified directory');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
console.log(`Found ${hwpFiles.length} HWP file(s)`);
|
|
48
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
49
|
+
let successCount = 0;
|
|
50
|
+
let errorCount = 0;
|
|
51
|
+
// Process each file
|
|
52
|
+
for (const filePath of hwpFiles) {
|
|
53
|
+
try {
|
|
54
|
+
const fileName = (0, path_1.basename)(filePath, '.hwp');
|
|
55
|
+
const data = (0, fs_1.readFileSync)(filePath);
|
|
56
|
+
let outputContent;
|
|
57
|
+
let outputExt;
|
|
58
|
+
if (format === 'json') {
|
|
59
|
+
const jsonString = toJson(data);
|
|
60
|
+
if (options.pretty) {
|
|
61
|
+
const json = JSON.parse(jsonString);
|
|
62
|
+
outputContent = JSON.stringify(json, null, 2);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
outputContent = jsonString;
|
|
66
|
+
}
|
|
67
|
+
outputExt = 'json';
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const result = toMarkdown(data, {
|
|
71
|
+
image: options.includeImages ? 'base64' : 'blob',
|
|
72
|
+
});
|
|
73
|
+
outputContent = result.markdown;
|
|
74
|
+
outputExt = 'md';
|
|
75
|
+
}
|
|
76
|
+
const outputPath = (0, path_1.join)(outputDir, `${fileName}.${outputExt}`);
|
|
77
|
+
(0, fs_1.writeFileSync)(outputPath, outputContent, 'utf-8');
|
|
78
|
+
console.log(`✓ ${filePath} → ${outputPath}`);
|
|
79
|
+
successCount++;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(`✗ ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
83
|
+
errorCount++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
87
|
+
console.log(`✓ Success: ${successCount}`);
|
|
88
|
+
if (errorCount > 0) {
|
|
89
|
+
console.log(`✗ Errors: ${errorCount}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error('Error:', error instanceof Error ? error.message : String(error));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractImagesCommand = extractImagesCommand;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
// CLI는 빌드된 NAPI 모듈을 사용합니다
|
|
7
|
+
// @ts-ignore - 런타임에 dist/index.js에서 로드됨 (빌드 후 경로: ../../index)
|
|
8
|
+
const { toMarkdown } = require('../../index');
|
|
9
|
+
function extractImagesCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('extract-images')
|
|
12
|
+
.description('Extract images from HWP file')
|
|
13
|
+
.argument('<input>', 'Input HWP file path')
|
|
14
|
+
.option('-o, --output-dir <dir>', 'Output directory for images (default: ./images)', './images')
|
|
15
|
+
.option('--format <format>', 'Image format filter (jpg, png, bmp, or all)', 'all')
|
|
16
|
+
.action((input, options) => {
|
|
17
|
+
try {
|
|
18
|
+
// Read HWP file
|
|
19
|
+
const data = (0, fs_1.readFileSync)(input);
|
|
20
|
+
// Convert to Markdown with blob images
|
|
21
|
+
const result = toMarkdown(data, {
|
|
22
|
+
image: 'blob',
|
|
23
|
+
});
|
|
24
|
+
if (result.images.length === 0) {
|
|
25
|
+
console.log('No images found in HWP file');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Create output directory
|
|
29
|
+
const outputDir = options.outputDir || './images';
|
|
30
|
+
if (!(0, fs_1.existsSync)(outputDir)) {
|
|
31
|
+
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
// Filter images by format if specified
|
|
34
|
+
const formatFilter = options.format?.toLowerCase();
|
|
35
|
+
const imagesToExtract = formatFilter === 'all'
|
|
36
|
+
? result.images
|
|
37
|
+
: result.images.filter((img) => img.format.toLowerCase() === formatFilter);
|
|
38
|
+
if (imagesToExtract.length === 0) {
|
|
39
|
+
console.log(`No images found with format: ${formatFilter}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Extract images
|
|
43
|
+
let extractedCount = 0;
|
|
44
|
+
for (const image of imagesToExtract) {
|
|
45
|
+
const extension = image.format || 'jpg';
|
|
46
|
+
const filename = `${image.id}.${extension}`;
|
|
47
|
+
const filepath = (0, path_1.join)(outputDir, filename);
|
|
48
|
+
(0, fs_1.writeFileSync)(filepath, image.data);
|
|
49
|
+
extractedCount++;
|
|
50
|
+
console.log(`✓ Extracted: ${filepath} (${image.data.length} bytes)`);
|
|
51
|
+
}
|
|
52
|
+
console.log(`\n✓ Extracted ${extractedCount} image(s) to ${outputDir}`);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error('Error:', error instanceof Error ? error.message : String(error));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|