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 17 package android.text; 18 19 20 import android.view.View; 21 22 import java.nio.CharBuffer; 23 24 /** 25 * Some objects that implement {@link TextDirectionHeuristic}. Use these with 26 * the {@link BidiFormatter#unicodeWrap unicodeWrap()} methods in {@link BidiFormatter}. 27 * Also notice that these direction heuristics correspond to the same types of constants 28 * provided in the {@link android.view.View} class for {@link android.view.View#setTextDirection 29 * setTextDirection()}, such as {@link android.view.View#TEXT_DIRECTION_RTL}. 30 * <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, 31 * you can use the support library's {@link android.support.v4.text.TextDirectionHeuristicsCompat} 32 * class. 33 * 34 */ 35 public class TextDirectionHeuristics { 36 37 /** 38 * Always decides that the direction is left to right. 39 */ 40 public static final TextDirectionHeuristic LTR = 41 new TextDirectionHeuristicInternal(null /* no algorithm */, false); 42 43 /** 44 * Always decides that the direction is right to left. 45 */ 46 public static final TextDirectionHeuristic RTL = 47 new TextDirectionHeuristicInternal(null /* no algorithm */, true); 48 49 /** 50 * Determines the direction based on the first strong directional character, including bidi 51 * format chars, falling back to left to right if it finds none. This is the default behavior 52 * of the Unicode Bidirectional Algorithm. 53 */ 54 public static final TextDirectionHeuristic FIRSTSTRONG_LTR = 55 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false); 56 57 /** 58 * Determines the direction based on the first strong directional character, including bidi 59 * format chars, falling back to right to left if it finds none. This is similar to the default 60 * behavior of the Unicode Bidirectional Algorithm, just with different fallback behavior. 61 */ 62 public static final TextDirectionHeuristic FIRSTSTRONG_RTL = 63 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true); 64 65 /** 66 * If the text contains any strong right to left non-format character, determines that the 67 * direction is right to left, falling back to left to right if it finds none. 68 */ 69 public static final TextDirectionHeuristic ANYRTL_LTR = 70 new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false); 71 72 /** 73 * Force the paragraph direction to the Locale direction. Falls back to left to right. 74 */ 75 public static final TextDirectionHeuristic LOCALE = TextDirectionHeuristicLocale.INSTANCE; 76 77 /** 78 * State constants for taking care about true / false / unknown 79 */ 80 private static final int STATE_TRUE = 0; 81 private static final int STATE_FALSE = 1; 82 private static final int STATE_UNKNOWN = 2; 83 84 /* Returns STATE_TRUE for strong RTL characters, STATE_FALSE for strong LTR characters, and 85 * STATE_UNKNOWN for everything else. 86 */ isRtlCodePoint(int codePoint)87 private static int isRtlCodePoint(int codePoint) { 88 switch (Character.getDirectionality(codePoint)) { 89 case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 90 return STATE_FALSE; 91 case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 92 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 93 return STATE_TRUE; 94 case Character.DIRECTIONALITY_UNDEFINED: 95 // Unassigned characters still have bidi direction, defined at: 96 // http://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt 97 98 if ((0x0590 <= codePoint && codePoint <= 0x08FF) || 99 (0xFB1D <= codePoint && codePoint <= 0xFDCF) || 100 (0xFDF0 <= codePoint && codePoint <= 0xFDFF) || 101 (0xFE70 <= codePoint && codePoint <= 0xFEFF) || 102 (0x10800 <= codePoint && codePoint <= 0x10FFF) || 103 (0x1E800 <= codePoint && codePoint <= 0x1EFFF)) { 104 // Unassigned RTL character 105 return STATE_TRUE; 106 } else if ( 107 // Potentially-unassigned Default_Ignorable. Ranges are from unassigned 108 // characters that have Unicode property Other_Default_Ignorable_Code_Point 109 // plus some enlargening to cover bidi isolates and simplify checks. 110 (0x2065 <= codePoint && codePoint <= 0x2069) || 111 (0xFFF0 <= codePoint && codePoint <= 0xFFF8) || 112 (0xE0000 <= codePoint && codePoint <= 0xE0FFF) || 113 // Non-character 114 (0xFDD0 <= codePoint && codePoint <= 0xFDEF) || 115 ((codePoint & 0xFFFE) == 0xFFFE) || 116 // Currency symbol 117 (0x20A0 <= codePoint && codePoint <= 0x20CF) || 118 // Unpaired surrogate 119 (0xD800 <= codePoint && codePoint <= 0xDFFF)) { 120 return STATE_UNKNOWN; 121 } else { 122 // Unassigned LTR character 123 return STATE_FALSE; 124 } 125 default: 126 return STATE_UNKNOWN; 127 } 128 } 129 130 /** 131 * Computes the text direction based on an algorithm. Subclasses implement 132 * {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the 133 * direction from the text alone. 134 */ 135 private static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic { 136 private final TextDirectionAlgorithm mAlgorithm; 137 TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm)138 public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) { 139 mAlgorithm = algorithm; 140 } 141 142 /** 143 * Return true if the default text direction is rtl. 144 */ defaultIsRtl()145 abstract protected boolean defaultIsRtl(); 146 147 @Override isRtl(char[] array, int start, int count)148 public boolean isRtl(char[] array, int start, int count) { 149 return isRtl(CharBuffer.wrap(array), start, count); 150 } 151 152 @Override isRtl(CharSequence cs, int start, int count)153 public boolean isRtl(CharSequence cs, int start, int count) { 154 if (cs == null || start < 0 || count < 0 || cs.length() - count < start) { 155 throw new IllegalArgumentException(); 156 } 157 if (mAlgorithm == null) { 158 return defaultIsRtl(); 159 } 160 return doCheck(cs, start, count); 161 } 162 doCheck(CharSequence cs, int start, int count)163 private boolean doCheck(CharSequence cs, int start, int count) { 164 switch(mAlgorithm.checkRtl(cs, start, count)) { 165 case STATE_TRUE: 166 return true; 167 case STATE_FALSE: 168 return false; 169 default: 170 return defaultIsRtl(); 171 } 172 } 173 } 174 175 private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl { 176 private final boolean mDefaultIsRtl; 177 TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, boolean defaultIsRtl)178 private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, 179 boolean defaultIsRtl) { 180 super(algorithm); 181 mDefaultIsRtl = defaultIsRtl; 182 } 183 184 @Override defaultIsRtl()185 protected boolean defaultIsRtl() { 186 return mDefaultIsRtl; 187 } 188 } 189 190 /** 191 * Interface for an algorithm to guess the direction of a paragraph of text. 192 */ 193 private static interface TextDirectionAlgorithm { 194 /** 195 * Returns whether the range of text is RTL according to the algorithm. 196 */ checkRtl(CharSequence cs, int start, int count)197 int checkRtl(CharSequence cs, int start, int count); 198 } 199 200 /** 201 * Algorithm that uses the first strong directional character to determine the paragraph 202 * direction. This is the standard Unicode Bidirectional Algorithm (steps P2 and P3), with the 203 * exception that if no strong character is found, UNKNOWN is returned. 204 */ 205 private static class FirstStrong implements TextDirectionAlgorithm { 206 @Override checkRtl(CharSequence cs, int start, int count)207 public int checkRtl(CharSequence cs, int start, int count) { 208 int result = STATE_UNKNOWN; 209 int openIsolateCount = 0; 210 for (int cp, i = start, end = start + count; 211 i < end && result == STATE_UNKNOWN; 212 i += Character.charCount(cp)) { 213 cp = Character.codePointAt(cs, i); 214 if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates 215 openIsolateCount += 1; 216 } else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI) 217 if (openIsolateCount > 0) openIsolateCount -= 1; 218 } else if (openIsolateCount == 0) { 219 // Only consider the characters outside isolate pairs 220 result = isRtlCodePoint(cp); 221 } 222 } 223 return result; 224 } 225 FirstStrong()226 private FirstStrong() { 227 } 228 229 public static final FirstStrong INSTANCE = new FirstStrong(); 230 } 231 232 /** 233 * Algorithm that uses the presence of any strong directional character of the type indicated 234 * in the constructor parameter to determine the direction of text. 235 * 236 * Characters inside isolate pairs are skipped. 237 */ 238 private static class AnyStrong implements TextDirectionAlgorithm { 239 private final boolean mLookForRtl; 240 241 @Override checkRtl(CharSequence cs, int start, int count)242 public int checkRtl(CharSequence cs, int start, int count) { 243 boolean haveUnlookedFor = false; 244 int openIsolateCount = 0; 245 for (int cp, i = start, end = start + count; i < end; i += Character.charCount(cp)) { 246 cp = Character.codePointAt(cs, i); 247 if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates 248 openIsolateCount += 1; 249 } else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI) 250 if (openIsolateCount > 0) openIsolateCount -= 1; 251 } else if (openIsolateCount == 0) { 252 // Only consider the characters outside isolate pairs 253 switch (isRtlCodePoint(cp)) { 254 case STATE_TRUE: 255 if (mLookForRtl) { 256 return STATE_TRUE; 257 } 258 haveUnlookedFor = true; 259 break; 260 case STATE_FALSE: 261 if (!mLookForRtl) { 262 return STATE_FALSE; 263 } 264 haveUnlookedFor = true; 265 break; 266 default: 267 break; 268 } 269 } 270 } 271 if (haveUnlookedFor) { 272 return mLookForRtl ? STATE_FALSE : STATE_TRUE; 273 } 274 return STATE_UNKNOWN; 275 } 276 AnyStrong(boolean lookForRtl)277 private AnyStrong(boolean lookForRtl) { 278 this.mLookForRtl = lookForRtl; 279 } 280 281 public static final AnyStrong INSTANCE_RTL = new AnyStrong(true); 282 public static final AnyStrong INSTANCE_LTR = new AnyStrong(false); 283 } 284 285 /** 286 * Algorithm that uses the Locale direction to force the direction of a paragraph. 287 */ 288 private static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl { 289 TextDirectionHeuristicLocale()290 public TextDirectionHeuristicLocale() { 291 super(null); 292 } 293 294 @Override defaultIsRtl()295 protected boolean defaultIsRtl() { 296 final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault()); 297 return (dir == View.LAYOUT_DIRECTION_RTL); 298 } 299 300 public static final TextDirectionHeuristicLocale INSTANCE = 301 new TextDirectionHeuristicLocale(); 302 } 303 } 304