proximity_info.cpp revision 5f6a247744abb66f8a3584b4fc175ca55caa65cb
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
32namespace latinime {
33
34static 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
43static 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
52ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr,
53        const int keyboardWidth, const int keyboardHeight, const int gridWidth,
54        const int gridHeight, const int mostCommonKeyWidth, const int mostCommonKeyHeight,
55        const jintArray proximityChars, const int keyCount, const jintArray keyXCoordinates,
56        const jintArray keyYCoordinates, const jintArray keyWidths, const jintArray keyHeights,
57        const jintArray keyCharCodes, const jfloatArray sweetSpotCenterXs,
58        const jfloatArray sweetSpotCenterYs, 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    const jsize localeCStrUtf8Length = env->GetStringUTFLength(localeJStr);
86    if (localeCStrUtf8Length >= MAX_LOCALE_STRING_LENGTH) {
87        AKLOGI("Locale string length too long: length=%d", localeCStrUtf8Length);
88        ASSERT(false);
89    }
90    memset(mLocaleStr, 0, sizeof(mLocaleStr));
91    env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr);
92    safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityCharsLength,
93            mProximityCharsArray);
94    safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates);
95    safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates);
96    safeGetOrFillZeroIntArrayRegion(env, keyWidths, KEY_COUNT, mKeyWidths);
97    safeGetOrFillZeroIntArrayRegion(env, keyHeights, KEY_COUNT, mKeyHeights);
98    safeGetOrFillZeroIntArrayRegion(env, keyCharCodes, KEY_COUNT, mKeyCodePoints);
99    safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterXs, KEY_COUNT, mSweetSpotCenterXs);
100    safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterYs, KEY_COUNT, mSweetSpotCenterYs);
101    safeGetOrFillZeroFloatArrayRegion(env, sweetSpotRadii, KEY_COUNT, mSweetSpotRadii);
102    initializeG();
103}
104
105ProximityInfo::~ProximityInfo() {
106    delete[] mProximityCharsArray;
107}
108
109bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
110    if (x < 0 || y < 0) {
111        if (DEBUG_DICT) {
112            AKLOGI("HasSpaceProximity: Illegal coordinates (%d, %d)", x, y);
113            // TODO: Enable this assertion.
114            //ASSERT(false);
115        }
116        return false;
117    }
118
119    const int startIndex = ProximityInfoUtils::getStartIndexFromCoordinates(x, y,
120            CELL_HEIGHT, CELL_WIDTH, GRID_WIDTH);
121    if (DEBUG_PROXIMITY_INFO) {
122        AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
123    }
124    int *proximityCharsArray = mProximityCharsArray;
125    for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
126        if (DEBUG_PROXIMITY_INFO) {
127            AKLOGI("Index: %d", mProximityCharsArray[startIndex + i]);
128        }
129        if (proximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
130            return true;
131        }
132    }
133    return false;
134}
135
136float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloatG(
137        const int keyId, const int x, const int y, const bool isGeometric) const {
138    const float centerX = static_cast<float>(getKeyCenterXOfKeyIdG(keyId, x, isGeometric));
139    const float centerY = static_cast<float>(getKeyCenterYOfKeyIdG(keyId, y, isGeometric));
140    const float touchX = static_cast<float>(x);
141    const float touchY = static_cast<float>(y);
142    return ProximityInfoUtils::getSquaredDistanceFloat(centerX, centerY, touchX, touchY)
143            / GeometryUtils::SQUARE_FLOAT(static_cast<float>(getMostCommonKeyWidth()));
144}
145
146int ProximityInfo::getCodePointOf(const int keyIndex) const {
147    if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
148        return NOT_A_CODE_POINT;
149    }
150    return mKeyIndexToLowerCodePointG[keyIndex];
151}
152
153int ProximityInfo::getOriginalCodePointOf(const int keyIndex) const {
154    if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
155        return NOT_A_CODE_POINT;
156    }
157    return mKeyIndexToOriginalCodePoint[keyIndex];
158}
159
160void ProximityInfo::initializeG() {
161    // TODO: Optimize
162    for (int i = 0; i < KEY_COUNT; ++i) {
163        const int code = mKeyCodePoints[i];
164        const int lowerCode = CharUtils::toLowerCase(code);
165        mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
166        mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
167        if (hasTouchPositionCorrectionData()) {
168            // Computes sweet spot center points for geometric input.
169            const float verticalScale = ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G;
170            const float sweetSpotCenterY = static_cast<float>(mSweetSpotCenterYs[i]);
171            const float gapY = sweetSpotCenterY - mCenterYsG[i];
172            mSweetSpotCenterYsG[i] = static_cast<int>(mCenterYsG[i] + gapY * verticalScale);
173        }
174        mLowerCodePointToKeyMap[lowerCode] = i;
175        mKeyIndexToOriginalCodePoint[i] = code;
176        mKeyIndexToLowerCodePointG[i] = lowerCode;
177    }
178    for (int i = 0; i < KEY_COUNT; i++) {
179        mKeyKeyDistancesG[i][i] = 0;
180        for (int j = i + 1; j < KEY_COUNT; j++) {
181            if (hasTouchPositionCorrectionData()) {
182                // Computes distances using sweet spots if they exist.
183                // We have two types of Y coordinate sweet spots, for geometric and for the others.
184                // The sweet spots for geometric input are used for calculating key-key distances
185                // here.
186                mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
187                        mSweetSpotCenterXs[i], mSweetSpotCenterYsG[i],
188                        mSweetSpotCenterXs[j], mSweetSpotCenterYsG[j]);
189            } else {
190                mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
191                        mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
192            }
193            mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
194        }
195    }
196}
197
198// referencePointX is used only for keys wider than most common key width. When the referencePointX
199// is NOT_A_COORDINATE, this method calculates the return value without using the line segment.
200// isGeometric is currently not used because we don't have extra X coordinates sweet spots for
201// geometric input.
202int ProximityInfo::getKeyCenterXOfKeyIdG(
203        const int keyId, const int referencePointX, const bool isGeometric) const {
204    if (keyId < 0) {
205        return 0;
206    }
207    int centerX = (hasTouchPositionCorrectionData()) ? static_cast<int>(mSweetSpotCenterXs[keyId])
208            : mCenterXsG[keyId];
209    const int keyWidth = mKeyWidths[keyId];
210    if (referencePointX != NOT_A_COORDINATE
211            && keyWidth > getMostCommonKeyWidth()) {
212        // For keys wider than most common keys, we use a line segment instead of the center point;
213        // thus, centerX is adjusted depending on referencePointX.
214        const int keyWidthHalfDiff = (keyWidth - getMostCommonKeyWidth()) / 2;
215        if (referencePointX < centerX - keyWidthHalfDiff) {
216            centerX -= keyWidthHalfDiff;
217        } else if (referencePointX > centerX + keyWidthHalfDiff) {
218            centerX += keyWidthHalfDiff;
219        } else {
220            centerX = referencePointX;
221        }
222    }
223    return centerX;
224}
225
226// When the referencePointY is NOT_A_COORDINATE, this method calculates the return value without
227// using the line segment.
228int ProximityInfo::getKeyCenterYOfKeyIdG(
229        const int keyId,  const int referencePointY, const bool isGeometric) const {
230    // TODO: Remove "isGeometric" and have separate "proximity_info"s for gesture and typing.
231    if (keyId < 0) {
232        return 0;
233    }
234    int centerY;
235    if (!hasTouchPositionCorrectionData()) {
236        centerY = mCenterYsG[keyId];
237    } else if (isGeometric) {
238        centerY = static_cast<int>(mSweetSpotCenterYsG[keyId]);
239    } else {
240        centerY = static_cast<int>(mSweetSpotCenterYs[keyId]);
241    }
242    if (referencePointY != NOT_A_COORDINATE &&
243            centerY + mKeyHeights[keyId] > KEYBOARD_HEIGHT && centerY < referencePointY) {
244        // When the distance between center point and bottom edge of the keyboard is shorter than
245        // the key height, we assume the key is located at the bottom row of the keyboard.
246        // The center point is extended to the bottom edge for such keys.
247        return referencePointY;
248    }
249    return centerY;
250}
251
252int ProximityInfo::getKeyKeyDistanceG(const int keyId0, const int keyId1) const {
253    if (keyId0 >= 0 && keyId1 >= 0) {
254        return mKeyKeyDistancesG[keyId0][keyId1];
255    }
256    return MAX_VALUE_FOR_WEIGHTING;
257}
258} // namespace latinime
259