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