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