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