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