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