@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 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,220 @@
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
+ ## 사용법
20
+
21
+ ### CLI (Command Line Interface)
13
22
 
14
- ## Install this test package
23
+ 명령줄에서 직접 HWP 파일을 변환할 수 있습니다:
15
24
 
16
25
  ```bash
17
- yarn add @napi-rs/package-template
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
- ## Ability
45
+ 자세한 내용은 [CLI 가이드](https://ohah.github.io/hwpjs/guide/cli)를 참고하세요.
21
46
 
22
- ### Build
47
+ ### Node.js
23
48
 
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).
49
+ ```typescript
50
+ import { readFileSync } from 'fs';
51
+ import { toJson, toMarkdown, fileHeader } from '@ohah/hwpjs';
25
52
 
26
- ### Test
53
+ // HWP 파일 읽기
54
+ const fileBuffer = readFileSync('./document.hwp');
27
55
 
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.
56
+ // JSON으로 변환
57
+ const jsonString = toJson(fileBuffer);
58
+ const document = JSON.parse(jsonString);
59
+ console.log(document);
29
60
 
30
- ### CI
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
- 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.
69
+ // FileHeader만 추출
70
+ const headerString = fileHeader(fileBuffer);
71
+ const header = JSON.parse(headerString);
72
+ console.log(header);
73
+ ```
33
74
 
34
- ### Release
75
+ ### Web (Browser)
35
76
 
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.
77
+ ```typescript
78
+ import { toJson, toMarkdown } from '@ohah/hwpjs';
37
79
 
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**.
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
- 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).
87
+ // JSON으로 변환
88
+ const jsonString = toJson(uint8Array);
89
+ const document = JSON.parse(jsonString);
41
90
 
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.
91
+ // Markdown으로 변환 (base64 이미지 포함)
92
+ const { markdown } = toMarkdown(uint8Array, {
93
+ image: 'base64',
94
+ });
43
95
 
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.
96
+ // 결과 표시
97
+ document.getElementById('output').innerHTML = markdown;
98
+ });
99
+ ```
45
100
 
46
- ## Develop requirements
101
+ ### React Native
47
102
 
48
- - Install the latest `Rust`
49
- - Install `Node.js@10+` which fully supported `Node-API`
50
- - Install `yarn@1.x`
103
+ ```typescript
104
+ import { toJson, toMarkdown } from '@ohah/hwpjs';
105
+ import * as FileSystem from 'expo-file-system';
51
106
 
52
- ## Test in local
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
- - yarn
55
- - yarn build
56
- - yarn test
114
+ // JSON으로 변환
115
+ const jsonString = toJson(uint8Array);
116
+ const document = JSON.parse(jsonString);
57
117
 
58
- And you will see:
118
+ // Markdown으로 변환
119
+ const { markdown, images } = toMarkdown(uint8Array, {
120
+ image: 'blob',
121
+ });
122
+ ```
59
123
 
60
- ```bash
61
- $ ava --verbose
124
+ ## API
62
125
 
63
- sync function from native code
64
- ✔ sleep function from native code (201ms)
65
-
126
+ ### `toJson(data: Buffer | Uint8Array): string`
66
127
 
67
- 2 tests passed
68
- ✨ Done in 1.12s.
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
- ## Release package
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
- Ensure you have set your **NPM_TOKEN** in the `GitHub` project setting.
178
+ ### `fileHeader(data: Buffer | Uint8Array): string`
74
179
 
75
- In `Settings -> Secrets`, add **NPM_TOKEN** into it.
180
+ HWP 파일의 FileHeader만 추출하여 JSON 문자열로 반환합니다.
76
181
 
77
- When you want to release the package:
182
+ **Parameters:**
183
+ - `data`: HWP 파일의 바이트 배열 (Buffer 또는 Uint8Array)
78
184
 
79
- ```bash
80
- npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]
185
+ **Returns:**
186
+ - JSON 문자열 (FileHeader 정보)
81
187
 
82
- git push
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
- GitHub actions will do the rest job for you.
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
- > WARN: Don't run `npm publish` manually.
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::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,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
+ }