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