Consolidate dummy proximity info to the spell checker info

Bug: 8783170

Change-Id: I067486e5ec1ae7cdef8e2121392464ba71ee8add
main
Satoshi Kataoka 2013-05-07 19:09:30 +09:00
parent 1eb1af75a7
commit 244a24e368
9 changed files with 142 additions and 544 deletions

View File

@ -29,17 +29,18 @@ import android.content.res.TypedArray;
import android.content.res.XmlResourceParser; import android.content.res.XmlResourceParser;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.Xml; import android.util.Xml;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.KeysCache; import com.android.inputmethod.keyboard.internal.KeysCache;
import com.android.inputmethod.latin.AdditionalSubtype;
import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.InputTypeUtils;
@ -72,6 +73,8 @@ public final class KeyboardLayoutSet {
private static final String TAG_ELEMENT = "Element"; private static final String TAG_ELEMENT = "Element";
private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "keyboard_layout_set_"; private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "keyboard_layout_set_";
private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480;
private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 800;
private final Context mContext; private final Context mContext;
private final Params mParams; private final Params mParams;
@ -282,8 +285,7 @@ public final class KeyboardLayoutSet {
return this; return this;
} }
@UsedForTesting public void disableTouchPositionCorrectionData() {
public void disableTouchPositionCorrectionDataForTest() {
mParams.mDisableTouchPositionCorrectionDataForTest = true; mParams.mDisableTouchPositionCorrectionDataForTest = true;
} }
@ -413,4 +415,47 @@ public final class KeyboardLayoutSet {
} }
} }
} }
public static KeyboardLayoutSet createKeyboardSetForSpellChecker(final Context context,
final String locale, final String layout) {
final InputMethodSubtype subtype =
AdditionalSubtype.createAdditionalSubtype(locale, layout, null);
return createKeyboardSet(context, subtype, SPELLCHECKER_DUMMY_KEYBOARD_WIDTH,
SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false);
}
public static KeyboardLayoutSet createKeyboardSetForTest(final Context context,
final InputMethodSubtype subtype, final int orientation,
final boolean testCasesHaveTouchCoordinates) {
final DisplayMetrics dm = context.getResources().getDisplayMetrics();
final int width;
final int height;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
width = Math.max(dm.widthPixels, dm.heightPixels);
height = Math.min(dm.widthPixels, dm.heightPixels);
} else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
width = Math.min(dm.widthPixels, dm.heightPixels);
height = Math.max(dm.widthPixels, dm.heightPixels);
} else {
throw new RuntimeException("Orientation should be ORIENTATION_LANDSCAPE or "
+ "ORIENTATION_PORTRAIT: orientation=" + orientation);
}
return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates);
}
private static KeyboardLayoutSet createKeyboardSet(final Context context,
final InputMethodSubtype subtype, final int width, final int height,
final boolean testCasesHaveTouchCoordinates) {
final EditorInfo editorInfo = new EditorInfo();
editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
context, editorInfo);
builder.setScreenGeometry(width, height);
builder.setSubtype(subtype);
if (!testCasesHaveTouchCoordinates) {
// For spell checker and tests
builder.disableTouchPositionCorrectionData();
}
return builder.build();
}
} }

View File

