@ohah/hwpjs 0.1.0-rc.1 → 0.1.0-rc.10

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.
Files changed (51) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +230 -49
  3. package/android/src/main/jni/include/cxx.h +1 -0
  4. package/android/src/main/jni/include/ffi.rs.h +5 -3
  5. package/android/src/main/jni/libs/arm64-v8a/libhwpjs-prebuilt.a +0 -0
  6. package/android/src/main/jni/libs/armeabi-v7a/libhwpjs-prebuilt.a +0 -0
  7. package/android/src/main/jni/libs/x86/libhwpjs-prebuilt.a +0 -0
  8. package/android/src/main/jni/libs/x86_64/libhwpjs-prebuilt.a +0 -0
  9. package/android/src/main/jni/src/ffi.rs.cc +17 -9
  10. package/bin/hwpjs.js +3 -0
  11. package/cpp/CxxHwpjsModule.cpp +3 -3
  12. package/cpp/bridging-generated.hpp +55 -0
  13. package/dist/cli/commands/batch.js +115 -0
  14. package/dist/cli/commands/extract-images.js +59 -0
  15. package/dist/cli/commands/info.js +59 -0
  16. package/dist/cli/commands/to-html.js +64 -0
  17. package/dist/cli/commands/to-json.js +46 -0
  18. package/dist/cli/commands/to-markdown.js +86 -0
  19. package/dist/cli/index.js +31 -0
  20. package/dist/hwpjs.darwin-arm64.node +0 -0
  21. package/dist/hwpjs.darwin-x64.node +0 -0
  22. package/dist/hwpjs.linux-x64-gnu.node +0 -0
  23. package/dist/hwpjs.wasi-browser.js +1 -0
  24. package/dist/hwpjs.wasi.cjs +2 -1
  25. package/dist/hwpjs.wasm32-wasi.wasm +0 -0
  26. package/dist/hwpjs.win32-arm64-msvc.node +0 -0
  27. package/dist/hwpjs.win32-ia32-msvc.node +0 -0
  28. package/dist/hwpjs.win32-x64-msvc.node +0 -0
  29. package/dist/index.d.ts +41 -0
  30. package/dist/index.js +59 -54
  31. package/dist/react-native/index.cjs +1 -1
  32. package/dist/react-native/index.cjs.map +1 -1
  33. package/dist/react-native/index.d.cts +3 -3
  34. package/dist/react-native/index.d.mts +3 -3
  35. package/dist/react-native/index.mjs +1 -1
  36. package/dist/react-native/index.mjs.map +1 -1
  37. package/fonts/NotoSansKR-Regular.ttf +0 -0
  38. package/fonts/README.md +35 -0
  39. package/ios/framework/libhwpjs.xcframework/ios-arm64/libhwpjs-prebuilt.a +0 -0
  40. package/ios/framework/libhwpjs.xcframework/ios-arm64_x86_64-simulator/libhwpjs-prebuilt.a +0 -0
  41. package/ios/include/cxx.h +1 -0
  42. package/ios/include/ffi.rs.h +5 -3
  43. package/ios/src/ffi.rs.cc +17 -9
  44. package/package.json +24 -13
  45. package/android/src/main/jni/libs/arm64-v8a/libreactnative-prebuilt.a +0 -0
  46. package/android/src/main/jni/libs/armeabi-v7a/libreactnative-prebuilt.a +0 -0
  47. package/android/src/main/jni/libs/x86/libreactnative-prebuilt.a +0 -0
  48. package/android/src/main/jni/libs/x86_64/libreactnative-prebuilt.a +0 -0
  49. package/ios/framework/libreactnative.xcframework/Info.plist +0 -44
  50. package/ios/framework/libreactnative.xcframework/ios-arm64/libreactnative-prebuilt.a +0 -0
  51. package/ios/framework/libreactnative.xcframework/ios-arm64_x86_64-simulator/libreactnative-prebuilt.a +0 -0
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 N-API for Rust
3
+ Copyright (c) 2021 ohah <bookyoon173@gmail.com>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,87 +1,268 @@
1
- # `@napi-rs/package-template`
1
+ # @ohah/hwpjs
2
2
 
