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