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