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