• 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 android.support.v4.text.TextDirectionHeuristicsCompat}
32  * class.
33  *
34  */
35 public class TextDirectionHeuristics {
36 
37     /**
38      * Always decides that the direction is left to right.
39      */
40     public static final TextDirectionHeuristic LTR =
41         new TextDirectionHeuristicInternal(null /* no algorithm */, false);
42 
43     /**
44      * Always decides that the direction is right to left.
45      */
46     public static final TextDirectionHeuristic RTL =
47         new TextDirectionHeuristicInternal(null /* no algorithm */, true);
48 
49     /**
50      * 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.
53      */
54     public static final TextDirectionHeuristic FIRSTSTRONG_LTR =
55         new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false);
56 
57     /**
58      * 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.
61      */
62     public static final TextDirectionHeuristic FIRSTSTRONG_RTL =
63         new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true);
64 
65     /**
66      * 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.
68      */
69     public static final TextDirectionHeuristic ANYRTL_LTR =
70         new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false);
71 
72     /**
73      * Force the paragraph direction to the Locale direction. Falls back to left to right.
74      */
75     public static final TextDirectionHeuristic LOCALE = TextDirectionHeuristicLocale.INSTANCE;
76 
77     /**
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 
isRtlText(int directionality)84     private static int isRtlText(int directionality) {
85         switch (directionality) {
86             case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
87                 return STATE_FALSE;
88             case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
89             case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
90                 return STATE_TRUE;
91             default:
92                 return STATE_UNKNOWN;
93         }
94     }
95 
isRtlTextOrFormat(int directionality)96     private static int isRtlTextOrFormat(int directionality) {
97         switch (directionality) {
98             case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
99             case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
100             case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
101                 return STATE_FALSE;
102             case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
103             case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
104             case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
105             case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
106                 return STATE_TRUE;
107             default:
108                 return STATE_UNKNOWN;
109         }
110     }
111 
112     /**
113      * Computes the text direction based on an algorithm.  Subclasses implement
114      * {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the
115      * direction from the text alone.
116      */
117     private static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic {
118         private final TextDirectionAlgorithm mAlgorithm;
119 
TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm)120         public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) {
121             mAlgorithm = algorithm;
122         }
123 
124         /**
125          * Return true if the default text direction is rtl.
126          */
defaultIsRtl()127         abstract protected boolean defaultIsRtl();
128 
129         @Override
isRtl(char[] array, int start, int count)130         public boolean isRtl(char[] array, int start, int count) {
131             return isRtl(CharBuffer.wrap(array), start, count);
132         }
133 
134         @Override
isRtl(CharSequence cs, int start, int count)135         public boolean isRtl(CharSequence cs, int start, int count) {
136             if (cs == null || start < 0 || count < 0 || cs.length() - count < start) {
137                 throw new IllegalArgumentException();
138             }
139             if (mAlgorithm == null) {
140                 return defaultIsRtl();
141             }
142             return doCheck(cs, start, count);
143         }
144 
doCheck(CharSequence cs, int start, int count)145         private boolean doCheck(CharSequence cs, int start, int count) {
146             switch(mAlgorithm.checkRtl(cs, start, count)) {
147                 case STATE_TRUE:
148                     return true;
149                 case STATE_FALSE:
150                     return false;
151                 default:
152                     return defaultIsRtl();
153             }
154         }
155     }
156 
157     private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl {
158         private final boolean mDefaultIsRtl;
159 
TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, boolean defaultIsRtl)160         private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm,
161                 boolean defaultIsRtl) {
162             super(algorithm);
163             mDefaultIsRtl = defaultIsRtl;
164         }
165 
166         @Override
defaultIsRtl()167         protected boolean defaultIsRtl() {
168             return mDefaultIsRtl;
169         }
170     }
171 
172     /**
173      * Interface for an algorithm to guess the direction of a paragraph of text.
174      */
175     private static interface TextDirectionAlgorithm {
176         /**
177          * Returns whether the range of text is RTL according to the algorithm.
178          */
checkRtl(CharSequence cs, int start, int count)179         int checkRtl(CharSequence cs, int start, int count);
180     }
181 
182     /**
183      * Algorithm that uses the first strong directional character to determine the paragraph
184      * direction. This is the standard Unicode Bidirectional algorithm.
185      */
186     private static class FirstStrong implements TextDirectionAlgorithm {
187         @Override
checkRtl(CharSequence cs, int start, int count)188         public int checkRtl(CharSequence cs, int start, int count) {
189             int result = STATE_UNKNOWN;
190             for (int i = start, e = start + count; i < e && result == STATE_UNKNOWN; ++i) {
191                 result = isRtlTextOrFormat(Character.getDirectionality(cs.charAt(i)));
192             }
193             return result;
194         }
195 
FirstStrong()196         private FirstStrong() {
197         }
198 
199         public static final FirstStrong INSTANCE = new FirstStrong();
200     }
201 
202     /**
203      * Algorithm that uses the presence of any strong directional non-format
204      * character (e.g. excludes LRE, LRO, RLE, RLO) to determine the
205      * direction of text.
206      */
207     private static class AnyStrong implements TextDirectionAlgorithm {
208         private final boolean mLookForRtl;
209 
210         @Override
checkRtl(CharSequence cs, int start, int count)211         public int checkRtl(CharSequence cs, int start, int count) {
212             boolean haveUnlookedFor = false;
213             for (int i = start, e = start + count; i < e; ++i) {
214                 switch (isRtlText(Character.getDirectionality(cs.charAt(i)))) {
215                     case STATE_TRUE:
216                         if (mLookForRtl) {
217                             return STATE_TRUE;
218                         }
219                         haveUnlookedFor = true;
220                         break;
221                     case STATE_FALSE:
222                         if (!mLookForRtl) {
223                             return STATE_FALSE;
224                         }
225                         haveUnlookedFor = true;
226                         break;
227                     default:
228                         break;
229                 }
230             }
231             if (haveUnlookedFor) {
232                 return mLookForRtl ? STATE_FALSE : STATE_TRUE;
233             }
234             return STATE_UNKNOWN;
235         }
236 
AnyStrong(boolean lookForRtl)237         private AnyStrong(boolean lookForRtl) {
238             this.mLookForRtl = lookForRtl;
239         }
240 
241         public static final AnyStrong INSTANCE_RTL = new AnyStrong(true);
242         public static final AnyStrong INSTANCE_LTR = new AnyStrong(false);
243     }
244 
245     /**
246      * Algorithm that uses the Locale direction to force the direction of a paragraph.
247      */
248     private static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl {
249 
TextDirectionHeuristicLocale()250         public TextDirectionHeuristicLocale() {
251             super(null);
252         }
253 
254         @Override
defaultIsRtl()255         protected boolean defaultIsRtl() {
256             final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault());
257             return (dir == View.LAYOUT_DIRECTION_RTL);
258         }
259 
260         public static final TextDirectionHeuristicLocale INSTANCE =
261                 new TextDirectionHeuristicLocale();
262     }
263 }
264