TextDirectionHeuristics.java revision 4b60c30838fbd635964f1e79c057de5048dcc66f
1/* 2 * Copyright (C) 2011 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 android.text; 18 19 20/** 21 * Some objects that implement TextDirectionHeuristic. 22 * @hide 23 */ 24public class TextDirectionHeuristics { 25 26 /** Always decides that the direction is left to right. */ 27 public static final TextDirectionHeuristic LTR = 28 new TextDirectionHeuristicInternal(null /* no algorithm */, false); 29 30 /** Always decides that the direction is right to left. */ 31 public static final TextDirectionHeuristic RTL = 32 new TextDirectionHeuristicInternal(null /* no algorithm */, true); 33 34 /** 35 * Determines the direction based on the first strong directional character, 36 * including bidi format chars, falling back to left to right if it 37 * finds none. This is the default behavior of the Unicode Bidirectional 38 * Algorithm. 39 */ 40 public static final TextDirectionHeuristic FIRSTSTRONG_LTR = 41 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false); 42 43 /** 44 * Determines the direction based on the first strong directional character, 45 * including bidi format chars, falling back to right to left if it 46 * finds none. This is similar to the default behavior of the Unicode 47 * Bidirectional Algorithm, just with different fallback behavior. 48 */ 49 public static final TextDirectionHeuristic FIRSTSTRONG_RTL = 50 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true); 51 52 /** 53 * If the text contains any strong left to right non-format character, determines 54 * that the direction is left to right, falling back to left to right if it 55 * finds none. 56 */ 57 public static final TextDirectionHeuristic ANYLTR_LTR = 58 new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_LTR, false); 59 60 /** 61 * If the text contains any strong left to right non-format character, determines 62 * that the direction is left to right, falling back to right to left if it 63 * finds none. 64 */ 65 public static final TextDirectionHeuristic ANYLTR_RTL = 66 new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_LTR, true); 67 68 /** 69 * If the text contains any strong right to left non-format character, determines 70 * that the direction is right to left, falling back to left to right if it 71 * finds none. 72 */ 73 public static final TextDirectionHeuristic ANYRTL_LTR = 74 new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false); 75 76 /** 77 * If the text contains any strong right to left non-format character, determines 78 * that the direction is right to left, falling back to right to left if it 79 * finds none. 80 */ 81 public static final TextDirectionHeuristic ANYRTL_RTL = 82 new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, true); 83 84 /** 85 * Examines only the strong directional non-format characters, and if either 86 * left to right or right to left characters are 60% or more of this total, 87 * determines that the direction follows the majority of characters. Falls 88 * back to left to right if neither direction meets this threshold. 89 */ 90 public static final TextDirectionHeuristic CHARCOUNT_LTR = 91 new TextDirectionHeuristicInternal(CharCount.INSTANCE_DEFAULT, false); 92 93 /** 94 * Examines only the strong directional non-format characters, and if either 95 * left to right or right to left characters are 60% or more of this total, 96 * determines that the direction follows the majority of characters. Falls 97 * back to right to left if neither direction meets this threshold. 98 */ 99 public static final TextDirectionHeuristic CHARCOUNT_RTL = 100 new TextDirectionHeuristicInternal(CharCount.INSTANCE_DEFAULT, true); 101 102 private static enum TriState { 103 TRUE, FALSE, UNKNOWN; 104 } 105 106 /** 107 * Computes the text direction based on an algorithm. Subclasses implement 108 * {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the 109 * direction from the text alone. 110 * @hide 111 */ 112 public static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic { 113 private final TextDirectionAlgorithm mAlgorithm; 114 115 public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) { 116 mAlgorithm = algorithm; 117 } 118 119 /** 120 * Return true if the default text direction is rtl. 121 */ 122 abstract protected boolean defaultIsRtl(); 123 124 @Override 125 public boolean isRtl(char[] chars, int start, int count) { 126 if (chars == null || start < 0 || count < 0 || chars.length - count < start) { 127 throw new IllegalArgumentException(); 128 } 129 if (mAlgorithm == null) { 130 return defaultIsRtl(); 131 } 132 return doCheck(chars, start, count); 133 } 134 135 private boolean doCheck(char[] chars, int start, int count) { 136 switch(mAlgorithm.checkRtl(chars, start, count)) { 137 case TRUE: 138 return true; 139 case FALSE: 140 return false; 141 default: 142 return defaultIsRtl(); 143 } 144 } 145 } 146 147 private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl { 148 private final boolean mDefaultIsRtl; 149 150 private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, 151 boolean defaultIsRtl) { 152 super(algorithm); 153 mDefaultIsRtl = defaultIsRtl; 154 } 155 156 @Override 157 protected boolean defaultIsRtl() { 158 return mDefaultIsRtl; 159 } 160 } 161 162 private static TriState isRtlText(int directionality) { 163 switch (directionality) { 164 case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 165 return TriState.FALSE; 166 case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 167 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 168 return TriState.TRUE; 169 default: 170 return TriState.UNKNOWN; 171 } 172 } 173 174 private static TriState isRtlTextOrFormat(int directionality) { 175 switch (directionality) { 176 case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 177 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: 178 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: 179 return TriState.FALSE; 180 case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 181 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 182 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: 183 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: 184 return TriState.TRUE; 185 default: 186 return TriState.UNKNOWN; 187 } 188 } 189 190 /** 191 * Interface for an algorithm to guess the direction of a paragraph of text. 192 * 193 * @hide 194 */ 195 public static interface TextDirectionAlgorithm { 196 /** 197 * Returns whether the range of text is RTL according to the algorithm. 198 * 199 * @hide 200 */ 201 TriState checkRtl(char[] text, int start, int count); 202 } 203 204 /** 205 * Algorithm that uses the first strong directional character to determine 206 * the paragraph direction. This is the standard Unicode Bidirectional 207 * algorithm. 208 * 209 * @hide 210 */ 211 public static class FirstStrong implements TextDirectionAlgorithm { 212 @Override 213 public TriState checkRtl(char[] text, int start, int count) { 214 TriState result = TriState.UNKNOWN; 215 for (int i = start, e = start + count; i < e && result == TriState.UNKNOWN; ++i) { 216 result = isRtlTextOrFormat(Character.getDirectionality(text[i])); 217 } 218 return result; 219 } 220 221 private FirstStrong() { 222 } 223 224 public static final FirstStrong INSTANCE = new FirstStrong(); 225 } 226 227 /** 228 * Algorithm that uses the presence of any strong directional non-format 229 * character (e.g. excludes LRE, LRO, RLE, RLO) to determine the 230 * direction of text. 231 * 232 * @hide 233 */ 234 public static class AnyStrong implements TextDirectionAlgorithm { 235 private final boolean mLookForRtl; 236 237 @Override 238 public TriState checkRtl(char[] text, int start, int count) { 239 boolean haveUnlookedFor = false; 240 for (int i = start, e = start + count; i < e; ++i) { 241 switch (isRtlText(Character.getDirectionality(text[i]))) { 242 case TRUE: 243 if (mLookForRtl) { 244 return TriState.TRUE; 245 } 246 haveUnlookedFor = true; 247 break; 248 case FALSE: 249 if (!mLookForRtl) { 250 return TriState.FALSE; 251 } 252 haveUnlookedFor = true; 253 break; 254 default: 255 break; 256 } 257 } 258 if (haveUnlookedFor) { 259 return mLookForRtl ? TriState.FALSE : TriState.TRUE; 260 } 261 return TriState.UNKNOWN; 262 } 263 264 private AnyStrong(boolean lookForRtl) { 265 this.mLookForRtl = lookForRtl; 266 } 267 268 public static final AnyStrong INSTANCE_RTL = new AnyStrong(true); 269 public static final AnyStrong INSTANCE_LTR = new AnyStrong(false); 270 } 271 272 /** 273 * Algorithm that uses the relative proportion of strong directional 274 * characters (excluding LRE, LRO, RLE, RLO) to determine the direction 275 * of the paragraph, if the proportion exceeds a given threshold. 276 * 277 * @hide 278 */ 279 public static class CharCount implements TextDirectionAlgorithm { 280 private final float mThreshold; 281 282 @Override 283 public TriState checkRtl(char[] text, int start, int count) { 284 int countLtr = 0; 285 int countRtl = 0; 286 for(int i = start, e = start + count; i < e; ++i) { 287 switch (isRtlText(Character.getDirectionality(text[i]))) { 288 case TRUE: 289 ++countLtr; 290 break; 291 case FALSE: 292 ++countRtl; 293 break; 294 default: 295 break; 296 } 297 } 298 int limit = (int)((countLtr + countRtl) * mThreshold); 299 if (limit > 0) { 300 if (countLtr > limit) { 301 return TriState.FALSE; 302 } 303 if (countRtl > limit) { 304 return TriState.TRUE; 305 } 306 } 307 return TriState.UNKNOWN; 308 } 309 310 private CharCount(float threshold) { 311 mThreshold = threshold; 312 } 313 314 public static CharCount withThreshold(float threshold) { 315 if (threshold < 0 || threshold > 1) { 316 throw new IllegalArgumentException(); 317 } 318 if (threshold == DEFAULT_THRESHOLD) { 319 return INSTANCE_DEFAULT; 320 } 321 return new CharCount(threshold); 322 } 323 324 public static final float DEFAULT_THRESHOLD = 0.6f; 325 public static final CharCount INSTANCE_DEFAULT = new CharCount(DEFAULT_THRESHOLD); 326 } 327} 328