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