aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/event
diff options
context:
space:
mode:
authorJean Chalard <jchalard@google.com>2014-08-01 23:47:33 +0900
committerJean Chalard <jchalard@google.com>2014-08-22 15:55:32 +0900
commitc6612ff05185503bedc56dc610aa300d01be2e82 (patch)
treeeae497ef3eea2adecfb4ef1155f03ade86b45cd0 /java/src/com/android/inputmethod/event
parent5a96395b78df7e201c580515ec9f54609a474a2d (diff)
downloadlatinime-c6612ff05185503bedc56dc610aa300d01be2e82.tar.gz
latinime-c6612ff05185503bedc56dc610aa300d01be2e82.tar.xz
latinime-c6612ff05185503bedc56dc610aa300d01be2e82.zip
[HW17] Straighten the dead char implementation.
This now supports all cases, including cases not supported by the framework. Change-Id: I9dfbe2829c28c5bcb64b0b5a2551e7806990c00f
Diffstat (limited to 'java/src/com/android/inputmethod/event')
-rw-r--r--java/src/com/android/inputmethod/event/DeadKeyCombiner.java265
1 files changed, 243 insertions, 22 deletions
diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
index 4f3f4d25f..88c70630d 100644
--- a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
+++ b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
@@ -17,10 +17,12 @@
package com.android.inputmethod.event;
import android.text.TextUtils;
+import android.util.SparseIntArray;
import android.view.KeyCharacterMap;
import com.android.inputmethod.latin.Constants;
+import java.text.Normalizer;
import java.util.ArrayList;
import javax.annotation.Nonnull;
@@ -29,9 +31,209 @@ import javax.annotation.Nonnull;
* A combiner that handles dead keys.
*/
public class DeadKeyCombiner implements Combiner {
+
+ private static class Data {
+ // This class data taken from KeyCharacterMap.java.
+
+ /* Characters used to display placeholders for dead keys. */
+ private static final int ACCENT_ACUTE = '\u00B4';
+ private static final int ACCENT_BREVE = '\u02D8';
+ private static final int ACCENT_CARON = '\u02C7';
+ private static final int ACCENT_CEDILLA = '\u00B8';
+ private static final int ACCENT_CIRCUMFLEX = '\u02C6';
+ private static final int ACCENT_COMMA_ABOVE = '\u1FBD';
+ private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC';
+ private static final int ACCENT_DOT_ABOVE = '\u02D9';
+ private static final int ACCENT_DOT_BELOW = Constants.CODE_PERIOD; // approximate
+ private static final int ACCENT_DOUBLE_ACUTE = '\u02DD';
+ private static final int ACCENT_GRAVE = '\u02CB';
+ private static final int ACCENT_HOOK_ABOVE = '\u02C0';
+ private static final int ACCENT_HORN = Constants.CODE_SINGLE_QUOTE; // approximate
+ private static final int ACCENT_MACRON = '\u00AF';
+ private static final int ACCENT_MACRON_BELOW = '\u02CD';
+ private static final int ACCENT_OGONEK = '\u02DB';
+ private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD';
+ private static final int ACCENT_RING_ABOVE = '\u02DA';
+ private static final int ACCENT_STROKE = Constants.CODE_DASH; // approximate
+ private static final int ACCENT_TILDE = '\u02DC';
+ private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB';
+ private static final int ACCENT_UMLAUT = '\u00A8';
+ private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
+ private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
+
+ /* Legacy dead key display characters used in previous versions of the API (before L)
+ * We still support these characters by mapping them to their non-legacy version. */
+ private static final int ACCENT_GRAVE_LEGACY = Constants.CODE_GRAVE_ACCENT;
+ private static final int ACCENT_CIRCUMFLEX_LEGACY = Constants.CODE_CIRCUMFLEX_ACCENT;
+ private static final int ACCENT_TILDE_LEGACY = Constants.CODE_TILDE;
+
+ /**
+ * Maps Unicode combining diacritical to display-form dead key.
+ */
+ private static final SparseIntArray sCombiningToAccent = new SparseIntArray();
+ private static final SparseIntArray sAccentToCombining = new SparseIntArray();
+ static {
+ // U+0300: COMBINING GRAVE ACCENT
+ addCombining('\u0300', ACCENT_GRAVE);
+ // U+0301: COMBINING ACUTE ACCENT
+ addCombining('\u0301', ACCENT_ACUTE);
+ // U+0302: COMBINING CIRCUMFLEX ACCENT
+ addCombining('\u0302', ACCENT_CIRCUMFLEX);
+ // U+0303: COMBINING TILDE
+ addCombining('\u0303', ACCENT_TILDE);
+ // U+0304: COMBINING MACRON
+ addCombining('\u0304', ACCENT_MACRON);
+ // U+0306: COMBINING BREVE
+ addCombining('\u0306', ACCENT_BREVE);
+ // U+0307: COMBINING DOT ABOVE
+ addCombining('\u0307', ACCENT_DOT_ABOVE);
+ // U+0308: COMBINING DIAERESIS
+ addCombining('\u0308', ACCENT_UMLAUT);
+ // U+0309: COMBINING HOOK ABOVE
+ addCombining('\u0309', ACCENT_HOOK_ABOVE);
+ // U+030A: COMBINING RING ABOVE
+ addCombining('\u030A', ACCENT_RING_ABOVE);
+ // U+030B: COMBINING DOUBLE ACUTE ACCENT
+ addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
+ // U+030C: COMBINING CARON
+ addCombining('\u030C', ACCENT_CARON);
+ // U+030D: COMBINING VERTICAL LINE ABOVE
+ addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
+ // U+030E: COMBINING DOUBLE VERTICAL LINE ABOVE
+ //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
+ // U+030F: COMBINING DOUBLE GRAVE ACCENT
+ //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
+ // U+0310: COMBINING CANDRABINDU
+ //addCombining('\u0310', ACCENT_CANDRABINDU);
+ // U+0311: COMBINING INVERTED BREVE
+ //addCombining('\u0311', ACCENT_INVERTED_BREVE);
+ // U+0312: COMBINING TURNED COMMA ABOVE
+ addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE);
+ // U+0313: COMBINING COMMA ABOVE
+ addCombining('\u0313', ACCENT_COMMA_ABOVE);
+ // U+0314: COMBINING REVERSED COMMA ABOVE
+ addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE);
+ // U+0315: COMBINING COMMA ABOVE RIGHT
+ addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT);
+ // U+031B: COMBINING HORN
+ addCombining('\u031B', ACCENT_HORN);
+ // U+0323: COMBINING DOT BELOW
+ addCombining('\u0323', ACCENT_DOT_BELOW);
+ // U+0326: COMBINING COMMA BELOW
+ //addCombining('\u0326', ACCENT_COMMA_BELOW);
+ // U+0327: COMBINING CEDILLA
+ addCombining('\u0327', ACCENT_CEDILLA);
+ // U+0328: COMBINING OGONEK
+ addCombining('\u0328', ACCENT_OGONEK);
+ // U+0329: COMBINING VERTICAL LINE BELOW
+ addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW);
+ // U+0331: COMBINING MACRON BELOW
+ addCombining('\u0331', ACCENT_MACRON_BELOW);
+ // U+0335: COMBINING SHORT STROKE OVERLAY
+ addCombining('\u0335', ACCENT_STROKE);
+ // U+0342: COMBINING GREEK PERISPOMENI
+ //addCombining('\u0342', ACCENT_PERISPOMENI);
+ // U+0344: COMBINING GREEK DIALYTIKA TONOS
+ //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS);
+ // U+0345: COMBINING GREEK YPOGEGRAMMENI
+ //addCombining('\u0345', ACCENT_YPOGEGRAMMENI);
+
+ // One-way mappings to equivalent preferred accents.
+ // U+0340: COMBINING GRAVE TONE MARK
+ sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
+ // U+0341: COMBINING ACUTE TONE MARK
+ sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
+ // U+0343: COMBINING GREEK KORONIS
+ sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+
+ // One-way legacy mappings to preserve compatibility with older applications.
+ // U+0300: COMBINING GRAVE ACCENT
+ sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
+ // U+0302: COMBINING CIRCUMFLEX ACCENT
+ sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
+ // U+0303: COMBINING TILDE
+ sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+ }
+
+ private static void addCombining(int combining, int accent) {
+ sCombiningToAccent.append(combining, accent);
+ sAccentToCombining.append(accent, combining);
+ }
+
+ // Caution! This may only contain chars, not supplementary code points. It's unlikely
+ // it will ever need to, but if it does we'll have to change this
+ private static final SparseIntArray sNonstandardDeadCombinations = new SparseIntArray();
+ static {
+ // Non-standard decompositions.
+ // Stroke modifier for Finnish multilingual keyboard and others.
+ // U+0110: LATIN CAPITAL LETTER D WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'D', '\u0110');
+ // U+01E4: LATIN CAPITAL LETTER G WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'G', '\u01e4');
+ // U+0126: LATIN CAPITAL LETTER H WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'H', '\u0126');
+ // U+0197: LATIN CAPITAL LETTER I WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'I', '\u0197');
+ // U+0141: LATIN CAPITAL LETTER L WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'L', '\u0141');
+ // U+00D8: LATIN CAPITAL LETTER O WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'O', '\u00d8');
+ // U+0166: LATIN CAPITAL LETTER T WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'T', '\u0166');
+ // U+0111: LATIN SMALL LETTER D WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'd', '\u0111');
+ // U+01E5: LATIN SMALL LETTER G WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'g', '\u01e5');
+ // U+0127: LATIN SMALL LETTER H WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'h', '\u0127');
+ // U+0268: LATIN SMALL LETTER I WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'i', '\u0268');
+ // U+0142: LATIN SMALL LETTER L WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'l', '\u0142');
+ // U+00F8: LATIN SMALL LETTER O WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 'o', '\u00f8');
+ // U+0167: LATIN SMALL LETTER T WITH STROKE
+ addNonStandardDeadCombination(ACCENT_STROKE, 't', '\u0167');
+ }
+
+ private static void addNonStandardDeadCombination(final int deadCodePoint,
+ final int spacingCodePoint, final int result) {
+ final int combination = (deadCodePoint << 16) | spacingCodePoint;
+ sNonstandardDeadCombinations.put(combination, result);
+ }
+
+ public static final int NOT_A_CHAR = 0;
+ public static final int BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION = 16;
+ // Get a non-standard combination
+ public static char getNonstandardCombination(final int deadCodePoint,
+ final int spacingCodePoint) {
+ final int combination = spacingCodePoint |
+ (deadCodePoint << BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION);
+ return (char)sNonstandardDeadCombinations.get(combination, NOT_A_CHAR);
+ }
+ }
+
// TODO: make this a list of events instead
final StringBuilder mDeadSequence = new StringBuilder();
+ @Nonnull
+ private Event createEventChainFromSequence(final @Nonnull CharSequence text,
+ final Event originalEvent) {
+ if (text.length() <= 0) {
+ return originalEvent;
+ } else {
+ Event lastEvent = null;
+ int codePoint = 0;
+ for (int i = text.length(); i > 0; i -= Character.charCount(codePoint)) {
+ codePoint = Character.codePointBefore(text, i);
+ final Event thisEvent = Event.createHardwareKeypressEvent(codePoint,
+ originalEvent.mKeyCode, lastEvent, false /* isKeyRepeat */);
+ lastEvent = thisEvent;
+ }
+ return lastEvent;
+ }
+ }
+
@Override
@Nonnull
public Event processEvent(final ArrayList<Event> previousEvents, final Event event) {
@@ -47,29 +249,48 @@ public class DeadKeyCombiner implements Combiner {
// simply returns the event as is. The majority of events will go through this path.
return event;
} else {
- // TODO: Allow combining for several dead chars rather than only the first one.
- // The framework doesn't know how to do this now.
- final int deadCodePoint = mDeadSequence.codePointAt(0);
- mDeadSequence.setLength(0);
- final int resultingCodePoint =
- KeyCharacterMap.getDeadChar(deadCodePoint, event.mCodePoint);
- if (0 == resultingCodePoint) {
- // We can't combine both characters. We need to commit the dead key as a separate
- // character, and the next char too unless it's a space (because as a special case,
- // dead key + space should result in only the dead key being committed - that's
- // how dead keys work).
- // If the event is a space, we should commit the dead char alone, but if it's
- // not, we need to commit both.
- // TODO: this is not necessarily triggered by hardware key events, so it's not
- // a good idea to masquerade as one. This should be typed as a software
- // composite event or something.
- return Event.createHardwareKeypressEvent(deadCodePoint, event.mKeyCode,
- Constants.CODE_SPACE == event.mCodePoint ? null : event /* next */,
- false /* isKeyRepeat */);
+ if (Character.isWhitespace(event.mCodePoint)
+ || event.mCodePoint == mDeadSequence.codePointBefore(mDeadSequence.length())) {
+ // When whitespace or twice the same dead key, we should output the dead sequence
+ // as is.
+ final Event resultEvent = createEventChainFromSequence(mDeadSequence.toString(),
+ event);
+ mDeadSequence.setLength(0);
+ return resultEvent;
+ } else if (event.isFunctionalKeyEvent()) {
+ if (Constants.CODE_DELETE == event.mKeyCode) {
+ // Remove the last code point
+ final int trimIndex = mDeadSequence.length() - Character.charCount(
+ mDeadSequence.codePointBefore(mDeadSequence.length()));
+ mDeadSequence.setLength(trimIndex);
+ return Event.createConsumedEvent(event);
+ } else {
+ return event;
+ }
+ } else if (event.isDead()) {
+ mDeadSequence.appendCodePoint(event.mCodePoint);
+ return Event.createConsumedEvent(event);
} else {
- // We could combine the characters.
- return Event.createHardwareKeypressEvent(resultingCodePoint, event.mKeyCode,
- null /* next */, false /* isKeyRepeat */);
+ // Combine normally.
+ final StringBuilder sb = new StringBuilder();
+ sb.appendCodePoint(event.mCodePoint);
+ int codePointIndex = 0;
+ while (codePointIndex < mDeadSequence.length()) {
+ final int deadCodePoint = mDeadSequence.codePointAt(codePointIndex);
+ final char replacementSpacingChar =
+ Data.getNonstandardCombination(deadCodePoint, event.mCodePoint);
+ if (Data.NOT_A_CHAR != replacementSpacingChar) {
+ sb.setCharAt(0, replacementSpacingChar);
+ } else {
+ final int combining = Data.sAccentToCombining.get(deadCodePoint);
+ sb.appendCodePoint(0 == combining ? deadCodePoint : combining);
+ }
+ codePointIndex += Character.isSupplementaryCodePoint(deadCodePoint) ? 2 : 1;
+ }
+ final String normalizedString = Normalizer.normalize(sb, Normalizer.Form.NFC);
+ final Event resultEvent = createEventChainFromSequence(normalizedString, event);
+ mDeadSequence.setLength(0);
+ return resultEvent;
}
}
}