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