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