3
- ![https://github.com/napi-rs/package-template/actions](https://github.com/napi-rs/package-template/workflows/CI/badge.svg)
3
+ HWP parser for Node.js, Web, and React Native
4
4
 
5
- > Template project for writing node packages with napi-rs.
5
+ 한글과컴퓨터의 한/글 문서 파일(.hwp)을 읽고 파싱하는 라이브러리입니다. Rust로 구현된 핵심 로직을 Node.js, Web, React Native 환경에서 사용할 수 있도록 제공합니다.
6
6
 
7
- # Usage
7
+ ## 설치
8
8
 
9
- 1. Click **Use this template**.
10
- 2. **Clone** your project.
11
- 3. Run `yarn install` to install dependencies.
12
- 4. Run `yarn napi rename -n [@your-scope/package-name] -b [binary-name]` command under the project folder to rename your package.
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
+ ## 사용법
13
20
 
14
- ## Install this test package
21
+ ### CLI (Command Line Interface)
22
+
23
+ 명령줄에서 직접 HWP 파일을 변환할 수 있습니다:
15
24
 
16
25
  ```bash
17
- yarn add @napi-rs/package-template
18
- ```
26
+ # 전역 설치
27
+ npm install -g @ohah/hwpjs
19
28
 
20
- ## Ability
29
+ # JSON 변환
30
+ hwpjs to-json document.hwp -o output.json --pretty
21
31
 
22
- ### Build
32
+ # Markdown 변환
33
+ hwpjs to-markdown document.hwp -o output.md --include-images
23
34
 
24
- After `yarn build/npm run build` command, you can see `package-template.[darwin|win32|linux].node` file in project root. This is the native addon built from [lib.rs](./src/lib.rs).
35
+ # PDF 변환 (폰트 디렉터리 지정 권장)
36
+ hwpjs to-pdf document.hwp -o output.pdf --font-dir ./fonts
37
+ # 이미지 미포함 PDF
38
+ hwpjs to-pdf document.hwp -o output.pdf --font-dir ./fonts --no-embed-images
25
39
 
26
- ### Test
40
+ # 파일 정보 확인
41
+ hwpjs info document.hwp
27
42
 
28
- With [ava](https://github.com/avajs/ava), run `yarn test/npm run test` to testing native addon. You can also switch to another testing framework if you want.
43
+ # 이미지 추출
44
+ hwpjs extract-images document.hwp -o ./images
29
45
 
30
- ### CI
46
+ # 배치 변환
47
+ hwpjs batch ./documents -o ./output --format json --recursive
48
+ # 배치 PDF 변환
49
+ hwpjs batch ./documents -o ./output --format pdf --font-dir ./fonts
50
+ ```
31
51
 
32
- With GitHub Actions, each commit and pull request will be built and tested automatically in [`node@20`, `@node22`] x [`macOS`, `Linux`, `Windows`] matrix. You will never be afraid of the native addon broken in these platforms.
52
+ 자세한 내용은 [CLI 가이드](https://ohah.github.io/hwpjs/guide/cli)를 참고하세요.
53
+
54
+ ### Node.js
55
+
56
+ ```typescript
57
+ import { readFileSync } from 'fs';
58
+ import { toJson, toMarkdown, toPdf, fileHeader } from '@ohah/hwpjs';
59
+
60
+ // HWP 파일 읽기
61
+ const fileBuffer = readFileSync('./document.hwp');
62
+
63
+ // PDF로 변환 (font_dir에 TTF/OTF 폰트 디렉터리 지정)
64
+ const pdfBuffer = toPdf(fileBuffer, {
65
+ font_dir: './fonts',
66
+ embed_images: true,
67
+ });
68
+ require('fs').writeFileSync('./output.pdf', pdfBuffer);
69
+
70
+ // JSON으로 변환
71
+ const jsonString = toJson(fileBuffer);
72
+ const document = JSON.parse(jsonString);
73
+ console.log(document);
74
+
75
+ // Markdown으로 변환
76
+ const { markdown, images } = toMarkdown(fileBuffer, {
77
+ image: 'blob', // 또는 'base64'
78
+ use_html: true,
79
+ include_version: true,
80
+ include_page_info: true,
81
+ });
82
+
83
+ // FileHeader만 추출
84
+ const headerString = fileHeader(fileBuffer);
85
+ const header = JSON.parse(headerString);
86
+ console.log(header);
87
+ ```
33
88
 
34
- ### Release
89
+ ### Web (Browser)
35
90
 
36
- Release native package is very difficult in old days. Native packages may ask developers who use it to install `build toolchain` like `gcc/llvm`, `node-gyp` or something more.
91
+ ```typescript
92
+ import { toJson, toMarkdown } from '@ohah/hwpjs';
37
93
 
38
- With `GitHub actions`, we can easily prebuild a `binary` for major platforms. And with `N-API`, we should never be afraid of **ABI Compatible**.
94
+ // File input에서 HWP 파일 읽기
95
+ const fileInput = document.querySelector('input[type="file"]');
96
+ fileInput.addEventListener('change', async (e) => {
97
+ const file = e.target.files[0];
98
+ const arrayBuffer = await file.arrayBuffer();
99
+ const uint8Array = new Uint8Array(arrayBuffer);
39
100
 
40
- The other problem is how to deliver prebuild `binary` to users. Downloading it in `postinstall` script is a common way that most packages do it right now. The problem with this solution is it introduced many other packages to download binary that has not been used by `runtime codes`. The other problem is some users may not easily download the binary from `GitHub/CDN` if they are behind a private network (But in most cases, they have a private NPM mirror).
101
+ // JSON으로 변환
102
+ const jsonString = toJson(uint8Array);
103
+ const document = JSON.parse(jsonString);
41
104
 
42
- In this package, we choose a better way to solve this problem. We release different `npm packages` for different platforms. And add it to `optionalDependencies` before releasing the `Major` package to npm.
105
+ // Markdown으로 변환 (base64 이미지 포함)
106
+ const { markdown } = toMarkdown(uint8Array, {
107
+ image: 'base64',
108
+ });
43
109
 
44
- `NPM` will choose which native package should download from `registry` automatically. You can see [npm](./npm) dir for details. And you can also run `yarn add @napi-rs/package-template` to see how it works.
110
+ // 결과 표시
111
+ document.getElementById('output').innerHTML = markdown;
112
+ });
113
+ ```
45
114
 
46
- ## Develop requirements
115
+ ### React Native
116
+
117
+ ```typescript
118
+ import { toJson, toMarkdown, toPdf } from '@ohah/hwpjs';
119
+ import * as FileSystem from 'expo-file-system';
120
+
121
+ // HWP 파일 읽기
122
+ const fileUri = 'file:///path/to/document.hwp';
123
+ const base64 = await FileSystem.readAsStringAsync(fileUri, {
124
+ encoding: FileSystem.EncodingType.Base64,
125
+ });
126
+ const uint8Array = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
127
+
128
+ // JSON으로 변환
129
+ const jsonString = toJson(uint8Array);
130
+ const document = JSON.parse(jsonString);
131
+
132
+ // Markdown으로 변환
133
+ const { markdown, images } = toMarkdown(uint8Array, {
134
+ image: 'blob',
135
+ });
136
+
137
+ // PDF로 변환 (한글 폰트 경로 필요)
138
+ // 패키지에 포함된 폰트: node_modules/@ohah/hwpjs/fonts/ 를 앱 문서 디렉터리 등에 복사한 뒤 그 경로를 넘기세요.
139
+ const pdfBuffer = toPdf(uint8Array, {
140
+ fontDir: '/path/to/fonts', // NotoSansKR-Regular.ttf 가 있는 디렉터리
141
+ embedImages: true,
142
+ });
143
+ await FileSystem.writeAsStringAsync(
144
+ `${FileSystem.documentDirectory}output.pdf`,
145
+ Buffer.from(pdfBuffer).toString('base64'),
146
+ { encoding: FileSystem.EncodingType.Base64 }
147
+ );
148
+ ```
47
149
 
48
- - Install the latest `Rust`
49
- - Install `Node.js@10+` which fully supported `Node-API`
50
- - Install `yarn@1.x`
150
+ ## API
51
151
 
52
- ## Test in local
152
+ ### `toJson(data: Buffer | Uint8Array): string`
53
153
 
54
- - yarn
55
- - yarn build
56
- - yarn test
154
+ HWP 파일을 JSON 문자열로 변환합니다.
57
155
 
58
- And you will see:
156
+ **Parameters:**
157
+ - `data`: HWP 파일의 바이트 배열 (Buffer 또는 Uint8Array)
59
158
 
60
- ```bash
61
- $ ava --verbose
159
+ **Returns:**
160
+ - JSON 문자열 (파싱된 HWP 문서)
62
161
 
63
- ✔ sync function from native code
64
- ✔ sleep function from native code (201ms)
65
-
162
+ **Example:**
163
+ ```typescript
164
+ const fileBuffer = readFileSync('./document.hwp');
165
+ const jsonString = toJson(fileBuffer);
166
+ const document = JSON.parse(jsonString);
167
+ ```
66
168
 
67
- 2 tests passed
68
- ✨ Done in 1.12s.
169
+ ### `toMarkdown(data: Buffer | Uint8Array, options?: ToMarkdownOptions): ToMarkdownResult`
170
+
171
+ HWP 파일을 Markdown 형식으로 변환합니다.
172
+
173
+ **Parameters:**
174
+ - `data`: HWP 파일의 바이트 배열 (Buffer 또는 Uint8Array)
175
+ - `options`: 변환 옵션 (선택)
176
+ - `image`: 이미지 형식 (`'base64'` 또는 `'blob'`, 기본값: `'blob'`)
177
+ - `use_html`: HTML 태그 사용 여부 (기본값: `false`)
178
+ - `include_version`: 버전 정보 포함 여부 (기본값: `false`)
179
+ - `include_page_info`: 페이지 정보 포함 여부 (기본값: `false`)
180
+
181
+ **Returns:**
182
+ - `ToMarkdownResult` 객체:
183
+ - `markdown`: Markdown 문자열
184
+ - `images`: 이미지 데이터 배열 (blob 형식인 경우)
185
+
186
+ **Example:**
187
+ ```typescript
188
+ // Base64 이미지 포함
189
+ const { markdown } = toMarkdown(fileBuffer, {
190
+ image: 'base64',
191
+ use_html: true,
192
+ });
193
+
194
+ // Blob 이미지 (별도 배열로 반환)
195
+ const { markdown, images } = toMarkdown(fileBuffer, {
196
+ image: 'blob',
197
+ });
198
+ // images 배열에서 이미지 데이터 사용
199
+ images.forEach(img => {
200
+ console.log(`Image ${img.id}: ${img.format}, ${img.data.length} bytes`);
201
+ });
69
202
  ```
70
203
 
71
- ## Release package
204
+ ### `fileHeader(data: Buffer | Uint8Array): string`
72
205
 
73
- Ensure you have set your **NPM_TOKEN** in the `GitHub` project setting.
206
+ HWP 파일의 FileHeader만 추출하여 JSON 문자열로 반환합니다.
74
207
 
75
- In `Settings -> Secrets`, add **NPM_TOKEN** into it.
208
+ **Parameters:**
209
+ - `data`: HWP 파일의 바이트 배열 (Buffer 또는 Uint8Array)
76
210
 
77
- When you want to release the package:
211
+ **Returns:**
212
+ - JSON 문자열 (FileHeader 정보)
78
213
 
79
- ```bash
80
- npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]
214
+ **Example:**
215
+ ```typescript
216
+ const fileBuffer = readFileSync('./document.hwp');
217
+ const headerString = fileHeader(fileBuffer);
218
+ const header = JSON.parse(headerString);
219
+ console.log(header.version);
220
+ ```
221
+
222
+ ### `toPdf(data: Buffer | Uint8Array, options?: ToPdfOptions): Buffer`
223
+
224
+ HWP 파일을 PDF 바이트로 변환합니다. 한글 등이 포함된 문서는 `font_dir`에 한글 지원 폰트(예: Noto Sans KR)를 넣어야 합니다.
81
225
 
82
- git push
226
+ **Parameters:**
227
+ - `data`: HWP 파일의 바이트 배열 (Buffer 또는 Uint8Array)
228
+ - `options`: 변환 옵션 (선택)
229
+ - `font_dir`: TTF/OTF 폰트가 있는 디렉터리 경로 (신뢰할 수 있는 경로만 사용)
230
+ - `embed_images`: PDF에 이미지 임베드 여부 (기본값: `true`)
231
+
232
+ **Returns:**
233
+ - PDF 파일 내용 (Buffer)
234
+
235
+ **Example:**
236
+ ```typescript
237
+ const pdfBuffer = toPdf(fileBuffer, {
238
+ font_dir: './fonts',
239
+ embed_images: true,
240
+ });
241
+ require('fs').writeFileSync('./output.pdf', pdfBuffer);
83
242
  ```
84
243
 
85
- GitHub actions will do the rest job for you.
244
+ ## 예제
245
+
246
+ 더 자세한 예제는 [예제 디렉토리](../../examples)를 참고하세요.
247
+
248
+ - [Node.js 예제](../../examples/node)
249
+ - [Web 예제](../../examples/web)
250
+ - [React Native 예제](../../examples/react-native)
251
+
252
+ ## 지원 플랫폼
253
+
254
+ ### Node.js
255
+ - Windows (x64, x86, arm64)
256
+ - macOS (x64, arm64)
257
+ - Linux (x64)
258
+
259
+ ### Web
260
+ - WASM (WebAssembly)
261
+
262
+ ### React Native
263
+ - iOS
264
+ - Android
265
+
266
+ ## 라이선스
86
267
 
87
- > WARN: Don't run `npm publish` manually.
268
+ MIT
@@ -247,6 +247,7 @@ public:
247
247
  using pointer = typename std::add_pointer<T>::type;
248
248
  using reference = typename std::add_lvalue_reference<T>::type;
249
249
  using element_type = T;
250
+ using element_type = T;
250
251
 
251
252
  reference operator*() const noexcept;
252
253
  pointer operator->() const noexcept;
@@ -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::String fileHeader(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<double> data);
1030
+ ::rust::Vec<::std::uint8_t> createVecFromSlice(::rust::Slice<::std::uint8_t const> data) noexcept;
1031
1031
 
1032
- ::rust::String toJson(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<double> data);
1032
+ ::rust::String fileHeader(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<::std::uint8_t> data);
1033
1033
 
1034
- ::craby::hwpjs::bridging::ToMarkdownResult toMarkdown(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<double> data, ::craby::hwpjs::bridging::ToMarkdownOptions options);
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
@@ -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
- ::rust::repr::PtrLen craby$hwpjs$bridging$cxxbridge1$190$hwpjs_file_header(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<double> *data, ::rust::String *return$) noexcept;
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$hwpjs_to_json(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<double> *data, ::rust::String *return$) noexcept;
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$hwpjs_to_markdown(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<double> *data, ::craby::hwpjs::bridging::ToMarkdownOptions *options, ::craby::hwpjs::bridging::ToMarkdownResult *return$) noexcept;
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::String fileHeader(::craby::hwpjs::bridging::Hwpjs &it_, ::rust::Vec<double> data) {
1125
- ::rust::ManuallyDrop<::rust::Vec<double>> data$(::std::move(data));
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<double> data) {
1135
- ::rust::ManuallyDrop<::rust::Vec<double>> data$(::std::move(data));
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<double> data, ::craby::hwpjs::bridging::ToMarkdownOptions options) {
1145
- ::rust::ManuallyDrop<::rust::Vec<double>> data$(::std::move(data));
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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../dist/cli/index.js');
@@ -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<double>>(rt, args[0], callInvoker);
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<double>>(rt, args[0], callInvoker);
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<double>>(rt, args[0], callInvoker);
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,26 @@ 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::Vec<uint8_t> vec;
79
+ vec.reserve(size);
80
+
81
+ std::memcpy(vec.data(), data, size);
82
+
83
+ return vec;
84
+ }
85
+
86
+ static jsi::Value toJs(jsi::Runtime& rt, const rust::Vec<uint8_t>& vec) {
87
+ auto buffer = std::make_shared<hwpjs::RustVecBuffer>(std::move(vec));
88
+ return jsi::ArrayBuffer(rt, buffer);
89
+ }
90
+ };
91
+
37
92
  template <typename T>
38
93
  struct Bridging<rust::Vec<T>> {
39
94
  static rust::Vec<T> fromJs(jsi::Runtime& rt, const jsi::Value &value, std::shared_ptr<CallInvoker> callInvoker) {
@@ -0,0 +1,115 @@
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, toHtml } = 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, markdown, html)', '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
+ .option('--images-dir <dir>', 'Directory to save images (only for html format, default: images)')
20
+ .action((inputDir, options) => {
21
+ try {
22
+ const outputDir = options.outputDir || './output';
23
+ const format = options.format || 'json';
24
+ // Create output directory
25
+ if (!require('fs').existsSync(outputDir)) {
26
+ (0, fs_1.mkdirSync)(outputDir, { recursive: true });
27
+ }
28
+ // Find all HWP files
29
+ const hwpFiles = [];
30
+ function findHwpFiles(dir, basePath = '') {
31
+ const entries = (0, fs_1.readdirSync)(dir);
32
+ for (const entry of entries) {
33
+ const fullPath = (0, path_1.join)(dir, entry);
34
+ const stat = (0, fs_1.statSync)(fullPath);
35
+ if (stat.isDirectory() && options.recursive) {
36
+ findHwpFiles(fullPath, (0, path_1.join)(basePath, entry));
37
+ }
38
+ else if (stat.isFile() && (0, path_1.extname)(entry).toLowerCase() === '.hwp') {
39
+ hwpFiles.push(fullPath);
40
+ }
41
+ }
42
+ }
43
+ findHwpFiles(inputDir);
44
+ if (hwpFiles.length === 0) {
45
+ console.log('No HWP files found in the specified directory');
46
+ return;
47
+ }
48
+ console.log(`Found ${hwpFiles.length} HWP file(s)`);
49
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
50
+ let successCount = 0;
51
+ let errorCount = 0;
52
+ // Process each file
53
+ for (const filePath of hwpFiles) {
54
+ try {
55
+ const fileName = (0, path_1.basename)(filePath, '.hwp');
56
+ const data = (0, fs_1.readFileSync)(filePath);
57
+ let outputContent;
58
+ let outputExt;
59
+ if (format === 'json') {
60
+ const jsonString = toJson(data);
61
+ if (options.pretty) {
62
+ const json = JSON.parse(jsonString);
63
+ outputContent = JSON.stringify(json, null, 2);
64
+ }
65
+ else {
66
+ outputContent = jsonString;
67
+ }
68
+ outputExt = 'json';
69
+ }
70
+ else if (format === 'html') {
71
+ // Determine image output directory for HTML
72
+ let imageOutputDir;
73
+ if (options.imagesDir) {
74
+ const imagesDir = (0, path_1.join)(outputDir, options.imagesDir);
75
+ if (!require('fs').existsSync(imagesDir)) {
76
+ (0, fs_1.mkdirSync)(imagesDir, { recursive: true });
77
+ }
78
+ imageOutputDir = imagesDir;
79
+ }
80
+ // If imagesDir is not specified, images will be embedded as base64
81
+ outputContent = toHtml(data, {
82
+ image_output_dir: imageOutputDir,
83
+ html_output_dir: outputDir,
84
+ });
85
+ outputExt = 'html';
86
+ }
87
+ else {
88
+ const result = toMarkdown(data, {
89
+ image: options.includeImages ? 'base64' : 'blob',
90
+ });
91
+ outputContent = result.markdown;
92
+ outputExt = 'md';
93
+ }
94
+ const outputPath = (0, path_1.join)(outputDir, `${fileName}.${outputExt}`);
95
+ (0, fs_1.writeFileSync)(outputPath, outputContent, 'utf-8');
96
+ console.log(`\u2713 ${filePath} \u2192 ${outputPath}`);
97
+ successCount++;
98
+ }
99
+ catch (error) {
100
+ console.error(`\u2717 ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
101
+ errorCount++;
102
+ }
103
+ }
104
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
105
+ console.log(`\u2713 Success: ${successCount}`);
106
+ if (errorCount > 0) {
107
+ console.log(`\u2717 Errors: ${errorCount}`);
108
+ }
109
+ }
110
+ catch (error) {
111
+ console.error('Error:', error instanceof Error ? error.message : String(error));
112
+ process.exit(1);
113
+ }
114
+ });
115
+ }