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