Skip to content

Commit 5df30ff

Browse files
authored
Merge pull request openvanilla#273 from zonble/icu_transform
Use ICU's text trasform to replace Iroha Kana input
2 parents e973ac0 + 0dcf7cc commit 5df30ff

9 files changed

Lines changed: 287 additions & 136 deletions

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ set(MCBOPOMOFO_LIB_SOURCES
5050
TimestampedPath.cpp
5151
NumberInputHelper.h
5252
NumberInputHelper.cpp
53+
IcuTransformInputHelper.h
54+
IcuTransformInputHelper.cpp
5355
)
5456

5557
# Setup some compiler option that is generally useful and compatible with Fcitx 5 (C++17)

src/IcuTransformInputHelper.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) 2022 and onwards The McBopomofo Authors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person
4+
// obtaining a copy of this software and associated documentation
5+
// files (the "Software"), to deal in the Software without
6+
// restriction, including without limitation the rights to use,
7+
// copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
// copies of the Software, and to permit persons to whom the
9+
// Software is furnished to do so, subject to the following
10+
// conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be
13+
// included in all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
// OTHER DEALINGS IN THE SOFTWARE.
23+
24+
#include "IcuTransformInputHelper.h"
25+
26+
#include <unicode/translit.h>
27+
#include <unicode/unistr.h>
28+
29+
#include <algorithm>
30+
#include <sstream>
31+
#include <string>
32+
#include <unordered_map>
33+
#include <vector>
34+
35+
namespace McBopomofo {
36+
namespace IcuTransformInputHelper {
37+
38+
static icu::Transliterator* getTransliterator(const std::string& id) {
39+
static std::unordered_map<std::string, std::unique_ptr<icu::Transliterator>>
40+
cache;
41+
auto it = cache.find(id);
42+
if (it == cache.end()) {
43+
UErrorCode status = U_ZERO_ERROR;
44+
auto* tr = icu::Transliterator::createInstance(
45+
icu::UnicodeString::fromUTF8(id), UTRANS_FORWARD, status);
46+
it = cache.emplace(id, std::unique_ptr<icu::Transliterator>(tr)).first;
47+
}
48+
return it->second.get();
49+
}
50+
51+
static std::string transformString(const std::string& id,
52+
const std::string& input) {
53+
auto* tr = getTransliterator(id);
54+
icu::UnicodeString uInput = icu::UnicodeString::fromUTF8(input);
55+
tr->transliterate(uInput);
56+
std::string output;
57+
uInput.toUTF8String(output);
58+
return output;
59+
}
60+
61+
std::vector<std::string> FillCandidatesWithString(const std::string& string) {
62+
std::vector<std::string> candidates;
63+
static const std::vector<std::string> transforms = {
64+
"Latin-Hiragana", // Hiragana script
65+
"Latin-Katakana", // Katakana script
66+
"Latin-Hangul", // Hangul script
67+
"Latin-Thai", // Thai script
68+
"Latin-Greek", // Greek script
69+
"Latin-Cyrillic", // Cyrillic script
70+
"Latin-Arabic", // Arabic script
71+
"Latin-Hebrew", // Hebrew script
72+
"Latin-Devanagari", // Devanagari script
73+
};
74+
for (const auto& transform : transforms) {
75+
std::string transformed = transformString(transform, string);
76+
if (!transformed.empty()) {
77+
candidates.emplace_back(transformed);
78+
}
79+
}
80+
return candidates;
81+
}
82+
} // namespace IcuTransformInputHelper
83+
} // namespace McBopomofo

