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