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