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