/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <cstring> // for memset() and memcpy() #include <sstream> // for debug prints #include <vector> #define LOG_TAG "LatinIME: proximity_info_state.cpp" #include "defines.h" #include "geometry_utils.h" #include "proximity_info.h" #include "proximity_info_state.h" #include "proximity_info_state_utils.h" namespace latinime { // TODO: Remove the dependency of "isGeometric" void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength, const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize, const int *const xCoordinates, const int *const yCoordinates, const int *const times, const int *const pointerIds, const bool isGeometric) { ASSERT(isGeometric || (inputSize < MAX_WORD_LENGTH)); mIsContinuousSuggestionPossible = ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible( inputSize, xCoordinates, yCoordinates, times, mSampledInputSize, &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledInputIndice); if (DEBUG_DICT) { AKLOGI("isContinuousSuggestionPossible = %s", (mIsContinuousSuggestionPossible ? "true" : "false")); } mProximityInfo = proximityInfo; mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData(); mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare(); mKeyCount = proximityInfo->getKeyCount(); mCellHeight = proximityInfo->getCellHeight(); mCellWidth = proximityInfo->getCellWidth(); mGridHeight = proximityInfo->getGridWidth(); mGridWidth = proximityInfo->getGridHeight(); memset(mInputProximities, 0, sizeof(mInputProximities)); if (!isGeometric && pointerId == 0) { mProximityInfo->initializeProximities(inputCodes, xCoordinates, yCoordinates, inputSize, mInputProximities); } /////////////////////// // Setup touch points int pushTouchPointStartIndex = 0; int lastSavedInputSize = 0; mMaxPointToKeyLength = maxPointToKeyLength; mSampledInputSize = 0; mMostProbableStringProbability = 0.0f; if (mIsContinuousSuggestionPossible && mSampledInputIndice.size() > 1) { // Just update difference. // Previous two points are never skipped. Thus, we pop 2 input point data here. pushTouchPointStartIndex = ProximityInfoStateUtils::trimLastTwoTouchPoints( &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledLengthCache, &mSampledInputIndice); lastSavedInputSize = mSampledInputXs.size(); } else { // Clear all data. mSampledInputXs.clear(); mSampledInputYs.clear(); mSampledTimes.clear(); mSampledInputIndice.clear(); mSampledLengthCache.clear(); mSampledNormalizedSquaredLengthCache.clear(); mSampledNearKeySets.clear(); mSampledSearchKeySets.clear(); mSpeedRates.clear(); mBeelineSpeedPercentiles.clear(); mCharProbabilities.clear(); mDirections.clear(); } if (DEBUG_GEO_FULL) { AKLOGI("Init ProximityInfoState: reused points = %d, last input size = %d", pushTouchPointStartIndex, lastSavedInputSize); } // TODO: Remove the dependency of "isGeometric" const float verticalSweetSpotScale = isGeometric ? ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G : ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE; if (xCoordinates && yCoordinates) { mSampledInputSize = ProximityInfoStateUtils::updateTouchPoints(mProximityInfo, mMaxPointToKeyLength, mInputProximities, xCoordinates, yCoordinates, times, pointerIds, verticalSweetSpotScale, inputSize, isGeometric, pointerId, pushTouchPointStartIndex, &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledLengthCache, &mSampledInputIndice); } if (mSampledInputSize > 0 && isGeometric) { mAverageSpeed = ProximityInfoStateUtils::refreshSpeedRates(inputSize, xCoordinates, yCoordinates, times, lastSavedInputSize, mSampledInputSize, &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledLengthCache, &mSampledInputIndice, &mSpeedRates, &mDirections); ProximityInfoStateUtils::refreshBeelineSpeedRates(mProximityInfo->getMostCommonKeyWidth(), mAverageSpeed, inputSize, xCoordinates, yCoordinates, times, mSampledInputSize, &mSampledInputXs, &mSampledInputYs, &mSampledInputIndice, &mBeelineSpeedPercentiles); } if (mSampledInputSize > 0) { ProximityInfoStateUtils::initGeometricDistanceInfos(mProximityInfo, mSampledInputSize, lastSavedInputSize, verticalSweetSpotScale, &mSampledInputXs, &mSampledInputYs, &mSampledNearKeySets, &mSampledNormalizedSquaredLengthCache); if (isGeometric) { // updates probabilities of skipping or mapping each key for all points. ProximityInfoStateUtils::updateAlignPointProbabilities( mMaxPointToKeyLength, mProximityInfo->getMostCommonKeyWidth(), mProximityInfo->getKeyCount(), lastSavedInputSize, mSampledInputSize, &mSampledInputXs, &mSampledInputYs, &mSpeedRates, &mSampledLengthCache, &mSampledNormalizedSquaredLengthCache, &mSampledNearKeySets, &mCharProbabilities); ProximityInfoStateUtils::updateSampledSearchKeySets(mProximityInfo, mSampledInputSize, lastSavedInputSize, &mSampledLengthCache, &mSampledNearKeySets, &mSampledSearchKeySets, &mSampledSearchKeyVectors); mMostProbableStringProbability = ProximityInfoStateUtils::getMostProbableString( mProximityInfo, mSampledInputSize, &mCharProbabilities, mMostProbableString); } } if (DEBUG_SAMPLING_POINTS) { ProximityInfoStateUtils::dump(isGeometric, inputSize, xCoordinates, yCoordinates, mSampledInputSize, &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSpeedRates, &mBeelineSpeedPercentiles); } // end /////////////////////// mTouchPositionCorrectionEnabled = mSampledInputSize > 0 && mHasTouchPositionCorrectionData && xCoordinates && yCoordinates; if (!isGeometric && pointerId == 0) { ProximityInfoStateUtils::initPrimaryInputWord( inputSize, mInputProximities, mPrimaryInputWord); if (mTouchPositionCorrectionEnabled) { ProximityInfoStateUtils::initNormalizedSquaredDistances( mProximityInfo, inputSize, xCoordinates, yCoordinates, mInputProximities, &mSampledInputXs, &mSampledInputYs, mNormalizedSquaredDistances); } } if (DEBUG_GEO_FULL) { AKLOGI("ProximityState init finished: %d points out of %d", mSampledInputSize, inputSize); } } // This function basically converts from a length to an edit distance. Accordingly, it's obviously // wrong to compare with mMaxPointToKeyLength. float ProximityInfoState::getPointToKeyLength( const int inputIndex, const int codePoint) const { const int keyId = mProximityInfo->getKeyIndexOf(codePoint); if (keyId != NOT_AN_INDEX) { const int index = inputIndex * mProximityInfo->getKeyCount() + keyId; return min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength); } if (isIntentionalOmissionCodePoint(codePoint)) { return 0.0f; } // If the char is not a key on the keyboard then return the max length. return static_cast<float>(MAX_VALUE_FOR_WEIGHTING); } float ProximityInfoState::getPointToKeyByIdLength( const int inputIndex, const int keyId) const { return ProximityInfoStateUtils::getPointToKeyByIdLength(mMaxPointToKeyLength, &mSampledNormalizedSquaredLengthCache, mProximityInfo->getKeyCount(), inputIndex, keyId); } // In the following function, c is the current character of the dictionary word currently examined. // currentChars is an array containing the keys close to the character the user actually typed at // the same position. We want to see if c is in it: if so, then the word contains at that position // a character close to what the user typed. // What the user typed is actually the first character of the array. // proximityIndex is a pointer to the variable where getProximityType returns the index of c // in the proximity chars of the input index. // Notice : accented characters do not have a proximity list, so they are alone in their list. The // non-accented version of the character should be considered "close", but not the other keys close // to the non-accented version. ProximityType ProximityInfoState::getProximityType(const int index, const int codePoint, const bool checkProximityChars, int *proximityIndex) const { const int *currentCodePoints = getProximityCodePointsAt(index); const int firstCodePoint = currentCodePoints[0]; const int baseLowerC = toBaseLowerCase(codePoint); // The first char in the array is what user typed. If it matches right away, that means the // user typed that same char for this pos. if (firstCodePoint == baseLowerC || firstCodePoint == codePoint) { return MATCH_CHAR; } if (!checkProximityChars) return SUBSTITUTION_CHAR; // If the non-accented, lowercased version of that first character matches c, then we have a // non-accented version of the accented character the user typed. Treat it as a close char. if (toBaseLowerCase(firstCodePoint) == baseLowerC) { return PROXIMITY_CHAR; } // Not an exact nor an accent-alike match: search the list of close keys int j = 1; while (j < MAX_PROXIMITY_CHARS_SIZE && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { const bool matched = (currentCodePoints[j] == baseLowerC || currentCodePoints[j] == codePoint); if (matched) { if (proximityIndex) { *proximityIndex = j; } return PROXIMITY_CHAR; } ++j; } if (j < MAX_PROXIMITY_CHARS_SIZE && currentCodePoints[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { ++j; while (j < MAX_PROXIMITY_CHARS_SIZE && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { const bool matched = (currentCodePoints[j] == baseLowerC || currentCodePoints[j] == codePoint); if (matched) { if (proximityIndex) { *proximityIndex = j; } return ADDITIONAL_PROXIMITY_CHAR; } ++j; } } // Was not included, signal this as a substitution character. return SUBSTITUTION_CHAR; } ProximityType ProximityInfoState::getProximityTypeG(const int index, const int codePoint) const { if (!isUsed()) { return UNRELATED_CHAR; } const int lowerCodePoint = toLowerCase(codePoint); const int baseLowerCodePoint = toBaseCodePoint(lowerCodePoint); for (int i = 0; i < static_cast<int>(mSampledSearchKeyVectors[index].size()); ++i) { if (mSampledSearchKeyVectors[index][i] == lowerCodePoint || mSampledSearchKeyVectors[index][i] == baseLowerCodePoint) { return MATCH_CHAR; } } return UNRELATED_CHAR; } bool ProximityInfoState::isKeyInSerchKeysAfterIndex(const int index, const int keyId) const { ASSERT(keyId >= 0 && index >= 0 && index < mSampledInputSize); return mSampledSearchKeySets[index].test(keyId); } float ProximityInfoState::getDirection(const int index0, const int index1) const { return ProximityInfoStateUtils::getDirection( &mSampledInputXs, &mSampledInputYs, index0, index1); } float ProximityInfoState::getLineToKeyDistance( const int from, const int to, const int keyId, const bool extend) const { if (from < 0 || from > mSampledInputSize - 1) { return 0.0f; } if (to < 0 || to > mSampledInputSize - 1) { return 0.0f; } const int x0 = mSampledInputXs[from]; const int y0 = mSampledInputYs[from]; const int x1 = mSampledInputXs[to]; const int y1 = mSampledInputYs[to]; const int keyX = mProximityInfo->getKeyCenterXOfKeyIdG(keyId); const int keyY = mProximityInfo->getKeyCenterYOfKeyIdG(keyId); return ProximityInfoUtils::pointToLineSegSquaredDistanceFloat( keyX, keyY, x0, y0, x1, y1, extend); } float ProximityInfoState::getMostProbableString(int *const codePointBuf) const { memcpy(codePointBuf, mMostProbableString, sizeof(mMostProbableString)); return mMostProbableStringProbability; } bool ProximityInfoState::hasSpaceProximity(const int index) const { ASSERT(0 <= index && index < mSampledInputSize); return mProximityInfo->hasSpaceProximity(getInputX(index), getInputY(index)); } // Returns a probability of mapping index to keyIndex. float ProximityInfoState::getProbability(const int index, const int keyIndex) const { ASSERT(0 <= index && index < mSampledInputSize); hash_map_compat<int, float>::const_iterator it = mCharProbabilities[index].find(keyIndex); if (it != mCharProbabilities[index].end()) { return it->second; } return static_cast<float>(MAX_VALUE_FOR_WEIGHTING); } } // namespace latinime