Home | History | Annotate | Download | only in layout
      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 
     31 namespace latinime {
     32 
     33 static 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 
     42 static 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 
     51 ProximityInfo::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 
    105 ProximityInfo::~ProximityInfo() {
    106     delete[] mProximityCharsArray;
    107 }
    108 
    109 bool 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 
    136 float 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 
    146 int 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 
    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         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.
    194 int 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.
    220 int 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 
    244 int 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