src/IcuTransformInputHelper.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) 2022 and onwards The McBopomofo Authors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person
4+
// obtaining a copy of this software and associated documentation
5+
// files (the "Software"), to deal in the Software without
6+
// restriction, including without limitation the rights to use,
7+
// copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
// copies of the Software, and to permit persons to whom the
9+
// Software is furnished to do so, subject to the following
10+
// conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be
13+
// included in all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
// OTHER DEALINGS IN THE SOFTWARE.
23+
24+
#ifndef MCBOPOMOFO_SRC_ICUTRANSFORMINPUTHELPER_H_
25+
#define MCBOPOMOFO_SRC_ICUTRANSFORMINPUTHELPER_H_
26+
27+
#include <memory>
28+
#include <string>
29+
#include <vector>
30+
31+
#include "Engine/gramambular2/language_model.h"
32+
33+
namespace McBopomofo {
34+
namespace IcuTransformInputHelper {
35+
36+
std::vector<std::string> FillCandidatesWithString(const std::string& string);
37+
38+
} // namespace IcuTransformInputHelper
39+
} // namespace McBopomofo
40+
41+
#endif /* MCBOPOMOFO_SRC_ICUTRANSFORMINPUTHELPER_H_ */

src/InputState.h

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -281,31 +281,28 @@ struct NumberInput : NotEmpty {
281281
std::vector<std::string> candidates;
282282
};
283283

284+
struct IcuTransformInput : NotEmpty {
285+
explicit IcuTransformInput(const std::string& string,
286+
std::vector<std::string> cs)
287+
: NotEmpty("[文字轉換] " + string, ("[文字轉換] " + string).length(), ""),
288+
string(string),
289+
candidates(std::move(cs)) {}
290+
IcuTransformInput(const IcuTransformInput& other)
291+
: NotEmpty("[文字轉換] " + other.string, ("[文字轉換] " + other.string).length(), ""),
292+
string(other.string),
293+
candidates(other.candidates) {}
294+
295+
std::string string;
296+
std::vector<std::string> candidates;
297+
};
298+
284299
struct Big5 : InputState {
285300
explicit Big5(std::string hexCode = "") : hexCode(std::move(hexCode)) {}
286301
Big5(Big5 const& code) : hexCode(code.hexCode) {}
287302
std::string composingBuffer() const { return "[Big5碼] " + hexCode; }
288303
std::string hexCode;
289304
};
290305

