1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/*
4*******************************************************************************
5*   Copyright (C) 2008-2010, International Business Machines
6*   Corporation and others.  All Rights Reserved.
7*******************************************************************************
8*/
9
10package com.ibm.icu.dev.test.bidi;
11
12import java.util.Arrays;
13
14import org.junit.Test;
15
16import com.ibm.icu.impl.Utility;
17import com.ibm.icu.text.Bidi;
18import com.ibm.icu.text.BidiRun;
19
20/**
21 * Regression test for Bidi multiple paragraphs
22 *
23 * @author Lina Kemmel, Matitiahu Allouche
24 */
25
26public class TestMultipleParagraphs extends BidiFmwk {
27
28    private static final String text =
29        "__ABC\u001c"                  /* Para #0 offset 0 */
30        + "__\u05d0DE\u001c"           /*       1        6 */
31        + "__123\u001c"                /*       2       12 */
32        + "\r\n"                       /*       3       18 */
33        + "FG\r"                       /*       4       20 */
34        + "\r"                         /*       5       23 */
35        + "HI\r\n"                     /*       6       24 */
36        + "\r\n"                       /*       7       28 */
37        + "\n"                         /*       8       30 */
38        + "\n"                         /*       9       31 */
39        + "JK\u001c";                  /*      10       32 */
40    private static final int paraCount = 11;
41    private static final int[] paraBounds = {
42        0, 6, 12, 18, 20, 23, 24, 28, 30, 31, 32, 35
43    };
44    private static final byte[] paraLevels = {
45        Bidi.LTR, Bidi.RTL, Bidi.LEVEL_DEFAULT_LTR, Bidi.LEVEL_DEFAULT_RTL, 22, 23
46    };
47    private static final byte[][] multiLevels = {
48        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
49        {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
50        {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
51        {0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0},
52        {22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22},
53        {23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23}
54    };
55    private static final String text2 = "\u05d0 1-2\u001c\u0630 1-2\u001c1-2";
56    private static final byte[] levels2 = {
57        1, 1, 2, 2, 2, 0, 1, 1, 2, 1, 2, 0, 2, 2, 2
58    };
59    private static final char[] multiparaTestString = {
60        0x5de, 0x5e0, 0x5e1, 0x5d4, 0x20,  0x5e1, 0x5e4, 0x5da,
61        0x20,  0xa,   0xa,   0x41,  0x72,  0x74,  0x69,  0x73,
62        0x74,  0x3a,  0x20,  0x5de, 0x5e0, 0x5e1, 0x5d4, 0x20,
63        0x5e1, 0x5e4, 0x5da, 0x20,  0xa,   0xa,   0x41,  0x6c,
64        0x62,  0x75,  0x6d,  0x3a,  0x20,  0x5de, 0x5e0, 0x5e1,
65        0x5d4, 0x20,  0x5e1, 0x5e4, 0x5da, 0x20,  0xa,   0xa,
66        0x54,  0x69,  0x6d,  0x65,  0x3a,  0x20,  0x32,  0x3a,
67        0x32,  0x37,  0xa,  0xa
68    };
69    private static final byte[] multiparaTestLevels = {
70        1, 1, 1, 1, 1, 1, 1, 1,
71        1, 1, 0, 0, 0, 0, 0, 0,
72        0, 0, 0, 1, 1, 1, 1, 1,
73        1, 1, 1, 0, 0, 0, 0, 0,
74        0, 0, 0, 0, 0, 1, 1, 1,
75        1, 1, 1, 1, 1, 0, 0, 0,
76        0, 0, 0, 0, 0, 0, 0, 0,
77        0, 0, 0, 0
78    };
79
80    @Test
81    public void testMultipleParagraphs()
82    {
83        byte gotLevel;
84        byte[] gotLevels;
85        boolean orderParagraphsLTR;
86        String src;
87        Bidi bidi = new Bidi();
88        Bidi bidiLine;
89        int count, paraStart, paraLimit, paraIndex, length;
90        int i, j, k;
91
92        logln("\nEntering TestMultipleParagraphs\n");
93        try {
94            bidi.setPara(text, Bidi.LTR, null);
95        } catch (IllegalArgumentException e) {
96            errln("1st Bidi.setPara failed, paraLevel = " + Bidi.LTR);
97        }
98
99        /* check paragraph count and boundaries */
100        if (paraCount != (count = bidi.countParagraphs())) {
101            errln("1st Bidi.countParagraphs returned " + count + ", should be " +
102                  paraCount);
103        }
104        BidiRun run;
105        for (i = 0; i < paraCount; i++) {
106            run = bidi.getParagraphByIndex(i);
107            paraStart = run.getStart();
108            paraLimit = run.getLimit();
109            if ((paraStart != paraBounds[i]) ||
110                (paraLimit != paraBounds[i + 1])) {
111                errln("Found boundaries of paragraph " + i + ": " +
112                      paraStart + "-" + paraLimit + "; expected: " +
113                      paraBounds[i] + "-" + paraBounds[i + 1]);
114            }
115        }
116
117        /* check with last paragraph not terminated by B */
118        char[] chars = text.toCharArray();
119        chars[chars.length - 1] = 'L';
120        src = new String(chars);
121        try {
122            bidi.setPara(src, Bidi.LTR, null);
123        } catch (IllegalArgumentException e) {
124            errln("2nd Bidi.setPara failed, paraLevel = " + Bidi.LTR);
125        }
126        if (paraCount != (count = bidi.countParagraphs())) {
127            errln("2nd Bidi.countParagraphs returned " + count +
128                  ", should be " + paraCount);
129        }
130        i = paraCount - 1;
131        run = bidi.getParagraphByIndex(i);
132        paraStart = run.getStart();
133        paraLimit = run.getLimit();
134        if ((paraStart != paraBounds[i]) ||
135            (paraLimit != paraBounds[i + 1])) {
136            errln("2nd Found boundaries of paragraph " + i + ": " +
137                  paraStart + "-" + paraLimit + "; expected: " +
138                  paraBounds[i] + "-" + paraBounds[i + 1]);
139        }
140
141        /* check paraLevel for all paragraphs under various paraLevel specs */
142        for (k = 0; k < 6; k++) {
143            try {
144                bidi.setPara(src, paraLevels[k], null);
145            } catch (IllegalArgumentException e) {
146                errln("3nd Bidi.setPara failed, paraLevel = " + paraLevels[k]);
147            }
148            for (i = 0; i < paraCount; i++) {
149                paraIndex = bidi.getParagraphIndex(paraBounds[i]);
150                run = bidi.getParagraph(paraBounds[i]);
151                if (paraIndex != i) {
152                    errln("#1 For paraLevel = " + paraLevels[k] +
153                          " paragraph = " + i + ", found paragraph" +
154                          " index = " + paraIndex + " expected = " + i);
155                }
156                gotLevel = run.getEmbeddingLevel();
157                if (gotLevel != multiLevels[k][i]) {
158                    errln("#2 For paraLevel = " + paraLevels[k] +
159                          " paragraph = " + i + ", found level = " + gotLevel +
160                          ", expected = " + multiLevels[k][i]);
161                }
162            }
163            gotLevel = bidi.getParaLevel();
164            if (gotLevel != multiLevels[k][0]) {
165                errln("#3 For paraLevel = " + paraLevels[k] +
166                      " getParaLevel = " + gotLevel + ", expected " +
167                      multiLevels[k][0]);
168            }
169        }
170
171        /* check that the result of Bidi.getParaLevel changes if the first
172         * paragraph has a different level
173         */
174        chars[0] = '\u05d2';            /* Hebrew letter Gimel */
175        src = new String(chars);
176        try {
177            bidi.setPara(src, Bidi.LEVEL_DEFAULT_LTR, null);
178        } catch (IllegalArgumentException e) {
179            errln("Bidi.setPara failed, paraLevel = " + Bidi.LEVEL_DEFAULT_LTR);
180        }
181        gotLevel = bidi.getParaLevel();
182        if (gotLevel != Bidi.RTL) {
183            errln("#4 For paraLevel = Bidi.LEVEL_DEFAULT_LTR getParaLevel = " +
184                  gotLevel + ", expected = " + Bidi.RTL);
185        }
186
187        /* check that line cannot overlap paragraph boundaries */
188        bidiLine = new Bidi();
189        i = paraBounds[1];
190        k = paraBounds[2] + 1;
191        try {
192            bidiLine = bidi.setLine(i, k);
193            errln("For line limits " + i + "-" + k
194                    + " got success, while expected failure");
195        } catch (Exception e) {}
196
197        i = paraBounds[1];
198        k = paraBounds[2];
199        try {
200            bidiLine = bidi.setLine(i, k);
201        } catch (Exception e) {
202            errln("For line limits " + i + "-" + k + " got failure");
203        }
204
205        /* check level of block separator at end of paragraph when orderParagraphsLTR==FALSE */
206        try {
207            bidi.setPara(src, Bidi.RTL, null);
208        } catch (IllegalArgumentException e) {
209            errln("Bidi.setPara failed, paraLevel = " + Bidi.RTL);
210        }
211        /* get levels through para Bidi block */
212        try {
213            gotLevels = bidi.getLevels();
214        } catch (Exception e) {
215            errln("Error on Bidi.getLevels");
216            gotLevels = new byte[bidi.getLength()];
217            Arrays.fill(gotLevels, (byte)-1);
218        }
219        for (i = 26; i < 32; i++) {
220            if (gotLevels[i] != Bidi.RTL) {
221                errln("For char " + i + "(0x" + Utility.hex(chars[i]) +
222                      "), level = " + gotLevels[i] + ", expected = " + Bidi.RTL);
223            }
224        }
225        /* get levels through para Line block */
226        i = paraBounds[1];
227        k = paraBounds[2];
228        try {
229            bidiLine = bidi.setLine(i, k);
230        } catch (Exception e) {
231            errln("For line limits " + i + "-" + k + " got failure");
232            return;
233        }
234        paraIndex = bidiLine.getParagraphIndex(i);
235        run = bidiLine.getParagraph(i);
236        try {
237            gotLevels = bidiLine.getLevels();
238        } catch (Exception e) {
239            errln("Error on bidiLine.getLevels");
240            gotLevels = new byte[bidiLine.getLength()];
241            Arrays.fill(gotLevels, (byte)-1);
242        }
243        length = bidiLine.getLength();
244        gotLevel = run.getEmbeddingLevel();
245        if ((gotLevel != Bidi.RTL) || (gotLevels[length - 1] != Bidi.RTL)) {
246            errln("For paragraph " + paraIndex + " with limits " +
247                  run.getStart() + "-" + run.getLimit() +
248                  ", paraLevel = " + gotLevel +
249                  "expected = " + Bidi.RTL +
250                  ", level of separator = " + gotLevels[length - 1] +
251                  " expected = " + Bidi.RTL);
252        }
253        orderParagraphsLTR = bidi.isOrderParagraphsLTR();
254        assertFalse("orderParagraphsLTR is true", orderParagraphsLTR);
255        bidi.orderParagraphsLTR(true);
256        orderParagraphsLTR = bidi.isOrderParagraphsLTR();
257        assertTrue("orderParagraphsLTR is false", orderParagraphsLTR);
258
259        /* check level of block separator at end of paragraph when orderParagraphsLTR==TRUE */
260        try {
261            bidi.setPara(src, Bidi.RTL, null);
262        } catch (IllegalArgumentException e) {
263            errln("Bidi.setPara failed, paraLevel = " + Bidi.RTL);
264        }
265        /* get levels through para Bidi block */
266        try {
267            gotLevels = bidi.getLevels();
268        } catch (Exception e) {
269            errln("Error on Bidi.getLevels");
270            gotLevels = new byte[bidi.getLength()];
271            Arrays.fill(gotLevels, (byte)-1);
272        }
273        for (i = 26; i < 32; i++) {
274            if (gotLevels[i] != 0) {
275                errln("For char " + i + "(0x" + Utility.hex(chars[i]) +
276                      "), level = "+ gotLevels[i] + ", expected = 0");
277            }
278        }
279        /* get levels through para Line block */
280        i = paraBounds[1];
281        k = paraBounds[2];
282        paraStart = run.getStart();
283        paraLimit = run.getLimit();
284        try {
285            bidiLine = bidi.setLine(paraStart, paraLimit);
286        } catch (Exception e) {
287            errln("For line limits " + paraStart + "-" + paraLimit +
288                  " got failure");
289        }
290        paraIndex = bidiLine.getParagraphIndex(i);
291        run = bidiLine.getParagraph(i);
292        try {
293            gotLevels = bidiLine.getLevels();
294        } catch (Exception e) {
295            errln("Error on bidiLine.getLevels");
296            gotLevels = new byte[bidiLine.getLength()];
297            Arrays.fill(gotLevels, (byte)-1);
298        }
299        length = bidiLine.getLength();
300        gotLevel = run.getEmbeddingLevel();
301        if ((gotLevel != Bidi.RTL) || (gotLevels[length - 1] != 0)) {
302            err("\nFor paragraph " + paraIndex + " with limits " +
303                run.getStart() + "-" + run.getLimit() +
304                ", paraLevel = " + gotLevel + "expected = " + Bidi.RTL +
305                ", level of separator = " + gotLevels[length - 1] +
306                " expected = 0\nlevels = ");
307            for (count = 0; count < length; count++) {
308                errcont(gotLevels[count] + "  ");
309            }
310            errcont("\n");
311        }
312
313        /* test that the concatenation of separate invocations of the bidi code
314         * on each individual paragraph in order matches the levels array that
315         * results from invoking bidi once over the entire multiparagraph tests
316         * (with orderParagraphsLTR false, of course)
317         */
318        src = text;                     /* restore original content */
319        bidi.orderParagraphsLTR(false);
320        try {
321            bidi.setPara(src, Bidi.LEVEL_DEFAULT_RTL, null);
322        } catch (IllegalArgumentException e) {
323            errln("Bidi.setPara failed, paraLevel = " + Bidi.LEVEL_DEFAULT_RTL);
324        }
325        try {
326            gotLevels = bidi.getLevels();
327        } catch (Exception e) {
328            errln("Error on bidiLine.getLevels");
329            gotLevels = new byte[bidi.getLength()];
330            Arrays.fill(gotLevels, (byte)-1);
331        }
332        for (i = 0; i < paraCount; i++) {
333            /* use pLine for individual paragraphs */
334            paraStart = paraBounds[i];
335            length = paraBounds[i + 1] - paraStart;
336            try {
337                bidiLine.setPara(src.substring(paraStart, paraStart + length),
338                                 Bidi.LEVEL_DEFAULT_RTL, null);
339            } catch (IllegalArgumentException e) {
340                errln("Bidi.setPara failed, paraLevel = " + Bidi.LEVEL_DEFAULT_RTL);
341            }
342            for (j = 0; j < length; j++) {
343                if ((k = bidiLine.getLevelAt(j)) !=
344                        (gotLevel = gotLevels[paraStart + j])) {
345                    errln("Checking paragraph concatenation: for paragraph[" +
346                          i + "], char[" + j + "] = 0x" +
347                          Utility.hex(src.charAt(paraStart + j)) +
348                          ", level = " + k + ", expected = " + gotLevel);
349                }
350            }
351        }
352
353        /* ensure that leading numerics in a paragraph are not treated as arabic
354           numerals because of arabic text in a preceding paragraph
355         */
356        src = text2;
357        bidi.orderParagraphsLTR(true);
358        try {
359            bidi.setPara(src, Bidi.RTL, null);
360        } catch (IllegalArgumentException e) {
361            errln("Bidi.setPara failed, paraLevel = " + Bidi.RTL);
362        }
363        try {
364            gotLevels = bidi.getLevels();
365        } catch (Exception e) {
366            errln("Error on Bidi.getLevels");
367            gotLevels = new byte[bidi.getLength()];
368            Arrays.fill(gotLevels, (byte)-1);
369        }
370        for (i = 0, length = src.length(); i < length; i++) {
371            if (gotLevels[i] != levels2[i]) {
372                errln("Checking leading numerics: for char " + i + "(0x" +
373                      Utility.hex(src.charAt(i)) + "), level = " +
374                      gotLevels[i] + ", expected = " + levels2[i]);
375            }
376        }
377
378        /* check handling of whitespace before end of paragraph separator when
379         * orderParagraphsLTR==TRUE, when last paragraph has, and lacks, a terminating B
380         */
381        chars = src.toCharArray();
382        Arrays.fill(chars, '\u0020');
383        bidi.orderParagraphsLTR(true);
384        for (i = 0x001c; i <= 0x0020; i += (0x0020-0x001c)) {
385            chars[4] = (char)i;         /* with and without terminating B */
386            for (j = 0x0041; j <= 0x05d0; j += (0x05d0-0x0041)) {
387                chars[0] = (char)j;     /* leading 'A' or Alef */
388                src = new String(chars);
389                for (gotLevel = 4; gotLevel <= 5; gotLevel++) {
390                    /* test even and odd paraLevel */
391                    try {
392                        bidi.setPara(src, gotLevel, null);
393                    } catch (IllegalArgumentException e) {
394                        errln("Bidi.setPara failed, paraLevel = " + gotLevel);
395                    }
396                    try {
397                        gotLevels = bidi.getLevels();
398                    } catch (Exception e) {
399                        errln("Error on Bidi.getLevels");
400                        gotLevels = new byte[bidi.getLength()];
401                        Arrays.fill(gotLevels, (byte)-1);
402                    }
403                    for (k = 1; k <= 3; k++) {
404                        if (gotLevels[k] != gotLevel) {
405                            errln("Checking trailing spaces for leading char 0x" +
406                                  Utility.hex(chars[0]) + ", last_char = " +
407                                  Utility.hex(chars[4]) + ", index = " + k +
408                                  "level = " + gotLevels[k] +
409                                  ", expected = " + gotLevel);
410                        }
411                    }
412                }
413            }
414        }
415
416        /* check default orientation when inverse bidi and paragraph starts
417         * with LTR strong char and ends with RTL strong char, with and without
418         * a terminating B
419         */
420        bidi.setReorderingMode(Bidi.REORDER_INVERSE_LIKE_DIRECT);
421        bidi.setPara("abc \u05d2\u05d1\n", Bidi.LEVEL_DEFAULT_LTR, null);
422        String out = bidi.writeReordered(0);
423        assertEquals("\nInvalid output", "\u05d1\u05d2 abc\n", out);
424        bidi.setPara("abc \u05d2\u05d1", Bidi.LEVEL_DEFAULT_LTR, null);
425        out = bidi.writeReordered(0);
426        assertEquals("\nInvalid output #1", "\u05d1\u05d2 abc", out);
427
428        /* check multiple paragraphs together with explicit levels
429         */
430        bidi.setReorderingMode(Bidi.REORDER_DEFAULT);
431        gotLevels = new byte[] {0,0,0,0,0,0,0,0,0,0};
432        bidi.setPara("ab\u05d1\u05d2\n\u05d3\u05d4123", Bidi.LTR, gotLevels);
433        out = bidi.writeReordered(0);
434        assertEquals("\nInvalid output #2", "ab\u05d2\u05d1\n123\u05d4\u05d3", out);
435        assertEquals("\nInvalid number of paras", 2, bidi.countParagraphs());
436
437        logln("\nExiting TestMultipleParagraphs\n");
438
439        /* check levels in multiple paragraphs with default para level
440         */
441        bidi = new Bidi();
442        bidi.setPara(multiparaTestString, Bidi.LEVEL_DEFAULT_LTR, null);
443        try {
444            gotLevels = bidi.getLevels();
445        } catch (Exception e) {
446            errln("Error on Bidi.getLevels for multiparaTestString");
447            return;
448        }
449        for (i = 0; i < multiparaTestString.length; i++) {
450            if (gotLevels[i] != multiparaTestLevels[i]) {
451                errln("Error on level for multiparaTestString at index " + i +
452                      ", expected=" + multiparaTestLevels[i] +
453                      ", actual=" + gotLevels[i]);
454            }
455        }
456    }
457}
458