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 <cstring>
22#include <cmath>
23
24#include "defines.h"
25#include "jni.h"
26#include "suggest/core/layout/additional_proximity_chars.h"
27#include "suggest/core/layout/geometry_utils.h"
28#include "suggest/core/layout/proximity_info_params.h"
29#include "utils/char_utils.h"
30
31namespace latinime {
32
33static AK_FORCE_INLINE void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray,
34        jsize len, jint *buffer) {
35    if (jArray && buffer) {
36        env->GetIntArrayRegion(jArray, 0, len, buffer);
37    } else if (buffer) {
38        memset(buffer, 0, len * sizeof(buffer[0]));
39    }
40}
41
42static AK_FORCE_INLINE void safeGetOrFillZeroFloatArrayRegion(JNIEnv *env, jfloatArray jArray,
43        jsize len, jfloat *buffer) {
44    if (jArray && buffer) {
45        env->GetFloatArrayRegion(jArray, 0, len, buffer);
46    } else if (buffer) {
47        memset(buffer, 0, len * sizeof(buffer[0]));
48    }
49}
50
51ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr,
52        const int keyboardWidth, const int keyboardHeight, const int gridWidth,
53        const int gridHeight, const int mostCommonKeyWidth, const int mostCommonKeyHeight,
54        const jintArray proximityChars, const int keyCount, const jintArray keyXCoordinates,
55        const jintArray keyYCoordinates, const jintArray keyWidths, const jintArray keyHeights,
56        const jintArray keyCharCodes, const jfloatArray sweetSpotCenterXs,
57        const jfloatArray sweetSpotCenterYs, const jfloatArray sweetSpotRadii)
58        : GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight), MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth),
59          MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
60          MOST_COMMON_KEY_HEIGHT(mostCommonKeyHeight),
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(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          mCodeToKeyMap() {
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 mKeyIndexToCodePointG[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        mCodeToKeyMap[lowerCode] = i;
168        mKeyIndexToCodePointG[i] = lowerCode;
169    }
170    for (int i = 0; i < KEY_COUNT; i++) {
171        mKeyKeyDistancesG[i][i] = 0;
172        for (int j = i + 1; j < KEY_COUNT; j++) {
173            if (hasTouchPositionCorrectionData()) {
174                // Computes distances using sweet spots if they exist.
175                // We have two types of Y coordinate sweet spots, for geometric and for the others.
176                // The sweet spots for geometric input are used for calculating key-key distances
177                // here.
178                mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
179                        mSweetSpotCenterXs[i], mSweetSpotCenterYsG[i],
180                        mSweetSpotCenterXs[j], mSweetSpotCenterYsG[j]);
181            } else {
182                mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
183                        mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
184            }
185            mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
186        }
187    }
188}
189
190// referencePointX is used only for keys wider than most common key width. When the referencePointX
191// is NOT_A_COORDINATE, this method calculates the return value without using the line segment.
192// isGeometric is currently not used because we don't have extra X coordinates sweet spots for
193// geometric input.
194int ProximityInfo::getKeyCenterXOfKeyIdG(
195        const int keyId, const int referencePointX, const bool isGeometric) const {
196    if (keyId < 0) {
197        return 0;
198    }
199    int centerX = (hasTouchPositionCorrectionData()) ? static_cast<int>(mSweetSpotCenterXs[keyId])
200            : mCenterXsG[keyId];
201    const int keyWidth = mKeyWidths[keyId];
202    if (referencePointX != NOT_A_COORDINATE
203            && keyWidth > getMostCommonKeyWidth()) {
204        // For keys wider than most common keys, we use a line segment instead of the center point;
205        // thus, centerX is adjusted depending on referencePointX.
206        const int keyWidthHalfDiff = (keyWidth - getMostCommonKeyWidth()) / 2;
207        if (referencePointX < centerX - keyWidthHalfDiff) {
208            centerX -= keyWidthHalfDiff;
209        } else if (referencePointX > centerX + keyWidthHalfDiff) {
210            centerX += keyWidthHalfDiff;
211        } else {
212            centerX = referencePointX;
213        }
214    }
215    return centerX;
216}
217
218// When the referencePointY is NOT_A_COORDINATE, this method calculates the return value without
219// using the line segment.
220int ProximityInfo::getKeyCenterYOfKeyIdG(
221        const int keyId,  const int referencePointY, const bool isGeometric) const {
222    // TODO: Remove "isGeometric" and have separate "proximity_info"s for gesture and typing.
223    if (keyId < 0) {
224        return 0;
225    }
226    int centerY;
227    if (!hasTouchPositionCorrectionData()) {
228        centerY = mCenterYsG[keyId];
229    } else if (isGeometric) {
230        centerY = static_cast<int>(mSweetSpotCenterYsG[keyId]);
231    } else {
232        centerY = static_cast<int>(mSweetSpotCenterYs[keyId]);
233    }
234    if (referencePointY != NOT_A_COORDINATE &&
235            centerY + mKeyHeights[keyId] > KEYBOARD_HEIGHT && centerY < referencePointY) {
236        // When the distance between center point and bottom edge of the keyboard is shorter than
237        // the key height, we assume the key is located at the bottom row of the keyboard.
238        // The center point is extended to the bottom edge for such keys.
239        return referencePointY;
240    }
241    return centerY;
242}
243
244int ProximityInfo::getKeyKeyDistanceG(const int keyId0, const int keyId1) const {
245    if (keyId0 >= 0 && keyId1 >= 0) {
246        return mKeyKeyDistancesG[keyId0][keyId1];
247    }
248    return MAX_VALUE_FOR_WEIGHTING;
249}
250} // namespace latinime
251