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