Support additional proximity characters

Change-Id: Ifbe0d7e4eafea1926bbce968eae4724dd5769689
main
satok 2012-01-31 17:15:43 +09:00
parent 8ca325f437
commit e05b3f4b3a
11 changed files with 327 additions and 68 deletions

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 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.
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string-array name="additional_proximitychars">
<!-- Empty entry terminates the proximity chars array. -->
<!-- Additional proximity chars for a -->
<item>a</item>
<item>e</item>
<item>i</item>
<item>o</item>
<item>u</item>
<item></item>
<!-- Additional proximity chars for e -->
<item>e</item>
<item>a</item>
<item>i</item>
<item>o</item>
<item>u</item>
<item></item>
<!-- Additional proximity chars for i -->
<item>i</item>
<item>a</item>
<item>e</item>
<item>o</item>
<item>u</item>
<item></item>
<!-- Additional proximity chars for o -->
<item>o</item>
<item>a</item>
<item>e</item>
<item>i</item>
<item>u</item>
<item></item>
<!-- Additional proximity chars for u -->
<item>u</item>
<item>a</item>
<item>e</item>
<item>i</item>
<item>o</item>
<item></item>
</string-array>
</resources>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 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.
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string-array name="additional_proximitychars">
</string-array>
</resources>

View File