@ -79,23 +79,6 @@ public class ProximityInfo {
mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection); mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
} }
/**
* Constructor for subclasses such as
* {@link com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo}.
*/
protected ProximityInfo(final int[] proximityCharsArray, final int gridWidth,
final int gridHeight) {
this("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
mNativeProximityInfo = setProximityInfoNative("" /* locale */,
gridWidth /* displayWidth */, gridHeight /* displayHeight */,
gridWidth, gridHeight, 1 /* mostCommonKeyWidth */,
1 /* mostCommonKeyHeight */, proximityCharsArray, 0 /* keyCount */,
null /*keyXCoordinates */, null /* keyYCoordinates */,
null /* keyWidths */, null /* keyHeights */, null /* keyCharCodes */,
null /* sweetSpotCenterXs */, null /* sweetSpotCenterYs */,
null /* sweetSpotRadii */);
}
private long mNativeProximityInfo; private long mNativeProximityInfo;
static { static {
JniUtils.loadNativeLibrary(); JniUtils.loadNativeLibrary();

View File

@ -16,7 +16,6 @@
package com.android.inputmethod.latin; package com.android.inputmethod.latin;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
@ -211,9 +210,8 @@ public final class WordComposer {
} }
/** /**
* Internal method to retrieve reasonable proximity info for a character. * Add a dummy key by retrieving reasonable coordinates
*/ */
@UsedForTesting
public void addKeyInfo(final int codePoint, final Keyboard keyboard) { public void addKeyInfo(final int codePoint, final Keyboard keyboard) {
final int x, y; final int x, y;
final Key key; final Key key;

View File

@ -23,7 +23,7 @@ import android.service.textservice.SpellCheckerService;
import android.util.Log; import android.util.Log;
import android.view.textservice.SuggestionsInfo; import android.view.textservice.SuggestionsInfo;
import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.BinaryDictionary;
import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.ContactsBinaryDictionary; import com.android.inputmethod.latin.ContactsBinaryDictionary;
@ -126,6 +126,19 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
return script; return script;
} }
private static String getKeyboardLayoutNameForScript(final int script) {
switch (script) {
case AndroidSpellCheckerService.SCRIPT_LATIN:
return "qwerty";
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
return "east_slavic";
case AndroidSpellCheckerService.SCRIPT_GREEK:
return "greek";
default:
throw new RuntimeException("Wrong script supplied: " + script);
}
}
@Override @Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
if (!PREF_USE_CONTACTS_KEY.equals(key)) return; if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
@ -385,9 +398,13 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
return pool; return pool;
} }
public DictAndProximity createDictAndProximity(final Locale locale) { public DictAndKeyboard createDictAndKeyboard(final Locale locale) {
final int script = getScriptFromLocale(locale); final int script = getScriptFromLocale(locale);
final ProximityInfo proximityInfo = new SpellCheckerProximityInfo(script); final String keyboardLayoutName = getKeyboardLayoutNameForScript(script);
final KeyboardLayoutSet keyboardLayoutSet =
KeyboardLayoutSet.createKeyboardSetForSpellChecker(this, locale.toString(),
keyboardLayoutName);
final DictionaryCollection dictionaryCollection = final DictionaryCollection dictionaryCollection =
DictionaryFactory.createMainDictionaryFromManager(this, locale, DictionaryFactory.createMainDictionaryFromManager(this, locale,
true /* useFullEditDistance */); true /* useFullEditDistance */);
@ -412,6 +429,6 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
mDictionaryCollectionsList.add( mDictionaryCollectionsList.add(
new WeakReference<DictionaryCollection>(dictionaryCollection)); new WeakReference<DictionaryCollection>(dictionaryCollection));
} }
return new DictAndProximity(dictionaryCollection, proximityInfo); return new DictAndKeyboard(dictionaryCollection, keyboardLayoutSet);
} }
} }

View File

