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 <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 
     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 
     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 
     52 ProximityInfo::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 
    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 mKeyIndexToLowerCodePointG[keyIndex];
    151 }
    152 
    153 int 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 
    160 void 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.
    202 int 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.
    228 int 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 
    252 int 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