1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package android.text;
18
19import static org.junit.Assert.fail;
20
21import android.support.test.filters.SmallTest;
22import android.support.test.runner.AndroidJUnit4;
23import android.text.Layout.Directions;
24import android.text.StaticLayoutTest.LayoutBuilder;
25
26import java.util.Arrays;
27import java.util.Formatter;
28import org.junit.Test;
29import org.junit.runner.RunWith;
30
31@SmallTest
32@RunWith(AndroidJUnit4.class)
33public class StaticLayoutDirectionsTest {
34    private static final char ALEF = '\u05d0';
35
36    private static Directions dirs(int ... dirs) {
37        return new Directions(dirs);
38    }
39
40    // constants from Layout that are package-protected
41    private static final int RUN_LENGTH_MASK = 0x03ffffff;
42    private static final int RUN_LEVEL_SHIFT = 26;
43    private static final int RUN_LEVEL_MASK = 0x3f;
44    private static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
45
46    private static final Directions DIRS_ALL_LEFT_TO_RIGHT =
47        new Directions(new int[] { 0, RUN_LENGTH_MASK });
48    private static final Directions DIRS_ALL_RIGHT_TO_LEFT =
49        new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
50
51    private static final int LVL1_1 = 1 | (1 << RUN_LEVEL_SHIFT);
52    private static final int LVL2_1 = 1 | (2 << RUN_LEVEL_SHIFT);
53    private static final int LVL2_2 = 2 | (2 << RUN_LEVEL_SHIFT);
54
55    private static String[] texts = {
56        "",
57        " ",
58        "a",
59        "a1",
60        "aA",
61        "a1b",
62        "a1A",
63        "aA1",
64        "aAb",
65        "aA1B",
66        "aA1B2",
67
68        // rtl
69        "A",
70        "A1",
71        "Aa",
72        "A1B",
73        "A1a",
74        "Aa1",
75        "AaB"
76    };
77
78    // Expected directions are an array of start/length+level pairs,
79    // in visual order from the leading margin.
80    private static Directions[] expected = {
81        DIRS_ALL_LEFT_TO_RIGHT,
82        DIRS_ALL_LEFT_TO_RIGHT,
83        DIRS_ALL_LEFT_TO_RIGHT,
84        DIRS_ALL_LEFT_TO_RIGHT,
85        dirs(0, 1, 1, LVL1_1),
86        DIRS_ALL_LEFT_TO_RIGHT,
87        dirs(0, 2, 2, LVL1_1),
88        dirs(0, 1, 2, LVL2_1, 1, LVL1_1),
89        dirs(0, 1, 1, LVL1_1, 2, 1),
90        dirs(0, 1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
91        dirs(0, 1, 4, LVL2_1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
92
93        // rtl
94        DIRS_ALL_RIGHT_TO_LEFT,
95        dirs(0, LVL1_1, 1, LVL2_1),
96        dirs(0, LVL1_1, 1, LVL2_1),
97        dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
98        dirs(0, LVL1_1, 1, LVL2_2),
99        dirs(0, LVL1_1, 1, LVL2_2),
100        dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
101    };
102
103    private static String pseudoBidiToReal(String src) {
104        char[] chars = src.toCharArray();
105        for (int j = 0; j < chars.length; ++j) {
106            char c = chars[j];
107            if (c >= 'A' && c <= 'D') {
108                chars[j] = (char)(ALEF + c - 'A');
109            }
110        }
111
112        return new String(chars, 0, chars.length);
113    }
114
115    @Test
116    public void testDirections() {
117        StringBuilder buf = new StringBuilder("\n");
118        Formatter f = new Formatter(buf);
119
120        LayoutBuilder b = StaticLayoutTest.builder();
121        for (int i = 0; i < texts.length; ++i) {
122            b.setText(pseudoBidiToReal(texts[i]));
123            checkDirections(b.build(), i, b.text, expected, f);
124        }
125        if (buf.length() > 1) {
126            fail(buf.toString());
127        }
128    }
129
130    @Test
131    public void testTrailingWhitespace() {
132        LayoutBuilder b = StaticLayoutTest.builder();
133        b.setText(pseudoBidiToReal("Ab   c"));
134        float width = b.paint.measureText(b.text, 0, 5);  // exclude 'c'
135        b.setWidth(Math.round(width));
136        Layout l = b.build();
137        if (l.getLineCount() != 2) {
138            throw new RuntimeException("expected 2 lines, got: " + l.getLineCount());
139        }
140        Directions result = l.getLineDirections(0);
141        Directions expected = dirs(0, LVL1_1, 1, LVL2_1, 2, 3 | (1 << Layout.RUN_LEVEL_SHIFT));
142        expectDirections("split line", expected, result);
143    }
144
145    @Test
146    public void testNextToRightOf() {
147        LayoutBuilder b = StaticLayoutTest.builder();
148        b.setText(pseudoBidiToReal("aA1B2"));
149        // visual a2B1A positions 04321
150        // 0: |a2B1A, strong is sol, after -> 0
151        // 1: a|2B1A, strong is a, after ->, 1
152        // 2: a2|B1A, strong is B, after -> 4
153        // 3: a2B|1A, strong is B, before -> 3
154        // 4: a2B1|A, strong is A, after -> 2
155        // 5: a2B1A|, strong is eol, before -> 5
156        int[] expected = { 0, 1, 4, 3, 2, 5 };
157        Layout l = b.build();
158        int n = 0;
159        for (int i = 1; i < expected.length; ++i) {
160            int t = l.getOffsetToRightOf(n);
161            if (t != expected[i]) {
162                fail("offset[" + i + "] to right of: " + n + " expected: " +
163                        expected[i] + " got: " + t);
164            }
165            n = t;
166        }
167    }
168
169    @Test
170    public void testNextToLeftOf() {
171        LayoutBuilder b = StaticLayoutTest.builder();
172        b.setText(pseudoBidiToReal("aA1B2"));
173        int[] expected = { 0, 1, 4, 3, 2, 5 };
174        Layout l = b.build();
175        int n = 5;
176        for (int i = expected.length - 1; --i >= 0;) {
177            int t = l.getOffsetToLeftOf(n);
178            if (t != expected[i]) {
179                fail("offset[" + i + "] to left of: " + n + " expected: " +
180                        expected[i] + " got: " + t);
181            }
182            n = t;
183        }
184    }
185
186    // utility for displaying arrays in hex
187    private static String hexArray(int[] array) {
188        StringBuilder sb = new StringBuilder();
189        sb.append('{');
190        for (int i : array) {
191            if (sb.length() > 1) {
192                sb.append(", ");
193            }
194            sb.append(Integer.toHexString(i));
195        }
196        sb.append('}');
197        return sb.toString();
198    }
199
200    private void checkDirections(Layout l, int i, String text,
201            Directions[] expectedDirs, Formatter f) {
202        Directions expected = expectedDirs[i];
203        Directions result = l.getLineDirections(0);
204        if (!Arrays.equals(expected.mDirections, result.mDirections)) {
205            f.format("%n[%2d] '%s', %s != %s", i, text,
206                    hexArray(expected.mDirections),
207                    hexArray(result.mDirections));
208        }
209    }
210
211    private void expectDirections(String msg, Directions expected, Directions result) {
212        if (!Arrays.equals(expected.mDirections, result.mDirections)) {
213            fail("expected: " + hexArray(expected.mDirections) +
214                    " got: " + hexArray(result.mDirections));
215        }
216    }
217}
218