@ -257,7 +257,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
} }
if (shouldFilterOut(inText, mScript)) { if (shouldFilterOut(inText, mScript)) {
DictAndProximity dictInfo = null; DictAndKeyboard dictInfo = null;
try { try {
dictInfo = mDictionaryPool.pollWithDefaultTimeout(); dictInfo = mDictionaryPool.pollWithDefaultTimeout();
if (!DictionaryPool.isAValidDictionary(dictInfo)) { if (!DictionaryPool.isAValidDictionary(dictInfo)) {
@ -286,7 +286,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
final int capitalizeType = StringUtils.getCapitalizationType(text); final int capitalizeType = StringUtils.getCapitalizationType(text);
boolean isInDict = true; boolean isInDict = true;
DictAndProximity dictInfo = null; DictAndKeyboard dictInfo = null;
try { try {
dictInfo = mDictionaryPool.pollWithDefaultTimeout(); dictInfo = mDictionaryPool.pollWithDefaultTimeout();
if (!DictionaryPool.isAValidDictionary(dictInfo)) { if (!DictionaryPool.isAValidDictionary(dictInfo)) {
@ -296,20 +296,13 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
final int length = text.length(); final int length = text.length();
for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) { for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
final int codePoint = text.codePointAt(i); final int codePoint = text.codePointAt(i);
// The getXYForCodePointAndScript method returns (Y << 16) + X composer.addKeyInfo(codePoint, dictInfo.getKeyboard(codePoint));
final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
codePoint, mScript);
if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
composer.add(codePoint,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
} else {
composer.add(codePoint, xy & 0xFFFF, xy >> 16);
}
} }
// TODO: make a spell checker option to block offensive words or not // TODO: make a spell checker option to block offensive words or not
final ArrayList<SuggestedWordInfo> suggestions = final ArrayList<SuggestedWordInfo> suggestions =
dictInfo.mDictionary.getSuggestions(composer, prevWord, dictInfo.mDictionary.getSuggestions(composer, prevWord,
dictInfo.mProximityInfo, true /* blockOffensiveWords */); dictInfo.getProximityInfo(),
true /* blockOffensiveWords */);
for (final SuggestedWordInfo suggestion : suggestions) { for (final SuggestedWordInfo suggestion : suggestions) {
final String suggestionStr = suggestion.mWord; final String suggestionStr = suggestion.mWord;
suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0, suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2011 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.
*/
package com.android.inputmethod.latin.spellcheck;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.keyboard.ProximityInfo;
/**
* A container for a Dictionary and a Keyboard.
*/
public final class DictAndKeyboard {
public final Dictionary mDictionary;
private final Keyboard mKeyboard;
private final Keyboard mManualShiftedKeyboard;
public DictAndKeyboard(
final Dictionary dictionary, final KeyboardLayoutSet keyboardLayoutSet) {
mDictionary = dictionary;
if (keyboardLayoutSet == null) {
mKeyboard = null;
mManualShiftedKeyboard = null;
return;
}
mKeyboard = keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
mManualShiftedKeyboard =
keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED);
}
public Keyboard getKeyboard(final int codePoint) {
if (mKeyboard == null) {
return null;
}
return mKeyboard.getKey(codePoint) != null ? mKeyboard : mManualShiftedKeyboard;
}
public ProximityInfo getProximityInfo() {
return mKeyboard == null ? null : mKeyboard.getProximityInfo();
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright (C) 2011 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.
*/
package com.android.inputmethod.latin.spellcheck;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.keyboard.ProximityInfo;
/**
* A simple container for both a Dictionary and a ProximityInfo.
*/
public final class DictAndProximity {
public final Dictionary mDictionary;
public final ProximityInfo mProximityInfo;
public DictAndProximity(final Dictionary dictionary, final ProximityInfo proximityInfo) {
mDictionary = dictionary;
mProximityInfo = proximityInfo;
}
}

View File

@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit;
* the client code, but may help with sloppy clients. * the client code, but may help with sloppy clients.
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> {
private final static String TAG = DictionaryPool.class.getSimpleName(); private final static String TAG = DictionaryPool.class.getSimpleName();
// How many seconds we wait for a dictionary to become available. Past this delay, we give up in // How many seconds we wait for a dictionary to become available. Past this delay, we give up in
// fear some bug caused a deadlock, and reset the whole pool. // fear some bug caused a deadlock, and reset the whole pool.
@ -47,7 +47,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity>
private int mSize; private int mSize;
private volatile boolean mClosed; private volatile boolean mClosed;
final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList(); final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
private final static DictAndProximity dummyDict = new DictAndProximity( private final static DictAndKeyboard dummyDict = new DictAndKeyboard(
new Dictionary(Dictionary.TYPE_MAIN) { new Dictionary(Dictionary.TYPE_MAIN) {
@Override @Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
@ -64,7 +64,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity>
} }
}, null); }, null);
static public boolean isAValidDictionary(final DictAndProximity dictInfo) { static public boolean isAValidDictionary(final DictAndKeyboard dictInfo) {
return null != dictInfo && dummyDict != dictInfo; return null != dictInfo && dummyDict != dictInfo;
} }
@ -79,32 +79,32 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity>
} }
@Override @Override
public DictAndProximity poll(final long timeout, final TimeUnit unit) public DictAndKeyboard poll(final long timeout, final TimeUnit unit)
throws InterruptedException { throws InterruptedException {
final DictAndProximity dict = poll(); final DictAndKeyboard dict = poll();
if (null != dict) return dict; if (null != dict) return dict;
synchronized(this) { synchronized(this) {
if (mSize >= mMaxSize) { if (mSize >= mMaxSize) {
// Our pool is already full. Wait until some dictionary is ready, or TIMEOUT // Our pool is already full. Wait until some dictionary is ready, or TIMEOUT
// expires to avoid a deadlock. // expires to avoid a deadlock.
final DictAndProximity result = super.poll(timeout, unit); final DictAndKeyboard result = super.poll(timeout, unit);
if (null == result) { if (null == result) {
Log.e(TAG, "Deadlock detected ! Resetting dictionary pool"); Log.e(TAG, "Deadlock detected ! Resetting dictionary pool");
clear(); clear();
mSize = 1; mSize = 1;
return mService.createDictAndProximity(mLocale); return mService.createDictAndKeyboard(mLocale);
} else { } else {
return result; return result;
} }
} else { } else {
++mSize; ++mSize;
return mService.createDictAndProximity(mLocale); return mService.createDictAndKeyboard(mLocale);
} }
} }
} }
// Convenience method // Convenience method
public DictAndProximity pollWithDefaultTimeout() { public DictAndKeyboard pollWithDefaultTimeout() {
try { try {
return poll(TIMEOUT, TimeUnit.SECONDS); return poll(TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -115,7 +115,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity>
public void close() { public void close() {
synchronized(this) { synchronized(this) {
mClosed = true; mClosed = true;
for (DictAndProximity dict : this) { for (DictAndKeyboard dict : this) {
dict.mDictionary.close(); dict.mDictionary.close();
} }
clear(); clear();
@ -123,7 +123,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity>
} }
@Override @Override
public boolean offer(final DictAndProximity dict) { public boolean offer(final DictAndKeyboard dict) {
if (mClosed) { if (mClosed) {
dict.mDictionary.close(); dict.mDictionary.close();
return super.offer(dummyDict); return super.offer(dummyDict);

View File

@ -1,462 +0,0 @@
/*
* Copyright (C) 2011 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.
*/
package com.android.inputmethod.latin.spellcheck;
import android.util.SparseIntArray;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.Constants;
public final class SpellCheckerProximityInfo extends ProximityInfo {
public SpellCheckerProximityInfo(final int script) {
super(getProximityForScript(script), PROXIMITY_GRID_WIDTH, PROXIMITY_GRID_HEIGHT);
}
private static final int NUL = Constants.NOT_A_CODE;
// This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
// native code - this value is passed at creation of the binary object and reused
// as the size of the passed array afterwards so they can't be different.
private static final int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
// The number of keys in a row of the grid used by the spell checker.
private static final int PROXIMITY_GRID_WIDTH = 11;
// The number of rows in the grid used by the spell checker.
private static final int PROXIMITY_GRID_HEIGHT = 3;
private static final int NOT_AN_INDEX = -1;
public static final int NOT_A_COORDINATE_PAIR = -1;
// Helper methods
static void buildProximityIndices(final int[] proximity, final int rowSize,
final SparseIntArray indices) {
for (int i = 0; i < proximity.length; i += rowSize) {
if (NUL != proximity[i]) indices.put(proximity[i], i / rowSize);
}
}
private static final class Latin {
// The proximity here is the union of
// - the proximity for a QWERTY keyboard.
// - the proximity for an AZERTY keyboard.
// - the proximity for a QWERTZ keyboard.
// ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other.
//
// The reasoning behind this construction is, almost any alphabetic text we may want
// to spell check has been entered with one of the keyboards above. Also, specifically
// to English, many spelling errors consist of the last vowel of the word being wrong
// because in English vowels tend to merge with each other in pronunciation.
/*
The Qwerty layout this represents looks like the following:
q w e r t y u i o p
a s d f g h j k l
z x c v b n m
*/
static final int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
// The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL,
'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
// Proximity for row 2. See comment above about size.
'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
// Proximity for row 3. See comment above about size.
'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL,
'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
// This is a mapping array from the code point to the index in the PROXIMITY array.
// When we check the spelling of a word, we need to pass (x,y) coordinates to the native
// code for each letter of the word. These are most easily computed from the index in the
// PROXIMITY array. Since we'll need to do that very often, the index lookup from the code
// point needs to be as fast as possible, and a map is probably the best way to do this.
// To avoid unnecessary boxing conversion to Integer, here we use SparseIntArray.
static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
static {
buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
}
}
private static final class Cyrillic {
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
// speakers on commonly misspelled words/letters.
/*
The Russian layout this represents looks like the following:
й ц у к е н г ш щ з х
ф ы в а п р о л д ж э
я ч с м и т ь б ю
This gives us the following table:
'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'к', 'у', 'в', 'а', 'п', 'е', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'е', 'к', 'а', 'п', 'р', 'н', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'н', 'е', 'п', 'р', 'о', 'г', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'г', 'н', 'р', 'о', 'л', 'ш', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ш', 'г', 'о', 'л', 'д', 'щ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'щ', 'ш', 'л', 'д', 'ж', 'з', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'з', 'щ', 'д', 'ж', 'э', 'х', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'х', 'з', 'ж', 'э', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ф', 'й', 'ц', 'ы', 'я', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ы', 'й', 'ц', 'у', 'ф', 'в', 'я', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'в', 'ц', 'у', 'к', 'ы', 'а', 'я', 'ч', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'а', 'у', 'к', 'е', 'в', 'п', 'ч', 'с', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'п', 'к', 'е', 'н', 'а', 'р', 'с', 'м', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'р', 'е', 'н', 'г', 'п', 'о', 'м', 'и', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'о', 'н', 'г', 'ш', 'р', 'л', 'и', 'т', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'л', 'г', 'ш', 'щ', 'о', 'д', 'т', 'ь', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'д', 'ш', 'щ', 'з', 'л', 'ж', 'ь', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ж', 'щ', 'з', 'х', 'д', 'э', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'э', 'з', 'х', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'я', 'ф', 'ы', 'в', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ч', 'ы', 'в', 'а', 'я', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'с', 'в', 'а', 'п', 'ч', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'м', 'а', 'п', 'р', 'с', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'и', 'п', 'р', 'о', 'м', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'т', 'р', 'о', 'л', 'и', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ь', 'о', 'л', 'д', 'т', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'б', 'л', 'д', 'ж', 'ь', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ю', 'д', 'ж', 'э', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
Using the following characters:
*/
private static final int CY_SHORT_I = '\u0439'; // й
private static final int CY_TSE = '\u0446'; // ц
private static final int CY_U = '\u0443'; // у
private static final int CY_KA = '\u043A'; // к
private static final int CY_IE = '\u0435'; // е
private static final int CY_EN = '\u043D'; // н
private static final int CY_GHE = '\u0433'; // г
private static final int CY_SHA = '\u0448'; // ш
private static final int CY_SHCHA = '\u0449'; // щ
private static final int CY_ZE = '\u0437'; // з
private static final int CY_HA = '\u0445'; // х
private static final int CY_EF = '\u0444'; // ф
private static final int CY_YERU = '\u044B'; // ы
private static final int CY_VE = '\u0432'; // в
private static final int CY_A = '\u0430'; // а
private static final int CY_PE = '\u043F'; // п
private static final int CY_ER = '\u0440'; // р
private static final int CY_O = '\u043E'; // о
private static final int CY_EL = '\u043B'; // л
private static final int CY_DE = '\u0434'; // д
private static final int CY_ZHE = '\u0436'; // ж
private static final int CY_E = '\u044D'; // э
private static final int CY_YA = '\u044F'; // я
private static final int CY_CHE = '\u0447'; // ч
private static final int CY_ES = '\u0441'; // с
private static final int CY_EM = '\u043C'; // м
private static final int CY_I = '\u0438'; // и
private static final int CY_TE = '\u0442'; // т
private static final int CY_SOFT_SIGN = '\u044C'; // ь
private static final int CY_BE = '\u0431'; // б
private static final int CY_YU = '\u044E'; // ю
static final int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
// The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
CY_SHORT_I, CY_TSE, CY_EF, CY_YERU, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_TSE, CY_SHORT_I, CY_EF, CY_YERU, CY_VE, CY_U, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_U, CY_TSE, CY_YERU, CY_VE, CY_A, CY_KA, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_KA, CY_U, CY_VE, CY_A, CY_PE, CY_IE, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_IE, CY_KA, CY_A, CY_PE, CY_ER, CY_EN, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_EN, CY_IE, CY_PE, CY_ER, CY_O, CY_GHE, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_GHE, CY_EN, CY_ER, CY_O, CY_EL, CY_SHA, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_SHA, CY_GHE, CY_O, CY_EL, CY_DE, CY_SHCHA, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_SHCHA, CY_SHA, CY_EL, CY_DE, CY_ZHE, CY_ZE, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_ZE, CY_SHCHA, CY_DE, CY_ZHE, CY_E, CY_HA, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_HA, CY_ZE, CY_ZHE, CY_E, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
// Proximity for row 2. See comment above about size.
CY_EF, CY_SHORT_I, CY_TSE, CY_YERU, CY_YA, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_YERU, CY_SHORT_I, CY_TSE, CY_U, CY_EF, CY_VE, CY_YA, CY_CHE,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_VE, CY_TSE, CY_U, CY_KA, CY_YERU, CY_A, CY_YA, CY_CHE,
CY_ES, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_A, CY_U, CY_KA, CY_IE, CY_VE, CY_PE, CY_CHE, CY_ES,
CY_EM, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_PE, CY_KA, CY_IE, CY_EN, CY_A, CY_ER, CY_ES, CY_EM,
CY_I, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_ER, CY_IE, CY_EN, CY_GHE, CY_PE, CY_O, CY_EM, CY_I,
CY_TE, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_O, CY_EN, CY_GHE, CY_SHA, CY_ER, CY_EL, CY_I, CY_TE,
CY_SOFT_SIGN, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_EL, CY_GHE, CY_SHA, CY_SHCHA, CY_O, CY_DE, CY_TE, CY_SOFT_SIGN,
CY_BE, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_DE, CY_SHA, CY_SHCHA, CY_ZE, CY_EL, CY_ZHE, CY_SOFT_SIGN, CY_BE,
CY_YU, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_ZHE, CY_SHCHA, CY_ZE, CY_HA, CY_DE, CY_E, CY_BE, CY_YU,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_E, CY_ZE, CY_HA, CY_YU, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
// Proximity for row 3. See comment above about size.
CY_YA, CY_EF, CY_YERU, CY_VE, CY_CHE, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_CHE, CY_YERU, CY_VE, CY_A, CY_YA, CY_ES, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_ES, CY_VE, CY_A, CY_PE, CY_CHE, CY_EM, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_EM, CY_A, CY_PE, CY_ER, CY_ES, CY_I, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_I, CY_PE, CY_ER, CY_O, CY_EM, CY_TE, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_TE, CY_ER, CY_O, CY_EL, CY_I, CY_SOFT_SIGN, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_SOFT_SIGN, CY_O, CY_EL, CY_DE, CY_TE, CY_BE, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_BE, CY_EL, CY_DE, CY_ZHE, CY_SOFT_SIGN, CY_YU, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
CY_YU, CY_DE, CY_ZHE, CY_E, CY_BE, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
static {
buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
}
}
private static final class Greek {
// TODO: The following table is solely based on the keyboard layout. Consult with Greek
// speakers on commonly misspelled words/letters.
/*
The Greek layout this represents looks like the following:
; ς ε ρ τ υ θ ι ο π
α σ δ φ γ η ξ κ λ
ζ χ ψ ω β ν μ
This gives us the following table:
'ς', 'ε', 'α', 'σ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ε', 'ς', 'ρ', 'σ', 'δ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ρ', 'ε', 'τ', 'δ', 'φ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'τ', 'ρ', 'υ', 'φ', 'γ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'υ', 'τ', 'θ', 'γ', 'η', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'θ', 'υ', 'ι', 'η', 'ξ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ι', 'θ', 'ο', 'ξ', 'κ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ο', 'ι', 'π', 'κ', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'π', 'ο', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'α', 'ς', 'σ', 'ζ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'σ', 'ς', 'ε', 'α', 'δ', 'ζ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'δ', 'ε', 'ρ', 'σ', 'φ', 'ζ', 'χ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'φ', 'ρ', 'τ', 'δ', 'γ', 'χ', 'ψ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'γ', 'τ', 'υ', 'φ', 'η', 'ψ', 'ω', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'η', 'υ', 'θ', 'γ', 'ξ', 'ω', 'β', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ξ', 'θ', 'ι', 'η', 'κ', 'β', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'κ', 'ι', 'ο', 'ξ', 'λ', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'λ', 'ο', 'π', 'κ', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ζ', 'α', 'σ', 'δ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'χ', 'σ', 'δ', 'φ', 'ζ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ψ', 'δ', 'φ', 'γ', 'χ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ω', 'φ', 'γ', 'η', 'ψ', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'β', 'γ', 'η', 'ξ', 'ω', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ν', 'η', 'ξ', 'κ', 'β', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'μ', 'ξ', 'κ', 'λ', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
Using the following characters:
*/
private static final int GR_FINAL_SIGMA = '\u03C2'; // ς
private static final int GR_EPSILON = '\u03B5'; // ε
private static final int GR_RHO = '\u03C1'; // ρ
private static final int GR_TAU = '\u03C4'; // τ
private static final int GR_UPSILON = '\u03C5'; // υ
private static final int GR_THETA = '\u03B8'; // θ
private static final int GR_IOTA = '\u03B9'; // ι
private static final int GR_OMICRON = '\u03BF'; // ο
private static final int GR_PI = '\u03C0'; // π
private static final int GR_ALPHA = '\u03B1'; // α
private static final int GR_SIGMA = '\u03C3'; // σ
private static final int GR_DELTA = '\u03B4'; // δ
private static final int GR_PHI = '\u03C6'; // φ
private static final int GR_GAMMA = '\u03B3'; // γ
private static final int GR_ETA = '\u03B7'; // η
private static final int GR_XI = '\u03BE'; // ξ
private static final int GR_KAPPA = '\u03BA'; // κ
private static final int GR_LAMDA = '\u03BB'; // λ
private static final int GR_ZETA = '\u03B6'; // ζ
private static final int GR_CHI = '\u03C7'; // χ
private static final int GR_PSI = '\u03C8'; // ψ
private static final int GR_OMEGA = '\u03C9'; // ω
private static final int GR_BETA = '\u03B2'; // β
private static final int GR_NU = '\u03BD'; // ν
private static final int GR_MU = '\u03BC'; // μ
static final int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
// The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_SIGMA, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_EPSILON, GR_FINAL_SIGMA, GR_RHO, GR_SIGMA, GR_DELTA, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_RHO, GR_EPSILON, GR_TAU, GR_DELTA, GR_PHI, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_TAU, GR_RHO, GR_UPSILON, GR_PHI, GR_GAMMA, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_UPSILON, GR_TAU, GR_THETA, GR_GAMMA, GR_ETA, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_THETA, GR_UPSILON, GR_IOTA, GR_ETA, GR_XI, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_IOTA, GR_THETA, GR_OMICRON, GR_XI, GR_KAPPA, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_OMICRON, GR_IOTA, GR_PI, GR_KAPPA, GR_LAMDA, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_PI, GR_OMICRON, GR_LAMDA, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_ALPHA, GR_FINAL_SIGMA, GR_SIGMA, GR_ZETA, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_SIGMA, GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_DELTA, GR_ZETA, GR_CHI, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_DELTA, GR_EPSILON, GR_RHO, GR_SIGMA, GR_PHI, GR_ZETA, GR_CHI, GR_PSI,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_PHI, GR_RHO, GR_TAU, GR_DELTA, GR_GAMMA, GR_CHI, GR_PSI, GR_OMEGA,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_GAMMA, GR_TAU, GR_UPSILON, GR_PHI, GR_ETA, GR_PSI, GR_OMEGA, GR_BETA,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_ETA, GR_UPSILON, GR_THETA, GR_GAMMA, GR_XI, GR_OMEGA, GR_BETA, GR_NU,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_XI, GR_THETA, GR_IOTA, GR_ETA, GR_KAPPA, GR_BETA, GR_NU, GR_MU,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_KAPPA, GR_IOTA, GR_OMICRON, GR_XI, GR_LAMDA, GR_NU, GR_MU, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_LAMDA, GR_OMICRON, GR_PI, GR_KAPPA, GR_MU, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_ZETA, GR_ALPHA, GR_SIGMA, GR_DELTA, GR_CHI, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_CHI, GR_SIGMA, GR_DELTA, GR_PHI, GR_ZETA, GR_PSI, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_PSI, GR_DELTA, GR_PHI, GR_GAMMA, GR_CHI, GR_OMEGA, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_OMEGA, GR_PHI, GR_GAMMA, GR_ETA, GR_PSI, GR_BETA, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_BETA, GR_GAMMA, GR_ETA, GR_XI, GR_OMEGA, GR_NU, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_NU, GR_ETA, GR_XI, GR_KAPPA, GR_BETA, GR_MU, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
GR_MU, GR_XI, GR_KAPPA, GR_LAMDA, GR_NU, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
static {
buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
}
}
private static int[] getProximityForScript(final int script) {
switch (script) {
case AndroidSpellCheckerService.SCRIPT_LATIN:
return Latin.PROXIMITY;
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
return Cyrillic.PROXIMITY;
case AndroidSpellCheckerService.SCRIPT_GREEK:
return Greek.PROXIMITY;
default:
throw new RuntimeException("Wrong script supplied: " + script);
}
}
private static int getIndexOfCodeForScript(final int codePoint, final int script) {
switch (script) {
case AndroidSpellCheckerService.SCRIPT_LATIN:
return Latin.INDICES.get(codePoint, NOT_AN_INDEX);
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
return Cyrillic.INDICES.get(codePoint, NOT_AN_INDEX);
case AndroidSpellCheckerService.SCRIPT_GREEK:
return Greek.INDICES.get(codePoint, NOT_AN_INDEX);
default:
throw new RuntimeException("Wrong script supplied: " + script);
}
}
// Returns (Y << 16) + X to avoid creating a temporary object. This is okay because
// X and Y are limited to PROXIMITY_GRID_WIDTH resp. PROXIMITY_GRID_HEIGHT which is very
// inferior to 1 << 16
// As an exception, this returns NOT_A_COORDINATE_PAIR if the key is not on the grid
public static int getXYForCodePointAndScript(final int codePoint, final int script) {
final int index = getIndexOfCodeForScript(codePoint, script);
if (NOT_AN_INDEX == index) return NOT_A_COORDINATE_PAIR;
final int y = index / PROXIMITY_GRID_WIDTH;
final int x = index % PROXIMITY_GRID_WIDTH;
if (y > PROXIMITY_GRID_HEIGHT) {
// Safety check, should be entirely useless
throw new RuntimeException("Wrong y coordinate in spell checker proximity");
}
return (y << 16) + x;
}
}