• 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  
84      /* Returns STATE_TRUE for strong RTL characters, STATE_FALSE for strong LTR characters, and
85       * STATE_UNKNOWN for everything else.
86       */
isRtlCodePoint(int codePoint)87      private static int isRtlCodePoint(int codePoint) {
88          switch (Character.getDirectionality(codePoint)) {
89              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;
94              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
97  
98                  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                  }
125              default:
126                  return STATE_UNKNOWN;
127          }
128      }
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.
134       */
135      private static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic {
136          private final TextDirectionAlgorithm mAlgorithm;
137  
TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm)138          public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) {
139              mAlgorithm = algorithm;
140          }
141  
142          /**
143           * Return true if the default text direction is rtl.
144           */
defaultIsRtl()145          abstract protected boolean defaultIsRtl();
146  
147          @Override
isRtl(char[] array, int start, int count)148          public boolean isRtl(char[] array, int start, int count) {
149              return isRtl(CharBuffer.wrap(array), start, count);
150          }
151  
152          @Override
isRtl(CharSequence cs, int start, int count)153          public boolean isRtl(CharSequence cs, int start, int count) {
154              if (cs == null || start < 0 || count < 0 || cs.length() - count < start) {
155                  throw new IllegalArgumentException();
156              }
157              if (mAlgorithm == null) {
158                  return defaultIsRtl();
159              }
160              return doCheck(cs, start, count);
161          }
162  
doCheck(CharSequence cs, int start, int count)163          private boolean doCheck(CharSequence cs, int start, int count) {
164              switch(mAlgorithm.checkRtl(cs, start, count)) {
165                  case STATE_TRUE:
166                      return true;
167                  case STATE_FALSE:
168                      return false;
169                  default:
170                      return defaultIsRtl();
171              }
172          }
173      }
174  
175      private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl {
176          private final boolean mDefaultIsRtl;
177  
TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, boolean defaultIsRtl)178          private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm,
179                  boolean defaultIsRtl) {
180              super(algorithm);
181              mDefaultIsRtl = defaultIsRtl;
182          }
183  
184          @Override
defaultIsRtl()185          protected boolean defaultIsRtl() {
186              return mDefaultIsRtl;
187          }
188      }
189  
190      /**
191       * Interface for an algorithm to guess the direction of a paragraph of text.
192       */
193      private static interface TextDirectionAlgorithm {
194          /**
195           * Returns whether the range of text is RTL according to the algorithm.
196           */
checkRtl(CharSequence cs, int start, int count)197          int checkRtl(CharSequence cs, int start, int count);
198      }
199  
200      /**
201       * Algorithm that uses the first strong directional character to determine the paragraph
202       * 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.
204       */
205      private static class FirstStrong implements TextDirectionAlgorithm {
206          @Override
checkRtl(CharSequence cs, int start, int count)207          public int checkRtl(CharSequence cs, int start, int count) {
208              int result = STATE_UNKNOWN;
209              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                  }
222              }
223              return result;
224          }
225  
FirstStrong()226          private FirstStrong() {
227          }
228  
229          public static final FirstStrong INSTANCE = new FirstStrong();
230      }
231  
232      /**
233       * 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.
237       */
238      private static class AnyStrong implements TextDirectionAlgorithm {
239          private final boolean mLookForRtl;
240  
241          @Override
checkRtl(CharSequence cs, int start, int count)242          public int checkRtl(CharSequence cs, int start, int count) {
243              boolean haveUnlookedFor = false;
244              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                      }
269                  }
270              }
271              if (haveUnlookedFor) {
272                  return mLookForRtl ? STATE_FALSE : STATE_TRUE;
273              }
274              return STATE_UNKNOWN;
275          }
276  
AnyStrong(boolean lookForRtl)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      /**
286       * Algorithm that uses the Locale direction to force the direction of a paragraph.
287       */
288      private static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl {
289  
TextDirectionHeuristicLocale()290          public TextDirectionHeuristicLocale() {
291              super(null);
292          }
293  
294          @Override
defaultIsRtl()295          protected boolean defaultIsRtl() {
296              final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault());
297              return (dir == View.LAYOUT_DIRECTION_RTL);
298          }
299  
300          public static final TextDirectionHeuristicLocale INSTANCE =
301                  new TextDirectionHeuristicLocale();
302      }
303  }
304