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