• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 
17 package android.text;
18 
19 
20 import android.view.View;
21 
22 import java.nio.CharBuffer;
23 
24 /**
25  * 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 androidx.core.text.TextDirectionHeuristicsCompat}
32  * class.
33  *
34  */
35 @android.ravenwood.annotation.RavenwoodKeepWholeClass
36 public class TextDirectionHeuristics {
37 
38     /**
39      * Always decides that the direction is left to right.
40      */
41     public static final TextDirectionHeuristic LTR =
42         new TextDirectionHeuristicInternal(null /* no algorithm */, false);
43 
44     /**
45      * Always decides that the direction is right to left.
46      */
47     public static final TextDirectionHeuristic RTL =
48         new TextDirectionHeuristicInternal(null /* no algorithm */, true);
49 
50     /**
51      * Determines the direction based on the first strong directional character, including bidi
52      * format chars, falling back to left to right if it finds none. This is the default behavior
53      * of the Unicode Bidirectional Algorithm.
54      */
55     public static final TextDirectionHeuristic FIRSTSTRONG_LTR =
56         new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false);
57 
58     /**
59      * Determines the direction based on the first strong directional character, including bidi
60      * format chars, falling back to right to left if it finds none. This is similar to the default
61      * behavior of the Unicode Bidirectional Algorithm, just with different fallback behavior.
62      */
63     public static final TextDirectionHeuristic FIRSTSTRONG_RTL =
64         new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true);
65 
66     /**
67      * If the text contains any strong right to left non-format character, determines that the
68      * direction is right to left, falling back to left to right if it finds none.
69      */
70     public static final TextDirectionHeuristic ANYRTL_LTR =
71         new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false);
72 
73     /**
74      * Force the paragraph direction to the Locale direction. Falls back to left to right.
75      */
76     public static final TextDirectionHeuristic LOCALE = TextDirectionHeuristicLocale.INSTANCE;
77 
78     /**
79      * State constants for taking care about true / false / unknown
80      */
81     private static final int STATE_TRUE = 0;
82     private static final int STATE_FALSE = 1;
83     private static final int STATE_UNKNOWN = 2;
84 
85     /* Returns STATE_TRUE for strong RTL characters, STATE_FALSE for strong LTR characters, and
86      * STATE_UNKNOWN for everything else.
87      */
isRtlCodePoint(int codePoint)88     private static int isRtlCodePoint(int codePoint) {
89         switch (Character.getDirectionality(codePoint)) {
90             case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
91                 return STATE_FALSE;
92             case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
93             case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
94                 return STATE_TRUE;
95             case Character.DIRECTIONALITY_UNDEFINED:
96                 // Unassigned characters still have bidi direction, defined at:
97                 // http://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt
98 
99                 if ((0x0590 <= codePoint && codePoint <= 0x08FF) ||
100                         (0xFB1D <= codePoint && codePoint <= 0xFDCF) ||
101                         (0xFDF0 <= codePoint && codePoint <= 0xFDFF) ||
102                         (0xFE70 <= codePoint && codePoint <= 0xFEFF) ||
103                         (0x10800 <= codePoint && codePoint <= 0x10FFF) ||
104                         (0x1E800 <= codePoint && codePoint <= 0x1EFFF)) {
105                     // Unassigned RTL character
106                     return STATE_TRUE;
107                 } else if (
108                         // Potentially-unassigned Default_Ignorable. Ranges are from unassigned
109                         // characters that have Unicode property Other_Default_Ignorable_Code_Point
110                         // plus some enlargening to cover bidi isolates and simplify checks.
111                         (0x2065 <= codePoint && codePoint <= 0x2069) ||
112                         (0xFFF0 <= codePoint && codePoint <= 0xFFF8) ||
113                         (0xE0000 <= codePoint && codePoint <= 0xE0FFF) ||
114                         // Non-character
115                         (0xFDD0 <= codePoint && codePoint <= 0xFDEF) ||
116                         ((codePoint & 0xFFFE) == 0xFFFE) ||
117                         // Currency symbol
118                         (0x20A0 <= codePoint && codePoint <= 0x20CF) ||
119                         // Unpaired surrogate
120                         (0xD800 <= codePoint && codePoint <= 0xDFFF)) {
121                     return STATE_UNKNOWN;
122                 } else {
123                     // Unassigned LTR character
124                     return STATE_FALSE;
125                 }
126             default:
127                 return STATE_UNKNOWN;
128         }
129     }
130 
131     /**
132      * Computes the text direction based on an algorithm.  Subclasses implement
133      * {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the
134      * direction from the text alone.
135      */
136     private static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic {
137         private final TextDirectionAlgorithm mAlgorithm;
138 
TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm)139         public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) {
140             mAlgorithm = algorithm;
141         }
142 
143         /**
144          * Return true if the default text direction is rtl.
145          */
defaultIsRtl()146         abstract protected boolean defaultIsRtl();
147 
148         @Override
isRtl(char[] array, int start, int count)149         public boolean isRtl(char[] array, int start, int count) {
150             return isRtl(CharBuffer.wrap(array), start, count);
151         }
152 
153         @Override
isRtl(CharSequence cs, int start, int count)154         public boolean isRtl(CharSequence cs, int start, int count) {
155             if (cs == null || start < 0 || count < 0 || cs.length() - count < start) {
156                 throw new IllegalArgumentException();
157             }
158             if (mAlgorithm == null) {
159                 return defaultIsRtl();
160             }
161             return doCheck(cs, start, count);
162         }
163 
doCheck(CharSequence cs, int start, int count)164         private boolean doCheck(CharSequence cs, int start, int count) {
165             switch(mAlgorithm.checkRtl(cs, start, count)) {
166                 case STATE_TRUE:
167                     return true;
168                 case STATE_FALSE:
169                     return false;
170                 default:
171                     return defaultIsRtl();
172             }
173         }
174     }
175 
176     private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl {
177         private final boolean mDefaultIsRtl;
178 
TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, boolean defaultIsRtl)179         private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm,
180                 boolean defaultIsRtl) {
181             super(algorithm);
182             mDefaultIsRtl = defaultIsRtl;
183         }
184 
185         @Override
defaultIsRtl()186         protected boolean defaultIsRtl() {
187             return mDefaultIsRtl;
188         }
189     }
190 
191     /**
192      * Interface for an algorithm to guess the direction of a paragraph of text.
193      */
194     private static interface TextDirectionAlgorithm {
195         /**
196          * Returns whether the range of text is RTL according to the algorithm.
197          */
checkRtl(CharSequence cs, int start, int count)198         int checkRtl(CharSequence cs, int start, int count);
199     }
200 
201     /**
202      * Algorithm that uses the first strong directional character to determine the paragraph
203      * direction. This is the standard Unicode Bidirectional Algorithm (steps P2 and P3), with the
204      * exception that if no strong character is found, UNKNOWN is returned.
205      */
206     private static class FirstStrong implements TextDirectionAlgorithm {
207         @Override
checkRtl(CharSequence cs, int start, int count)208         public int checkRtl(CharSequence cs, int start, int count) {
209             int result = STATE_UNKNOWN;
210             int openIsolateCount = 0;
211             for (int cp, i = start, end = start + count;
212                     i < end && result == STATE_UNKNOWN;
213                     i += Character.charCount(cp)) {
214                 cp = Character.codePointAt(cs, i);
215                 if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates
216                     openIsolateCount += 1;
217                 } else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI)
218                     if (openIsolateCount > 0) openIsolateCount -= 1;
219                 } else if (openIsolateCount == 0) {
220                     // Only consider the characters outside isolate pairs
221                     result = isRtlCodePoint(cp);
222                 }
223             }
224             return result;
225         }
226 
FirstStrong()227         private FirstStrong() {
228         }
229 
230         public static final FirstStrong INSTANCE = new FirstStrong();
231     }
232 
233     /**
234      * Algorithm that uses the presence of any strong directional character of the type indicated
235      * in the constructor parameter to determine the direction of text.
236      *
237      * Characters inside isolate pairs are skipped.
238      */
239     private static class AnyStrong implements TextDirectionAlgorithm {
240         private final boolean mLookForRtl;
241 
242         @Override
checkRtl(CharSequence cs, int start, int count)243         public int checkRtl(CharSequence cs, int start, int count) {
244             boolean haveUnlookedFor = false;
245             int openIsolateCount = 0;
246             for (int cp, i = start, end = start + count; i < end; i += Character.charCount(cp)) {
247                 cp = Character.codePointAt(cs, i);
248                 if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates
249                     openIsolateCount += 1;
250                 } else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI)
251                     if (openIsolateCount > 0) openIsolateCount -= 1;
252                 } else if (openIsolateCount == 0) {
253                     // Only consider the characters outside isolate pairs
254                     switch (isRtlCodePoint(cp)) {
255                         case STATE_TRUE:
256                             if (mLookForRtl) {
257                                 return STATE_TRUE;
258                             }
259                             haveUnlookedFor = true;
260                             break;
261                         case STATE_FALSE:
262                             if (!mLookForRtl) {
263                                 return STATE_FALSE;
264                             }
265                             haveUnlookedFor = true;
266                             break;
267                         default:
268                             break;
269                     }
270                 }
271             }
272             if (haveUnlookedFor) {
273                 return mLookForRtl ? STATE_FALSE : STATE_TRUE;
274             }
275             return STATE_UNKNOWN;
276         }
277 
AnyStrong(boolean lookForRtl)278         private AnyStrong(boolean lookForRtl) {
279             this.mLookForRtl = lookForRtl;
280         }
281 
282         public static final AnyStrong INSTANCE_RTL = new AnyStrong(true);
283         public static final AnyStrong INSTANCE_LTR = new AnyStrong(false);
284     }
285 
286     /**
287      * Algorithm that uses the Locale direction to force the direction of a paragraph.
288      */
289     private static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl {
290 
TextDirectionHeuristicLocale()291         public TextDirectionHeuristicLocale() {
292             super(null);
293         }
294 
295         @Override
defaultIsRtl()296         protected boolean defaultIsRtl() {
297             final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault());
298             return (dir == View.LAYOUT_DIRECTION_RTL);
299         }
300 
301         public static final TextDirectionHeuristicLocale INSTANCE =
302                 new TextDirectionHeuristicLocale();
303     }
304 }
305