1// © 2016 and later: Unicode, Inc. and others. 2// License & terms of use: http://www.unicode.org/copyright.html#License 3 4package com.ibm.icu.dev.test.bidi; 5 6import org.junit.Test; 7 8import com.ibm.icu.dev.test.TestFmwk; 9import com.ibm.icu.lang.UCharacter; 10import com.ibm.icu.text.ArabicShaping; 11import com.ibm.icu.text.Bidi; 12import com.ibm.icu.text.BidiTransform; 13import com.ibm.icu.text.BidiTransform.Mirroring; 14import com.ibm.icu.text.BidiTransform.Order; 15 16/** 17 * Verify Bidi Layout Transformations 18 * 19 * @author Lina Kemmel 20 * 21 */ 22public class TestBidiTransform extends TestFmwk { 23 24 static final char LATN_ZERO = '\u0030'; 25 static final char ARAB_ZERO = '\u0660'; 26 static final char MIN_HEB_LETTER = '\u05d0'; 27 static final char MIN_ARAB_LETTER = '\u0630'; // relevant to this test only 28 static final char MIN_SHAPED_LETTER = '\ufeab'; // relevant to this test only 29 30 31 private BidiTransform bidiTransform; 32 private Bidi bidi; 33 34 public TestBidiTransform() {} 35 36 @Test 37 public void testBidiTransform() { 38 logln("\nEntering TestBidiTransform\n"); 39 40 bidi = new Bidi(); 41 bidiTransform = new BidiTransform(); 42 43 autoDirectionTest(); 44 allTransformOptionsTest(); 45 46 logln("\nExiting TestBidiTransform\n"); 47 } 48 49 /** 50 * Tests various combinations of base directions, with the input either 51 * <code>Bidi.LEVEL_DEFAULT_LTR</code> or 52 * <code>Bidi.LEVEL_DEFAULT_LTR</code>, and the output either 53 * <code>Bidi.LEVEL_LTR</code> or <code>Bidi.LEVEL_RTL</code>. Order is 54 * always <code>Order.LOGICAL</code> for the input and 55 * <code>Order.VISUAL</code> for the output. 56 */ 57 private void autoDirectionTest() { 58 final String[] inTexts = { 59 "abc \u05d0\u05d1", 60 "... abc \u05d0\u05d1", 61 "\u05d0\u05d1 abc", 62 "... \u05d0\u05d1 abc", 63 ".*^" 64 }; 65 final byte[] inLevels = { 66 Bidi.LEVEL_DEFAULT_LTR, Bidi.LEVEL_DEFAULT_RTL 67 }; 68 final byte[] outLevels = { 69 Bidi.LTR, Bidi.RTL 70 }; 71 logln("\nEntering autoDirectionTest\n"); 72 73 for (String inText : inTexts) { 74 for (byte inLevel : inLevels) { 75 for (byte outLevel : outLevels) { 76 String outText = bidiTransform.transform(inText, inLevel, Order.LOGICAL, 77 outLevel, Order.VISUAL, Mirroring.OFF, 0); 78 bidi.setPara(inText, inLevel, null); 79 String expectedText = bidi.writeReordered(Bidi.REORDER_DEFAULT); 80 if ((outLevel & 1) != 0) { 81 expectedText = Bidi.writeReverse(expectedText, Bidi.OUTPUT_REVERSE); 82 } 83 logResultsForDir(inText, outText, expectedText, inLevel, outLevel); 84 } 85 } 86 } 87 logln("\nExiting autoDirectionTest\n"); 88 } 89 90 /** 91 * This method covers: 92 * <ul> 93 * <li>all possible combinations of ordering schemes and <strong>explicit</strong> 94 * base levels, applied to both input and output,</li> 95 * <li>selected tests for auto direction (systematically, auto direction is 96 * covered in a dedicated test) applied on both input and output,</li> 97 * <li>all possible combinations of mirroring, digits and letters applied 98 * to output only.</li> 99 * </ul> 100 */ 101 private void allTransformOptionsTest() { 102 final String inText = "a[b]c \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 23\u0660 e\u0631456 f \ufeaf \u0661\u0662"; 103 104 final Object[][] testCases = { 105 { Bidi.LTR, Order.LOGICAL, Bidi.LTR, Order.LOGICAL, 106 inText, // reordering without mirroring 107 "a[b]c \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 d \u0630 23\u0660 e\u0631456 f \ufeaf \u0661\u0662", // mirroring 108 "a[b]c \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u0662\u0663\u0660 e\u0631\u0664\u0665\u0666 f \ufeaf \u0661\u0662", // context digit shaping 109 "1: Logical LTR ==> Logical LTR" }, // message 110 { Bidi.LTR, Order.LOGICAL, Bidi.LTR, Order.VISUAL, 111 "a[b]c 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 d 23\u0660 \u0630 e456\u0631 f \u0661\u0662 \ufeaf", 112 "a[b]c 1 \u05d4(\u05d3 \u05d2\u05d1)\u05d0 d 23\u0660 \u0630 e456\u0631 f \u0661\u0662 \ufeaf", 113 "a[b]c 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 d \u0662\u0663\u0660 \u0630 e\u0664\u0665\u0666\u0631 f \u0661\u0662 \ufeaf", 114 "2: Logical LTR ==> Visual LTR" }, 115 { Bidi.LTR, Order.LOGICAL, Bidi.RTL, Order.LOGICAL, 116 "\ufeaf \u0661\u0662 f \u0631e456 \u0630 23\u0660 d \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 a[b]c", 117 "\ufeaf \u0661\u0662 f \u0631e456 \u0630 23\u0660 d \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 a[b]c", 118 "\ufeaf \u0661\u0662 f \u0631e\u0664\u0665\u0666 \u0630 \u0662\u0663\u0660 d \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 a[b]c", 119 "3: Logical LTR ==> Logical RTL" }, 120 { Bidi.LTR, Order.LOGICAL, Bidi.RTL, Order.VISUAL, 121 "\ufeaf \u0662\u0661 f \u0631654e \u0630 \u066032 d \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 c]b[a", 122 "\ufeaf \u0662\u0661 f \u0631654e \u0630 \u066032 d \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 c]b[a", 123 "\ufeaf \u0662\u0661 f \u0631\u0666\u0665\u0664e \u0630 \u0660\u0663\u0662 d \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 c]b[a", 124 "4: Logical LTR ==> Visual RTL" }, 125 126 { Bidi.RTL, Order.LOGICAL, Bidi.RTL, Order.LOGICAL, inText, 127 "a[b]c \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 d \u0630 23\u0660 e\u0631456 f \ufeaf \u0661\u0662", 128 "a[b]c \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 23\u0660 e\u0631456 f \ufeaf \u0661\u0662", 129 "5: Logical RTL ==> Logical RTL" }, 130 { Bidi.RTL, Order.LOGICAL, Bidi.RTL, Order.VISUAL, 131 "c]b[a \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 132 "c]b[a \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 133 "c]b[a \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 134 "6: Logical RTL ==> Visual RTL" }, 135 { Bidi.RTL, Order.LOGICAL, Bidi.LTR, Order.LOGICAL, 136 "\ufeaf \u0661\u0662 f 456\u0631e 23\u0630 \u0660 d 1 \u05d0(\u05d1\u05d2 \u05d3)\u05d4 a[b]c", 137 "\ufeaf \u0661\u0662 f 456\u0631e 23\u0630 \u0660 d 1 \u05d0)\u05d1\u05d2 \u05d3(\u05d4 a[b]c", 138 "\ufeaf \u0661\u0662 f 456\u0631e 23\u0630 \u0660 d 1 \u05d0(\u05d1\u05d2 \u05d3)\u05d4 a[b]c", 139 "7: Logical RTL ==> Logical LTR" }, 140 { Bidi.RTL, Order.LOGICAL, Bidi.LTR, Order.VISUAL, 141 "\u0661\u0662 \ufeaf f 456\u0631e 23\u0660 \u0630 d 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 a[b]c", 142 "\u0661\u0662 \ufeaf f 456\u0631e 23\u0660 \u0630 d 1 \u05d4(\u05d3 \u05d2\u05d1)\u05d0 a[b]c", 143 "\u0661\u0662 \ufeaf f 456\u0631e 23\u0660 \u0630 d 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 a[b]c", 144 "8: Logical RTL ==> Visual LTR" }, 145 146 { Bidi.LTR, Order.VISUAL, Bidi.LTR, Order.VISUAL, inText, 147 "a[b]c \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 d \u0630 23\u0660 e\u0631456 f \ufeaf \u0661\u0662", 148 "a[b]c \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u0662\u0663\u0660 e\u0631\u0664\u0665\u0666 f \ufeaf \u0661\u0662", 149 "9: Visual LTR ==> Visual LTR" }, 150 { Bidi.LTR, Order.VISUAL, Bidi.LTR, Order.LOGICAL, 151 "a[b]c 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 d 23\u0660 \u0630 e456\u0631 f \u0661\u0662 \ufeaf", 152 "a[b]c 1 \u05d4(\u05d3 \u05d2\u05d1)\u05d0 d 23\u0660 \u0630 e456\u0631 f \u0661\u0662 \ufeaf", 153 "a[b]c 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 d 23\u0660 \u0630 e456\u0631 f \u0661\u0662 \ufeaf", 154 "10: Visual LTR ==> Logical LTR" }, 155 { Bidi.LTR, Order.VISUAL, Bidi.RTL, Order.VISUAL, 156 "\u0662\u0661 \ufeaf f 654\u0631e \u066032 \u0630 d 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 c]b[a", 157 "\u0662\u0661 \ufeaf f 654\u0631e \u066032 \u0630 d 1 \u05d4(\u05d3 \u05d2\u05d1)\u05d0 c]b[a", 158 "\u0662\u0661 \ufeaf f \u0666\u0665\u0664\u0631e \u0660\u0663\u0662 \u0630 d 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 c]b[a", 159 "11: Visual LTR ==> Visual RTL" }, 160 { Bidi.LTR, Order.VISUAL, Bidi.RTL, Order.LOGICAL, 161 "\u0661\u0662 \ufeaf f 456\u0631e 23\u0660 \u0630 d 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 a[b]c", 162 "\u0661\u0662 \ufeaf f 456\u0631e 23\u0660 \u0630 d 1 \u05d4(\u05d3 \u05d2\u05d1)\u05d0 a[b]c", 163 "\u0661\u0662 \ufeaf f \u0664\u0665\u0666\u0631e \u0662\u0663\u0660 \u0630 d 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 a[b]c", 164 "12: Visual LTR ==> Logical RTL" }, 165 166 { Bidi.RTL, Order.VISUAL, Bidi.RTL, Order.VISUAL, inText, 167 "a[b]c \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 d \u0630 23\u0660 e\u0631456 f \ufeaf \u0661\u0662", 168 "a[b]c \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 23\u0660 e\u0631456 f \ufeaf \u0661\u0662", 169 "13: Visual RTL ==> Visual RTL" }, 170 { Bidi.RTL, Order.VISUAL, Bidi.RTL, Order.LOGICAL, 171 "c]b[a \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 172 "c]b[a \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 173 "c]b[a \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 174 "14: Visual RTL ==> Logical RTL" }, 175 { Bidi.RTL, Order.VISUAL, Bidi.LTR, Order.VISUAL, 176 "\u0662\u0661 \ufeaf f 654\u0631e \u066032 \u0630 d 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 c]b[a", 177 "\u0662\u0661 \ufeaf f 654\u0631e \u066032 \u0630 d 1 \u05d4(\u05d3 \u05d2\u05d1)\u05d0 c]b[a", 178 "\u0662\u0661 \ufeaf f 654\u0631e \u066032 \u0630 d 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 c]b[a", 179 "15: Visual RTL ==> Visual LTR" }, 180 { Bidi.RTL, Order.VISUAL, Bidi.LTR, Order.LOGICAL, 181 "\ufeaf \u0662\u0661 f 654\u0631e \u066032 \u0630 d 1 \u05d0(\u05d1\u05d2 \u05d3)\u05d4 c]b[a", 182 "\ufeaf \u0662\u0661 f 654\u0631e \u066032 \u0630 d 1 \u05d0)\u05d1\u05d2 \u05d3(\u05d4 c]b[a", 183 "\ufeaf \u0662\u0661 f 654\u0631e \u066032 \u0630 d 1 \u05d0(\u05d1\u05d2 \u05d3)\u05d4 c]b[a", 184 "16: Visual RTL ==> Logical LTR" }, 185 186 { Bidi.LEVEL_DEFAULT_RTL, Order.LOGICAL, Bidi.LTR, Order.VISUAL, 187 "a[b]c 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 d 23\u0660 \u0630 e456\u0631 f \u0661\u0662 \ufeaf", 188 "a[b]c 1 \u05d4(\u05d3 \u05d2\u05d1)\u05d0 d 23\u0660 \u0630 e456\u0631 f \u0661\u0662 \ufeaf", 189 "a[b]c 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 d \u0662\u0663\u0660 \u0630 e\u0664\u0665\u0666\u0631 f \u0661\u0662 \ufeaf", 190 "17: Logical DEFAULT_RTL ==> Visual LTR" }, 191 { Bidi.RTL, Order.LOGICAL, Bidi.LEVEL_DEFAULT_LTR, Order.VISUAL, 192 "c]b[a \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 193 "c]b[a \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 194 "c]b[a \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 195 "18: Logical RTL ==> Visual DEFAULT_LTR" }, 196 { Bidi.LEVEL_DEFAULT_LTR, Order.LOGICAL, Bidi.LTR, Order.VISUAL, 197 "a[b]c 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 d 23\u0660 \u0630 e456\u0631 f \u0661\u0662 \ufeaf", 198 "a[b]c 1 \u05d4(\u05d3 \u05d2\u05d1)\u05d0 d 23\u0660 \u0630 e456\u0631 f \u0661\u0662 \ufeaf", 199 "a[b]c 1 \u05d4)\u05d3 \u05d2\u05d1(\u05d0 d \u0662\u0663\u0660 \u0630 e\u0664\u0665\u0666\u0631 f \u0661\u0662 \ufeaf", 200 "19: Logical DEFAULT_LTR ==> Visual LTR" }, 201 { Bidi.RTL, Order.LOGICAL, Bidi.LEVEL_DEFAULT_RTL, Order.VISUAL, 202 "c]b[a \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 203 "c]b[a \u05d0)\u05d1\u05d2 \u05d3(\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 204 "c]b[a \u05d0(\u05d1\u05d2 \u05d3)\u05d4 1 d \u0630 \u066032 e\u0631654 f \ufeaf \u0662\u0661", 205 "20: Logical RTL ==> Visual DEFAULT_RTL" }, 206 }; 207 208 final int[] digits = { 209 ArabicShaping.DIGITS_NOOP, ArabicShaping.DIGITS_EN2AN, ArabicShaping.DIGITS_AN2EN, ArabicShaping.DIGITS_EN2AN_INIT_AL 210 }; 211 final int[] letters = { 212 ArabicShaping.LETTERS_NOOP, ArabicShaping.LETTERS_SHAPE, ArabicShaping.LETTERS_UNSHAPE 213 }; 214 215 char[] expectedChars; 216 217 logln("\nEntering allTransformOptionsTest\n"); 218 219 // Test various combinations of base level, order, mirroring, digits and letters 220 for (Object[] test : testCases) { 221 expectedChars = ((String)test[5]).toCharArray(); 222 verifyResultsForAllOpts(test, inText, bidiTransform.transform(inText, (Byte)test[0], (Order)test[1], 223 (Byte)test[2], (Order)test[3], Mirroring.ON, 0), expectedChars, 0, 0); 224 225 for (int digit : digits) { 226 expectedChars = ((String)(digit == ArabicShaping.DIGITS_EN2AN_INIT_AL ? test[6] : test[4])) 227 .toCharArray(); 228 for (int letter : letters) { 229 verifyResultsForAllOpts(test, inText, bidiTransform.transform(inText, (Byte)test[0], 230 (Order)test[1], (Byte)test[2], (Order)test[3], Mirroring.OFF, digit | letter), 231 expectedChars, digit, letter); 232 } 233 } 234 } 235 logln("\nExiting allTransformOptionsTest\n"); 236 } 237 238 private void logResultsForDir(String inText, String outText, String expected, 239 byte inLevel, byte outLevel) { 240 241 assertEquals("inLevel: " + inLevel + ", outLevel: " + outLevel 242 /* TODO: BidiFwk#u16ToPseudo isn't good for us, needs an update to be used here */ 243 + "\ninText: " + pseudoScript(inText) + "\noutText: " + pseudoScript(outText) 244 + "\nexpected: " + pseudoScript(expected) + "\n", expected, outText); 245 } 246 247 private void verifyResultsForAllOpts(Object[] test, String inText, String outText, char[] expectedChars, int digits, int letters) { 248 switch (digits) { 249 case ArabicShaping.DIGITS_AN2EN: 250 shapeDigits(expectedChars, ARAB_ZERO, LATN_ZERO); 251 break; 252 case ArabicShaping.DIGITS_EN2AN: 253 shapeDigits(expectedChars, LATN_ZERO, ARAB_ZERO); 254 break; 255 default: 256 break; 257 } 258 switch (letters) { 259 case ArabicShaping.LETTERS_SHAPE: 260 shapeLetters(expectedChars, 0); 261 break; 262 case ArabicShaping.LETTERS_UNSHAPE: 263 shapeLetters(expectedChars, 1); 264 break; 265 default: 266 break; 267 } 268 String expected = new String(expectedChars); 269 assertEquals("\nTest " + test[7] + "\ndigits: " + digits + ", letters: " + letters 270 /* TODO: BidiFwk#u16ToPseudo isn't good for us, needs an update to be used here */ 271 + "\ninText: " + pseudoScript(inText) + "\noutText: " + pseudoScript(outText) 272 + "\nexpected: " + pseudoScript(expected) + "\n", expected, outText); 273 } 274 275 /* 276 * Using the following conventions: 277 * AL unshaped: A-E 278 * AL shaped: F-J 279 * R: K-Z 280 * EN: 0-4 281 * AN: 5-9 282 */ 283 private static char substituteChar(char uch, char baseReal, 284 char basePseudo, char max) { 285 char dest = (char)(basePseudo + (uch - baseReal)); 286 return dest > max ? max : dest; 287 } 288 289 private static String pseudoScript(String text) { 290 char[] uchars = text.toCharArray(); 291 for (int i = uchars.length; i-- > 0;) { 292 char uch = uchars[i]; 293 switch (UCharacter.getDirectionality(uch)) { 294 case UCharacter.RIGHT_TO_LEFT: 295 uchars[i] = substituteChar(uch, MIN_HEB_LETTER, 'K', 'Z'); 296 break; 297 case UCharacter.RIGHT_TO_LEFT_ARABIC: 298 if (uch > 0xFE00) { 299 uchars[i] = substituteChar(uch, MIN_SHAPED_LETTER, 'F', 'J'); 300 } else { 301 uchars[i] = substituteChar(uch, MIN_ARAB_LETTER, 'A', 'E'); 302 } 303 break; 304 case UCharacter.ARABIC_NUMBER: 305 uchars[i] = substituteChar(uch, ARAB_ZERO, '5', '9'); 306 break; 307 default: 308 break; 309 } 310 } 311 return new String(uchars); 312 } 313 314 private static void shapeDigits(char[] chars, char srcZero, char destZero) { 315 for (int i = chars.length; i-- > 0;) { 316 if (chars[i] >= srcZero && chars[i] <= srcZero + 9) { 317 chars[i] = substituteChar(chars[i], srcZero, destZero, (char)(destZero + 9)); 318 } 319 } 320 } 321 322 /* 323 * TODO: the goal is not to thoroughly test ArabicShaping, so the test can be quite trivial, 324 * but maybe still more sophisticated? 325 */ 326 private static final String letters = "\u0630\ufeab\u0631\ufead\u0632\ufeaf"; 327 328 private static void shapeLetters(char[] chars, int indexParity) { 329 for (int i = chars.length; i-- > 0;) { 330 int index = letters.indexOf(chars[i]); 331 if (index >= 0 && (index & 1) == indexParity) { 332 chars[i] = letters.charAt(index ^ 1); 333 } 334 } 335 } 336} 337