@ -19,12 +19,14 @@ package com.android.inputmethod.keyboard;
import android.util.Log;
import java.util.Arrays;
import java.util.List;
public class KeyDetector {
private static final String TAG = KeyDetector.class.getSimpleName();
private static final boolean DEBUG = false;
public static final int NOT_A_CODE = -1;
private static final int ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE = 2;
private final int mKeyHysteresisDistanceSquared;
@ -154,8 +156,9 @@ public class KeyDetector {
return distances.length;
}
private void getNearbyKeyCodes(final int[] allCodes) {
private void getNearbyKeyCodes(final int primaryCode, final int[] allCodes) {
final Key[] neighborKeys = mNeighborKeys;
final int maxCodesSize = allCodes.length;
// allCodes[0] should always have the key code even if it is a non-letter key.
if (neighborKeys[0] == null) {
@ -164,7 +167,7 @@ public class KeyDetector {
}
int numCodes = 0;
for (int j = 0; j < neighborKeys.length && numCodes < allCodes.length; j++) {
for (int j = 0; j < neighborKeys.length && numCodes < maxCodesSize; j++) {
final Key key = neighborKeys[j];
if (key == null)
break;
@ -174,6 +177,38 @@ public class KeyDetector {
continue;
allCodes[numCodes++] = code;
}
if (maxCodesSize <= numCodes) {
return;
}
if (primaryCode != NOT_A_CODE) {
final List<Integer> additionalChars =
mKeyboard.getAdditionalProximityChars().get(primaryCode);
if (additionalChars == null || additionalChars.size() == 0) {
return;
}
int currentCodesSize = numCodes;
allCodes[numCodes++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
if (maxCodesSize <= numCodes) {
return;
}
// TODO: This is O(N^2). Assuming additionalChars.size() is up to 4 or 5.
for (int i = 0; i < additionalChars.size(); ++i) {
final int additionalChar = additionalChars.get(i);
boolean contains = false;
for (int j = 0; j < currentCodesSize; ++j) {
if (additionalChar == allCodes[j]) {
contains = true;
break;
}
}
if (!contains) {
allCodes[numCodes++] = additionalChar;
if (maxCodesSize <= numCodes) {
return;
}
}
}
}
}
/**
@ -205,7 +240,7 @@ public class KeyDetector {
}
if (allCodes != null && allCodes.length > 0) {
getNearbyKeyCodes(allCodes);
getNearbyKeyCodes(primaryKey != null ? primaryKey.mCode : NOT_A_CODE, allCodes);
if (DEBUG) {
Log.d(TAG, "x=" + x + " y=" + y
+ " primary=" + printableCode(primaryKey)

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@ -38,10 +39,12 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -130,6 +133,8 @@ public class Keyboard {
private final ProximityInfo mProximityInfo;
public final Map<Integer, List<Integer>> mAdditionalProximityChars;
public Keyboard(Params params) {
mId = params.mId;
mThemeId = params.mThemeId;
@ -146,10 +151,12 @@ public class Keyboard {
mKeys = Collections.unmodifiableSet(params.mKeys);
mShiftKeys = Collections.unmodifiableSet(params.mShiftKeys);
mIconsSet = params.mIconsSet;
mAdditionalProximityChars = params.mAdditionalProximityChars;
mProximityInfo = new ProximityInfo(
params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection);
mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection,
params.mAdditionalProximityChars);
}
public ProximityInfo getProximityInfo() {
@ -227,6 +234,9 @@ public class Keyboard {
public final Set<Key> mKeys = new HashSet<Key>();
public final Set<Key> mShiftKeys = new HashSet<Key>();
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
// TODO: Should be in Key instead of Keyboard.Params?
public final Map<Integer, List<Integer>> mAdditionalProximityChars =
new HashMap<Integer, List<Integer>>();
public KeyboardSet.KeysCache mKeysCache;
@ -358,6 +368,10 @@ public class Keyboard {
return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
}
public Map<Integer, List<Integer>> getAdditionalProximityChars() {
return mAdditionalProximityChars;
}
public static String printableCode(int code) {
switch (code) {
case CODE_SHIFT: return "shift";
@ -614,6 +628,7 @@ public class Keyboard {
mParams = params;
setTouchPositionCorrectionData(context, params);
setAdditionalProximityChars(context, params);
params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
@ -636,6 +651,25 @@ public class Keyboard {
params.mTouchPositionCorrection.load(data);
}
private static void setAdditionalProximityChars(Context context, Params params) {
final String[] additionalChars =
context.getResources().getStringArray(R.array.additional_proximitychars);
int currentPrimaryIndex = 0;
for (int i = 0; i < additionalChars.length; ++i) {
final String additionalChar = additionalChars[i];
if (TextUtils.isEmpty(additionalChar)) {
currentPrimaryIndex = 0;
} else if (currentPrimaryIndex == 0) {
currentPrimaryIndex = additionalChar.charAt(0);
params.mAdditionalProximityChars.put(
currentPrimaryIndex, new ArrayList<Integer>());
} else if (currentPrimaryIndex != 0) {
final int c = additionalChar.charAt(0);
params.mAdditionalProximityChars.get(currentPrimaryIndex).add(c);
}
}
}
public void setAutoGenerate(KeyboardSet.KeysCache keysCache) {
mParams.mKeysCache = keysCache;
}

View File

@ -24,6 +24,9 @@ import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ProximityInfo {
@ -44,7 +47,8 @@ public class ProximityInfo {
private final Key[][] mGridNeighbors;
ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int keyWidth,
int keyHeight, Set<Key> keys, TouchPositionCorrection touchPositionCorrection) {
int keyHeight, Set<Key> keys, TouchPositionCorrection touchPositionCorrection,
Map<Integer, List<Integer>> additionalProximityChars) {
mGridWidth = gridWidth;
mGridHeight = gridHeight;
mGridSize = mGridWidth * mGridHeight;
@ -58,20 +62,20 @@ public class ProximityInfo {
// No proximity required. Keyboard might be mini keyboard.
return;
}
computeNearestNeighbors(keyWidth, keys, touchPositionCorrection);
computeNearestNeighbors(keyWidth, keys, touchPositionCorrection, additionalProximityChars);
}
public static ProximityInfo createDummyProximityInfo() {
return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key>emptySet(), null);
return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key> emptySet(),
null, Collections.<Integer, List<Integer>> emptyMap());
}
public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity) {
final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
spellCheckerProximityInfo.mNativeProximityInfo =
spellCheckerProximityInfo.setProximityInfoNative(
SpellCheckerProximityInfo.ROW_SIZE,
480, 300, 11, 3, proximity,
0, null, null, null, null, null, null, null, null);
SpellCheckerProximityInfo.ROW_SIZE, 480, 300, 11, 3, proximity, 0,
null, null, null, null, null, null, null, null);
return spellCheckerProximityInfo;
}
@ -79,11 +83,13 @@ public class ProximityInfo {
static {
Utils.loadNativeLibrary();
}
private native long setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray,
int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii);
private native void releaseProximityInfoNative(long nativeProximityInfo);
private final void setProximityInfo(Key[][] gridNeighborKeys, int keyboardWidth,
@ -138,7 +144,7 @@ public class ProximityInfo {
final float radius = touchPositionCorrection.mRadii[row];
sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth;
sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight;
sweetSpotRadii[i] = radius * (float)Math.sqrt(
sweetSpotRadii[i] = radius * (float) Math.sqrt(
hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
}
}
@ -168,7 +174,12 @@ public class ProximityInfo {
}
private void computeNearestNeighbors(int defaultWidth, Set<Key> keys,
TouchPositionCorrection touchPositionCorrection) {
TouchPositionCorrection touchPositionCorrection,
Map<Integer, List<Integer>> additionalProximityChars) {
final Map<Integer, Key> keyCodeMap = new HashMap<Integer, Key>();
for (final Key key : keys) {
keyCodeMap.put(key.mCode, key);
}
final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
final int threshold = thresholdBase * thresholdBase;
// Round-up so we don't have any pixels outside the grid
@ -186,6 +197,27 @@ public class ProximityInfo {
neighborKeys[count++] = key;
}
}
int currentCodesSize = count;
for (int i = 0; i < currentCodesSize; ++i) {
final int c = neighborKeys[i].mCode;
final List<Integer> additionalChars = additionalProximityChars.get(c);
if (additionalChars == null || additionalChars.size() == 0) {
continue;
}
for (int j = 0; j < additionalChars.size(); ++j) {
final int additionalChar = additionalChars.get(j);
boolean contains = false;
for (int k = 0; k < count; ++k) {
if(additionalChar == neighborKeys[k].mCode) {
contains = true;
break;
}
}
if (!contains) {
neighborKeys[count++] = keyCodeMap.get(additionalChar);
}
}
}
mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
Arrays.copyOfRange(neighborKeys, 0, count);
}
@ -199,7 +231,7 @@ public class ProximityInfo {
return EMPTY_KEY_ARRAY;
}
if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) {
int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth);
int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth);
if (index < mGridSize) {
return mGridNeighbors[index];
}

View File

@ -16,8 +16,6 @@
package com.android.inputmethod.latin;
import android.text.TextUtils;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
@ -33,7 +31,7 @@ public class WordComposer {
public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
public static final int NOT_A_COORDINATE = -1;
final int N = BinaryDictionary.MAX_WORD_LENGTH;
final static int N = BinaryDictionary.MAX_WORD_LENGTH;
private ArrayList<int[]> mCodes;
private int[] mXCoordinates;

View File

@ -383,7 +383,10 @@ Correction::CorrectionType Correction::processCharAndCalcState(
incrementInputIndex();
} else {
--mTransposedCount;
if (DEBUG_CORRECTION) {
if (DEBUG_CORRECTION
&& (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
AKLOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
@ -404,13 +407,17 @@ Correction::CorrectionType Correction::processCharAndCalcState(
: mProximityInfo->getMatchedProximityId(
mInputIndex, c, checkProximityChars, &proximityIndex);
if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) {
if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId
|| ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
if (canTryCorrection && mOutputIndex > 0
&& mCorrectionStates[mOutputIndex].mProximityMatching
&& mCorrectionStates[mOutputIndex].mExceeding
&& isEquivalentChar(mProximityInfo->getMatchedProximityId(
mInputIndex, mWord[mOutputIndex - 1], false))) {
if (DEBUG_CORRECTION) {
if (DEBUG_CORRECTION
&& (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
}
// Conversion p->e
@ -429,7 +436,8 @@ Correction::CorrectionType Correction::processCharAndCalcState(
}
}
if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) {
if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId
|| ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
// TODO: Optimize
// As the current char turned out to be an unrelated char,
// we will try other correction-types. Please note that mCorrectionStates[mOutputIndex]
@ -481,12 +489,47 @@ Correction::CorrectionType Correction::processCharAndCalcState(
++mExcessiveCount;
incrementInputIndex();
}
if (DEBUG_CORRECTION
&& (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
if (mTransposing) {
AKLOGI("TRANSPOSE: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
} else {
AKLOGI("EXCEED: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
}
}
} else if (mSkipping) {
// 3. Skip correction
++mSkippedCount;
if (DEBUG_CORRECTION
&& (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("SKIP: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
}
return processSkipChar(c, isTerminal, false);
} else if (ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
// As a last resort, use additional proximity characters
mProximityMatching = true;
++mProximityCount;
mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
if (DEBUG_CORRECTION
&& (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("ADDITIONALPROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
}
} else {
if (DEBUG_CORRECTION) {
if (DEBUG_CORRECTION
&& (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
AKLOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
@ -506,6 +549,13 @@ Correction::CorrectionType Correction::processCharAndCalcState(
++mProximityCount;
mDistances[mOutputIndex] =
mProximityInfo->getNormalizedSquaredDistance(mInputIndex, proximityIndex);
if (DEBUG_CORRECTION
&& (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("PROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
}
}
addCharToCurrentWord(c);
@ -539,7 +589,9 @@ Correction::CorrectionType Correction::processCharAndCalcState(
|| isSameAsUserTypedLength) && isTerminal) {
mTerminalInputIndex = mInputIndex - 1;
mTerminalOutputIndex = mOutputIndex - 1;
if (DEBUG_CORRECTION) {
if (DEBUG_CORRECTION
&& (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
@ -678,7 +730,7 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
if (excessiveCount > 0) {
multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
if (!lastCharExceeded && !proximityInfo->existsAdjacentProximityChars(excessivePos)) {
if (DEBUG_CORRECTION_FREQ) {
if (DEBUG_DICT_FULL) {
AKLOGI("Double excessive demotion");
}
// If an excessive character is not adjacent to the left char or the right char,
@ -687,51 +739,46 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
}
}
const bool performTouchPositionCorrection =
CALIBRATE_SCORE_BY_TOUCH_COORDINATES && proximityInfo->touchPositionCorrectionEnabled()
&& skippedCount == 0 && excessiveCount == 0 && transposedCount == 0;
// Score calibration by touch coordinates is being done only for pure-fat finger typing error
// cases.
// TODO: Remove this constraint.
if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES && proximityInfo->touchPositionCorrectionEnabled()
&& skippedCount == 0 && excessiveCount == 0 && transposedCount == 0) {
for (int i = 0; i < outputLength; ++i) {
const int squaredDistance = correction->mDistances[i];
if (i < adjustedProximityMatchedCount) {
multiplyIntCapped(typedLetterMultiplier, &finalFreq);
}
if (squaredDistance >= 0) {
// Promote or demote the score according to the distance from the sweet spot
static const float A = ZERO_DISTANCE_PROMOTION_RATE / 100.0f;
static const float B = 1.0f;
static const float C = 0.5f;
static const float R1 = NEUTRAL_SCORE_SQUARED_RADIUS;
static const float R2 = HALF_SCORE_SQUARED_RADIUS;
const float x = (float)squaredDistance
/ ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
const float factor = (x < R1)
? (A * (R1 - x) + B * x) / R1
: (B * (R2 - x) + C * (x - R1)) / (R2 - R1);
// factor is piecewise linear function like:
// A -_ .
// ^-_ .
// B \ .
// \ .
// C \ .
// 0 R1 R2
if (factor <= 0) {
return -1;
}
multiplyRate((int)(factor * 100), &finalFreq);
} else if (squaredDistance == PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO) {
multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
}
}
} else {
// Promotion for a word with proximity characters
for (int i = 0; i < adjustedProximityMatchedCount; ++i) {
// A word with proximity corrections
if (DEBUG_DICT_FULL) {
AKLOGI("Found a proximity correction.");
}
for (int i = 0; i < outputLength; ++i) {
const int squaredDistance = correction->mDistances[i];
if (i < adjustedProximityMatchedCount) {
multiplyIntCapped(typedLetterMultiplier, &finalFreq);
}
if (performTouchPositionCorrection && squaredDistance >= 0) {
// Promote or demote the score according to the distance from the sweet spot
static const float A = ZERO_DISTANCE_PROMOTION_RATE / 100.0f;
static const float B = 1.0f;
static const float C = 0.5f;
static const float MIN = 0.3f;
static const float R1 = NEUTRAL_SCORE_SQUARED_RADIUS;
static const float R2 = HALF_SCORE_SQUARED_RADIUS;
const float x = (float)squaredDistance
/ ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
const float factor = max((x < R1)
? (A * (R1 - x) + B * x) / R1
: (B * (R2 - x) + C * (x - R1)) / (R2 - R1), MIN);
// factor is piecewise linear function like:
// A -_ .
// ^-_ .
// B \ .
// \_ .
// C ------------.
// .
// 0 R1 R2 .
multiplyRate((int)(factor * 100), &finalFreq);
} else if (performTouchPositionCorrection
&& squaredDistance == PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO) {
multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
} else if (squaredDistance == ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO) {
multiplyRate(WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
} else if (i < adjustedProximityMatchedCount) {
multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
}
}
@ -794,7 +841,8 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
AKLOGI("calc: %d, %d", outputLength, sameLength);
}
if (DEBUG_CORRECTION_FREQ) {
if (DEBUG_CORRECTION_FREQ
&& (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
DUMP_WORD(correction->mWord, outputLength);
AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount,
skippedCount, transposedCount, excessiveCount, outputLength, lastCharExceeded,

View File

@ -85,7 +85,7 @@ class Correction {
}
}
Correction(const int typedLetterMultiplier, const int fullWordMultiplier);
Correction(const int typedLetterMultiplier, const int fullWordMultiplier);
void initCorrection(
const ProximityInfo *pi, const int inputLength, const int maxWordLength);
void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);

View File

@ -172,6 +172,7 @@ static void prof_out(void) {
#define NOT_A_COORDINATE -1
#define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO -2
#define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO -3
#define ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO -4
#define NOT_A_INDEX -1
#define NOT_A_FREQUENCY -1
@ -194,6 +195,7 @@ static void prof_out(void) {
#define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 70
#define FULL_MATCHED_WORDS_PROMOTION_RATE 120
#define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90
#define WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE 30
#define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105
#define WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE 160
#define CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE 45
@ -210,6 +212,9 @@ static void prof_out(void) {
// This is only used for the size of array. Not to be used in c functions.
#define MAX_WORD_LENGTH_INTERNAL 48
// This must be equal to ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE in KeyDetector.java
#define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
// Word limit for sub queues used in WordsPriorityQueuePool. Sub queues are temporary queues used
// for better performance.
// Holds up to 1 candidate for each word
@ -241,4 +246,8 @@ static void prof_out(void) {
// The ratio of neutral area radius to sweet spot radius.
#define NEUTRAL_AREA_RADIUS_RATIO 1.3f
// DEBUG
#define INPUTLENGTH_FOR_DEBUG -1
#define MIN_OUTPUT_INDEX_FOR_DEBUG -1
#endif // LATINIME_DEFINES_H

View File

@ -261,7 +261,8 @@ ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId(const int inde
// Not an exact nor an accent-alike match: search the list of close keys
int j = 1;
while (j < MAX_PROXIMITY_CHARS_SIZE && currentChars[j] > 0) {
while (j < MAX_PROXIMITY_CHARS_SIZE
&& currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
if (matched) {
if (proximityIndex) {
@ -271,6 +272,21 @@ ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId(const int inde
}
++j;
}
if (j < MAX_PROXIMITY_CHARS_SIZE
&& currentChars[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
++j;
while (j < MAX_PROXIMITY_CHARS_SIZE
&& currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
if (matched) {
if (proximityIndex) {
*proximityIndex = j;
}
return ADDITIONAL_PROXIMITY_CHAR;
}
++j;
}
}
// Was not included, signal this as an unrelated character.
return UNRELATED_CHAR;

View File

@ -38,7 +38,9 @@ class ProximityInfo {
// It is a char located nearby on the keyboard
NEAR_PROXIMITY_CHAR,
// It is an unrelated char
UNRELATED_CHAR
UNRELATED_CHAR,
// Additional proximity char which can differ by language.
ADDITIONAL_PROXIMITY_CHAR
} ProximityType;
ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,