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 androidx.core.text;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertNull;
21
22import android.support.test.filters.SmallTest;
23import android.support.test.runner.AndroidJUnit4;
24import android.text.SpannableString;
25import android.text.Spanned;
26import android.text.style.RelativeSizeSpan;
27
28import org.junit.Test;
29import org.junit.runner.RunWith;
30
31import java.util.Locale;
32
33@RunWith(AndroidJUnit4.class)
34@SmallTest
35public class BidiFormatterTest {
36
37    private static final BidiFormatter LTR_FMT = BidiFormatter.getInstance(false /* LTR context */);
38    private static final BidiFormatter RTL_FMT = BidiFormatter.getInstance(true /* RTL context */);
39
40    private static final BidiFormatter LTR_FMT_EXIT_RESET =
41            new BidiFormatter.Builder(false /* LTR context */).stereoReset(false).build();
42    private static final BidiFormatter RTL_FMT_EXIT_RESET =
43            new BidiFormatter.Builder(true /* RTL context */).stereoReset(false).build();
44
45    private static final String EN = "abba";
46    private static final String HE = "\u05E0\u05E1";
47
48    private static final String LRM = "\u200E";
49    private static final String RLM = "\u200F";
50    private static final String LRE = "\u202A";
51    private static final String RLE = "\u202B";
52    private static final String PDF = "\u202C";
53
54    @Test
55    public void testIsRtlContext() {
56        assertEquals(false, LTR_FMT.isRtlContext());
57        assertEquals(true, RTL_FMT.isRtlContext());
58
59        assertEquals(false, BidiFormatter.getInstance(Locale.ENGLISH).isRtlContext());
60        assertEquals(true, BidiFormatter.getInstance(true).isRtlContext());
61    }
62
63    @Test
64    public void testBuilderIsRtlContext() {
65        assertEquals(false, new BidiFormatter.Builder(false).build().isRtlContext());
66        assertEquals(true, new BidiFormatter.Builder(true).build().isRtlContext());
67    }
68
69    @Test
70    public void testIsRtl() {
71        assertEquals(true, BidiFormatter.getInstance(true).isRtl(HE));
72        assertEquals(true, BidiFormatter.getInstance(false).isRtl(HE));
73
74        assertEquals(false, BidiFormatter.getInstance(true).isRtl(EN));
75        assertEquals(false, BidiFormatter.getInstance(false).isRtl(EN));
76    }
77
78    @Test
79    public void testUnicodeWrap() {
80        // Make sure an input of null doesn't crash anything.
81        assertNull(LTR_FMT.unicodeWrap(null));
82
83        // Uniform directionality in opposite context.
84        assertEquals("uniform dir opposite to LTR context",
85                RLE + "." + HE + "." + PDF + LRM,
86                LTR_FMT_EXIT_RESET.unicodeWrap("." + HE + "."));
87        assertEquals("uniform dir opposite to LTR context, stereo reset",
88                LRM + RLE + "." + HE + "." + PDF + LRM,
89                LTR_FMT.unicodeWrap("." + HE + "."));
90        assertEquals("uniform dir opposite to LTR context, stereo reset, no isolation",
91                RLE + "." + HE + "." + PDF,
92                LTR_FMT.unicodeWrap("." + HE + ".", false));
93        assertEquals("neutral treated as opposite to LTR context",
94                RLE + "." + PDF + LRM,
95                LTR_FMT_EXIT_RESET.unicodeWrap(".", TextDirectionHeuristicsCompat.RTL));
96        assertEquals("uniform dir opposite to RTL context",
97                LRE + "." + EN + "." + PDF + RLM,
98                RTL_FMT_EXIT_RESET.unicodeWrap("." + EN + "."));
99        assertEquals("uniform dir opposite to RTL context, stereo reset",
100                RLM + LRE + "." + EN + "." + PDF + RLM,
101                RTL_FMT.unicodeWrap("." + EN + "."));
102        assertEquals("uniform dir opposite to RTL context, stereo reset, no isolation",
103                LRE + "." + EN + "." + PDF,
104                RTL_FMT.unicodeWrap("." + EN + ".", false));
105        assertEquals("neutral treated as opposite to RTL context",
106                LRE + "." + PDF + RLM,
107                RTL_FMT_EXIT_RESET.unicodeWrap(".", TextDirectionHeuristicsCompat.LTR));
108
109        // We test mixed-directionality cases only with an explicit overall directionality parameter
110        // because the estimation logic is outside the sphere of BidiFormatter, and different
111        // estimators will treat them differently.
112
113        // Overall directionality matching context, but with opposite exit directionality.
114        assertEquals("exit dir opposite to LTR context",
115                EN + HE + LRM,
116                LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE, TextDirectionHeuristicsCompat.LTR));
117        assertEquals("exit dir opposite to LTR context, stereo reset",
118                EN + HE + LRM,
119                LTR_FMT.unicodeWrap(EN + HE, TextDirectionHeuristicsCompat.LTR));
120        assertEquals("exit dir opposite to LTR context, stereo reset, no isolation",
121                EN + HE,
122                LTR_FMT.unicodeWrap(EN + HE, TextDirectionHeuristicsCompat.LTR, false));
123
124        assertEquals("exit dir opposite to RTL context",
125                HE + EN + RLM,
126                RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN, TextDirectionHeuristicsCompat.RTL));
127        assertEquals("exit dir opposite to RTL context, stereo reset",
128                HE + EN + RLM,
129                RTL_FMT.unicodeWrap(HE + EN, TextDirectionHeuristicsCompat.RTL));
130        assertEquals("exit dir opposite to RTL context, stereo reset, no isolation",
131                HE + EN,
132                RTL_FMT.unicodeWrap(HE + EN, TextDirectionHeuristicsCompat.RTL, false));
133
134        // Overall directionality matching context, but with opposite entry directionality.
135        assertEquals("entry dir opposite to LTR context",
136                HE + EN,
137                LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN, TextDirectionHeuristicsCompat.LTR));
138        assertEquals("entry dir opposite to LTR context, stereo reset",
139                LRM + HE + EN,
140                LTR_FMT.unicodeWrap(HE + EN, TextDirectionHeuristicsCompat.LTR));
141        assertEquals("entry dir opposite to LTR context, stereo reset, no isolation",
142                HE + EN,
143                LTR_FMT.unicodeWrap(HE + EN, TextDirectionHeuristicsCompat.LTR, false));
144
145        assertEquals("entry dir opposite to RTL context",
146                EN + HE,
147                RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE, TextDirectionHeuristicsCompat.RTL));
148        assertEquals("entry dir opposite to RTL context, stereo reset",
149                RLM + EN + HE,
150                RTL_FMT.unicodeWrap(EN + HE, TextDirectionHeuristicsCompat.RTL));
151        assertEquals("entry dir opposite to RTL context, stereo reset, no isolation",
152                EN + HE,
153                RTL_FMT.unicodeWrap(EN + HE, TextDirectionHeuristicsCompat.RTL, false));
154
155        // Overall directionality matching context, but with opposite entry and exit directionality.
156        assertEquals("entry and exit dir opposite to LTR context",
157                HE + EN + HE + LRM,
158                LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
159        assertEquals("entry and exit dir opposite to LTR context, stereo reset",
160                LRM + HE + EN + HE + LRM,
161                LTR_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
162        assertEquals("entry and exit dir opposite to LTR context, no isolation",
163                HE + EN + HE,
164                LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR,
165                        false));
166
167        assertEquals("entry and exit dir opposite to RTL context",
168                EN + HE + EN + RLM,
169                RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL));
170        assertEquals("entry and exit dir opposite to RTL context, no isolation",
171                EN + HE + EN,
172                RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL,
173                        false));
174
175        // Entry and exit directionality matching context, but with opposite overall directionality.
176        assertEquals("overall dir (but not entry or exit dir) opposite to LTR context",
177                RLE + EN + HE + EN + PDF + LRM,
178                LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL));
179        assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, stereo reset",
180                LRM + RLE + EN + HE + EN + PDF + LRM,
181                LTR_FMT.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL));
182        assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, no isolation",
183                RLE + EN + HE + EN + PDF,
184                LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL,
185                        false));
186
187        assertEquals("overall dir (but not entry or exit dir) opposite to RTL context",
188                LRE + HE + EN + HE + PDF + RLM,
189                RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
190        assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, stereo reset",
191                RLM + LRE + HE + EN + HE + PDF + RLM,
192                RTL_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
193        assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, no isolation",
194                LRE + HE + EN + HE + PDF,
195                RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR,
196                        false));
197    }
198
199    @Test
200    public void testCharSequenceApis() {
201        final CharSequence CS_HE = new SpannableString(HE);
202        assertEquals(true, BidiFormatter.getInstance(true).isRtl(CS_HE));
203
204        final SpannableString CS_EN_HE = new SpannableString(EN + HE);
205        final Object RELATIVE_SIZE_SPAN = new RelativeSizeSpan(1.2f);
206        CS_EN_HE.setSpan(RELATIVE_SIZE_SPAN, 0, EN.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
207
208        Spanned wrapped;
209        Object[] spans;
210
211        wrapped = (Spanned) LTR_FMT.unicodeWrap(CS_EN_HE);
212        assertEquals(EN + HE + LRM, wrapped.toString());
213        spans = wrapped.getSpans(0, wrapped.length(), Object.class);
214        assertEquals(1, spans.length);
215        assertEquals(RELATIVE_SIZE_SPAN, spans[0]);
216        assertEquals(0, wrapped.getSpanStart(RELATIVE_SIZE_SPAN));
217        assertEquals(EN.length(), wrapped.getSpanEnd(RELATIVE_SIZE_SPAN));
218
219        wrapped = (Spanned) LTR_FMT.unicodeWrap(CS_EN_HE, TextDirectionHeuristicsCompat.LTR);
220        assertEquals(EN + HE + LRM, wrapped.toString());
221        spans = wrapped.getSpans(0, wrapped.length(), Object.class);
222        assertEquals(1, spans.length);
223        assertEquals(RELATIVE_SIZE_SPAN, spans[0]);
224        assertEquals(0, wrapped.getSpanStart(RELATIVE_SIZE_SPAN));
225        assertEquals(EN.length(), wrapped.getSpanEnd(RELATIVE_SIZE_SPAN));
226
227        wrapped = (Spanned) LTR_FMT.unicodeWrap(CS_EN_HE, false);
228        assertEquals(EN + HE, wrapped.toString());
229        spans = wrapped.getSpans(0, wrapped.length(), Object.class);
230        assertEquals(1, spans.length);
231        assertEquals(RELATIVE_SIZE_SPAN, spans[0]);
232        assertEquals(0, wrapped.getSpanStart(RELATIVE_SIZE_SPAN));
233        assertEquals(EN.length(), wrapped.getSpanEnd(RELATIVE_SIZE_SPAN));
234
235        wrapped = (Spanned) LTR_FMT.unicodeWrap(CS_EN_HE, TextDirectionHeuristicsCompat.LTR, false);
236        assertEquals(EN + HE, wrapped.toString());
237        spans = wrapped.getSpans(0, wrapped.length(), Object.class);
238        assertEquals(1, spans.length);
239        assertEquals(RELATIVE_SIZE_SPAN, spans[0]);
240        assertEquals(0, wrapped.getSpanStart(RELATIVE_SIZE_SPAN));
241        assertEquals(EN.length(), wrapped.getSpanEnd(RELATIVE_SIZE_SPAN));
242    }
243}
244