1 /* 2 * Copyright (C) 2012 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 #include <cstring> // for memset() and memcpy() 18 #include <sstream> // for debug prints 19 #include <vector> 20 21 #define LOG_TAG "LatinIME: proximity_info_state.cpp" 22 23 #include "defines.h" 24 #include "geometry_utils.h" 25 #include "proximity_info.h" 26 #include "proximity_info_state.h" 27 #include "proximity_info_state_utils.h" 28 29 namespace latinime { 30 31 // TODO: Remove the dependency of "isGeometric" 32 void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength, 33 const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize, 34 const int *const xCoordinates, const int *const yCoordinates, const int *const times, 35 const int *const pointerIds, const bool isGeometric) { 36 ASSERT(isGeometric || (inputSize < MAX_WORD_LENGTH)); 37 mIsContinuousSuggestionPossible = 38 ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible( 39 inputSize, xCoordinates, yCoordinates, times, mSampledInputSize, 40 &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledInputIndice); 41 if (DEBUG_DICT) { 42 AKLOGI("isContinuousSuggestionPossible = %s", 43 (mIsContinuousSuggestionPossible ? "true" : "false")); 44 } 45 46 mProximityInfo = proximityInfo; 47 mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData(); 48 mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare(); 49 mKeyCount = proximityInfo->getKeyCount(); 50 mCellHeight = proximityInfo->getCellHeight(); 51 mCellWidth = proximityInfo->getCellWidth(); 52 mGridHeight = proximityInfo->getGridWidth(); 53 mGridWidth = proximityInfo->getGridHeight(); 54 55 memset(mInputProximities, 0, sizeof(mInputProximities)); 56 57 if (!isGeometric && pointerId == 0) { 58 mProximityInfo->initializeProximities(inputCodes, xCoordinates, yCoordinates, 59 inputSize, mInputProximities); 60 } 61 62 /////////////////////// 63 // Setup touch points 64 int pushTouchPointStartIndex = 0; 65 int lastSavedInputSize = 0; 66 mMaxPointToKeyLength = maxPointToKeyLength; 67 mSampledInputSize = 0; 68 mMostProbableStringProbability = 0.0f; 69 70 if (mIsContinuousSuggestionPossible && mSampledInputIndice.size() > 1) { 71 // Just update difference. 72 // Previous two points are never skipped. Thus, we pop 2 input point data here. 73 pushTouchPointStartIndex = ProximityInfoStateUtils::trimLastTwoTouchPoints( 74 &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledLengthCache, 75 &mSampledInputIndice); 76 lastSavedInputSize = mSampledInputXs.size(); 77 } else { 78 // Clear all data. 79 mSampledInputXs.clear(); 80 mSampledInputYs.clear(); 81 mSampledTimes.clear(); 82 mSampledInputIndice.clear(); 83 mSampledLengthCache.clear(); 84 mSampledNormalizedSquaredLengthCache.clear(); 85 mSampledNearKeySets.clear(); 86 mSampledSearchKeySets.clear(); 87 mSpeedRates.clear(); 88 mBeelineSpeedPercentiles.clear(); 89 mCharProbabilities.clear(); 90 mDirections.clear(); 91 } 92 93 if (DEBUG_GEO_FULL) { 94 AKLOGI("Init ProximityInfoState: reused points = %d, last input size = %d", 95 pushTouchPointStartIndex, lastSavedInputSize); 96 } 97 98 // TODO: Remove the dependency of "isGeometric" 99 const float verticalSweetSpotScale = isGeometric 100 ? ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G 101 : ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE; 102 103 if (xCoordinates && yCoordinates) { 104 mSampledInputSize = ProximityInfoStateUtils::updateTouchPoints(mProximityInfo, 105 mMaxPointToKeyLength, mInputProximities, xCoordinates, yCoordinates, times, 106 pointerIds, verticalSweetSpotScale, inputSize, isGeometric, pointerId, 107 pushTouchPointStartIndex, &mSampledInputXs, &mSampledInputYs, &mSampledTimes, 108 &mSampledLengthCache, &mSampledInputIndice); 109 } 110 111 if (mSampledInputSize > 0 && isGeometric) { 112 mAverageSpeed = ProximityInfoStateUtils::refreshSpeedRates(inputSize, xCoordinates, 113 yCoordinates, times, lastSavedInputSize, mSampledInputSize, &mSampledInputXs, 114 &mSampledInputYs, &mSampledTimes, &mSampledLengthCache, &mSampledInputIndice, 115 &mSpeedRates, &mDirections); 116 ProximityInfoStateUtils::refreshBeelineSpeedRates(mProximityInfo->getMostCommonKeyWidth(), 117 mAverageSpeed, inputSize, xCoordinates, yCoordinates, times, mSampledInputSize, 118 &mSampledInputXs, &mSampledInputYs, &mSampledInputIndice, 119 &mBeelineSpeedPercentiles); 120 } 121 122 if (mSampledInputSize > 0) { 123 ProximityInfoStateUtils::initGeometricDistanceInfos(mProximityInfo, mSampledInputSize, 124 lastSavedInputSize, verticalSweetSpotScale, &mSampledInputXs, &mSampledInputYs, 125 &mSampledNearKeySets, &mSampledNormalizedSquaredLengthCache); 126 if (isGeometric) { 127 // updates probabilities of skipping or mapping each key for all points. 128 ProximityInfoStateUtils::updateAlignPointProbabilities( 129 mMaxPointToKeyLength, mProximityInfo->getMostCommonKeyWidth(), 130 mProximityInfo->getKeyCount(), lastSavedInputSize, mSampledInputSize, 131 &mSampledInputXs, &mSampledInputYs, &mSpeedRates, &mSampledLengthCache, 132 &mSampledNormalizedSquaredLengthCache, &mSampledNearKeySets, 133 &mCharProbabilities); 134 ProximityInfoStateUtils::updateSampledSearchKeySets(mProximityInfo, 135 mSampledInputSize, lastSavedInputSize, &mSampledLengthCache, 136 &mSampledNearKeySets, &mSampledSearchKeySets, 137 &mSampledSearchKeyVectors); 138 mMostProbableStringProbability = ProximityInfoStateUtils::getMostProbableString( 139 mProximityInfo, mSampledInputSize, &mCharProbabilities, mMostProbableString); 140 141 } 142 } 143 144 if (DEBUG_SAMPLING_POINTS) { 145 ProximityInfoStateUtils::dump(isGeometric, inputSize, xCoordinates, yCoordinates, 146 mSampledInputSize, &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSpeedRates, 147 &mBeelineSpeedPercentiles); 148 } 149 // end 150 /////////////////////// 151 152 mTouchPositionCorrectionEnabled = mSampledInputSize > 0 && mHasTouchPositionCorrectionData 153 && xCoordinates && yCoordinates; 154 if (!isGeometric && pointerId == 0) { 155 ProximityInfoStateUtils::initPrimaryInputWord( 156 inputSize, mInputProximities, mPrimaryInputWord); 157 if (mTouchPositionCorrectionEnabled) { 158 ProximityInfoStateUtils::initNormalizedSquaredDistances( 159 mProximityInfo, inputSize, xCoordinates, yCoordinates, mInputProximities, 160 &mSampledInputXs, &mSampledInputYs, mNormalizedSquaredDistances); 161 } 162 } 163 if (DEBUG_GEO_FULL) { 164 AKLOGI("ProximityState init finished: %d points out of %d", mSampledInputSize, inputSize); 165 } 166 } 167 168 // This function basically converts from a length to an edit distance. Accordingly, it's obviously 169 // wrong to compare with mMaxPointToKeyLength. 170 float ProximityInfoState::getPointToKeyLength( 171 const int inputIndex, const int codePoint) const { 172 const int keyId = mProximityInfo->getKeyIndexOf(codePoint); 173 if (keyId != NOT_AN_INDEX) { 174 const int index = inputIndex * mProximityInfo->getKeyCount() + keyId; 175 return min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength); 176 } 177 if (isIntentionalOmissionCodePoint(codePoint)) { 178 return 0.0f; 179 } 180 // If the char is not a key on the keyboard then return the max length. 181 return static_cast<float>(MAX_VALUE_FOR_WEIGHTING); 182 } 183 184 float ProximityInfoState::getPointToKeyByIdLength( 185 const int inputIndex, const int keyId) const { 186 return ProximityInfoStateUtils::getPointToKeyByIdLength(mMaxPointToKeyLength, 187 &mSampledNormalizedSquaredLengthCache, mProximityInfo->getKeyCount(), inputIndex, 188 keyId); 189 } 190 191 // In the following function, c is the current character of the dictionary word currently examined. 192 // currentChars is an array containing the keys close to the character the user actually typed at 193 // the same position. We want to see if c is in it: if so, then the word contains at that position 194 // a character close to what the user typed. 195 // What the user typed is actually the first character of the array. 196 // proximityIndex is a pointer to the variable where getProximityType returns the index of c 197 // in the proximity chars of the input index. 198 // Notice : accented characters do not have a proximity list, so they are alone in their list. The 199 // non-accented version of the character should be considered "close", but not the other keys close 200 // to the non-accented version. 201 ProximityType ProximityInfoState::getProximityType(const int index, const int codePoint, 202 const bool checkProximityChars, int *proximityIndex) const { 203 const int *currentCodePoints = getProximityCodePointsAt(index); 204 const int firstCodePoint = currentCodePoints[0]; 205 const int baseLowerC = toBaseLowerCase(codePoint); 206 207 // The first char in the array is what user typed. If it matches right away, that means the 208 // user typed that same char for this pos. 209 if (firstCodePoint == baseLowerC || firstCodePoint == codePoint) { 210 return MATCH_CHAR; 211 } 212 213 if (!checkProximityChars) return SUBSTITUTION_CHAR; 214 215 // If the non-accented, lowercased version of that first character matches c, then we have a 216 // non-accented version of the accented character the user typed. Treat it as a close char. 217 if (toBaseLowerCase(firstCodePoint) == baseLowerC) { 218 return PROXIMITY_CHAR; 219 } 220 221 // Not an exact nor an accent-alike match: search the list of close keys 222 int j = 1; 223 while (j < MAX_PROXIMITY_CHARS_SIZE 224 && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { 225 const bool matched = (currentCodePoints[j] == baseLowerC 226 || currentCodePoints[j] == codePoint); 227 if (matched) { 228 if (proximityIndex) { 229 *proximityIndex = j; 230 } 231 return PROXIMITY_CHAR; 232 } 233 ++j; 234 } 235 if (j < MAX_PROXIMITY_CHARS_SIZE 236 && currentCodePoints[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { 237 ++j; 238 while (j < MAX_PROXIMITY_CHARS_SIZE 239 && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { 240 const bool matched = (currentCodePoints[j] == baseLowerC 241 || currentCodePoints[j] == codePoint); 242 if (matched) { 243 if (proximityIndex) { 244 *proximityIndex = j; 245 } 246 return ADDITIONAL_PROXIMITY_CHAR; 247 } 248 ++j; 249 } 250 } 251 // Was not included, signal this as a substitution character. 252 return SUBSTITUTION_CHAR; 253 } 254 255 ProximityType ProximityInfoState::getProximityTypeG(const int index, const int codePoint) const { 256 if (!isUsed()) { 257 return UNRELATED_CHAR; 258 } 259 const int lowerCodePoint = toLowerCase(codePoint); 260 const int baseLowerCodePoint = toBaseCodePoint(lowerCodePoint); 261 for (int i = 0; i < static_cast<int>(mSampledSearchKeyVectors[index].size()); ++i) { 262 if (mSampledSearchKeyVectors[index][i] == lowerCodePoint 263 || mSampledSearchKeyVectors[index][i] == baseLowerCodePoint) { 264 return MATCH_CHAR; 265 } 266 } 267 return UNRELATED_CHAR; 268 } 269 270 bool ProximityInfoState::isKeyInSerchKeysAfterIndex(const int index, const int keyId) const { 271 ASSERT(keyId >= 0 && index >= 0 && index < mSampledInputSize); 272 return mSampledSearchKeySets[index].test(keyId); 273 } 274 275 float ProximityInfoState::getDirection(const int index0, const int index1) const { 276 return ProximityInfoStateUtils::getDirection( 277 &mSampledInputXs, &mSampledInputYs, index0, index1); 278 } 279 280 float ProximityInfoState::getLineToKeyDistance( 281 const int from, const int to, const int keyId, const bool extend) const { 282 if (from < 0 || from > mSampledInputSize - 1) { 283 return 0.0f; 284 } 285 if (to < 0 || to > mSampledInputSize - 1) { 286 return 0.0f; 287 } 288 const int x0 = mSampledInputXs[from]; 289 const int y0 = mSampledInputYs[from]; 290 const int x1 = mSampledInputXs[to]; 291 const int y1 = mSampledInputYs[to]; 292 293 const int keyX = mProximityInfo->getKeyCenterXOfKeyIdG(keyId); 294 const int keyY = mProximityInfo->getKeyCenterYOfKeyIdG(keyId); 295 296 return ProximityInfoUtils::pointToLineSegSquaredDistanceFloat( 297 keyX, keyY, x0, y0, x1, y1, extend); 298 } 299 300 float ProximityInfoState::getMostProbableString(int *const codePointBuf) const { 301 memcpy(codePointBuf, mMostProbableString, sizeof(mMostProbableString)); 302 return mMostProbableStringProbability; 303 } 304 305 bool ProximityInfoState::hasSpaceProximity(const int index) const { 306 ASSERT(0 <= index && index < mSampledInputSize); 307 return mProximityInfo->hasSpaceProximity(getInputX(index), getInputY(index)); 308 } 309 310 // Returns a probability of mapping index to keyIndex. 311 float ProximityInfoState::getProbability(const int index, const int keyIndex) const { 312 ASSERT(0 <= index && index < mSampledInputSize); 313 hash_map_compat<int, float>::const_iterator it = mCharProbabilities[index].find(keyIndex); 314 if (it != mCharProbabilities[index].end()) { 315 return it->second; 316 } 317 return static_cast<float>(MAX_VALUE_FOR_WEIGHTING); 318 } 319 } // namespace latinime 320