291-
struct Iroha : InputState {
292-
explicit Iroha(std::string code = "") : code(std::move(code)) {}
293-
Iroha(Iroha const& code) : code(code.code) {}
294-
std::string composingBuffer() const { return "[伊呂波] " + code; }
295-
std::string code;
296-
};
297-
298-
struct IrohaCandidate : InputState {
299-
explicit IrohaCandidate(std::string code = "",
300-
std::vector<std::string> candidates = {})
301-
: code(std::move(code)), candidates(std::move(candidates)) {}
302-
IrohaCandidate(IrohaCandidate const& other)
303-
: code(other.code), candidates(other.candidates) {}
304-
std::string composingBuffer() const { return "[伊呂波] " + code; }
305-
std::string code;
306-
std::vector<std::string> candidates;
307-
};
308-
309306
struct SelectingDateMacro : InputState {
310307
explicit SelectingDateMacro(
311308
const std::function<std::string(std::string)>& converter);
@@ -332,8 +329,9 @@ struct SelectingFeature : InputState {
332329
features.emplace_back("數字輸入", []() {
333330
return std::make_unique<NumberInput>("", std::vector<std::string>());
334331
});
335-
features.emplace_back("伊呂波假名輸入",
336-
[]() { return std::make_unique<Iroha>(""); });
332+
features.emplace_back("日韓多語拼音轉換", []() {
333+
return std::make_unique<IcuTransformInput>("", std::vector<std::string>());
334+
});
337335
}
338336

339337
std::unique_ptr<InputState> nextState(size_t index) {

src/KeyHandler.cpp

Lines changed: 57 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333

3434
#include "Big5Utils/Big5Utils.h"
3535
#include "BopomofoBraille/Converter.h"
36+
#include "IcuTransformInputHelper.h"
37+
#include "Log.h"
3638
#include "NumberInputHelper.h"
3739
#include "UTF8Helper.h"
3840

@@ -137,11 +139,6 @@ bool KeyHandler::handle(Key key, McBopomofo::InputState* state,
137139
return handleBig5(key, big5, stateCallback, errorCallback);
138140
}
139141

140-
auto* iroha = dynamic_cast<InputStates::Iroha*>(state);
141-
if (iroha != nullptr) {
142-
return handleIroha(key, iroha, stateCallback, errorCallback);
143-
}
144-
145142
// From Key's definition, if shiftPressed is true, it can't be a simple key
146143
// that can be represented by ASCII.
147144
char simpleAscii =
@@ -1234,6 +1231,61 @@ bool KeyHandler::handleNumberInput(Key key,
12341231
return true;
12351232
}
12361233

1234+
bool KeyHandler::handleIcuTransformInput(Key key,
1235+
McBopomofo::InputStates::IcuTransformInput* state,
1236+
StateCallback stateCallback,
1237+
KeyHandler::ErrorCallback errorCallback) {
1238+
if (key.ascii == Key::ESC) {
1239+
stateCallback(std::make_unique<InputStates::EmptyIgnoringPrevious>());
1240+
return true;
1241+
}
1242+
if (key.isDeleteKeys()) {
1243+
std::string string = state->string;
1244+
if (!string.empty()) {
1245+
string = string.substr(0, string.length() - 1);
1246+
auto candidates =
1247+
IcuTransformInputHelper::FillCandidatesWithString(string);
1248+
auto newState =
1249+
std::make_unique<InputStates::IcuTransformInput>(string, candidates);
1250+
stateCallback(std::move(newState));
1251+
return true;
1252+
} else {
1253+
errorCallback();
1254+
return true;
1255+
}
1256+
}
1257+
1258+
char selectionKeys[10] = {'!', '@', '#', '$', '%', '^', '&', '*', '(', ')'};
1259+
for (size_t i = 0; i < state->candidates.size() && i < 10; ++i) {
1260+
if (key.ascii == selectionKeys[i]) {
1261+
return false;
1262+
}
1263+
}
1264+
1265+
if (isprint(key.ascii)) {
1266+
if (state->string.length() > 100) {
1267+
errorCallback();
1268+
return true;
1269+
}
1270+
std::string newString = state->string + key.ascii;
1271+
auto candidates =
1272+
IcuTransformInputHelper::FillCandidatesWithString(newString);
1273+
auto newState =
1274+
std::make_unique<InputStates::IcuTransformInput>(newString, candidates);
1275+
stateCallback(std::move(newState));
1276+
return true;
1277+
} else if (!state->candidates.empty()) {
1278+
// If the candidate panel is visible, let it handle the key.
1279+
return false;
1280+
}
1281+
1282+
// If the buffer is empty, all other keys (e.g. cursor keys) exit the state.
1283+
stateCallback(std::make_unique<InputStates::EmptyIgnoringPrevious>());
1284+
return true;
1285+
}
1286+
1287+
1288+
12371289
bool KeyHandler::handleBig5(Key key, McBopomofo::InputStates::Big5* state,
12381290
StateCallback stateCallback,
12391291
KeyHandler::ErrorCallback errorCallback) {
@@ -1279,78 +1331,6 @@ bool KeyHandler::handleBig5(Key key, McBopomofo::InputStates::Big5* state,
12791331
return true;
12801332
}
12811333

1282-
bool KeyHandler::handleIroha(Key key, McBopomofo::InputStates::Iroha* state,
1283-
StateCallback stateCallback,
1284-
KeyHandler::ErrorCallback errorCallback) {
1285-
if (key.ascii == Key::ESC) {
1286-
stateCallback(std::make_unique<InputStates::EmptyIgnoringPrevious>());
1287-
return true;
1288-
}
1289-
1290-
if (key.ascii == Key::RETURN || key.ascii == Key::SPACE) {
1291-
std::string code = state->code;
1292-
if (code.empty()) {
1293-
stateCallback(std::make_unique<InputStates::EmptyIgnoringPrevious>());
1294-
return true;
1295-
}
1296-
1297-
std::string unigram = "_kana_" + code;
1298-
if (lm_->hasUnigrams(unigram)) {
1299-
auto unigrams = lm_->getUnigrams(unigram);
1300-
if (unigrams.size() == 1) {
1301-
std::string value = unigrams[0].value();
1302-
auto seq = std::make_unique<InputStates::StateSequence>();
1303-
seq->push_back(std::make_unique<InputStates::Committing>(value));
1304-
seq->push_back(std::make_unique<InputStates::Iroha>(""));
1305-
stateCallback(std::move(seq));
1306-
} else {
1307-
std::vector<std::string> candidates;
1308-
for (const auto& unigram : unigrams) {
1309-
candidates.emplace_back(unigram.value());
1310-
}
1311-
auto newState =
1312-
std::make_unique<InputStates::IrohaCandidate>(code, candidates);
1313-
stateCallback(std::move(newState));
1314-
}
1315-
return true;
1316-
} else {
1317-
errorCallback();
1318-
auto newState = std::make_unique<InputStates::Iroha>("");
1319-
stateCallback(std::move(newState));
1320-
return true;
1321-
}
1322-
}
1323-
1324-
if (key.isDeleteKeys()) {
1325-
std::string code = state->code;
1326-
if (!code.empty()) {
1327-
code = code.substr(0, code.length() - 1);
1328-
auto newState = std::make_unique<InputStates::Iroha>(code);
1329-
stateCallback(std::move(newState));
1330-
} else {
1331-
stateCallback(std::make_unique<InputStates::EmptyIgnoringPrevious>());
1332-
}
1333-
return true;
1334-
}
1335-
1336-
if ((key.ascii >= 'a' && key.ascii <= 'z') ||
1337-
(key.ascii >= 'A' && key.ascii <= 'Z')) {
1338-
std::string code = state->code;
1339-
if (code.length() <= 4) // Iroha code is 4 hex digits.
1340-
{
1341-
std::string code =
1342-
state->code + static_cast<char>(std::tolower(key.ascii));
1343-
auto newState = std::make_unique<InputStates::Iroha>(code);
1344-
stateCallback(std::move(newState));
1345-
} else {
1346-
errorCallback();
1347-
}
1348-
} else {
1349-
errorCallback();
1350-
}
1351-
return true;
1352-
}
1353-
13541334
#pragma endregion Key_Handling
13551335

13561336
#pragma region Output

src/KeyHandler.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ class KeyHandler {
8383
bool handleNumberInput(Key key, InputStates::NumberInput* state,
8484
StateCallback stateCallback,
8585
KeyHandler::ErrorCallback errorCallback);
86+
87+
bool handleIcuTransformInput(
88+
Key key, McBopomofo::InputStates::IcuTransformInput* state,
89+
StateCallback stateCallback, KeyHandler::ErrorCallback errorCallback);
90+
8691
// Candidate selected. Can assume the context is in a candidate state.
8792
void candidateSelected(
8893
const InputStates::ChoosingCandidate::Candidate& candidate,
@@ -236,9 +241,6 @@ class KeyHandler {
236241
bool handleBig5(Key key, McBopomofo::InputStates::Big5* state,
237242
StateCallback stateCallback,
238243
KeyHandler::ErrorCallback errorCallback);
239-
bool handleIroha(Key key, McBopomofo::InputStates::Iroha* state,
240-
StateCallback stateCallback,
241-
KeyHandler::ErrorCallback errorCallback);
242244

243245
bool handleTabKey(bool isShiftPressed, McBopomofo::InputState* state,
244246
const StateCallback& stateCallback,

0 commit comments

Comments
 (0)