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