blob: 9bb8b29ae2278bcb12a819bb387984a966c94dd8 [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 <cassert>
18#include <cmath>
19#include <cstring>
satok8fbd5522011-02-22 17:28:55 +090020
satok817e5172011-03-04 06:06:45 -080021#define LOG_TAG "LatinIME: proximity_info.cpp"
22
satok552c3c22012-03-13 16:33:47 +090023#include "additional_proximity_chars.h"
Ken Wakasa77e8e812012-08-02 19:48:08 +090024#include "char_utils.h"
Ken Wakasa3b088a22012-05-16 23:05:32 +090025#include "defines.h"
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +090026#include "geometry_utils.h"
Ken Wakasabb005f72012-08-08 20:43:47 +090027#include "jni.h"
satok8fbd5522011-02-22 17:28:55 +090028#include "proximity_info.h"
29
30namespace latinime {
Ken Wakasace9e52a2011-06-18 13:09:55 +090031
Ken Wakasa162c2112012-08-24 14:51:15 +090032/* static */ const float ProximityInfo::NOT_A_DISTANCE_FLOAT = -1.0f;
33
Ken Wakasabb005f72012-08-08 20:43:47 +090034static inline void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray, jsize len,
35 jint *buffer) {
36 if (jArray && buffer) {
37 env->GetIntArrayRegion(jArray, 0, len, buffer);
38 } else if (buffer) {
Ken Wakasa063c3e22012-08-10 22:10:04 +090039 memset(buffer, 0, len * sizeof(jint));
Yusuke Nojimade2f8422011-09-27 11:15:18 +090040 }
41}
42
Ken Wakasabb005f72012-08-08 20:43:47 +090043static inline void safeGetOrFillZeroFloatArrayRegion(JNIEnv *env, jfloatArray jArray, jsize len,
44 jfloat *buffer) {
45 if (jArray && buffer) {
46 env->GetFloatArrayRegion(jArray, 0, len, buffer);
47 } else if (buffer) {
Ken Wakasa063c3e22012-08-10 22:10:04 +090048 memset(buffer, 0, len * sizeof(jfloat));
Ken Wakasabb005f72012-08-08 20:43:47 +090049 }
50}
51
Ken Wakasa01511452012-08-09 15:58:15 +090052ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr, const int maxProximityCharsSize,
satok552c3c22012-03-13 16:33:47 +090053 const int keyboardWidth, const int keyboardHeight, const int gridWidth,
Ken Wakasabb005f72012-08-08 20:43:47 +090054 const int gridHeight, const int mostCommonKeyWidth, const jintArray proximityChars,
55 const int keyCount, const jintArray keyXCoordinates, const jintArray keyYCoordinates,
56 const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes,
57 const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs,
58 const jfloatArray sweetSpotRadii)
Ken Wakasa162c2112012-08-24 14:51:15 +090059 : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), GRID_WIDTH(gridWidth),
60 GRID_HEIGHT(gridHeight), MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth),
satoka70ee6e2012-03-07 15:12:22 +090061 MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
satok817e5172011-03-04 06:06:45 -080062 CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
Yusuke Nojima0e1f6562011-09-21 12:02:47 +090063 CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
Yusuke Nojima258bfe62011-09-28 12:59:43 +090064 KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
Keisuke Kuroyanagi95a49a52012-09-04 17:00:24 +090065 KEYBOARD_WIDTH(keyboardWidth), KEYBOARD_HEIGHT(keyboardHeight),
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +090066 HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
67 && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
Ken Wakasa162c2112012-08-24 14:51:15 +090068 && sweetSpotCenterYs && sweetSpotRadii),
69 mProximityCharsArray(new int32_t[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE
70 /* proximityGridLength */]) {
satok1035bc92012-06-13 16:07:54 -070071 const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
satok817e5172011-03-04 06:06:45 -080072 if (DEBUG_PROXIMITY_INFO) {
satok9fb6f472012-01-13 18:01:22 +090073 AKLOGI("Create proximity info array %d", proximityGridLength);
satok817e5172011-03-04 06:06:45 -080074 }
Ken Wakasa01511452012-08-09 15:58:15 +090075 const jsize localeCStrUtf8Length = env->GetStringUTFLength(localeJStr);
Ken Wakasa9e0c7112012-08-09 22:26:58 +090076 if (localeCStrUtf8Length >= MAX_LOCALE_STRING_LENGTH) {
77 AKLOGI("Locale string length too long: length=%d", localeCStrUtf8Length);
78 assert(false);
79 }
80 memset(mLocaleStr, 0, sizeof(mLocaleStr));
81 env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr);
Ken Wakasabb005f72012-08-08 20:43:47 +090082 safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityGridLength, mProximityCharsArray);
83 safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates);
84 safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates);
85 safeGetOrFillZeroIntArrayRegion(env, keyWidths, KEY_COUNT, mKeyWidths);
86 safeGetOrFillZeroIntArrayRegion(env, keyHeights, KEY_COUNT, mKeyHeights);
Ken Wakasaf2789812012-09-04 12:49:46 +090087 safeGetOrFillZeroIntArrayRegion(env, keyCharCodes, KEY_COUNT, mKeyCodePoints);
Ken Wakasabb005f72012-08-08 20:43:47 +090088 safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterXs, KEY_COUNT, mSweetSpotCenterXs);
89 safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterYs, KEY_COUNT, mSweetSpotCenterYs);
90 safeGetOrFillZeroFloatArrayRegion(env, sweetSpotRadii, KEY_COUNT, mSweetSpotRadii);
Ken Wakasaf2789812012-09-04 12:49:46 +090091 initializeCodePointToKeyIndex();
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +090092 initializeG();
Yusuke Nojima0e1f6562011-09-21 12:02:47 +090093}
94
Yusuke Nojima0e1f6562011-09-21 12:02:47 +090095// Build the reversed look up table from the char code to the index in mKeyXCoordinates,
96// mKeyYCoordinates, mKeyWidths, mKeyHeights, mKeyCharCodes.
Ken Wakasaf2789812012-09-04 12:49:46 +090097void ProximityInfo::initializeCodePointToKeyIndex() {
98 memset(mCodePointToKeyIndex, -1, sizeof(mCodePointToKeyIndex));
Yusuke Nojima0e1f6562011-09-21 12:02:47 +090099 for (int i = 0; i < KEY_COUNT; ++i) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900100 const int code = mKeyCodePoints[i];
Yusuke Nojimaad358352011-09-29 16:44:54 +0900101 if (0 <= code && code <= MAX_CHAR_CODE) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900102 mCodePointToKeyIndex[code] = i;
Yusuke Nojimaad358352011-09-29 16:44:54 +0900103 }
Yusuke Nojima0e1f6562011-09-21 12:02:47 +0900104 }
satok8fbd5522011-02-22 17:28:55 +0900105}
106
107ProximityInfo::~ProximityInfo() {
108 delete[] mProximityCharsArray;
109}
satok817e5172011-03-04 06:06:45 -0800110
111inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const {
satok3c4bb772011-03-04 22:50:19 -0800112 return ((y / CELL_HEIGHT) * GRID_WIDTH + (x / CELL_WIDTH))
satok817e5172011-03-04 06:06:45 -0800113 * MAX_PROXIMITY_CHARS_SIZE;
satok8fbd5522011-02-22 17:28:55 +0900114}
satok817e5172011-03-04 06:06:45 -0800115
116bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
satok744dab62011-12-15 22:29:05 +0900117 if (x < 0 || y < 0) {
118 if (DEBUG_DICT) {
satok9fb6f472012-01-13 18:01:22 +0900119 AKLOGI("HasSpaceProximity: Illegal coordinates (%d, %d)", x, y);
Ken Wakasaf2789812012-09-04 12:49:46 +0900120 // TODO: Enable this assertion.
121 //assert(false);
satok744dab62011-12-15 22:29:05 +0900122 }
123 return false;
124 }
125
satok817e5172011-03-04 06:06:45 -0800126 const int startIndex = getStartIndexFromCoordinates(x, y);
127 if (DEBUG_PROXIMITY_INFO) {
satok9fb6f472012-01-13 18:01:22 +0900128 AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
satok817e5172011-03-04 06:06:45 -0800129 }
Ken Wakasa0bbb9172012-07-25 17:51:43 +0900130 int32_t *proximityCharsArray = mProximityCharsArray;
satok817e5172011-03-04 06:06:45 -0800131 for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
132 if (DEBUG_PROXIMITY_INFO) {
satok9fb6f472012-01-13 18:01:22 +0900133 AKLOGI("Index: %d", mProximityCharsArray[startIndex + i]);
satok817e5172011-03-04 06:06:45 -0800134 }
Satoshi Kataoka3e8c58f2012-06-05 17:55:52 +0900135 if (proximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
satok817e5172011-03-04 06:06:45 -0800136 return true;
137 }
138 }
139 return false;
140}
Ken Wakasace9e52a2011-06-18 13:09:55 +0900141
Satoshi Kataokae7398cd2012-08-13 20:20:04 +0900142static inline float getNormalizedSquaredDistanceFloat(float x1, float y1, float x2, float y2,
143 float scale) {
144 return squareFloat((x1 - x2) / scale) + squareFloat((y1 - y2) / scale);
145}
146
147float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloat(
148 const int keyId, const int x, const int y) const {
Ken Wakasaf2789812012-09-04 12:49:46 +0900149 const float centerX = static_cast<float>(getKeyCenterXOfKeyIdG(keyId));
150 const float centerY = static_cast<float>(getKeyCenterYOfKeyIdG(keyId));
Satoshi Kataokae7398cd2012-08-13 20:20:04 +0900151 const float touchX = static_cast<float>(x);
152 const float touchY = static_cast<float>(y);
153 const float keyWidth = static_cast<float>(getMostCommonKeyWidth());
154 return getNormalizedSquaredDistanceFloat(centerX, centerY, touchX, touchY, keyWidth);
155}
156
satok9df4a452012-03-23 16:05:18 +0900157int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) const {
Jean Chalard081616c2012-03-22 17:39:27 +0900158 if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
satoka70ee6e2012-03-07 15:12:22 +0900159 const int left = mKeyXCoordinates[keyId];
160 const int top = mKeyYCoordinates[keyId];
satok1caff472012-03-14 23:17:12 +0900161 const int right = left + mKeyWidths[keyId];
satoka70ee6e2012-03-07 15:12:22 +0900162 const int bottom = top + mKeyHeights[keyId];
163 const int edgeX = x < left ? left : (x > right ? right : x);
164 const int edgeY = y < top ? top : (y > bottom ? bottom : y);
165 const int dx = x - edgeX;
166 const int dy = y - edgeY;
167 return dx * dx + dy * dy;
168}
169
170void ProximityInfo::calculateNearbyKeyCodes(
satok9df4a452012-03-23 16:05:18 +0900171 const int x, const int y, const int32_t primaryKey, int *inputCodes) const {
Satoshi Kataoka3e8c58f2012-06-05 17:55:52 +0900172 int32_t *proximityCharsArray = mProximityCharsArray;
satoka70ee6e2012-03-07 15:12:22 +0900173 int insertPos = 0;
174 inputCodes[insertPos++] = primaryKey;
175 const int startIndex = getStartIndexFromCoordinates(x, y);
Jean Chalard52612a02012-03-23 19:38:23 +0900176 if (startIndex >= 0) {
Jean Chalard88ec1252012-03-23 19:25:10 +0900177 for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
Satoshi Kataoka3e8c58f2012-06-05 17:55:52 +0900178 const int32_t c = proximityCharsArray[startIndex + i];
Jean Chalard88ec1252012-03-23 19:25:10 +0900179 if (c < KEYCODE_SPACE || c == primaryKey) {
180 continue;
181 }
Ken Wakasaf2789812012-09-04 12:49:46 +0900182 const int keyIndex = getKeyIndexOf(c);
Jean Chalard88ec1252012-03-23 19:25:10 +0900183 const bool onKey = isOnKey(keyIndex, x, y);
184 const int distance = squaredDistanceToEdge(keyIndex, x, y);
185 if (onKey || distance < MOST_COMMON_KEY_WIDTH_SQUARE) {
186 inputCodes[insertPos++] = c;
187 if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
188 if (DEBUG_DICT) {
189 assert(false);
190 }
191 return;
satok0cb20972012-03-13 22:07:56 +0900192 }
satoka70ee6e2012-03-07 15:12:22 +0900193 }
194 }
Jean Chalard88ec1252012-03-23 19:25:10 +0900195 const int additionalProximitySize =
Ken Wakasa01511452012-08-09 15:58:15 +0900196 AdditionalProximityChars::getAdditionalCharsSize(mLocaleStr, primaryKey);
Jean Chalard88ec1252012-03-23 19:25:10 +0900197 if (additionalProximitySize > 0) {
198 inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
199 if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
Jean Chalard3094d122012-03-23 19:36:07 +0900200 if (DEBUG_DICT) {
201 assert(false);
202 }
203 return;
satok1caff472012-03-14 23:17:12 +0900204 }
satok1caff472012-03-14 23:17:12 +0900205
Ken Wakasa0bbb9172012-07-25 17:51:43 +0900206 const int32_t *additionalProximityChars =
Ken Wakasa01511452012-08-09 15:58:15 +0900207 AdditionalProximityChars::getAdditionalChars(mLocaleStr, primaryKey);
Jean Chalard88ec1252012-03-23 19:25:10 +0900208 for (int j = 0; j < additionalProximitySize; ++j) {
209 const int32_t ac = additionalProximityChars[j];
210 int k = 0;
211 for (; k < insertPos; ++k) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900212 if (static_cast<int>(ac) == inputCodes[k]) {
Jean Chalard88ec1252012-03-23 19:25:10 +0900213 break;
214 }
satok5eec5742012-03-13 18:26:23 +0900215 }
Jean Chalard88ec1252012-03-23 19:25:10 +0900216 if (k < insertPos) {
217 continue;
satok0cb20972012-03-13 22:07:56 +0900218 }
Jean Chalard88ec1252012-03-23 19:25:10 +0900219 inputCodes[insertPos++] = ac;
220 if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
221 if (DEBUG_DICT) {
222 assert(false);
223 }
224 return;
Jean Chalard3094d122012-03-23 19:36:07 +0900225 }
satok0cb20972012-03-13 22:07:56 +0900226 }
satok5eec5742012-03-13 18:26:23 +0900227 }
Jean Chalard52612a02012-03-23 19:38:23 +0900228 }
satok0cb20972012-03-13 22:07:56 +0900229 // Add a delimiter for the proximity characters
satok1caff472012-03-14 23:17:12 +0900230 for (int i = insertPos; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900231 inputCodes[i] = NOT_A_CODE_POINT;
satok1caff472012-03-14 23:17:12 +0900232 }
satoka70ee6e2012-03-07 15:12:22 +0900233}
234
Ken Wakasaf2789812012-09-04 12:49:46 +0900235int ProximityInfo::getKeyIndexOf(const int c) const {
satok1caff472012-03-14 23:17:12 +0900236 if (KEY_COUNT == 0) {
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +0900237 // We do not have the coordinate data
Jean Chalardbbc25602012-03-23 17:05:03 +0900238 return NOT_AN_INDEX;
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +0900239 }
Tadashi G. Takaoka6e3cb272011-11-11 14:26:13 +0900240 const unsigned short baseLowerC = toBaseLowerCase(c);
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +0900241 if (baseLowerC > MAX_CHAR_CODE) {
Jean Chalardbbc25602012-03-23 17:05:03 +0900242 return NOT_AN_INDEX;
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +0900243 }
Ken Wakasaf2789812012-09-04 12:49:46 +0900244 return mCodePointToKeyIndex[baseLowerC];
Yusuke Nojimaa4c1f1c2011-10-06 19:12:20 +0900245}
Satoshi Kataokaefb63242012-06-27 14:52:40 +0900246
Ken Wakasaf2789812012-09-04 12:49:46 +0900247int ProximityInfo::getCodePointOf(const int keyIndex) const {
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900248 if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900249 return NOT_A_CODE_POINT;
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900250 }
Ken Wakasaf2789812012-09-04 12:49:46 +0900251 return mKeyIndexToCodePointG[keyIndex];
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900252}
253
254void ProximityInfo::initializeG() {
Satoshi Kataokae7398cd2012-08-13 20:20:04 +0900255 // TODO: Optimize
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900256 for (int i = 0; i < KEY_COUNT; ++i) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900257 const int code = mKeyCodePoints[i];
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900258 const int lowerCode = toBaseLowerCase(code);
259 mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
260 mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
261 if (code != lowerCode && lowerCode >= 0 && lowerCode <= MAX_CHAR_CODE) {
Ken Wakasaf2789812012-09-04 12:49:46 +0900262 mCodePointToKeyIndex[lowerCode] = i;
263 mKeyIndexToCodePointG[i] = lowerCode;
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900264 } else {
Ken Wakasaf2789812012-09-04 12:49:46 +0900265 mKeyIndexToCodePointG[i] = code;
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900266 }
267 }
268 for (int i = 0; i < KEY_COUNT; i++) {
269 mKeyKeyDistancesG[i][i] = 0;
270 for (int j = i + 1; j < KEY_COUNT; j++) {
Ken Wakasabcec82d2012-08-12 11:10:48 +0900271 mKeyKeyDistancesG[i][j] = getDistanceInt(
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900272 mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
273 mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
274 }
275 }
276}
277
Ken Wakasa5964d4e2012-09-10 16:49:36 +0900278int ProximityInfo::getKeyCenterXOfCodePointG(int charCode) const {
Ken Wakasaf2789812012-09-04 12:49:46 +0900279 return getKeyCenterXOfKeyIdG(getKeyIndexOf(charCode));
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900280}
281
Ken Wakasa5964d4e2012-09-10 16:49:36 +0900282int ProximityInfo::getKeyCenterYOfCodePointG(int charCode) const {
Ken Wakasaf2789812012-09-04 12:49:46 +0900283 return getKeyCenterYOfKeyIdG(getKeyIndexOf(charCode));
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900284}
285
Ken Wakasa5964d4e2012-09-10 16:49:36 +0900286int ProximityInfo::getKeyCenterXOfKeyIdG(int keyId) const {
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900287 if (keyId >= 0) {
288 return mCenterXsG[keyId];
289 }
290 return 0;
291}
292
Ken Wakasa5964d4e2012-09-10 16:49:36 +0900293int ProximityInfo::getKeyCenterYOfKeyIdG(int keyId) const {
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900294 if (keyId >= 0) {
295 return mCenterYsG[keyId];
296 }
297 return 0;
298}
299
300int ProximityInfo::getKeyKeyDistanceG(int key0, int key1) const {
Ken Wakasaf2789812012-09-04 12:49:46 +0900301 const int keyId0 = getKeyIndexOf(key0);
302 const int keyId1 = getKeyIndexOf(key1);
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900303 if (keyId0 >= 0 && keyId1 >= 0) {
304 return mKeyKeyDistancesG[keyId0][keyId1];
305 }
Jean Chalard07aea402012-08-29 20:09:27 +0900306 return MAX_POINT_TO_KEY_LENGTH;
Satoshi Kataoka6b4a1d72012-08-10 15:42:56 +0900307}
Ken Wakasace9e52a2011-06-18 13:09:55 +0900308} // namespace latinime