blob: 8ad9c77dc9ca996adaa17e05210d4f6d2e19f17f [file] [log] [blame]
satok8fbd5522011-02-22 17:28:55 +09001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Ken Wakasaf1008c52012-07-31 17:56:40 +090017#include <cstring>
satok8fbd5522011-02-22 17:28:55 +090018
satok817e5172011-03-04 06:06:45 -080019#define LOG_TAG "LatinIME: proximity_info.cpp"
20
satok552c3c22012-03-13 16:33:47 +090021#include "additional_proximity_chars.h"
Ken Wakasa77e8e812012-08-02 19:48:08 +090022#include "char_utils.h"
Ken Wakasa3b088a22012-05-16 23:05:32 +090023#include "defines.h"
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +090024#include "geometry_utils.h"
Ken Wakasabb005f72012-08-08 20:43:47 +090025#include "jni.h"
satok8fbd5522011-02-22 17:28:55 +090026#include "proximity_info.h"
27
28namespace latinime {
Ken Wakasace9e52a2011-06-18 13:09:55 +090029
Ken Wakasa162c2112012-08-24 14:51:15 +090030/* static */ const float ProximityInfo::NOT_A_DISTANCE_FLOAT = -1.0f;
31
Ken Wakasa6e663492012-11-03 02:50:47 +090032static AK_FORCE_INLINE void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray,
33 jsize len, jint *buffer) {
Ken Wakasabb005f72012-08-08 20:43:47 +090034 if (jArray && buffer) {
35 env->GetIntArrayRegion(jArray, 0, len, buffer);
36 } else if (buffer) {
Ken Wakasa44d9c1e2012-11-01 17:05:08 +090037 memset(buffer, 0, len * sizeof(buffer[0]));
Yusuke Nojimade2f8422011-09-27 11:15:18 +090038 }
39}
40
Ken Wakasa6e663492012-11-03 02:50:47 +090041static AK_FORCE_INLINE void safeGetOrFillZeroFloatArrayRegion(JNIEnv *env, jfloatArray jArray,
42 jsize len, jfloat *buffer) {
Ken Wakasabb005f72012-08-08 20:43:47 +090043 if (jArray && buffer) {
44 env->GetFloatArrayRegion(jArray, 0, len, buffer);
45 } else if (buffer) {
Ken Wakasa44d9c1e2012-11-01 17:05:08 +090046 memset(buffer, 0, len * sizeof(buffer[0]));
Ken Wakasabb005f72012-08-08 20:43:47 +090047 }
48}
49
Ken Wakasa01511452012-08-09 15:58:15 +090050ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr, const int maxProximityCharsSize,
satok552c3c22012-03-13 16:33:47 +090051 const int keyboardWidth, const int keyboardHeight, const int gridWidth,
Ken Wakasabb005f72012-08-08 20:43:47 +090052 const int gridHeight, const int mostCommonKeyWidth, const jintArray proximityChars,
53 const int keyCount, const jintArray keyXCoordinates, const jintArray keyYCoordinates,
54 const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes,
55 const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs,
56 const jfloatArray sweetSpotRadii)
Ken Wakasa162c2112012-08-24 14:51:15 +090057 : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), GRID_WIDTH(gridWidth),
58 GRID_HEIGHT(gridHeight), MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth),
satoka70ee6e2012-03-07 15:12:22 +090059 MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
satok817e5172011-03-04 06:06:45 -080060 CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
Yusuke Nojima0e1f6562011-09-21 12:02:47 +090061 CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
Yusuke Nojima258bfe62011-09-28 12:59:43 +090062 KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
Keisuke Kuroyanagi95a49a52012-09-04 17:00:24 +090063 KEYBOARD_WIDTH(keyboardWidth), KEYBOARD_HEIGHT(keyboardHeight),
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +090064 HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
65 && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
Ken Wakasa162c2112012-08-24 14:51:15 +090066 && sweetSpotCenterYs && sweetSpotRadii),
Ken Wakasa1d516fb2012-12-03 19:43:15 +090067 mProximityCharsArray(new int[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE
Tom Ouyang13216852012-09-03 12:50:21 -070068 /* proximityGridLength */]),
69 mCodeToKeyMap() {
satok1035bc92012-06-13 16:07:54 -070070 const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
satok817e5172011-03-04 06:06:45 -080071 if (DEBUG_PROXIMITY_INFO) {
satok9fb6f472012-01-13 18:01:22 +090072 AKLOGI("Create proximity info array %d", proximityGridLength);
satok817e5172011-03-04 06:06:45 -080073 }
Ken Wakasa01511452012-08-09 15:58:15 +090074 const jsize localeCStrUtf8Length = env->GetStringUTFLength(localeJStr);
Ken Wakasa9e0c7112012-08-09 22:26:58 +090075 if (localeCStrUtf8Length >= MAX_LOCALE_STRING_LENGTH) {
76 AKLOGI("Locale string length too long: length=%d", localeCStrUtf8Length);
Ken Wakasaccebd5c2013-01-09 15:21:44 +090077 ASSERT(false);
Ken Wakasa9e0c7112012-08-09 22:26:58 +090078 }
79 memset(mLocaleStr, 0, sizeof(mLocaleStr));
80 env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr);
Ken Wakasabb005f72012-08-08 20:43:47 +090081 safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityGridLength, mProximityCharsArray);
82 safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates);
83 safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates);
84 safeGetOrFillZeroIntArrayRegion(env, keyWidths, KEY_COUNT, mKeyWidths);
85 safeGetOrFillZeroIntArrayRegion(env, keyHeights, KEY_COUNT, mKeyHeights);
Ken Wakasaf2789812012-09-04 12:49:46 +090086 safeGetOrFillZeroIntArrayRegion(env, keyCharCodes, KEY_COUNT, mKeyCodePoints);
Ken Wakasabb005f72012-08-08 20:43:47 +090087 safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterXs, KEY_COUNT, mSweetSpotCenterXs);
88 safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterYs, KEY_COUNT, mSweetSpotCenterYs);
89 safeGetOrFillZeroFloatArrayRegion(env, sweetSpotRadii, KEY_COUNT, mSweetSpotRadii);
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +090090 initializeG();
Yusuke Nojima0e1f6562011-09-21 12:02:47 +090091}
92
satok8fbd5522011-02-22 17:28:55 +090093ProximityInfo::~ProximityInfo() {
94 delete[] mProximityCharsArray;
95}
satok817e5172011-03-04 06:06:45 -080096
97inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const {
satok3c4bb772011-03-04 22:50:19 -080098 return ((y / CELL_HEIGHT) * GRID_WIDTH + (x / CELL_WIDTH))
satok817e5172011-03-04 06:06:45 -080099 * MAX_PROXIMITY_CHARS_SIZE;
satok8fbd5522011-02-22 17:28:55 +0900100}
satok817e5172011-03-04 06:06:45 -0800101
102bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
satok744dab62011-12-15 22:29:05 +0900103 if (x < 0 || y < 0) {
104 if (DEBUG_DICT) {
satok9fb6f472012-01-13 18:01:22 +0900105 AKLOGI("HasSpaceProximity: Illegal coordinates (%d, %d)", x, y);
Ken Wakasaf2789812012-09-04 12:49:46 +0900106 // TODO: Enable this assertion.
Ken Wakasaccebd5c2013-01-09 15:21:44 +0900107 //ASSERT(false);
satok744dab62011-12-15 22:29:05 +0900108 }
109 return false;
110 }
111
satok817e5172011-03-04 06:06:45 -0800112 const int startIndex = getStartIndexFromCoordinates(x, y);
113 if (DEBUG_PROXIMITY_INFO) {
satok9fb6f472012-01-13 18:01:22 +0900114 AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
satok817e5172011-03-04 06:06:45 -0800115 }
Ken Wakasa1d516fb2012-12-03 19:43:15 +0900116 int *proximityCharsArray = mProximityCharsArray;
satok817e5172011-03-04 06:06:45 -0800117 for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
118 if (DEBUG_PROXIMITY_INFO) {
satok9fb6f472012-01-13 18:01:22 +0900119 AKLOGI("Index: %d", mProximityCharsArray[startIndex + i]);
satok817e5172011-03-04 06:06:45 -0800120 }
Satoshi Kataoka3e8c58f2012-06-05 17:55:52 +0900121 if (proximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
satok817e5172011-03-04 06:06:45 -0800122 return true;
123 }
124 }
125 return false;
126}
Ken Wakasace9e52a2011-06-18 13:09:55 +0900127
Satoshi Kataoka0edab9d2012-09-24 18:29:31 +0900128float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloatG(
Satoshi Kataokae7398cd2012-08-13 20:20:04 +0900129 const int keyId, const int x, const int y) const {
Satoshi Kataoka0edab9d2012-09-24 18:29:31 +0900130 const static float verticalSweetSpotScaleForGeometric = 1.1f;
131 const bool correctTouchPosition = hasTouchPositionCorrectionData();
132 const float centerX = static_cast<float>(correctTouchPosition
133 ? getSweetSpotCenterXAt(keyId)
134 : getKeyCenterXOfKeyIdG(keyId));
135 const float visualKeyCenterY = static_cast<float>(getKeyCenterYOfKeyIdG(keyId));
136 float centerY;
137 if (correctTouchPosition) {
138 const float sweetSpotCenterY = static_cast<float>(getSweetSpotCenterYAt(keyId));
139 const float gapY = sweetSpotCenterY - visualKeyCenterY;
140 centerY = visualKeyCenterY + gapY * verticalSweetSpotScaleForGeometric;
141 } else {
142 centerY = visualKeyCenterY;
143 }
Satoshi Kataokae7398cd2012-08-13 20:20:04 +0900144 const float touchX = static_cast<float>(x);
145 const float touchY = static_cast<float>(y);
146 const float keyWidth = static_cast<float>(getMostCommonKeyWidth());
147 return getNormalizedSquaredDistanceFloat(centerX, centerY, touchX, touchY, keyWidth);
148}
149
satok9df4a452012-03-23 16:05:18 +0900150int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) const {
Jean Chalard081616c2012-03-22 17:39:27 +0900151 if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
satoka70ee6e2012-03-07 15:12:22 +0900152 const int left = mKeyXCoordinates[keyId];
153 const int top = mKeyYCoordinates[keyId];
satok1caff472012-03-14 23:17:12 +0900154 const int right = left + mKeyWidths[keyId];
satoka70ee6e2012-03-07 15:12:22 +0900155 const int bottom = top + mKeyHeights[keyId];
156 const int edgeX = x < left ? left : (x > right ? right : x);
157 const int edgeY = y < top ? top : (y > bottom ? bottom : y);
158 const int dx = x - edgeX;
159 const int dy = y - edgeY;
160 return dx * dx + dy * dy;
161}
162
163void ProximityInfo::calculateNearbyKeyCodes(
Ken Wakasa1d516fb2012-12-03 19:43:15 +0900164 const int x, const int y, const int primaryKey, int *inputCodes) const {
165 int *proximityCharsArray = mProximityCharsArray;
satoka70ee6e2012-03-07 15:12:22 +0900166 int insertPos = 0;
167 inputCodes[insertPos++] = primaryKey;
168 const int startIndex = getStartIndexFromCoordinates(x, y);
Jean Chalard52612a02012-03-23 19:38:23 +0900169 if (startIndex >= 0) {
Jean Chalard88ec1252012-03-23 19:25:10 +0900170 for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
Ken Wakasa1d516fb2012-12-03 19:43:15 +0900171 const int c = proximityCharsArray[startIndex + i];
Jean Chalard88ec1252012-03-23 19:25:10 +0900172 if (c < KEYCODE_SPACE || c == primaryKey) {
173 continue;
174 }
Ken Wakasaf2789812012-09-04 12:49:46 +0900175 const int keyIndex = getKeyIndexOf(c);
Jean Chalard88ec1252012-03-23 19:25:10 +0900176 const bool onKey = isOnKey(keyIndex, x, y);
177 const int distance = squaredDistanceToEdge(keyIndex, x, y);
178 if (onKey || distance < MOST_COMMON_KEY_WIDTH_SQUARE) {
179 inputCodes[insertPos++] = c;
180 if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
181 if (DEBUG_DICT) {
Ken Wakasaccebd5c2013-01-09 15:21:44 +0900182 ASSERT(false);
Jean Chalard88ec1252012-03-23 19:25:10 +0900183 }
184 return;
satok0cb20972012-03-13 22:07:56 +0900185 }
satoka70ee6e2012-03-07 15:12:22 +0900186 }
187 }
Jean Chalard88ec1252012-03-23 19:25:10 +0900188 const int additionalProximitySize =
Ken Wakasa01511452012-08-09 15:58:15 +0900189 AdditionalProximityChars::getAdditionalCharsSize(mLocaleStr, primaryKey);
Jean Chalard88ec1252012-03-23 19:25:10 +0900190 if (additionalProximitySize > 0) {
191 inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
192 if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
Jean Chalard3094d122012-03-23 19:36:07 +0900193 if (DEBUG_DICT) {
Ken Wakasaccebd5c2013-01-09 15:21:44 +0900194 ASSERT(false);
Jean Chalard3094d122012-03-23 19:36:07 +0900195 }
196 return;
satok1caff472012-03-14 23:17:12 +0900197 }
satok1caff472012-03-14 23:17:12 +0900198
Ken Wakasa1d516fb2012-12-03 19:43:15 +0900199 const int *additionalProximityChars =
Ken Wakasa01511452012-08-09 15:58:15 +0900200 AdditionalProximityChars::getAdditionalChars(mLocaleStr, primaryKey);
Jean Chalard88ec1252012-03-23 19:25:10 +0900201 for (int j = 0; j < additionalProximitySize; ++j) {
Ken Wakasa1d516fb2012-12-03 19:43:15 +0900202 const int ac = additionalProximityChars[j];
Jean Chalard88ec1252012-03-23 19:25:10 +0900203 int k = 0;
204 for (; k < insertPos; ++k) {
Ken Wakasa1d516fb2012-12-03 19:43:15 +0900205 if (ac == inputCodes[k]) {
Jean Chalard88ec1252012-03-23 19:25:10 +0900206 break;
207 }
satok5eec5742012-03-13 18:26:23 +0900208 }
Jean Chalard88ec1252012-03-23 19:25:10 +0900209 if (k < insertPos) {
210 continue;
satok0cb20972012-03-13 22:07:56 +0900211 }
Jean Chalard88ec1252012-03-23 19:25:10 +0900212 inputCodes[insertPos++] = ac;
213 if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
214 if (DEBUG_DICT) {
Ken Wakasaccebd5c2013-01-09 15:21:44 +0900215 ASSERT(false);
Jean Chalard88ec1252012-03-23 19:25:10 +0900216 }
217 return;
Jean Chalard3094d122012-03-23 19:36:07 +0900218 }
satok0cb20972012-03-13 22:07:56 +0900219 }
satok5eec5742012-03-13 18:26:23 +0900220 }
Jean Chalard52612a02012-03-23 19:38:23 +0900221 }
satok0cb20972012-03-13 22:07:56 +0900222 // Add a delimiter for the proximity characters
satok1caff472012-03-14 23:17:12 +0900223 for (int i = insertPos; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900224 inputCodes[i] = NOT_A_CODE_POINT;
satok1caff472012-03-14 23:17:12 +0900225 }
satoka70ee6e2012-03-07 15:12:22 +0900226}
227
Ken Wakasaf2789812012-09-04 12:49:46 +0900228int ProximityInfo::getKeyIndexOf(const int c) const {
satok1caff472012-03-14 23:17:12 +0900229 if (KEY_COUNT == 0) {
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +0900230 // We do not have the coordinate data
Jean Chalardbbc25602012-03-23 17:05:03 +0900231 return NOT_AN_INDEX;
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +0900232 }
Keisuke Kuroyanagiff74cc32012-10-11 13:08:06 +0900233 if (c == NOT_A_CODE_POINT) {
234 return NOT_AN_INDEX;
235 }
Ken Wakasa1d516fb2012-12-03 19:43:15 +0900236 const int lowerCode = toLowerCase(c);
Tom Ouyangf34ec5a2012-09-25 00:06:31 -0700237 hash_map_compat<int, int>::const_iterator mapPos = mCodeToKeyMap.find(lowerCode);
Tom Ouyang13216852012-09-03 12:50:21 -0700238 if (mapPos != mCodeToKeyMap.end()) {
239 return mapPos->second;
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +0900240 }
Tom Ouyang13216852012-09-03 12:50:21 -0700241 return NOT_AN_INDEX;
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +0900242}
Satoshi Kataokaefb63242012-06-27 14:52:40 +0900243
Ken Wakasaf2789812012-09-04 12:49:46 +0900244int ProximityInfo::getCodePointOf(const int keyIndex) const {
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900245 if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900246 return NOT_A_CODE_POINT;
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900247 }
Ken Wakasaf2789812012-09-04 12:49:46 +0900248 return mKeyIndexToCodePointG[keyIndex];
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900249}
250
251void ProximityInfo::initializeG() {
Satoshi Kataokae7398cd2012-08-13 20:20:04 +0900252 // TODO: Optimize
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900253 for (int i = 0; i < KEY_COUNT; ++i) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900254 const int code = mKeyCodePoints[i];
Ken Wakasa1d516fb2012-12-03 19:43:15 +0900255 const int lowerCode = toLowerCase(code);
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900256 mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
257 mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
Tom Ouyang13216852012-09-03 12:50:21 -0700258 mCodeToKeyMap[lowerCode] = i;
259 mKeyIndexToCodePointG[i] = lowerCode;
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900260 }
261 for (int i = 0; i < KEY_COUNT; i++) {
262 mKeyKeyDistancesG[i][i] = 0;
263 for (int j = i + 1; j < KEY_COUNT; j++) {
Ken Wakasabcec82d2012-08-12 11:10:48 +0900264 mKeyKeyDistancesG[i][j] = getDistanceInt(
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900265 mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
266 mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
267 }
268 }
269}
270
Ken Wakasa5964d4e2012-09-10 16:49:36 +0900271int ProximityInfo::getKeyCenterXOfCodePointG(int charCode) const {
Ken Wakasaf2789812012-09-04 12:49:46 +0900272 return getKeyCenterXOfKeyIdG(getKeyIndexOf(charCode));
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900273}
274
Ken Wakasa5964d4e2012-09-10 16:49:36 +0900275int ProximityInfo::getKeyCenterYOfCodePointG(int charCode) const {
Ken Wakasaf2789812012-09-04 12:49:46 +0900276 return getKeyCenterYOfKeyIdG(getKeyIndexOf(charCode));
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900277}
278
Ken Wakasa5964d4e2012-09-10 16:49:36 +0900279int ProximityInfo::getKeyCenterXOfKeyIdG(int keyId) const {
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900280 if (keyId >= 0) {
281 return mCenterXsG[keyId];
282 }
283 return 0;
284}
285
Ken Wakasa5964d4e2012-09-10 16:49:36 +0900286int ProximityInfo::getKeyCenterYOfKeyIdG(int keyId) const {
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900287 if (keyId >= 0) {
288 return mCenterYsG[keyId];
289 }
290 return 0;
291}
292
Keisuke Kuroyanagiff74cc32012-10-11 13:08:06 +0900293int ProximityInfo::getKeyKeyDistanceG(const int keyId0, const int keyId1) const {
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900294 if (keyId0 >= 0 && keyId1 >= 0) {
295 return mKeyKeyDistancesG[keyId0][keyId1];
296 }
Jean Chalard07aea402012-08-29 20:09:27 +0900297 return MAX_POINT_TO_KEY_LENGTH;
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900298}
Ken Wakasace9e52a2011-06-18 13:09:55 +0900299} // namespace latinime