blob: 5f9ffc5bf2f014bb7a66a14ed40cfee3c1169f0d [file] [log] [blame]
Doug Feltcb3791202011-07-07 11:57:48 -07001// Copyright 2011 Google Inc. All Rights Reserved.
2
3package android.text;
4
5
6/**
7 * Some objects that implement TextDirectionHeuristic.
8 * @hide
9 */
10public class TextDirectionHeuristics {
11
12 /** Always decides that the direction is left to right. */
13 public static final TextDirectionHeuristic LTR =
14 new TextDirectionHeuristicInternal(null /* no algorithm */, false);
15
16 /** Always decides that the direction is right to left. */
17 public static final TextDirectionHeuristic RTL =
18 new TextDirectionHeuristicInternal(null /* no algorithm */, true);
19
20 /**
21 * Determines the direction based on the first strong directional character,
22 * including bidi format chars, falling back to left to right if it
23 * finds none. This is the default behavior of the Unicode Bidirectional
24 * Algorithm.
25 */
26 public static final TextDirectionHeuristic FIRSTSTRONG_LTR =
27 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false);
28
29 /**
30 * Determines the direction based on the first strong directional character,
31 * including bidi format chars, falling back to right to left if it
32 * finds none. This is similar to the default behavior of the Unicode
33 * Bidirectional Algorithm, just with different fallback behavior.
34 */
35 public static final TextDirectionHeuristic FIRSTSTRONG_RTL =
36 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true);
37
38 /**
39 * If the text contains any strong right to left non-format character, determines
40 * that the direction is right to left, falling back to left to right if it
41 * finds none.
42 */
43 public static final TextDirectionHeuristic ANYRTL_LTR =
44 new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false);
45
46 /**
47 * If the text contains any strong left to right non-format character, determines
48 * that the direction is left to right, falling back to right to left if it
49 * finds none.
50 */
51 public static final TextDirectionHeuristic ANYLTR_RTL =
52 new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_LTR, true);
53
54 /**
55 * Examines only the strong directional non-format characters, and if either
56 * left to right or right to left characters are 60% or more of this total,
57 * determines that the direction follows the majority of characters. Falls
58 * back to left to right if neither direction meets this threshold.
59 */
60 public static final TextDirectionHeuristic CHARCOUNT_LTR =
61 new TextDirectionHeuristicInternal(CharCount.INSTANCE_DEFAULT, false);
62
63 /**
64 * Examines only the strong directional non-format characters, and if either
65 * left to right or right to left characters are 60% or more of this total,
66 * determines that the direction follows the majority of characters. Falls
67 * back to right to left if neither direction meets this threshold.
68 */
69 public static final TextDirectionHeuristic CHARCOUNT_RTL =
70 new TextDirectionHeuristicInternal(CharCount.INSTANCE_DEFAULT, true);
71
72 private static enum TriState {
73 TRUE, FALSE, UNKNOWN;
74 }
75
76 /**
77 * Computes the text direction based on an algorithm. Subclasses implement
78 * {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the
79 * direction from the text alone.
80 * @hide
81 */
82 public static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic {
83 private final TextDirectionAlgorithm mAlgorithm;
84
85 public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) {
86 mAlgorithm = algorithm;
87 }
88
89 /**
90 * Return true if the default text direction is rtl.
91 */
92 abstract protected boolean defaultIsRtl();
93
94 @Override
95 public boolean isRtl(CharSequence text, int start, int end) {
96 if (text == null || start < 0 || end < start || text.length() < end) {
97 throw new IllegalArgumentException();
98 }
99 if (mAlgorithm == null) {
100 return defaultIsRtl();
101 }
102 text = text.subSequence(start, end);
103 char[] chars = text.toString().toCharArray();
104 return doCheck(chars, 0, chars.length);
105 }
106
107 @Override
108 public boolean isRtl(char[] chars, int start, int count) {
109 if (chars == null || start < 0 || count < 0 || chars.length - count < start) {
110 throw new IllegalArgumentException();
111 }
112 if (mAlgorithm == null) {
113 return defaultIsRtl();
114 }
115 return doCheck(chars, start, count);
116 }
117
118 private boolean doCheck(char[] chars, int start, int count) {
119 switch(mAlgorithm.checkRtl(chars, start, count)) {
120 case TRUE:
121 return true;
122 case FALSE:
123 return false;
124 default:
125 return defaultIsRtl();
126 }
127 }
128 }
129
130 private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl {
131 private final boolean mDefaultIsRtl;
132
133 private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm,
134 boolean defaultIsRtl) {
135 super(algorithm);
136 mDefaultIsRtl = defaultIsRtl;
137 }
138
139 @Override
140 protected boolean defaultIsRtl() {
141 return mDefaultIsRtl;
142 }
143 }
144
145 private static TriState isRtlText(int directionality) {
146 switch (directionality) {
147 case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
148 return TriState.FALSE;
149 case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
150 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
151 return TriState.TRUE;
152 default:
153 return TriState.UNKNOWN;
154 }
155 }
156
157 private static TriState isRtlTextOrFormat(int directionality) {
158 switch (directionality) {
159 case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
160 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
161 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
162 return TriState.FALSE;
163 case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
164 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
165 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
166 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
167 return TriState.TRUE;
168 default:
169 return TriState.UNKNOWN;
170 }
171 }
172
173 /**
174 * Interface for an algorithm to guess the direction of a paragraph of text.
175 *
176 * @hide
177 */
178 public static interface TextDirectionAlgorithm {
179 /**
180 * Returns whether the range of text is RTL according to the algorithm.
181 *
182 * @hide
183 */
184 TriState checkRtl(char[] text, int start, int count);
185 }
186
187 /**
188 * Algorithm that uses the first strong directional character to determine
189 * the paragraph direction. This is the standard Unicode Bidirectional
190 * algorithm.
191 *
192 * @hide
193 */
194 public static class FirstStrong implements TextDirectionAlgorithm {
195 @Override
196 public TriState checkRtl(char[] text, int start, int count) {
197 TriState result = TriState.UNKNOWN;
198 for (int i = start, e = start + count; i < e && result == TriState.UNKNOWN; ++i) {
199 result = isRtlTextOrFormat(Character.getDirectionality(text[i]));
200 }
201 return result;
202 }
203
204 private FirstStrong() {
205 }
206
207 public static final FirstStrong INSTANCE = new FirstStrong();
208 }
209
210 /**
211 * Algorithm that uses the presence of any strong directional non-format
212 * character (e.g. excludes LRE, LRO, RLE, RLO) to determine the
213 * direction of text.
214 *
215 * @hide
216 */
217 public static class AnyStrong implements TextDirectionAlgorithm {
218 private final boolean mLookForRtl;
219
220 @Override
221 public TriState checkRtl(char[] text, int start, int count) {
222 boolean haveUnlookedFor = false;
223 for (int i = start, e = start + count; i < e; ++i) {
224 switch (isRtlText(Character.getDirectionality(text[i]))) {
225 case TRUE:
226 if (mLookForRtl) {
227 return TriState.TRUE;
228 }
229 haveUnlookedFor = true;
230 break;
231 case FALSE:
232 if (!mLookForRtl) {
233 return TriState.FALSE;
234 }
235 haveUnlookedFor = true;
236 break;
237 default:
238 break;
239 }
240 }
241 if (haveUnlookedFor) {
242 return mLookForRtl ? TriState.FALSE : TriState.TRUE;
243 }
244 return TriState.UNKNOWN;
245 }
246
247 private AnyStrong(boolean lookForRtl) {
248 this.mLookForRtl = lookForRtl;
249 }
250
251 public static final AnyStrong INSTANCE_RTL = new AnyStrong(true);
252 public static final AnyStrong INSTANCE_LTR = new AnyStrong(false);
253 }
254
255 /**
256 * Algorithm that uses the relative proportion of strong directional
257 * characters (excluding LRE, LRO, RLE, RLO) to determine the direction
258 * of the paragraph, if the proportion exceeds a given threshold.
259 *
260 * @hide
261 */
262 public static class CharCount implements TextDirectionAlgorithm {
263 private final float mThreshold;
264
265 @Override
266 public TriState checkRtl(char[] text, int start, int count) {
267 int countLtr = 0;
268 int countRtl = 0;
269 for(int i = start, e = start + count; i < e; ++i) {
270 switch (isRtlText(Character.getDirectionality(text[i]))) {
271 case TRUE:
272 ++countLtr;
273 break;
274 case FALSE:
275 ++countRtl;
276 break;
277 default:
278 break;
279 }
280 }
281 int limit = (int)((countLtr + countRtl) * mThreshold);
282 if (limit > 0) {
283 if (countLtr > limit) {
284 return TriState.FALSE;
285 }
286 if (countRtl > limit) {
287 return TriState.TRUE;
288 }
289 }
290 return TriState.UNKNOWN;
291 }
292
293 private CharCount(float threshold) {
294 mThreshold = threshold;
295 }
296
297 public static CharCount withThreshold(float threshold) {
298 if (threshold < 0 || threshold > 1) {
299 throw new IllegalArgumentException();
300 }
301 if (threshold == DEFAULT_THRESHOLD) {
302 return INSTANCE_DEFAULT;
303 }
304 return new CharCount(threshold);
305 }
306
307 public static final float DEFAULT_THRESHOLD = 0.6f;
308 public static final CharCount INSTANCE_DEFAULT = new CharCount(DEFAULT_THRESHOLD);
309 }
310}