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