diff options
Diffstat (limited to 'java/src/org/kelar/inputmethod/latin/spellcheck/SentenceLevelAdapter.java')
-rw-r--r-- | java/src/org/kelar/inputmethod/latin/spellcheck/SentenceLevelAdapter.java | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/java/src/org/kelar/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/org/kelar/inputmethod/latin/spellcheck/SentenceLevelAdapter.java new file mode 100644 index 000000000..4dbcd092e --- /dev/null +++ b/java/src/org/kelar/inputmethod/latin/spellcheck/SentenceLevelAdapter.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2014 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 org.kelar.inputmethod.latin.spellcheck; + +import android.annotation.TargetApi; +import android.content.res.Resources; +import android.os.Build; +import android.view.textservice.SentenceSuggestionsInfo; +import android.view.textservice.SuggestionsInfo; +import android.view.textservice.TextInfo; + +import org.kelar.inputmethod.compat.TextInfoCompatUtils; +import org.kelar.inputmethod.latin.common.Constants; +import org.kelar.inputmethod.latin.settings.SpacingAndPunctuations; +import org.kelar.inputmethod.latin.utils.RunInLocale; + +import java.util.ArrayList; +import java.util.Locale; + +/** + * This code is mostly lifted directly from android.service.textservice.SpellCheckerService in + * the framework; maybe that should be protected instead, so that implementers don't have to + * rewrite everything for any small change. + */ +public class SentenceLevelAdapter { + private static class EmptySentenceSuggestionsInfosInitializationHolder { + public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS = + new SentenceSuggestionsInfo[]{}; + } + private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null); + + public static SentenceSuggestionsInfo[] getEmptySentenceSuggestionsInfo() { + return EmptySentenceSuggestionsInfosInitializationHolder.EMPTY_SENTENCE_SUGGESTIONS_INFOS; + } + + /** + * Container for split TextInfo parameters + */ + public static class SentenceWordItem { + public final TextInfo mTextInfo; + public final int mStart; + public final int mLength; + public SentenceWordItem(TextInfo ti, int start, int end) { + mTextInfo = ti; + mStart = start; + mLength = end - start; + } + } + + /** + * Container for originally queried TextInfo and parameters + */ + public static class SentenceTextInfoParams { + final TextInfo mOriginalTextInfo; + final ArrayList<SentenceWordItem> mItems; + final int mSize; + public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) { + mOriginalTextInfo = ti; + mItems = items; + mSize = items.size(); + } + } + + private static class WordIterator { + private final SpacingAndPunctuations mSpacingAndPunctuations; + public WordIterator(final Resources res, final Locale locale) { + final RunInLocale<SpacingAndPunctuations> job = + new RunInLocale<SpacingAndPunctuations>() { + @Override + protected SpacingAndPunctuations job(final Resources r) { + return new SpacingAndPunctuations(r); + } + }; + mSpacingAndPunctuations = job.runInLocale(res, locale); + } + + public int getEndOfWord(final CharSequence sequence, final int fromIndex) { + final int length = sequence.length(); + int index = fromIndex < 0 ? 0 : Character.offsetByCodePoints(sequence, fromIndex, 1); + while (index < length) { + final int codePoint = Character.codePointAt(sequence, index); + if (mSpacingAndPunctuations.isWordSeparator(codePoint)) { + // If it's a period, we want to stop here only if it's followed by another + // word separator. In all other cases we stop here. + if (Constants.CODE_PERIOD == codePoint) { + final int indexOfNextCodePoint = + index + Character.charCount(Constants.CODE_PERIOD); + if (indexOfNextCodePoint < length + && mSpacingAndPunctuations.isWordSeparator( + Character.codePointAt(sequence, indexOfNextCodePoint))) { + return index; + } + } else { + return index; + } + } + index += Character.charCount(codePoint); + } + return index; + } + + public int getBeginningOfNextWord(final CharSequence sequence, final int fromIndex) { + final int length = sequence.length(); + if (fromIndex >= length) { + return -1; + } + int index = fromIndex < 0 ? 0 : Character.offsetByCodePoints(sequence, fromIndex, 1); + while (index < length) { + final int codePoint = Character.codePointAt(sequence, index); + if (!mSpacingAndPunctuations.isWordSeparator(codePoint)) { + return index; + } + index += Character.charCount(codePoint); + } + return -1; + } + } + + private final WordIterator mWordIterator; + public SentenceLevelAdapter(final Resources res, final Locale locale) { + mWordIterator = new WordIterator(res, locale); + } + + public SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) { + final WordIterator wordIterator = mWordIterator; + final CharSequence originalText = + TextInfoCompatUtils.getCharSequenceOrString(originalTextInfo); + final int cookie = originalTextInfo.getCookie(); + final int start = -1; + final int end = originalText.length(); + final ArrayList<SentenceWordItem> wordItems = new ArrayList<>(); + int wordStart = wordIterator.getBeginningOfNextWord(originalText, start); + int wordEnd = wordIterator.getEndOfWord(originalText, wordStart); + while (wordStart <= end && wordEnd != -1 && wordStart != -1) { + if (wordEnd >= start && wordEnd > wordStart) { + final TextInfo ti = TextInfoCompatUtils.newInstance(originalText, wordStart, + wordEnd, cookie, originalText.subSequence(wordStart, wordEnd).hashCode()); + wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd)); + } + wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd); + if (wordStart == -1) { + break; + } + wordEnd = wordIterator.getEndOfWord(originalText, wordStart); + } + return new SentenceTextInfoParams(originalTextInfo, wordItems); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public static SentenceSuggestionsInfo reconstructSuggestions( + SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) { + if (results == null || results.length == 0) { + return null; + } + if (originalTextInfoParams == null) { + return null; + } + final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie(); + final int originalSequence = + originalTextInfoParams.mOriginalTextInfo.getSequence(); + + final int querySize = originalTextInfoParams.mSize; + final int[] offsets = new int[querySize]; + final int[] lengths = new int[querySize]; + final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize]; + for (int i = 0; i < querySize; ++i) { + final SentenceWordItem item = originalTextInfoParams.mItems.get(i); + SuggestionsInfo result = null; + for (int j = 0; j < results.length; ++j) { + final SuggestionsInfo cur = results[j]; + if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) { + result = cur; + result.setCookieAndSequence(originalCookie, originalSequence); + break; + } + } + offsets[i] = item.mStart; + lengths[i] = item.mLength; + reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO; + } + return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths); + } +} |