inputx_nihongo_wasm/lib.rs
1//! WASM bindings for `inputx-nihongo`. Exposes a JS-friendly
2//! `JapaneseEngine` so browser / Node consumers can drive the
3//! Japanese IME engine via `wasm-pack` output.
4//!
5//! Build:
6//!
7//! wasm-pack build crates/inputx-nihongo-wasm --target web --release
8//!
9//! Usage (JS):
10//!
11//! import init, { JapaneseEngine, KanaKind } from "@goliapkg/nihongo";
12//! await init();
13//! const eng = new JapaneseEngine();
14//! for (const c of "shinjuku") eng.handleLetter(c.charCodeAt(0));
15//! console.log(eng.candidates());
16//! // [
17//! // { word: "新宿", kind: KanaKind.Kanji, freq: N,
18//! // composed: false, proximityMilli: 1000 },
19//! // { word: "しんじゅく", kind: KanaKind.Hiragana, freq: 0, ... },
20//! // { word: "シンジュク", kind: KanaKind.Katakana, freq: 0, ... },
21//! // ]
22//! const committed = eng.commitIndex(0); // "新宿"
23
24use wasm_bindgen::prelude::*;
25
26use inputx_nihongo::{
27 JapaneseEngine as CoreEngine, KanaKind as CoreKanaKind,
28};
29
30/// Kana kind mirrored for JS. Use as `KanaKind.Kanji` etc. Numeric
31/// values match the facade enum's discriminants.
32#[wasm_bindgen]
33#[derive(Copy, Clone)]
34pub enum KanaKind {
35 Hiragana = 0,
36 Katakana = 1,
37 Kanji = 2,
38}
39
40impl From<CoreKanaKind> for KanaKind {
41 fn from(k: CoreKanaKind) -> Self {
42 match k {
43 CoreKanaKind::Hiragana => KanaKind::Hiragana,
44 CoreKanaKind::Katakana => KanaKind::Katakana,
45 CoreKanaKind::Kanji => KanaKind::Kanji,
46 }
47 }
48}
49
50/// JP engine wrapping the embedded romaji / jukugo / kanji tables.
51/// Cheap to construct (all data is in const tables).
52#[wasm_bindgen]
53pub struct JapaneseEngine {
54 engine: CoreEngine,
55}
56
57#[wasm_bindgen]
58impl JapaneseEngine {
59 #[wasm_bindgen(constructor)]
60 pub fn new() -> JapaneseEngine {
61 Self { engine: CoreEngine::new() }
62 }
63
64 /// Push one ASCII letter byte (`'a'..='z'`, `'A'..='Z'`) into the
65 /// buffer and re-render candidates. The `-` byte is also accepted
66 /// (chōonpu `ー`) when the buffer is non-empty.
67 ///
68 /// Returns `true` if the byte was accepted; rejected bytes leave
69 /// state unchanged so the host controller can route the byte
70 /// elsewhere (locale punct mapping, raw passthrough).
71 #[wasm_bindgen(js_name = handleLetter)]
72 pub fn handle_letter(&mut self, byte: u8) -> bool {
73 self.engine.handle_letter(byte)
74 }
75
76 /// Pop one byte off the buffer. Returns `true` if state changed.
77 pub fn backspace(&mut self) -> bool {
78 self.engine.backspace()
79 }
80
81 /// Drop the buffer entirely. Returns `true` if state changed.
82 pub fn escape(&mut self) -> bool {
83 self.engine.escape()
84 }
85
86 /// Current buffer rendered as a string (for the preedit display).
87 pub fn preedit(&self) -> String {
88 self.engine.preedit().to_string()
89 }
90
91 #[wasm_bindgen(js_name = isComposing, getter)]
92 pub fn is_composing(&self) -> bool {
93 self.engine.is_composing()
94 }
95
96 /// Current candidates as a `js_sys::Array` of `Object`s. Each
97 /// object carries:
98 ///
99 /// - `word: string`
100 /// - `kind: KanaKind` (enum numeric value)
101 /// - `freq: number` (0–100, higher = more common)
102 /// - `composed: boolean` (`true` for mechanical
103 /// `compose_sentence` products — particle/copula glue)
104 /// - `proximityMilli: number` (1000 = exact match, < 1000 =
105 /// prefix-prediction candidate whose reading the buffer is
106 /// only a prefix of, e.g. `shinjuk → 新宿` at 875)
107 pub fn candidates(&self) -> js_sys::Array {
108 let arr = js_sys::Array::new();
109 for c in self.engine.candidates() {
110 let obj = js_sys::Object::new();
111 let _ = js_sys::Reflect::set(
112 &obj,
113 &"word".into(),
114 &JsValue::from_str(&c.word),
115 );
116 let kind: KanaKind = c.kind.into();
117 let _ = js_sys::Reflect::set(
118 &obj,
119 &"kind".into(),
120 &JsValue::from_f64(kind as u32 as f64),
121 );
122 let _ = js_sys::Reflect::set(
123 &obj,
124 &"freq".into(),
125 &JsValue::from_f64(c.freq as f64),
126 );
127 let _ = js_sys::Reflect::set(
128 &obj,
129 &"composed".into(),
130 &JsValue::from_bool(c.composed),
131 );
132 let _ = js_sys::Reflect::set(
133 &obj,
134 &"proximityMilli".into(),
135 &JsValue::from_f64(c.proximity_milli as f64),
136 );
137 arr.push(&obj);
138 }
139 arr
140 }
141
142 /// Commit the candidate at `index`. Returns the committed text and
143 /// clears the buffer. Returns `null` (via empty `JsValue`) if
144 /// `index` is out of range; state is unchanged on out-of-range.
145 #[wasm_bindgen(js_name = commitIndex)]
146 pub fn commit_index(&mut self, index: usize) -> Option<String> {
147 self.engine.commit_index(index)
148 }
149}
150
151impl Default for JapaneseEngine {
152 fn default() -> Self {
153 Self::new()
154 }
155}