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 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
98ProximityInfo::~ProximityInfo() {
99    delete[] mProximityCharsArray;
100}
101
102bool 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
129float 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
139int 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
146int 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
153void 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.
195int 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.
221int 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
245int 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