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