1 /*
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
17 #define LOG_TAG "LatinIME: proximity_info.cpp"
18
19 #include "suggest/core/layout/proximity_info.h"
20
21 #include <algorithm>
22 #include <cstring>
23 #include <cmath>
24
25 #include "defines.h"
26 #include "jni.h"
27 #include "suggest/core/layout/additional_proximity_chars.h"
28 #include "suggest/core/layout/geometry_utils.h"
29 #include "suggest/core/layout/proximity_info_params.h"
30 #include "utils/char_utils.h"
31
32 namespace latinime {
33
safeGetOrFillZeroIntArrayRegion(JNIEnv * env,jintArray jArray,jsize len,jint * buffer)34 static AK_FORCE_INLINE void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray,
35 jsize len, jint *buffer) {
36 if (jArray && buffer) {
37 env->GetIntArrayRegion(jArray, 0, len, buffer);
38 } else if (buffer) {
39 memset(buffer, 0, len * sizeof(buffer[0]));
40 }
41 }
42
safeGetOrFillZeroFloatArrayRegion(JNIEnv * env,jfloatArray jArray,jsize len,jfloat * buffer)43 static AK_FORCE_INLINE void safeGetOrFillZeroFloatArrayRegion(JNIEnv *env, jfloatArray jArray,
44 jsize len, jfloat *buffer) {
45 if (jArray && buffer) {
46 env->GetFloatArrayRegion(jArray, 0, len, buffer);
47 } else if (buffer) {
48 memset(buffer, 0, len * sizeof(buffer[0]));
49 }
50 }
51
ProximityInfo(JNIEnv * env,const int keyboardWidth,const int keyboardHeight,const int gridWidth,const int gridHeight,const int mostCommonKeyWidth,const int mostCommonKeyHeight,const jintArray proximityChars,const int keyCount,const jintArray keyXCoordinates,const jintArray keyYCoordinates,const jintArray keyWidths,const jintArray keyHeights,const jintArray keyCharCodes,const jfloatArray sweetSpotCenterXs,const jfloatArray sweetSpotCenterYs,const jfloatArray sweetSpotRadii)52 ProximityInfo::ProximityInfo(JNIEnv *env, const int keyboardWidth, const int keyboardHeight,
53 const int gridWidth, const int gridHeight, const int mostCommonKeyWidth,
54 const int mostCommonKeyHeight, const jintArray proximityChars, const int keyCount,
55 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)
59 : GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight), MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth),
60 MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
61 NORMALIZED_SQUARED_MOST_COMMON_KEY_HYPOTENUSE(1.0f +
62 GeometryUtils::SQUARE_FLOAT(static_cast<float>(mostCommonKeyHeight) /
63 static_cast<float>(mostCommonKeyWidth))),
64 CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
65 CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
66 KEY_COUNT(std::min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
67 KEYBOARD_WIDTH(keyboardWidth), KEYBOARD_HEIGHT(keyboardHeight),
68 KEYBOARD_HYPOTENUSE(hypotf(KEYBOARD_WIDTH, KEYBOARD_HEIGHT)),
69 HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
70 && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
71 && sweetSpotCenterYs && sweetSpotRadii),
72 mProximityCharsArray(new int[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE
73 /* proximityCharsLength */]),
74 mLowerCodePointToKeyMap() {
75 /* Let's check the input array length here to make sure */
76 const jsize proximityCharsLength = env->GetArrayLength(proximityChars);
77 if (proximityCharsLength != GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE) {
78 AKLOGE("Invalid proximityCharsLength: %d", proximityCharsLength);
79 ASSERT(false);
80 return;
81 }
82 if (DEBUG_PROXIMITY_INFO) {
83 AKLOGI("Create proximity info array %d", proximityCharsLength);
84 }
85 safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityCharsLength,
86 mProximityCharsArray);
87 safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates);
88 safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates);
89 safeGetOrFillZeroIntArrayRegion(env, keyWidths, KEY_COUNT, mKeyWidths);
90 safeGetOrFillZeroIntArrayRegion(env, keyHeights, KEY_COUNT, mKeyHeights);
91 safeGetOrFillZeroIntArrayRegion(env, keyCharCodes, KEY_COUNT, mKeyCodePoints);
92 safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterXs, KEY_COUNT, mSweetSpotCenterXs);
93 safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterYs, KEY_COUNT, mSweetSpotCenterYs);
94 safeGetOrFillZeroFloatArrayRegion(env, sweetSpotRadii, KEY_COUNT, mSweetSpotRadii);
95 initializeG();
96 }
97
~ProximityInfo()98 ProximityInfo::~ProximityInfo() {
99 delete[] mProximityCharsArray;
100 }
101
hasSpaceProximity(const int x,const int y) const102 bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
103 if (x < 0 || y < 0) {
104 if (DEBUG_DICT) {
105 AKLOGI("HasSpaceProximity: Illegal coordinates (%d, %d)", x, y);
106 // TODO: Enable this assertion.
107 //ASSERT(false);
108 }
109 return false;
110 }
111
112 const int startIndex = ProximityInfoUtils::getStartIndexFromCoordinates(x, y,
113 CELL_HEIGHT, CELL_WIDTH, GRID_WIDTH);
114 if (DEBUG_PROXIMITY_INFO) {
115 AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
116 }
117 int *proximityCharsArray = mProximityCharsArray;
118 for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
119 if (DEBUG_PROXIMITY_INFO) {
120 AKLOGI("Index: %d", mProximityCharsArray[startIndex + i]);
121 }
122 if (proximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
123 return true;
124 }
125 }
126 return false;
127 }
128
getNormalizedSquaredDistanceFromCenterFloatG(const int keyId,const int x,const int y,const bool isGeometric) const129 float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloatG(
130 const int keyId, const int x, const int y, const bool isGeometric) const {
131 const float centerX = static_cast<float>(getKeyCenterXOfKeyIdG(keyId, x, isGeometric));
132 const float centerY = static_cast<float>(getKeyCenterYOfKeyIdG(keyId, y, isGeometric));
133 const float touchX = static_cast<float>(x);
134 const float touchY = static_cast<float>(y);
135 return ProximityInfoUtils::getSquaredDistanceFloat(centerX, centerY, touchX, touchY)
136 / GeometryUtils::SQUARE_FLOAT(static_cast<float>(getMostCommonKeyWidth()));
137 }
138
getCodePointOf(const int keyIndex) const139 int ProximityInfo::getCodePointOf(const int keyIndex) const {
140 if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
141 return NOT_A_CODE_POINT;
142 }
143 return mKeyIndexToLowerCodePointG[keyIndex];
144 }
145
getOriginalCodePointOf(const int keyIndex) const146 int ProximityInfo::getOriginalCodePointOf(const int keyIndex) const {
147 if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
148 return NOT_A_CODE_POINT;
149 }
150 return mKeyIndexToOriginalCodePoint[keyIndex];
151 }
152
initializeG()153 void ProximityInfo::initializeG() {
154 // TODO: Optimize
155 for (int i = 0; i < KEY_COUNT; ++i) {
156 const int code = mKeyCodePoints[i];
157 const int lowerCode = CharUtils::toLowerCase(code);
158 mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
159 mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
160 if (hasTouchPositionCorrectionData()) {
161 // Computes sweet spot center points for geometric input.
162 const float verticalScale = ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G;
163 const float sweetSpotCenterY = static_cast<float>(mSweetSpotCenterYs[i]);
164 const float gapY = sweetSpotCenterY - mCenterYsG[i];
165 mSweetSpotCenterYsG[i] = static_cast<int>(mCenterYsG[i] + gapY * verticalScale);
166 }
167 mLowerCodePointToKeyMap[lowerCode] = i;
168 mKeyIndexToOriginalCodePoint[i] = code;
169 mKeyIndexToLowerCodePointG[i] = lowerCode;
170 }
171 for (int i = 0; i < KEY_COUNT; i++) {
172 mKeyKeyDistancesG[i][i] = 0;
173 for (int j = i + 1; j < KEY_COUNT; j++) {
174 if (hasTouchPositionCorrectionData()) {
175 // Computes distances using sweet spots if they exist.
176 // We have two types of Y coordinate sweet spots, for geometric and for the others.
177 // The sweet spots for geometric input are used for calculating key-key distances
178 // here.
179 mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
180 mSweetSpotCenterXs[i], mSweetSpotCenterYsG[i],
181 mSweetSpotCenterXs[j], mSweetSpotCenterYsG[j]);
182 } else {
183 mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
184 mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
185 }
186 mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
187 }
188 }
189 }
190
191 // referencePointX is used only for keys wider than most common key width. When the referencePointX
192 // is NOT_A_COORDINATE, this method calculates the return value without using the line segment.
193 // isGeometric is currently not used because we don't have extra X coordinates sweet spots for
194 // geometric input.
getKeyCenterXOfKeyIdG(const int keyId,const int referencePointX,const bool isGeometric) const195 int ProximityInfo::getKeyCenterXOfKeyIdG(
196 const int keyId, const int referencePointX, const bool isGeometric) const {
197 if (keyId < 0) {
198 return 0;
199 }
200 int centerX = (hasTouchPositionCorrectionData()) ? static_cast<int>(mSweetSpotCenterXs[keyId])
201 : mCenterXsG[keyId];
202 const int keyWidth = mKeyWidths[keyId];
203 if (referencePointX != NOT_A_COORDINATE
204 && keyWidth > getMostCommonKeyWidth()) {
205 // For keys wider than most common keys, we use a line segment instead of the center point;
206 // thus, centerX is adjusted depending on referencePointX.
207 const int keyWidthHalfDiff = (keyWidth - getMostCommonKeyWidth()) / 2;
208 if (referencePointX < centerX - keyWidthHalfDiff) {
209 centerX -= keyWidthHalfDiff;
210 } else if (referencePointX > centerX + keyWidthHalfDiff) {
211 centerX += keyWidthHalfDiff;
212 } else {
213 centerX = referencePointX;
214 }
215 }
216 return centerX;
217 }
218
219 // When the referencePointY is NOT_A_COORDINATE, this method calculates the return value without
220 // using the line segment.
getKeyCenterYOfKeyIdG(const int keyId,const int referencePointY,const bool isGeometric) const221 int ProximityInfo::getKeyCenterYOfKeyIdG(
222 const int keyId, const int referencePointY, const bool isGeometric) const {
223 // TODO: Remove "isGeometric" and have separate "proximity_info"s for gesture and typing.
224 if (keyId < 0) {
225 return 0;
226 }
227 int centerY;
228 if (!hasTouchPositionCorrectionData()) {
229 centerY = mCenterYsG[keyId];
230 } else if (isGeometric) {
231 centerY = static_cast<int>(mSweetSpotCenterYsG[keyId]);
232 } else {
233 centerY = static_cast<int>(mSweetSpotCenterYs[keyId]);
234 }
235 if (referencePointY != NOT_A_COORDINATE &&
236 centerY + mKeyHeights[keyId] > KEYBOARD_HEIGHT && centerY < referencePointY) {
237 // When the distance between center point and bottom edge of the keyboard is shorter than
238 // the key height, we assume the key is located at the bottom row of the keyboard.
239 // The center point is extended to the bottom edge for such keys.
240 return referencePointY;
241 }
242 return centerY;
243 }
244
getKeyKeyDistanceG(const int keyId0,const int keyId1) const245 int ProximityInfo::getKeyKeyDistanceG(const int keyId0, const int keyId1) const {
246 if (keyId0 >= 0 && keyId1 >= 0) {
247 return mKeyKeyDistancesG[keyId0][keyId1];
248 }
249 return MAX_VALUE_FOR_WEIGHTING;
250 }
251 } // namespace latinime
252