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