MyanmarReordering.java revision 8e38b12e9ccc48bcb18b2eeec4d53d19cf7a29c9
1/* 2 * Copyright (C) 2014 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 17package com.android.inputmethod.event; 18 19import com.android.inputmethod.latin.Constants; 20 21import java.util.ArrayList; 22import java.util.Arrays; 23 24/** 25 * A combiner that reorders input for Myanmar. 26 */ 27public class MyanmarReordering implements Combiner { 28 // U+1031 MYANMAR VOWEL SIGN E 29 private final static int VOWEL_E = 0x1031; // Code point for vowel E that we need to reorder 30 // U+200C ZERO WIDTH NON-JOINER 31 // U+200B ZERO WIDTH SPACE 32 private final static int ZERO_WIDTH_NON_JOINER = 0x200B; // should be 0x200C 33 34 private final ArrayList<Event> mCurrentEvents = new ArrayList<>(); 35 36 // List of consonants : 37 // U+1000 MYANMAR LETTER KA 38 // U+1001 MYANMAR LETTER KHA 39 // U+1002 MYANMAR LETTER GA 40 // U+1003 MYANMAR LETTER GHA 41 // U+1004 MYANMAR LETTER NGA 42 // U+1005 MYANMAR LETTER CA 43 // U+1006 MYANMAR LETTER CHA 44 // U+1007 MYANMAR LETTER JA 45 // U+1008 MYANMAR LETTER JHA 46 // U+1009 MYANMAR LETTER NYA 47 // U+100A MYANMAR LETTER NNYA 48 // U+100B MYANMAR LETTER TTA 49 // U+100C MYANMAR LETTER TTHA 50 // U+100D MYANMAR LETTER DDA 51 // U+100E MYANMAR LETTER DDHA 52 // U+100F MYANMAR LETTER NNA 53 // U+1010 MYANMAR LETTER TA 54 // U+1011 MYANMAR LETTER THA 55 // U+1012 MYANMAR LETTER DA 56 // U+1013 MYANMAR LETTER DHA 57 // U+1014 MYANMAR LETTER NA 58 // U+1015 MYANMAR LETTER PA 59 // U+1016 MYANMAR LETTER PHA 60 // U+1017 MYANMAR LETTER BA 61 // U+1018 MYANMAR LETTER BHA 62 // U+1019 MYANMAR LETTER MA 63 // U+101A MYANMAR LETTER YA 64 // U+101B MYANMAR LETTER RA 65 // U+101C MYANMAR LETTER LA 66 // U+101D MYANMAR LETTER WA 67 // U+101E MYANMAR LETTER SA 68 // U+101F MYANMAR LETTER HA 69 // U+1020 MYANMAR LETTER LLA 70 // U+103F MYANMAR LETTER GREAT SA 71 private static boolean isConsonant(final int codePoint) { 72 return (codePoint >= 0x1000 && codePoint <= 0x1020) || 0x103F == codePoint; 73 } 74 75 // List of medials : 76 // U+103B MYANMAR CONSONANT SIGN MEDIAL YA 77 // U+103C MYANMAR CONSONANT SIGN MEDIAL RA 78 // U+103D MYANMAR CONSONANT SIGN MEDIAL WA 79 // U+103E MYANMAR CONSONANT SIGN MEDIAL HA 80 // U+105E MYANMAR CONSONANT SIGN MON MEDIAL NA 81 // U+105F MYANMAR CONSONANT SIGN MON MEDIAL MA 82 // U+1060 MYANMAR CONSONANT SIGN MON MEDIAL LA 83 // U+1082 MYANMAR CONSONANT SIGN SHAN MEDIAL WA 84 private static int[] MEDIAL_LIST = { 0x103B, 0x103C, 0x103D, 0x103E, 85 0x105E, 0x105F, 0x1060, 0x1082}; 86 private static boolean isMedial(final int codePoint) { 87 return Arrays.binarySearch(MEDIAL_LIST, codePoint) >= 0; 88 } 89 90 private static boolean isConsonantOrMedial(final int codePoint) { 91 return isConsonant(codePoint) || isMedial(codePoint); 92 } 93 94 private Event getLastEvent() { 95 final int size = mCurrentEvents.size(); 96 if (size <= 0) { 97 return null; 98 } 99 return mCurrentEvents.get(size - 1); 100 } 101 102 private CharSequence getCharSequence() { 103 final StringBuilder s = new StringBuilder(); 104 for (final Event e : mCurrentEvents) { 105 s.appendCodePoint(e.mCodePoint); 106 } 107 return s; 108 } 109 110 /** 111 * Clears the currently combining stream of events and returns the resulting software text 112 * event corresponding to the stream. Optionally adds a new event to the cleared stream. 113 * @param newEvent the new event to add to the stream. null if none. 114 * @return the resulting software text event. Never null. 115 */ 116 private Event clearAndGetResultingEvent(final Event newEvent) { 117 final CharSequence combinedText; 118 if (mCurrentEvents.size() > 0) { 119 combinedText = getCharSequence(); 120 mCurrentEvents.clear(); 121 } else { 122 combinedText = null; 123 } 124 if (null != newEvent) { 125 mCurrentEvents.add(newEvent); 126 } 127 return null == combinedText ? Event.createConsumedEvent(newEvent) 128 : Event.createSoftwareTextEvent(combinedText, Event.NOT_A_KEY_CODE); 129 } 130 131 @Override 132 public Event processEvent(ArrayList<Event> previousEvents, Event newEvent) { 133 final int codePoint = newEvent.mCodePoint; 134 if (VOWEL_E == codePoint) { 135 final Event lastEvent = getLastEvent(); 136 if (null == lastEvent) { 137 mCurrentEvents.add(newEvent); 138 return Event.createConsumedEvent(newEvent); 139 } else if (isConsonantOrMedial(lastEvent.mCodePoint)) { 140 final Event resultingEvent = clearAndGetResultingEvent(null); 141 mCurrentEvents.add(Event.createSoftwareKeypressEvent(ZERO_WIDTH_NON_JOINER, 142 Event.NOT_A_KEY_CODE, 143 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, 144 false /* isKeyRepeat */)); 145 mCurrentEvents.add(newEvent); 146 return resultingEvent; 147 } else { // VOWEL_E == lastCodePoint. But if that was anything else this is correct too. 148 return clearAndGetResultingEvent(newEvent); 149 } 150 } if (isConsonant(codePoint)) { 151 final Event lastEvent = getLastEvent(); 152 if (null == lastEvent) { 153 mCurrentEvents.add(newEvent); 154 return Event.createConsumedEvent(newEvent); 155 } else if (VOWEL_E == lastEvent.mCodePoint) { 156 final int eventSize = mCurrentEvents.size(); 157 if (eventSize >= 2 158 && mCurrentEvents.get(eventSize - 2).mCodePoint == ZERO_WIDTH_NON_JOINER) { 159 // We have a ZWJN before a vowel E. We need to remove the ZWNJ and then 160 // reorder the vowel with respect to the consonant. 161 mCurrentEvents.remove(eventSize - 1); 162 mCurrentEvents.remove(eventSize - 2); 163 mCurrentEvents.add(newEvent); 164 mCurrentEvents.add(lastEvent); 165 return Event.createConsumedEvent(newEvent); 166 } 167 // If there is already a consonant, then we are starting a new syllable. 168 for (int i = eventSize - 2; i >= 0; --i) { 169 if (isConsonant(mCurrentEvents.get(i).mCodePoint)) { 170 return clearAndGetResultingEvent(newEvent); 171 } 172 } 173 // If we come here, we didn't have a consonant so we reorder 174 mCurrentEvents.remove(eventSize - 1); 175 mCurrentEvents.add(newEvent); 176 mCurrentEvents.add(lastEvent); 177 return Event.createConsumedEvent(newEvent); 178 } else { // lastCodePoint is a consonant/medial. But if it's something else it's fine 179 return clearAndGetResultingEvent(newEvent); 180 } 181 } else if (isMedial(codePoint)) { 182 final Event lastEvent = getLastEvent(); 183 if (null == lastEvent) { 184 mCurrentEvents.add(newEvent); 185 return Event.createConsumedEvent(newEvent); 186 } else if (VOWEL_E == lastEvent.mCodePoint) { 187 final int eventSize = mCurrentEvents.size(); 188 // If there is already a consonant, then we are in the middle of a syllable, and we 189 // need to reorder. 190 boolean hasConsonant = false; 191 for (int i = eventSize - 2; i >= 0; --i) { 192 if (isConsonant(mCurrentEvents.get(i).mCodePoint)) { 193 hasConsonant = true; 194 break; 195 } 196 } 197 if (hasConsonant) { 198 mCurrentEvents.remove(eventSize - 1); 199 mCurrentEvents.add(newEvent); 200 mCurrentEvents.add(lastEvent); 201 return Event.createConsumedEvent(newEvent); 202 } 203 // Otherwise, we just commit everything. 204 return clearAndGetResultingEvent(null); 205 } else { // lastCodePoint is a consonant/medial. But if it's something else it's fine 206 return clearAndGetResultingEvent(newEvent); 207 } 208 } else if (Constants.CODE_DELETE == newEvent.mKeyCode) { 209 final Event lastEvent = getLastEvent(); 210 final int eventSize = mCurrentEvents.size(); 211 if (null != lastEvent) { 212 if (VOWEL_E == lastEvent.mCodePoint) { 213 // We have a VOWEL_E at the end. There are four cases. 214 // - The vowel is the only code point in the buffer. Remove it. 215 // - The vowel is preceded by a ZWNJ. Remove both vowel E and ZWNJ. 216 // - The vowel is preceded by a consonant/medial, remove the consonant/medial. 217 // - In all other cases, it's strange, so just remove the last code point. 218 if (eventSize <= 1) { 219 mCurrentEvents.clear(); 220 } else { // eventSize >= 2 221 final int previousCodePoint = mCurrentEvents.get(eventSize - 2).mCodePoint; 222 if (previousCodePoint == ZERO_WIDTH_NON_JOINER) { 223 mCurrentEvents.remove(eventSize - 1); 224 mCurrentEvents.remove(eventSize - 2); 225 } else if (isConsonantOrMedial(previousCodePoint)) { 226 mCurrentEvents.remove(eventSize - 2); 227 } else { 228 mCurrentEvents.remove(eventSize - 1); 229 } 230 } 231 return Event.createConsumedEvent(newEvent); 232 } else if (eventSize > 0) { 233 mCurrentEvents.remove(eventSize - 1); 234 return Event.createConsumedEvent(newEvent); 235 } 236 } 237 } 238 // This character is not part of the combining scheme, so we should reset everything. 239 if (mCurrentEvents.size() > 0) { 240 // If we have events in flight, then add the new event and return the resulting event. 241 mCurrentEvents.add(newEvent); 242 return clearAndGetResultingEvent(null); 243 } else { 244 // If we don't have any events in flight, then just pass this one through. 245 return newEvent; 246 } 247 } 248 249 @Override 250 public CharSequence getCombiningStateFeedback() { 251 return getCharSequence(); 252 } 253 254 @Override 255 public void reset() { 256 mCurrentEvents.clear(); 257 } 258} 259