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 TextDirectionHeuristic. 26 * 27 */ 28 public class TextDirectionHeuristics { 29 30 /** 31 * Always decides that the direction is left to right. 32 */ 33 public static final TextDirectionHeuristic LTR = 34 new TextDirectionHeuristicInternal(null /* no algorithm */, false); 35 36 /** 37 * Always decides that the direction is right to left. 38 */ 39 public static final TextDirectionHeuristic RTL = 40 new TextDirectionHeuristicInternal(null /* no algorithm */, true); 41 42 /** 43 * Determines the direction based on the first strong directional character, including bidi 44 * format chars, falling back to left to right if it finds none. This is the default behavior 45 * of the Unicode Bidirectional Algorithm. 46 */ 47 public static final TextDirectionHeuristic FIRSTSTRONG_LTR = 48 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false); 49 50 /** 51 * Determines the direction based on the first strong directional character, including bidi 52 * format chars, falling back to right to left if it finds none. This is similar to the default 53 * behavior of the Unicode Bidirectional Algorithm, just with different fallback behavior. 54 */ 55 public static final TextDirectionHeuristic FIRSTSTRONG_RTL = 56 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true); 57 58 /** 59 * If the text contains any strong right to left non-format character, determines that the 60 * direction is right to left, falling back to left to right if it finds none. 61 */ 62 public static final TextDirectionHeuristic ANYRTL_LTR = 63 new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false); 64 65 /** 66 * Force the paragraph direction to the Locale direction. Falls back to left to right. 67 */ 68 public static final TextDirectionHeuristic LOCALE = TextDirectionHeuristicLocale.INSTANCE; 69 70 /** 71 * State constants for taking care about true / false / unknown 72 */ 73 private static final int STATE_TRUE = 0; 74 private static final int STATE_FALSE = 1; 75 private static final int STATE_UNKNOWN = 2; 76 isRtlText(int directionality)77 private static int isRtlText(int directionality) { 78 switch (directionality) { 79 case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 80 return STATE_FALSE; 81 case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 82 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 83 return STATE_TRUE; 84 default: 85 return STATE_UNKNOWN; 86 } 87 } 88 isRtlTextOrFormat(int directionality)89 private static int isRtlTextOrFormat(int directionality) { 90 switch (directionality) { 91 case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 92 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: 93 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: 94 return STATE_FALSE; 95 case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 96 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 97 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: 98 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: 99 return STATE_TRUE; 100 default: 101 return STATE_UNKNOWN; 102 } 103 } 104 105 /** 106 * Computes the text direction based on an algorithm. Subclasses implement 107 * {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the 108 * direction from the text alone. 109 */ 110 private static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic { 111 private final TextDirectionAlgorithm mAlgorithm; 112 TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm)113 public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) { 114 mAlgorithm = algorithm; 115 } 116 117 /** 118 * Return true if the default text direction is rtl. 119 */ defaultIsRtl()120 abstract protected boolean defaultIsRtl(); 121 122 @Override isRtl(char[] array, int start, int count)123 public boolean isRtl(char[] array, int start, int count) { 124 return isRtl(CharBuffer.wrap(array), start, count); 125 } 126 127 @Override isRtl(CharSequence cs, int start, int count)128 public boolean isRtl(CharSequence cs, int start, int count) { 129 if (cs == null || start < 0 || count < 0 || cs.length() - count < start) { 130 throw new IllegalArgumentException(); 131 } 132 if (mAlgorithm == null) { 133 return defaultIsRtl(); 134 } 135 return doCheck(cs, start, count); 136 } 137 doCheck(CharSequence cs, int start, int count)138 private boolean doCheck(CharSequence cs, int start, int count) { 139 switch(mAlgorithm.checkRtl(cs, start, count)) { 140 case STATE_TRUE: 141 return true; 142 case STATE_FALSE: 143 return false; 144 default: 145 return defaultIsRtl(); 146 } 147 } 148 } 149 150 private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl { 151 private final boolean mDefaultIsRtl; 152 TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, boolean defaultIsRtl)153 private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, 154 boolean defaultIsRtl) { 155 super(algorithm); 156 mDefaultIsRtl = defaultIsRtl; 157 } 158 159 @Override defaultIsRtl()160 protected boolean defaultIsRtl() { 161 return mDefaultIsRtl; 162 } 163 } 164 165 /** 166 * Interface for an algorithm to guess the direction of a paragraph of text. 167 */ 168 private static interface TextDirectionAlgorithm { 169 /** 170 * Returns whether the range of text is RTL according to the algorithm. 171 */ checkRtl(CharSequence cs, int start, int count)172 int checkRtl(CharSequence cs, int start, int count); 173 } 174 175 /** 176 * Algorithm that uses the first strong directional character to determine the paragraph 177 * direction. This is the standard Unicode Bidirectional algorithm. 178 */ 179 private static class FirstStrong implements TextDirectionAlgorithm { 180 @Override checkRtl(CharSequence cs, int start, int count)181 public int checkRtl(CharSequence cs, int start, int count) { 182 int result = STATE_UNKNOWN; 183 for (int i = start, e = start + count; i < e && result == STATE_UNKNOWN; ++i) { 184 result = isRtlTextOrFormat(Character.getDirectionality(cs.charAt(i))); 185 } 186 return result; 187 } 188 FirstStrong()189 private FirstStrong() { 190 } 191 192 public static final FirstStrong INSTANCE = new FirstStrong(); 193 } 194 195 /** 196 * Algorithm that uses the presence of any strong directional non-format 197 * character (e.g. excludes LRE, LRO, RLE, RLO) to determine the 198 * direction of text. 199 */ 200 private static class AnyStrong implements TextDirectionAlgorithm { 201 private final boolean mLookForRtl; 202 203 @Override checkRtl(CharSequence cs, int start, int count)204 public int checkRtl(CharSequence cs, int start, int count) { 205 boolean haveUnlookedFor = false; 206 for (int i = start, e = start + count; i < e; ++i) { 207 switch (isRtlText(Character.getDirectionality(cs.charAt(i)))) { 208 case STATE_TRUE: 209 if (mLookForRtl) { 210 return STATE_TRUE; 211 } 212 haveUnlookedFor = true; 213 break; 214 case STATE_FALSE: 215 if (!mLookForRtl) { 216 return STATE_FALSE; 217 } 218 haveUnlookedFor = true; 219 break; 220 default: 221 break; 222 } 223 } 224 if (haveUnlookedFor) { 225 return mLookForRtl ? STATE_FALSE : STATE_TRUE; 226 } 227 return STATE_UNKNOWN; 228 } 229 AnyStrong(boolean lookForRtl)230 private AnyStrong(boolean lookForRtl) { 231 this.mLookForRtl = lookForRtl; 232 } 233 234 public static final AnyStrong INSTANCE_RTL = new AnyStrong(true); 235 public static final AnyStrong INSTANCE_LTR = new AnyStrong(false); 236 } 237 238 /** 239 * Algorithm that uses the Locale direction to force the direction of a paragraph. 240 */ 241 private static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl { 242 TextDirectionHeuristicLocale()243 public TextDirectionHeuristicLocale() { 244 super(null); 245 } 246 247 @Override defaultIsRtl()248 protected boolean defaultIsRtl() { 249 final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault()); 250 return (dir == View.LAYOUT_DIRECTION_RTL); 251 } 252 253 public static final TextDirectionHeuristicLocale INSTANCE = 254 new TextDirectionHeuristicLocale(); 255 } 256 } 257