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 isRtlText(int directionality)84 private static int isRtlText(int directionality) { 85 switch (directionality) { 86 case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 87 return STATE_FALSE; 88 case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 89 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 90 return STATE_TRUE; 91 default: 92 return STATE_UNKNOWN; 93 } 94 } 95 isRtlTextOrFormat(int directionality)96 private static int isRtlTextOrFormat(int directionality) { 97 switch (directionality) { 98 case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 99 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: 100 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: 101 return STATE_FALSE; 102 case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 103 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 104 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: 105 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: 106 return STATE_TRUE; 107 default: 108 return STATE_UNKNOWN; 109 } 110 } 111 112 /** 113 * Computes the text direction based on an algorithm. Subclasses implement 114 * {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the 115 * direction from the text alone. 116 */ 117 private static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic { 118 private final TextDirectionAlgorithm mAlgorithm; 119 TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm)120 public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) { 121 mAlgorithm = algorithm; 122 } 123 124 /** 125 * Return true if the default text direction is rtl. 126 */ defaultIsRtl()127 abstract protected boolean defaultIsRtl(); 128 129 @Override isRtl(char[] array, int start, int count)130 public boolean isRtl(char[] array, int start, int count) { 131 return isRtl(CharBuffer.wrap(array), start, count); 132 } 133 134 @Override isRtl(CharSequence cs, int start, int count)135 public boolean isRtl(CharSequence cs, int start, int count) { 136 if (cs == null || start < 0 || count < 0 || cs.length() - count < start) { 137 throw new IllegalArgumentException(); 138 } 139 if (mAlgorithm == null) { 140 return defaultIsRtl(); 141 } 142 return doCheck(cs, start, count); 143 } 144 doCheck(CharSequence cs, int start, int count)145 private boolean doCheck(CharSequence cs, int start, int count) { 146 switch(mAlgorithm.checkRtl(cs, start, count)) { 147 case STATE_TRUE: 148 return true; 149 case STATE_FALSE: 150 return false; 151 default: 152 return defaultIsRtl(); 153 } 154 } 155 } 156 157 private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl { 158 private final boolean mDefaultIsRtl; 159 TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, boolean defaultIsRtl)160 private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, 161 boolean defaultIsRtl) { 162 super(algorithm); 163 mDefaultIsRtl = defaultIsRtl; 164 } 165 166 @Override defaultIsRtl()167 protected boolean defaultIsRtl() { 168 return mDefaultIsRtl; 169 } 170 } 171 172 /** 173 * Interface for an algorithm to guess the direction of a paragraph of text. 174 */ 175 private static interface TextDirectionAlgorithm { 176 /** 177 * Returns whether the range of text is RTL according to the algorithm. 178 */ checkRtl(CharSequence cs, int start, int count)179 int checkRtl(CharSequence cs, int start, int count); 180 } 181 182 /** 183 * Algorithm that uses the first strong directional character to determine the paragraph 184 * direction. This is the standard Unicode Bidirectional algorithm. 185 */ 186 private static class FirstStrong implements TextDirectionAlgorithm { 187 @Override checkRtl(CharSequence cs, int start, int count)188 public int checkRtl(CharSequence cs, int start, int count) { 189 int result = STATE_UNKNOWN; 190 for (int i = start, e = start + count; i < e && result == STATE_UNKNOWN; ++i) { 191 result = isRtlTextOrFormat(Character.getDirectionality(cs.charAt(i))); 192 } 193 return result; 194 } 195 FirstStrong()196 private FirstStrong() { 197 } 198 199 public static final FirstStrong INSTANCE = new FirstStrong(); 200 } 201 202 /** 203 * Algorithm that uses the presence of any strong directional non-format 204 * character (e.g. excludes LRE, LRO, RLE, RLO) to determine the 205 * direction of text. 206 */ 207 private static class AnyStrong implements TextDirectionAlgorithm { 208 private final boolean mLookForRtl; 209 210 @Override checkRtl(CharSequence cs, int start, int count)211 public int checkRtl(CharSequence cs, int start, int count) { 212 boolean haveUnlookedFor = false; 213 for (int i = start, e = start + count; i < e; ++i) { 214 switch (isRtlText(Character.getDirectionality(cs.charAt(i)))) { 215 case STATE_TRUE: 216 if (mLookForRtl) { 217 return STATE_TRUE; 218 } 219 haveUnlookedFor = true; 220 break; 221 case STATE_FALSE: 222 if (!mLookForRtl) { 223 return STATE_FALSE; 224 } 225 haveUnlookedFor = true; 226 break; 227 default: 228 break; 229 } 230 } 231 if (haveUnlookedFor) { 232 return mLookForRtl ? STATE_FALSE : STATE_TRUE; 233 } 234 return STATE_UNKNOWN; 235 } 236 AnyStrong(boolean lookForRtl)237 private AnyStrong(boolean lookForRtl) { 238 this.mLookForRtl = lookForRtl; 239 } 240 241 public static final AnyStrong INSTANCE_RTL = new AnyStrong(true); 242 public static final AnyStrong INSTANCE_LTR = new AnyStrong(false); 243 } 244 245 /** 246 * Algorithm that uses the Locale direction to force the direction of a paragraph. 247 */ 248 private static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl { 249 TextDirectionHeuristicLocale()250 public TextDirectionHeuristicLocale() { 251 super(null); 252 } 253 254 @Override defaultIsRtl()255 protected boolean defaultIsRtl() { 256 final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault()); 257 return (dir == View.LAYOUT_DIRECTION_RTL); 258 } 259 260 public static final TextDirectionHeuristicLocale INSTANCE = 261 new TextDirectionHeuristicLocale(); 262 } 263 } 264