| /* |
| * 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 |