@laisuk/opencc-fmmseg-wasm 0.1.0 → 0.2.1

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/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # opencc-fmmseg-wasm
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@laisuk/opencc-fmmseg-wasm)](https://www.npmjs.com/package/@laisuk/opencc-fmmseg-wasm)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@laisuk/opencc-fmmseg-wasm)](https://www.npmjs.com/package/@laisuk/opencc-fmmseg-wasm)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6
+ [![WebAssembly](https://img.shields.io/badge/WebAssembly-enabled-blue)](https://webassembly.org/)
7
+
3
8
  OpenCC FMM segmentation WebAssembly bindings for browsers and JavaScript runtimes.
4
9
 
5
10
  This package provides high-quality Simplified Chinese ↔ Traditional Chinese conversion powered by the Rust [
@@ -16,6 +21,8 @@ Features:
16
21
  * Traditional Chinese regional variants
17
22
  * Japanese Shinjitai conversion support
18
23
  * Chinese script detection (`zho_check`)
24
+ * In-memory Office / EPUB document conversion
25
+ * Zero-dependency Node.js CLI
19
26
 
20
27
  ---
21
28
 
@@ -190,6 +197,88 @@ Returns:
190
197
 
191
198
  ---
192
199
 
200
+ ## Office / EPUB Conversion
201
+
202
+ Office and EPUB conversion runs fully locally in the browser or Node.js. Files are passed in and returned as bytes;
203
+ nothing is uploaded to a backend server.
204
+
205
+ This is useful for converting text inside:
206
+
207
+ ```text
208
+ docx, xlsx, pptx, odt, ods, odp, epub
209
+ ```
210
+
211
+ File size is limited by available browser or Node.js memory, but there is no upload or server-side limit. Font
212
+ preservation is supported with the `keepFont` option.
213
+
214
+ ```javascript
215
+ convert_office_bytes(inputBytes, format, config, punctuation, keepFont)
216
+ ```
217
+
218
+ Parameters:
219
+
220
+ * `inputBytes`: `Uint8Array` document bytes
221
+ * `format`: `docx`, `xlsx`, `pptx`, `odt`, `ods`, `odp`, or `epub`
222
+ * `config`: OpenCC config string, such as `"s2t"`
223
+ * `punctuation`: whether to convert punctuation variants
224
+ * `keepFont`: whether to preserve font declarations where supported
225
+
226
+ Returns:
227
+
228
+ * converted output bytes
229
+
230
+ ### Browser Office Example
231
+
232
+ ```javascript
233
+ import init, {convert_office_bytes} from "@laisuk/opencc-fmmseg-wasm";
234
+
235
+ await init();
236
+
237
+ const file = document.querySelector("input[type=file]").files[0];
238
+ const inputBytes = new Uint8Array(await file.arrayBuffer());
239
+
240
+ const outputBytes = convert_office_bytes(
241
+ inputBytes,
242
+ "docx",
243
+ "s2t",
244
+ true,
245
+ true
246
+ );
247
+
248
+ const blob = new Blob([outputBytes], {
249
+ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
250
+ });
251
+
252
+ const a = document.createElement("a");
253
+ a.href = URL.createObjectURL(blob);
254
+ a.download = "converted.docx";
255
+ a.click();
256
+ URL.revokeObjectURL(a.href);
257
+ ```
258
+
259
+ ### Node.js Office Example
260
+
261
+ ```javascript
262
+ import fs from "fs";
263
+ import init, {convert_office_bytes} from "@laisuk/opencc-fmmseg-wasm";
264
+
265
+ await init();
266
+
267
+ const inputBytes = fs.readFileSync("input.docx");
268
+
269
+ const outputBytes = convert_office_bytes(
270
+ inputBytes,
271
+ "docx",
272
+ "s2t",
273
+ true,
274
+ true
275
+ );
276
+
277
+ fs.writeFileSync("output.docx", outputBytes);
278
+ ```
279
+
280
+ ---
281
+
193
282
  ## Browser Example
194
283
 
195
284
  ```html
@@ -221,6 +310,52 @@ Returns:
221
310
 
222
311
  ---
223
312
 
313
+ ## Node.js CLI
314
+
315
+ The package includes a zero-dependency Node.js CLI:
316
+
317
+ ```bash
318
+ opencc-fmmseg convert -i input.txt -o output.txt -c s2t -p
319
+ ```
320
+
321
+ ```bash
322
+ opencc-fmmseg office -i input.docx -o output.docx -c s2t -p --keep-font
323
+ ```
324
+
325
+ ### Text Conversion Options
326
+
327
+ ```text
328
+ -i, --input <file> Input text file
329
+ -o, --output <file> Output text file
330
+ -c, --config <conversion> Conversion config
331
+ -p, --punct Enable punctuation conversion
332
+ --in-enc <encoding> Input encoding
333
+ --out-enc <encoding> Output encoding
334
+ ```
335
+
336
+ ### Office / EPUB Options
337
+
338
+ ```text
339
+ -i, --input <file> Input Office / EPUB file
340
+ -o, --output <file> Output file
341
+ -c, --config <conversion> Conversion config
342
+ -p, --punct Enable punctuation conversion
343
+ --format <format> docx | xlsx | pptx | odt | ods | odp | epub
344
+ --auto-ext Append extension to output if missing
345
+ --keep-font Preserve font-family information
346
+ --no-keep-font Do not preserve font-family information
347
+ ```
348
+
349
+ For `office`, the format is inferred from the input file extension when `--format` is omitted.
350
+
351
+ If `-o, --output` is omitted, `office` writes:
352
+
353
+ ```text
354
+ <input-name>_converted.<ext>
355
+ ```
356
+
357
+ ---
358
+
224
359
  ## TypeScript Support
225
360
 
226
361
  The package includes generated TypeScript definitions from `wasm-bindgen`.
package/bin/opencc.js ADDED
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import process from "process";
6
+
7
+ import init, {
8
+ OpenccWasm,
9
+ convert_office_bytes
10
+ } from "../pkg/opencc_fmmseg_wasm.js";
11
+
12
+ const OFFICE_FORMATS = new Set([
13
+ "docx",
14
+ "xlsx",
15
+ "pptx",
16
+ "odt",
17
+ "ods",
18
+ "odp",
19
+ "epub"
20
+ ]);
21
+
22
+ let wasmInitialized = false;
23
+
24
+ async function ensureWasmInitialized() {
25
+ if (wasmInitialized) {
26
+ return;
27
+ }
28
+
29
+ const wasmPath = new URL("../pkg/opencc_fmmseg_wasm_bg.wasm", import.meta.url);
30
+ const wasmBytes = fs.readFileSync(wasmPath);
31
+
32
+ await init({
33
+ module_or_path: wasmBytes
34
+ });
35
+
36
+ wasmInitialized = true;
37
+ }
38
+
39
+ function printHelp() {
40
+ console.log(`
41
+ opencc-fmmseg WASM CLI
42
+
43
+ Usage:
44
+ opencc.js convert [options]
45
+ opencc.js office [options]
46
+
47
+ Commands:
48
+ convert Convert plain text
49
+ office Convert Office / EPUB documents
50
+
51
+ Convert options:
52
+ -i, --input <file> Input text file; stdin if omitted
53
+ -o, --output <file> Output text file; stdout if omitted
54
+ -c, --config <conversion> Conversion config (default: s2t)
55
+ -p, --punct Enable punctuation conversion
56
+ --in-enc <encoding> Input encoding (default: utf8)
57
+ --out-enc <encoding> Output encoding (default: utf8)
58
+
59
+ Office options:
60
+ -i, --input <file> Input Office / EPUB file
61
+ -o, --output <file> Output file
62
+ -c, --config <conversion> Conversion config (default: s2t)
63
+ -p, --punct Enable punctuation conversion
64
+ --format <format> docx | xlsx | pptx | odt | ods | odp | epub
65
+ --auto-ext Append extension to output if missing
66
+ --keep-font Preserve font-family information (default)
67
+ --no-keep-font Do not preserve font-family information
68
+
69
+ General options:
70
+ -h, --help Show help
71
+
72
+ Examples:
73
+ node ./bin/opencc.js convert -i a.txt -o b.txt -c s2t
74
+ node ./bin/opencc.js convert -i a.txt -o b.txt -c s2tw -p
75
+ cat a.txt | node ./bin/opencc.js convert -c t2s
76
+
77
+ node ./bin/opencc.js office -i a.docx -o b.docx -c s2t -p
78
+ node ./bin/opencc.js office -i a.epub -c s2tw --auto-ext
79
+ `);
80
+ }
81
+
82
+ function getArg(args, shortName, longName, defaultValue = null) {
83
+ const candidates = [];
84
+
85
+ if (shortName) {
86
+ candidates.push(shortName);
87
+ }
88
+ if (longName) {
89
+ candidates.push(longName);
90
+ }
91
+
92
+ for (const name of candidates) {
93
+ const index = args.indexOf(name);
94
+
95
+ if (index !== -1 && index + 1 < args.length) {
96
+ return args[index + 1];
97
+ }
98
+ }
99
+
100
+ return defaultValue;
101
+ }
102
+
103
+ function hasFlag(args, shortName, longName) {
104
+ return (
105
+ (shortName && args.includes(shortName)) ||
106
+ (longName && args.includes(longName))
107
+ );
108
+ }
109
+
110
+ function readInputText(filePath, encoding) {
111
+ if (!filePath) {
112
+ return fs.readFileSync(0, encoding);
113
+ }
114
+
115
+ if (!fs.existsSync(filePath)) {
116
+ console.error(`Error: Input file not found: ${filePath}`);
117
+ process.exit(1);
118
+ }
119
+
120
+ return fs.readFileSync(filePath, encoding);
121
+ }
122
+
123
+ function writeOutputText(filePath, text, encoding) {
124
+ if (!filePath) {
125
+ process.stdout.write(text);
126
+ return;
127
+ }
128
+
129
+ fs.writeFileSync(filePath, text, encoding);
130
+ }
131
+
132
+ function inferOfficeFormat(inputFile, explicitFormat) {
133
+ if (explicitFormat) {
134
+ const normalized = explicitFormat.trim().toLowerCase();
135
+
136
+ if (!OFFICE_FORMATS.has(normalized)) {
137
+ throw new Error(
138
+ `Invalid office format: ${explicitFormat}. ` +
139
+ `Valid formats: ${Array.from(OFFICE_FORMATS).join(", ")}`
140
+ );
141
+ }
142
+
143
+ return normalized;
144
+ }
145
+
146
+ const ext = path.extname(inputFile).slice(1).toLowerCase();
147
+
148
+ if (!OFFICE_FORMATS.has(ext)) {
149
+ throw new Error(
150
+ `Invalid Office file extension: .${ext || "(none)"}. ` +
151
+ "Valid extensions: .docx | .xlsx | .pptx | .odt | .ods | .odp | .epub"
152
+ );
153
+ }
154
+
155
+ return ext;
156
+ }
157
+
158
+ function makeDefaultOfficeOutput(inputFile, officeFormat, autoExt) {
159
+ const parsed = path.parse(inputFile);
160
+ const ext = autoExt && OFFICE_FORMATS.has(officeFormat)
161
+ ? `.${officeFormat}`
162
+ : parsed.ext;
163
+
164
+ return path.join(
165
+ parsed.dir || process.cwd(),
166
+ `${parsed.name}_converted${ext}`
167
+ );
168
+ }
169
+
170
+ function applyAutoExt(outputFile, officeFormat, autoExt) {
171
+ if (!autoExt || path.extname(outputFile)) {
172
+ return outputFile;
173
+ }
174
+
175
+ if (!OFFICE_FORMATS.has(officeFormat)) {
176
+ return outputFile;
177
+ }
178
+
179
+ return `${outputFile}.${officeFormat}`;
180
+ }
181
+
182
+ async function runConvert(args) {
183
+ const input = getArg(args, "-i", "--input");
184
+ const output = getArg(args, "-o", "--output");
185
+ const config = getArg(args, "-c", "--config", "s2t");
186
+ const inEnc = getArg(args, null, "--in-enc", "utf8");
187
+ const outEnc = getArg(args, null, "--out-enc", "utf8");
188
+ const punct = hasFlag(args, "-p", "--punct");
189
+
190
+ await ensureWasmInitialized();
191
+
192
+ const cc = new OpenccWasm(config);
193
+
194
+ const inputText = readInputText(input, inEnc);
195
+ const outputText = cc.convert(inputText, punct);
196
+
197
+ writeOutputText(output, outputText, outEnc);
198
+
199
+ const inFrom = input || "<stdin>";
200
+ const outTo = output || "stdout";
201
+
202
+ if (process.stderr.isTTY) {
203
+ if (!output && outputText && !outputText.endsWith("\n")) {
204
+ console.error();
205
+ }
206
+
207
+ console.error(`Conversion completed (${config}): ${inFrom} -> ${outTo}`);
208
+ }
209
+ }
210
+
211
+ async function runOffice(args) {
212
+ const input = getArg(args, "-i", "--input");
213
+ let output = getArg(args, "-o", "--output");
214
+ const config = getArg(args, "-c", "--config", "s2t");
215
+ const explicitFormat = getArg(args, null, "--format");
216
+ const punct = hasFlag(args, "-p", "--punct");
217
+ const autoExt = hasFlag(args, null, "--auto-ext");
218
+ const keepFont = !hasFlag(args, null, "--no-keep-font");
219
+
220
+ if (!input) {
221
+ throw new Error("Input file is missing.");
222
+ }
223
+
224
+ if (!fs.existsSync(input) || !fs.statSync(input).isFile()) {
225
+ throw new Error(`Input file not found: ${input}`);
226
+ }
227
+
228
+ const officeFormat = inferOfficeFormat(input, explicitFormat);
229
+
230
+ if (!output) {
231
+ output = makeDefaultOfficeOutput(input, officeFormat, autoExt);
232
+ console.error(`Output file not specified. Using: ${output}`);
233
+ } else {
234
+ output = applyAutoExt(output, officeFormat, autoExt);
235
+ }
236
+
237
+ await ensureWasmInitialized();
238
+
239
+ const inputBytes = fs.readFileSync(input);
240
+
241
+ const outputBytes = convert_office_bytes(
242
+ inputBytes,
243
+ officeFormat,
244
+ config,
245
+ punct,
246
+ keepFont
247
+ );
248
+
249
+ fs.writeFileSync(output, outputBytes);
250
+
251
+ console.error(`Conversion completed (${config}, ${officeFormat}): ${input} -> ${output}`);
252
+ }
253
+
254
+ async function main() {
255
+ const args = process.argv.slice(2);
256
+
257
+ if (args.length === 0 || hasFlag(args, "-h", "--help")) {
258
+ printHelp();
259
+ return;
260
+ }
261
+
262
+ const command = args[0];
263
+
264
+ switch (command) {
265
+ case "convert":
266
+ if (hasFlag(args.slice(1), "-h", "--help")) {
267
+ printHelp();
268
+ return;
269
+ }
270
+ await runConvert(args.slice(1));
271
+ break;
272
+
273
+ case "office":
274
+ if (hasFlag(args.slice(1), "-h", "--help")) {
275
+ printHelp();
276
+ return;
277
+ }
278
+ await runOffice(args.slice(1));
279
+ break;
280
+
281
+ default:
282
+ console.error(`Unknown command: ${command}`);
283
+ printHelp();
284
+ process.exit(1);
285
+ }
286
+ }
287
+
288
+ main().catch(err => {
289
+ console.error(err && err.message ? err.message : err);
290
+ process.exit(1);
291
+ });
@@ -36,11 +36,14 @@ export enum WasmOpenccConfig {
36
36
  T2jp = 16,
37
37
  }
38
38
 
39
+ export function convert_office_bytes(input: Uint8Array, format: string, config: string, punctuation: boolean, keep_font: boolean): Uint8Array;
40
+
39
41
  export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
40
42
 
41
43
  export interface InitOutput {
42
44
  readonly memory: WebAssembly.Memory;
43
45
  readonly __wbg_openccwasm_free: (a: number, b: number) => void;
46
+ readonly convert_office_bytes: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => [number, number, number, number];
44
47
  readonly openccwasm_convert: (a: number, b: number, c: number, d: number) => [number, number];
45
48
  readonly openccwasm_debugPing: (a: number) => [number, number];
46
49
  readonly openccwasm_getConfig: (a: number) => [number, number];
@@ -55,9 +58,9 @@ export interface InitOutput {
55
58
  readonly __wbindgen_externrefs: WebAssembly.Table;
56
59
  readonly __wbindgen_malloc: (a: number, b: number) => number;
57
60
  readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
61
+ readonly __externref_table_dealloc: (a: number) => void;
58
62
  readonly __wbindgen_free: (a: number, b: number, c: number) => void;
59
63
  readonly __externref_drop_slice: (a: number, b: number) => void;
60
- readonly __externref_table_dealloc: (a: number) => void;
61
64
  readonly __wbindgen_start: () => void;
62
65
  }
63
66
 
@@ -167,6 +167,30 @@ export const WasmOpenccConfig = Object.freeze({
167
167
  Jp2t: 15, "15": "Jp2t",
168
168
  T2jp: 16, "16": "T2jp",
169
169
  });
170
+
171
+ /**
172
+ * @param {Uint8Array} input
173
+ * @param {string} format
174
+ * @param {string} config
175
+ * @param {boolean} punctuation
176
+ * @param {boolean} keep_font
177
+ * @returns {Uint8Array}
178
+ */
179
+ export function convert_office_bytes(input, format, config, punctuation, keep_font) {
180
+ const ptr0 = passArray8ToWasm0(input, wasm.__wbindgen_malloc);
181
+ const len0 = WASM_VECTOR_LEN;
182
+ const ptr1 = passStringToWasm0(format, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
183
+ const len1 = WASM_VECTOR_LEN;
184
+ const ptr2 = passStringToWasm0(config, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
185
+ const len2 = WASM_VECTOR_LEN;
186
+ const ret = wasm.convert_office_bytes(ptr0, len0, ptr1, len1, ptr2, len2, punctuation, keep_font);
187
+ if (ret[3]) {
188
+ throw takeFromExternrefTable0(ret[2]);
189
+ }
190
+ var v4 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
191
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
192
+ return v4;
193
+ }
170
194
  function __wbg_get_imports() {
171
195
  const import0 = {
172
196
  __proto__: null,
@@ -209,6 +233,11 @@ function getArrayJsValueFromWasm0(ptr, len) {
209
233
  return result;
210
234
  }
211
235
 
236
+ function getArrayU8FromWasm0(ptr, len) {
237
+ ptr = ptr >>> 0;
238
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
239
+ }
240
+
212
241
  let cachedDataViewMemory0 = null;
213
242
  function getDataViewMemory0() {
214
243
  if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
@@ -233,6 +262,13 @@ function isLikeNone(x) {
233
262
  return x === undefined || x === null;
234
263
  }
235
264
 
265
+ function passArray8ToWasm0(arg, malloc) {
266
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
267
+ getUint8ArrayMemory0().set(arg, ptr / 1);
268
+ WASM_VECTOR_LEN = arg.length;
269
+ return ptr;
270
+ }
271
+
236
272
  function passStringToWasm0(arg, malloc, realloc) {
237
273
  if (realloc === undefined) {
238
274
  const buf = cachedTextEncoder.encode(arg);
Binary file
package/package.json CHANGED
@@ -2,13 +2,30 @@
2
2
  "name": "@laisuk/opencc-fmmseg-wasm",
3
3
  "type": "module",
4
4
  "description": "WebAssembly bindings for opencc-fmmseg, a high-performance OpenCC-compatible Simplified/Traditional Chinese converter.",
5
- "version": "0.1.0",
5
+ "version": "0.2.1",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/laisuk/opencc-fmmseg-wasm"
9
+ "url": "git+https://github.com/laisuk/opencc-fmmseg-wasm.git"
10
+ },
11
+ "keywords": [
12
+ "opencc",
13
+ "chinese",
14
+ "simplified-chinese",
15
+ "traditional-chinese",
16
+ "wasm",
17
+ "webassembly",
18
+ "opencc-fmmseg",
19
+ "text-conversion",
20
+ "cjk",
21
+ "zh-hans",
22
+ "zh-hant"
23
+ ],
24
+ "bin": {
25
+ "opencc-fmmseg": "bin/opencc.js"
10
26
  },
11
27
  "files": [
28
+ "bin/",
12
29
  "opencc_fmmseg_wasm_bg.wasm",
13
30
  "opencc_fmmseg_wasm.js",
14
31
  "opencc_fmmseg_wasm.d.ts"