blob: 354c15fae66ff02c9a7b389e6cb274287f38175d [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 Meglio3fb824b2012-02-28 17:58:31 -080020import android.view.View;
Fabrice Di Meglio7810b5f2011-08-24 18:26:14 -070021
Fabrice Di Meglio57a85742013-01-31 13:29:36 -080022import java.nio.CharBuffer;
23
Doug Feltcb3791202011-07-07 11:57:48 -070024/**
Scott Maine4426622013-05-22 18:17:44 -070025 * Some objects that implement {@link TextDirectionHeuristic}. Use these with
26 * the {@link BidiFormatter#unicodeWrap unicodeWrap()} methods in {@link BidiFormatter}.
27 * Also notice that these direction heuristics correspond to the same types of constants
28 * provided in the {@link android.view.View} class for {@link android.view.View#setTextDirection
29 * setTextDirection()}, such as {@link android.view.View#TEXT_DIRECTION_RTL}.
30 * <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
31 * you can use the support library's {@link android.support.v4.text.TextDirectionHeuristicsCompat}
32 * class.
Gilles Debunnecefb4bc2012-05-02 18:14:56 -070033 *
Doug Feltcb3791202011-07-07 11:57:48 -070034 */
35public class TextDirectionHeuristics {
36
Fabrice Di Meglio57a85742013-01-31 13:29:36 -080037 /**
38 * Always decides that the direction is left to right.
39 */
Doug Feltcb3791202011-07-07 11:57:48 -070040 public static final TextDirectionHeuristic LTR =
41 new TextDirectionHeuristicInternal(null /* no algorithm */, false);
42
Fabrice Di Meglio57a85742013-01-31 13:29:36 -080043 /**
44 * Always decides that the direction is right to left.
45 */
Doug Feltcb3791202011-07-07 11:57:48 -070046 public static final TextDirectionHeuristic RTL =
47 new TextDirectionHeuristicInternal(null /* no algorithm */, true);
48
49 /**
Fabrice Di Meglio57a85742013-01-31 13:29:36 -080050 * Determines the direction based on the first strong directional character, including bidi
51 * format chars, falling back to left to right if it finds none. This is the default behavior
52 * of the Unicode Bidirectional Algorithm.
Doug Feltcb3791202011-07-07 11:57:48 -070053 */
54 public static final TextDirectionHeuristic FIRSTSTRONG_LTR =
55 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false);
56
57 /**
Fabrice Di Meglio57a85742013-01-31 13:29:36 -080058 * Determines the direction based on the first strong directional character, including bidi
59 * format chars, falling back to right to left if it finds none. This is similar to the default
60 * behavior of the Unicode Bidirectional Algorithm, just with different fallback behavior.
Doug Feltcb3791202011-07-07 11:57:48 -070061 */
62 public static final TextDirectionHeuristic FIRSTSTRONG_RTL =
63 new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true);
64
65 /**
Fabrice Di Meglio57a85742013-01-31 13:29:36 -080066 * If the text contains any strong right to left non-format character, determines that the
67 * direction is right to left, falling back to left to right if it finds none.
Fabrice Di Meglio4b60c302011-08-17 16:56:55 -070068 */
69 public static final TextDirectionHeuristic ANYRTL_LTR =
70 new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false);
71
72 /**
Fabrice Di Meglio7810b5f2011-08-24 18:26:14 -070073 * Force the paragraph direction to the Locale direction. Falls back to left to right.
74 */
75 public static final TextDirectionHeuristic LOCALE = TextDirectionHeuristicLocale.INSTANCE;
76
Fabrice Di Meglio57a85742013-01-31 13:29:36 -080077 /**
78 * State constants for taking care about true / false / unknown
79 */
80 private static final int STATE_TRUE = 0;
81 private static final int STATE_FALSE = 1;
82 private static final int STATE_UNKNOWN = 2;
83
Roozbeh Pournader47360102015-04-09 21:49:29 -070084 /* Returns STATE_TRUE for strong RTL characters, STATE_FALSE for strong LTR characters, and
85 * STATE_UNKNOWN for everything else.
86 */
87 private static int isRtlCodePoint(int codePoint) {
88 switch (Character.getDirectionality(codePoint)) {
Fabrice Di Meglio57a85742013-01-31 13:29:36 -080089 case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
90 return STATE_FALSE;
91 case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
92 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
93 return STATE_TRUE;
Roozbeh Pournader47360102015-04-09 21:49:29 -070094 case Character.DIRECTIONALITY_UNDEFINED:
95 // Unassigned characters still have bidi direction, defined at:
96 // http://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt
Fabrice Di Meglio57a85742013-01-31 13:29:36 -080097
Roozbeh Pournader47360102015-04-09 21:49:29 -070098 if ((0x0590 <= codePoint && codePoint <= 0x08FF) ||
99 (0xFB1D <= codePoint && codePoint <= 0xFDCF) ||
100 (0xFDF0 <= codePoint && codePoint <= 0xFDFF) ||
101 (0xFE70 <= codePoint && codePoint <= 0xFEFF) ||
102 (0x10800 <= codePoint && codePoint <= 0x10FFF) ||
103 (0x1E800 <= codePoint && codePoint <= 0x1EFFF)) {
104 // Unassigned RTL character
105 return STATE_TRUE;
106 } else if (
107 // Potentially-unassigned Default_Ignorable. Ranges are from unassigned
108 // characters that have Unicode property Other_Default_Ignorable_Code_Point
109 // plus some enlargening to cover bidi isolates and simplify checks.
110 (0x2065 <= codePoint && codePoint <= 0x2069) ||
111 (0xFFF0 <= codePoint && codePoint <= 0xFFF8) ||
112 (0xE0000 <= codePoint && codePoint <= 0xE0FFF) ||
113 // Non-character
114 (0xFDD0 <= codePoint && codePoint <= 0xFDEF) ||
115 ((codePoint & 0xFFFE) == 0xFFFE) ||
116 // Currency symbol
117 (0x20A0 <= codePoint && codePoint <= 0x20CF) ||
118 // Unpaired surrogate
119 (0xD800 <= codePoint && codePoint <= 0xDFFF)) {
120 return STATE_UNKNOWN;
121 } else {
122 // Unassigned LTR character
123 return STATE_FALSE;
124 }
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800125 default:
126 return STATE_UNKNOWN;
127 }
Doug Feltcb3791202011-07-07 11:57:48 -0700128 }
129
130 /**
131 * Computes the text direction based on an algorithm. Subclasses implement
132 * {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the
133 * direction from the text alone.
Doug Feltcb3791202011-07-07 11:57:48 -0700134 */
Fabrice Di Meglioe7beae32012-02-13 15:23:57 -0800135 private static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic {
Doug Feltcb3791202011-07-07 11:57:48 -0700136 private final TextDirectionAlgorithm mAlgorithm;
137
138 public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) {
139 mAlgorithm = algorithm;
140 }
141
142 /**
143 * Return true if the default text direction is rtl.
144 */
145 abstract protected boolean defaultIsRtl();
146
147 @Override
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800148 public boolean isRtl(char[] array, int start, int count) {
149 return isRtl(CharBuffer.wrap(array), start, count);
150 }
151
152 @Override
153 public boolean isRtl(CharSequence cs, int start, int count) {
154 if (cs == null || start < 0 || count < 0 || cs.length() - count < start) {
Doug Feltcb3791202011-07-07 11:57:48 -0700155 throw new IllegalArgumentException();
156 }
157 if (mAlgorithm == null) {
158 return defaultIsRtl();
159 }
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800160 return doCheck(cs, start, count);
Doug Feltcb3791202011-07-07 11:57:48 -0700161 }
162
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800163 private boolean doCheck(CharSequence cs, int start, int count) {
164 switch(mAlgorithm.checkRtl(cs, start, count)) {
165 case STATE_TRUE:
Doug Feltcb3791202011-07-07 11:57:48 -0700166 return true;
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800167 case STATE_FALSE:
Doug Feltcb3791202011-07-07 11:57:48 -0700168 return false;
169 default:
170 return defaultIsRtl();
171 }
172 }
173 }
174
175 private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl {
176 private final boolean mDefaultIsRtl;
177
178 private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm,
179 boolean defaultIsRtl) {
180 super(algorithm);
181 mDefaultIsRtl = defaultIsRtl;
182 }
183
184 @Override
185 protected boolean defaultIsRtl() {
186 return mDefaultIsRtl;
187 }
188 }
189
Doug Feltcb3791202011-07-07 11:57:48 -0700190 /**
191 * Interface for an algorithm to guess the direction of a paragraph of text.
Doug Feltcb3791202011-07-07 11:57:48 -0700192 */
Fabrice Di Meglioe7beae32012-02-13 15:23:57 -0800193 private static interface TextDirectionAlgorithm {
Doug Feltcb3791202011-07-07 11:57:48 -0700194 /**
195 * Returns whether the range of text is RTL according to the algorithm.
Doug Feltcb3791202011-07-07 11:57:48 -0700196 */
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800197 int checkRtl(CharSequence cs, int start, int count);
Doug Feltcb3791202011-07-07 11:57:48 -0700198 }
199
200 /**
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800201 * Algorithm that uses the first strong directional character to determine the paragraph
Roozbeh Pournader47360102015-04-09 21:49:29 -0700202 * direction. This is the standard Unicode Bidirectional Algorithm (steps P2 and P3), with the
203 * exception that if no strong character is found, UNKNOWN is returned.
Doug Feltcb3791202011-07-07 11:57:48 -0700204 */
Fabrice Di Meglioe7beae32012-02-13 15:23:57 -0800205 private static class FirstStrong implements TextDirectionAlgorithm {
Doug Feltcb3791202011-07-07 11:57:48 -0700206 @Override
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800207 public int checkRtl(CharSequence cs, int start, int count) {
208 int result = STATE_UNKNOWN;
Roozbeh Pournader47360102015-04-09 21:49:29 -0700209 int openIsolateCount = 0;
210 for (int cp, i = start, end = start + count;
211 i < end && result == STATE_UNKNOWN;
212 i += Character.charCount(cp)) {
213 cp = Character.codePointAt(cs, i);
214 if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates
215 openIsolateCount += 1;
216 } else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI)
217 if (openIsolateCount > 0) openIsolateCount -= 1;
218 } else if (openIsolateCount == 0) {
219 // Only consider the characters outside isolate pairs
220 result = isRtlCodePoint(cp);
221 }
Doug Feltcb3791202011-07-07 11:57:48 -0700222 }
223 return result;
224 }
225
226 private FirstStrong() {
227 }
228
229 public static final FirstStrong INSTANCE = new FirstStrong();
230 }
231
232 /**
Roozbeh Pournader47360102015-04-09 21:49:29 -0700233 * Algorithm that uses the presence of any strong directional character of the type indicated
234 * in the constructor parameter to determine the direction of text.
235 *
236 * Characters inside isolate pairs are skipped.
Doug Feltcb3791202011-07-07 11:57:48 -0700237 */
Fabrice Di Meglioe7beae32012-02-13 15:23:57 -0800238 private static class AnyStrong implements TextDirectionAlgorithm {
Doug Feltcb3791202011-07-07 11:57:48 -0700239 private final boolean mLookForRtl;
240
241 @Override
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800242 public int checkRtl(CharSequence cs, int start, int count) {
Doug Feltcb3791202011-07-07 11:57:48 -0700243 boolean haveUnlookedFor = false;
Roozbeh Pournader47360102015-04-09 21:49:29 -0700244 int openIsolateCount = 0;
245 for (int cp, i = start, end = start + count; i < end; i += Character.charCount(cp)) {
246 cp = Character.codePointAt(cs, i);
247 if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates
248 openIsolateCount += 1;
249 } else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI)
250 if (openIsolateCount > 0) openIsolateCount -= 1;
251 } else if (openIsolateCount == 0) {
252 // Only consider the characters outside isolate pairs
253 switch (isRtlCodePoint(cp)) {
254 case STATE_TRUE:
255 if (mLookForRtl) {
256 return STATE_TRUE;
257 }
258 haveUnlookedFor = true;
259 break;
260 case STATE_FALSE:
261 if (!mLookForRtl) {
262 return STATE_FALSE;
263 }
264 haveUnlookedFor = true;
265 break;
266 default:
267 break;
268 }
Doug Feltcb3791202011-07-07 11:57:48 -0700269 }
270 }
271 if (haveUnlookedFor) {
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800272 return mLookForRtl ? STATE_FALSE : STATE_TRUE;
Doug Feltcb3791202011-07-07 11:57:48 -0700273 }
Fabrice Di Meglio57a85742013-01-31 13:29:36 -0800274 return STATE_UNKNOWN;
Doug Feltcb3791202011-07-07 11:57:48 -0700275 }
276
277 private AnyStrong(boolean lookForRtl) {
278 this.mLookForRtl = lookForRtl;
279 }
280
281 public static final AnyStrong INSTANCE_RTL = new AnyStrong(true);
282 public static final AnyStrong INSTANCE_LTR = new AnyStrong(false);
283 }
284
285 /**
Fabrice Di Meglio7810b5f2011-08-24 18:26:14 -0700286 * Algorithm that uses the Locale direction to force the direction of a paragraph.
287 */
Fabrice Di Meglioe7beae32012-02-13 15:23:57 -0800288 private static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl {
Fabrice Di Meglio7810b5f2011-08-24 18:26:14 -0700289
290 public TextDirectionHeuristicLocale() {
291 super(null);
292 }
293
294 @Override
295 protected boolean defaultIsRtl() {
Fabrice Di Megliod3d9f3f2012-09-18 12:55:32 -0700296 final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault());
Fabrice Di Meglio3fb824b2012-02-28 17:58:31 -0800297 return (dir == View.LAYOUT_DIRECTION_RTL);
Fabrice Di Meglio7810b5f2011-08-24 18:26:14 -0700298 }
299
300 public static final TextDirectionHeuristicLocale INSTANCE =
301 new TextDirectionHeuristicLocale();
302 }
Doug Feltcb3791202011-07-07 11:57:48 -0700303}