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