1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.text.cts;
18 
19 import static android.text.TextDirectionHeuristics.LTR;
20 import static android.text.TextDirectionHeuristics.RTL;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assert.fail;
27 
28 import android.content.Context;
29 import android.graphics.Color;
30 import android.graphics.Rect;
31 import android.graphics.Typeface;
32 import android.support.test.InstrumentationRegistry;
33 import android.support.test.filters.SmallTest;
34 import android.support.test.runner.AndroidJUnit4;
35 import android.text.Layout;
36 import android.text.PrecomputedText;
37 import android.text.PrecomputedText.Params;
38 import android.text.Spannable;
39 import android.text.SpannableStringBuilder;
40 import android.text.Spanned;
41 import android.text.TextDirectionHeuristics;
42 import android.text.TextPaint;
43 import android.text.style.BackgroundColorSpan;
44 import android.text.style.LocaleSpan;
45 import android.text.style.TextAppearanceSpan;
46 
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 import java.util.Locale;
51 
52 @SmallTest
53 @RunWith(AndroidJUnit4.class)
54 public class PrecomputedTextTest {
55 
56     private static final CharSequence NULL_CHAR_SEQUENCE = null;
57     private static final String STRING = "Hello, World!";
58     private static final String MULTIPARA_STRING = "Hello,\nWorld!";
59 
60     private static final int SPAN_START = 3;
61     private static final int SPAN_END = 7;
62     private static final LocaleSpan SPAN = new LocaleSpan(Locale.US);
63     private static final Spanned SPANNED;
64     static {
65         final SpannableStringBuilder ssb = new SpannableStringBuilder(STRING);
ssb.setSpan(SPAN, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)66         ssb.setSpan(SPAN, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
67         SPANNED = ssb;
68     }
69 
70     private static final TextPaint PAINT = new TextPaint();
71 
72     @Test
testParams_create()73     public void testParams_create() {
74         assertNotNull(new Params.Builder(PAINT).build());
75         assertNotNull(new Params.Builder(PAINT)
76                 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE).build());
77         assertNotNull(new Params.Builder(PAINT)
78                 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
79                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL).build());
80         assertNotNull(new Params.Builder(PAINT)
81                 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
82                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
83                 .setTextDirection(LTR).build());
84     }
85 
86     @Test
testParams_SetGet()87     public void testParams_SetGet() {
88         assertEquals(Layout.BREAK_STRATEGY_SIMPLE, new Params.Builder(PAINT)
89                 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE).build().getBreakStrategy());
90         assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, new Params.Builder(PAINT)
91                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE).build()
92                         .getHyphenationFrequency());
93         assertEquals(RTL, new Params.Builder(PAINT).setTextDirection(RTL).build()
94                 .getTextDirection());
95     }
96 
97     @Test
testParams_GetDefaultValues()98     public void testParams_GetDefaultValues() {
99         assertEquals(Layout.BREAK_STRATEGY_HIGH_QUALITY,
100                      new Params.Builder(PAINT).build().getBreakStrategy());
101         assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL,
102                      new Params.Builder(PAINT).build().getHyphenationFrequency());
103         assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR,
104                      new Params.Builder(PAINT).build().getTextDirection());
105     }
106 
107     @Test
testParams_equals()108     public void testParams_equals() {
109         final Params base = new Params.Builder(PAINT)
110                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
111                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
112                 .setTextDirection(LTR).build();
113 
114         assertFalse(base.equals(null));
115         assertTrue(base.equals(base));
116         assertFalse(base.equals(new Object()));
117 
118         Params other = new Params.Builder(PAINT)
119                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
120                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
121                 .setTextDirection(LTR).build();
122         assertTrue(base.equals(other));
123         assertTrue(other.equals(base));
124         assertEquals(base.hashCode(), other.hashCode());
125 
126         other = new Params.Builder(PAINT)
127                 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
128                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
129                 .setTextDirection(LTR).build();
130         assertFalse(base.equals(other));
131         assertFalse(other.equals(base));
132 
133         other = new Params.Builder(PAINT)
134                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
135                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
136                 .setTextDirection(LTR).build();
137         assertFalse(base.equals(other));
138         assertFalse(other.equals(base));
139 
140 
141         other = new Params.Builder(PAINT)
142                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
143                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
144                 .setTextDirection(RTL).build();
145         assertFalse(base.equals(other));
146         assertFalse(other.equals(base));
147 
148 
149         TextPaint anotherPaint = new TextPaint(PAINT);
150         anotherPaint.setTextSize(PAINT.getTextSize() * 2.0f);
151         other = new Params.Builder(anotherPaint)
152                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
153                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
154                 .setTextDirection(LTR).build();
155         assertFalse(base.equals(other));
156         assertFalse(other.equals(base));
157 
158     }
159 
160     @Test
testCreate_withNull()161     public void testCreate_withNull() {
162         final Params param = new Params.Builder(PAINT).build();
163         try {
164             PrecomputedText.create(NULL_CHAR_SEQUENCE, param);
165             fail();
166         } catch (NullPointerException e) {
167             // pass
168         }
169         try {
170             PrecomputedText.create(STRING, null);
171             fail();
172         } catch (NullPointerException e) {
173             // pass
174         }
175     }
176 
177     @Test
testCharSequenceInteface()178     public void testCharSequenceInteface() {
179         final Params param = new Params.Builder(PAINT).build();
180         final CharSequence s = PrecomputedText.create(STRING, param);
181         assertEquals(STRING.length(), s.length());
182         assertEquals('H', s.charAt(0));
183         assertEquals('e', s.charAt(1));
184         assertEquals('l', s.charAt(2));
185         assertEquals('l', s.charAt(3));
186         assertEquals('o', s.charAt(4));
187         assertEquals(',', s.charAt(5));
188         assertEquals("Hello, World!", s.toString());
189 
190         final CharSequence s3 = s.subSequence(0, 3);
191         assertEquals(3, s3.length());
192         assertEquals('H', s3.charAt(0));
193         assertEquals('e', s3.charAt(1));
194         assertEquals('l', s3.charAt(2));
195 
196     }
197 
198     @Test
testSpannedInterface_Spanned()199     public void testSpannedInterface_Spanned() {
200         final Params param = new Params.Builder(PAINT).build();
201         final Spanned s = PrecomputedText.create(SPANNED, param);
202         final LocaleSpan[] spans = s.getSpans(0, s.length(), LocaleSpan.class);
203         assertNotNull(spans);
204         assertEquals(1, spans.length);
205         assertEquals(SPAN, spans[0]);
206 
207         assertEquals(SPAN_START, s.getSpanStart(SPAN));
208         assertEquals(SPAN_END, s.getSpanEnd(SPAN));
209         assertTrue((s.getSpanFlags(SPAN) & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) != 0);
210 
211         assertEquals(SPAN_START, s.nextSpanTransition(0, s.length(), LocaleSpan.class));
212         assertEquals(SPAN_END, s.nextSpanTransition(SPAN_START, s.length(), LocaleSpan.class));
213     }
214 
215     @Test
testSpannedInterface_Spannable()216     public void testSpannedInterface_Spannable() {
217         final BackgroundColorSpan span = new BackgroundColorSpan(Color.RED);
218         final Params param = new Params.Builder(PAINT).build();
219         final Spannable s = PrecomputedText.create(STRING, param);
220         assertEquals(0, s.getSpans(0, s.length(), BackgroundColorSpan.class).length);
221 
222         s.setSpan(span, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
223 
224         final BackgroundColorSpan[] spans = s.getSpans(0, s.length(), BackgroundColorSpan.class);
225         assertEquals(SPAN_START, s.getSpanStart(span));
226         assertEquals(SPAN_END, s.getSpanEnd(span));
227         assertTrue((s.getSpanFlags(span) & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) != 0);
228 
229         assertEquals(SPAN_START, s.nextSpanTransition(0, s.length(), BackgroundColorSpan.class));
230         assertEquals(SPAN_END,
231                 s.nextSpanTransition(SPAN_START, s.length(), BackgroundColorSpan.class));
232 
233         s.removeSpan(span);
234         assertEquals(0, s.getSpans(0, s.length(), BackgroundColorSpan.class).length);
235     }
236 
237     @Test(expected = IllegalArgumentException.class)
testSpannedInterface_Spannable_setSpan_MetricsAffectingSpan()238     public void testSpannedInterface_Spannable_setSpan_MetricsAffectingSpan() {
239         final Params param = new Params.Builder(PAINT).build();
240         final Spannable s = PrecomputedText.create(SPANNED, param);
241         s.setSpan(SPAN, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
242     }
243 
244     @Test(expected = IllegalArgumentException.class)
testSpannedInterface_Spannable_removeSpan_MetricsAffectingSpan()245     public void testSpannedInterface_Spannable_removeSpan_MetricsAffectingSpan() {
246         final Params param = new Params.Builder(PAINT).build();
247         final Spannable s = PrecomputedText.create(SPANNED, param);
248         s.removeSpan(SPAN);
249     }
250 
251     @Test
testSpannedInterface_String()252     public void testSpannedInterface_String() {
253         final Params param = new Params.Builder(PAINT).build();
254         final Spanned s = PrecomputedText.create(STRING, param);
255         LocaleSpan[] spans = s.getSpans(0, s.length(), LocaleSpan.class);
256         assertNotNull(spans);
257         assertEquals(0, spans.length);
258 
259         assertEquals(-1, s.getSpanStart(SPAN));
260         assertEquals(-1, s.getSpanEnd(SPAN));
261         assertEquals(0, s.getSpanFlags(SPAN));
262 
263         assertEquals(s.length(), s.nextSpanTransition(0, s.length(), LocaleSpan.class));
264     }
265 
266     @Test
testGetParagraphCount()267     public void testGetParagraphCount() {
268         final Params param = new Params.Builder(PAINT).build();
269         final PrecomputedText pm = PrecomputedText.create(STRING, param);
270         assertEquals(1, pm.getParagraphCount());
271         assertEquals(0, pm.getParagraphStart(0));
272         assertEquals(STRING.length(), pm.getParagraphEnd(0));
273 
274         final PrecomputedText pm1 = PrecomputedText.create(MULTIPARA_STRING, param);
275         assertEquals(2, pm1.getParagraphCount());
276         assertEquals(0, pm1.getParagraphStart(0));
277         assertEquals(7, pm1.getParagraphEnd(0));
278         assertEquals(7, pm1.getParagraphStart(1));
279         assertEquals(pm1.length(), pm1.getParagraphEnd(1));
280     }
281 
282     @Test
testGetWidth()283     public void testGetWidth() {
284         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
285 
286         // The test font has following coverage and width.
287         // U+0020: 10em
288         // U+002E (.): 10em
289         // U+0043 (C): 100em
290         // U+0049 (I): 1em
291         // U+004C (L): 50em
292         // U+0056 (V): 5em
293         // U+0058 (X): 10em
294         // U+005F (_): 0em
295         // U+FFFD (invalid surrogate will be replaced to this): 7em
296         // U+10331 (\uD800\uDF31): 10em
297         final Typeface tf = new Typeface.Builder(context.getAssets(),
298                 "fonts/StaticLayoutLineBreakingTestFont.ttf").build();
299         final TextPaint paint = new TextPaint();
300         paint.setTypeface(tf);
301         paint.setTextSize(1);  // Make 1em = 1px
302 
303         final Params param = new Params.Builder(paint).build();
304         assertEquals(0.0f, PrecomputedText.create("", param).getWidth(0, 0), 0.0f);
305 
306         assertEquals(0.0f, PrecomputedText.create("I", param).getWidth(0, 0), 0.0f);
307         assertEquals(0.0f, PrecomputedText.create("I", param).getWidth(1, 1), 0.0f);
308         assertEquals(1.0f, PrecomputedText.create("I", param).getWidth(0, 1), 0.0f);
309 
310         assertEquals(0.0f, PrecomputedText.create("V", param).getWidth(0, 0), 0.0f);
311         assertEquals(0.0f, PrecomputedText.create("V", param).getWidth(1, 1), 0.0f);
312         assertEquals(5.0f, PrecomputedText.create("V", param).getWidth(0, 1), 0.0f);
313 
314         assertEquals(0.0f, PrecomputedText.create("IV", param).getWidth(0, 0), 0.0f);
315         assertEquals(0.0f, PrecomputedText.create("IV", param).getWidth(1, 1), 0.0f);
316         assertEquals(0.0f, PrecomputedText.create("IV", param).getWidth(2, 2), 0.0f);
317         assertEquals(1.0f, PrecomputedText.create("IV", param).getWidth(0, 1), 0.0f);
318         assertEquals(5.0f, PrecomputedText.create("IV", param).getWidth(1, 2), 0.0f);
319         assertEquals(6.0f, PrecomputedText.create("IV", param).getWidth(0, 2), 0.0f);
320 
321         assertEquals(0.0f, PrecomputedText.create("I\nV", param).getWidth(0, 0), 0.0f);
322         assertEquals(0.0f, PrecomputedText.create("I\nV", param).getWidth(1, 1), 0.0f);
323         assertEquals(0.0f, PrecomputedText.create("I\nV", param).getWidth(2, 2), 0.0f);
324         assertEquals(0.0f, PrecomputedText.create("I\nV", param).getWidth(3, 3), 0.0f);
325         assertEquals(1.0f, PrecomputedText.create("I\nV", param).getWidth(0, 1), 0.0f);
326         assertEquals(1.0f, PrecomputedText.create("I\nV", param).getWidth(0, 2), 0.0f);
327         assertEquals(5.0f, PrecomputedText.create("I\nV", param).getWidth(2, 3), 0.0f);
328     }
329 
330     @Test
testGetWidth_multiStyle()331     public void testGetWidth_multiStyle() {
332         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
333         final SpannableStringBuilder ssb = new SpannableStringBuilder("II");
334         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 1 /* text size */,
335                 null /* color */, null /* link color */), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
336         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */,
337                 null /* color */, null /* link color */), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
338 
339         final Typeface tf = new Typeface.Builder(context.getAssets(),
340                 "fonts/StaticLayoutLineBreakingTestFont.ttf").build();
341         final TextPaint paint = new TextPaint();
342         paint.setTypeface(tf);
343         paint.setTextSize(1);  // Make 1em = 1px
344 
345         final Params param = new Params.Builder(paint).build();
346 
347         assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(0, 0), 0.0f);
348         assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(1, 1), 0.0f);
349         assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(2, 2), 0.0f);
350 
351         assertEquals(1.0f, PrecomputedText.create(ssb, param).getWidth(0, 1), 0.0f);
352         assertEquals(5.0f, PrecomputedText.create(ssb, param).getWidth(1, 2), 0.0f);
353 
354         assertEquals(6.0f, PrecomputedText.create(ssb, param).getWidth(0, 2), 0.0f);
355     }
356 
357     @Test
testGetWidth_multiStyle2()358     public void testGetWidth_multiStyle2() {
359         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
360         final SpannableStringBuilder ssb = new SpannableStringBuilder("IVI");
361         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 1 /* text size */,
362                 null /* color */, null /* link color */), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
363         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */,
364                 null /* color */, null /* link color */), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
365         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */,
366                 null /* color */, null /* link color */), 2, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
367 
368         final Typeface tf = new Typeface.Builder(context.getAssets(),
369                 "fonts/StaticLayoutLineBreakingTestFont.ttf").build();
370         final TextPaint paint = new TextPaint();
371         paint.setTypeface(tf);
372         paint.setTextSize(1);  // Make 1em = 1px
373 
374         final Params param = new Params.Builder(paint).build();
375 
376         assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(0, 0), 0.0f);
377         assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(1, 1), 0.0f);
378         assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(2, 2), 0.0f);
379         assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(3, 3), 0.0f);
380 
381         assertEquals(1.0f, PrecomputedText.create(ssb, param).getWidth(0, 1), 0.0f);
382         assertEquals(25.0f, PrecomputedText.create(ssb, param).getWidth(1, 2), 0.0f);
383         assertEquals(5.0f, PrecomputedText.create(ssb, param).getWidth(2, 3), 0.0f);
384 
385         assertEquals(26.0f, PrecomputedText.create(ssb, param).getWidth(0, 2), 0.0f);
386         assertEquals(30.0f, PrecomputedText.create(ssb, param).getWidth(1, 3), 0.0f);
387         assertEquals(31.0f, PrecomputedText.create(ssb, param).getWidth(0, 3), 0.0f);
388     }
389 
390     @Test(expected = IllegalArgumentException.class)
testGetWidth_negative_start_offset()391     public void testGetWidth_negative_start_offset() {
392         final Params param = new Params.Builder(PAINT).build();
393         PrecomputedText.create("a", param).getWidth(-1, 0);
394     }
395 
396     @Test(expected = IllegalArgumentException.class)
testGetWidth_negative_end_offset()397     public void testGetWidth_negative_end_offset() {
398         final Params param = new Params.Builder(PAINT).build();
399         PrecomputedText.create("a", param).getWidth(0, -1);
400     }
401 
402     @Test(expected = IllegalArgumentException.class)
testGetWidth_index_out_of_bounds_start_offset()403     public void testGetWidth_index_out_of_bounds_start_offset() {
404         final Params param = new Params.Builder(PAINT).build();
405         PrecomputedText.create("a", param).getWidth(2, 2);
406     }
407 
408     @Test(expected = IllegalArgumentException.class)
testGetWidth_index_out_of_bounds_end_offset()409     public void testGetWidth_index_out_of_bounds_end_offset() {
410         final Params param = new Params.Builder(PAINT).build();
411         PrecomputedText.create("a", param).getWidth(0, 2);
412     }
413 
414     @Test(expected = IllegalArgumentException.class)
testGetWidth_reverse_offset()415     public void testGetWidth_reverse_offset() {
416         final Params param = new Params.Builder(PAINT).build();
417         PrecomputedText.create("a", param).getWidth(1, 0);
418     }
419 
420     @Test(expected = IllegalArgumentException.class)
testGetWidth_across_paragraph_boundary()421     public void testGetWidth_across_paragraph_boundary() {
422         final Params param = new Params.Builder(PAINT).build();
423         PrecomputedText.create("a\nb", param).getWidth(0, 3);
424     }
425 
426     @Test
testGetBounds()427     public void testGetBounds() {
428         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
429 
430         // The test font has following coverage and width.
431         // U+0020: 10em
432         // U+002E (.): 10em
433         // U+0043 (C): 100em
434         // U+0049 (I): 1em
435         // U+004C (L): 50em
436         // U+0056 (V): 5em
437         // U+0058 (X): 10em
438         // U+005F (_): 0em
439         // U+FFFD (invalid surrogate will be replaced to this): 7em
440         // U+10331 (\uD800\uDF31): 10em
441         final Typeface tf = new Typeface.Builder(context.getAssets(),
442                 "fonts/StaticLayoutLineBreakingTestFont.ttf").build();
443         final TextPaint paint = new TextPaint();
444         paint.setTypeface(tf);
445         paint.setTextSize(1);  // Make 1em = 1px
446 
447         final Params param = new Params.Builder(paint).build();
448         final Rect rect = new Rect();
449 
450         rect.set(0, 0, 0, 0);
451         PrecomputedText.create("", param).getBounds(0, 0, rect);
452         assertEquals(new Rect(0, 0, 0, 0), rect);
453 
454         rect.set(0, 0, 0, 0);
455         PrecomputedText.create("I", param).getBounds(0, 1, rect);
456         assertEquals(new Rect(0, -1, 1, 0), rect);
457 
458         rect.set(0, 0, 0, 0);
459         PrecomputedText.create("I", param).getBounds(1, 1, rect);
460         assertEquals(new Rect(0, 0, 0, 0), rect);
461 
462         rect.set(0, 0, 0, 0);
463         PrecomputedText.create("IV", param).getBounds(0, 0, rect);
464         assertEquals(new Rect(0, 0, 0, 0), rect);
465 
466         rect.set(0, 0, 0, 0);
467         PrecomputedText.create("IV", param).getBounds(0, 0, rect);
468         assertEquals(new Rect(0, 0, 0, 0), rect);
469 
470         rect.set(0, 0, 0, 0);
471         PrecomputedText.create("IV", param).getBounds(1, 1, rect);
472         assertEquals(new Rect(0, 0, 0, 0), rect);
473 
474         rect.set(0, 0, 0, 0);
475         PrecomputedText.create("IV", param).getBounds(2, 2, rect);
476         assertEquals(new Rect(0, 0, 0, 0), rect);
477 
478         rect.set(0, 0, 0, 0);
479         PrecomputedText.create("IV", param).getBounds(0, 1, rect);
480         assertEquals(new Rect(0, -1, 1, 0), rect);
481 
482         rect.set(0, 0, 0, 0);
483         PrecomputedText.create("IV", param).getBounds(1, 2, rect);
484         assertEquals(new Rect(0, -5, 5, 0), rect);
485 
486         rect.set(0, 0, 0, 0);
487         PrecomputedText.create("IV", param).getBounds(0, 2, rect);
488         assertEquals(new Rect(0, -5, 6, 0), rect);
489     }
490 
491     @Test
testGetBounds_multiStyle()492     public void testGetBounds_multiStyle() {
493         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
494         final SpannableStringBuilder ssb = new SpannableStringBuilder("II");
495         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 1 /* text size */,
496                 null /* color */, null /* link color */), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
497         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */,
498                 null /* color */, null /* link color */), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
499 
500         final Typeface tf = new Typeface.Builder(context.getAssets(),
501                 "fonts/StaticLayoutLineBreakingTestFont.ttf").build();
502         final TextPaint paint = new TextPaint();
503         paint.setTypeface(tf);
504         paint.setTextSize(1);  // Make 1em = 1px
505 
506         final Params param = new Params.Builder(paint).build();
507         final Rect rect = new Rect();
508 
509         rect.set(0, 0, 0, 0);
510         PrecomputedText.create(ssb, param).getBounds(0, 0, rect);
511         assertEquals(new Rect(0, 0, 0, 0), rect);
512 
513         rect.set(0, 0, 0, 0);
514         PrecomputedText.create(ssb, param).getBounds(0, 1, rect);
515         assertEquals(new Rect(0, -1, 1, 0), rect);
516 
517         rect.set(0, 0, 0, 0);
518         PrecomputedText.create(ssb, param).getBounds(1, 2, rect);
519         assertEquals(new Rect(0, -5, 5, 0), rect);
520 
521         rect.set(0, 0, 0, 0);
522         PrecomputedText.create(ssb, param).getBounds(0, 2, rect);
523         assertEquals(new Rect(0, -5, 6, 0), rect);
524     }
525 
526     @Test
testGetBounds_multiStyle2()527     public void testGetBounds_multiStyle2() {
528         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
529         final SpannableStringBuilder ssb = new SpannableStringBuilder("IVI");
530         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 1 /* text size */,
531                 null /* color */, null /* link color */), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
532         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */,
533                 null /* color */, null /* link color */), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
534         ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */,
535                 null /* color */, null /* link color */), 2, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
536 
537         final Typeface tf = new Typeface.Builder(context.getAssets(),
538                 "fonts/StaticLayoutLineBreakingTestFont.ttf").build();
539         final TextPaint paint = new TextPaint();
540         paint.setTypeface(tf);
541         paint.setTextSize(1);  // Make 1em = 1px
542 
543         final Params param = new Params.Builder(paint).build();
544         final Rect rect = new Rect();
545 
546         rect.set(0, 0, 0, 0);
547         PrecomputedText.create(ssb, param).getBounds(0, 0, rect);
548         assertEquals(new Rect(0, 0, 0, 0), rect);
549 
550         rect.set(0, 0, 0, 0);
551         PrecomputedText.create(ssb, param).getBounds(0, 1, rect);
552         assertEquals(new Rect(0, -1, 1, 0), rect);
553 
554         rect.set(0, 0, 0, 0);
555         PrecomputedText.create(ssb, param).getBounds(1, 2, rect);
556         assertEquals(new Rect(0, -25, 25, 0), rect);
557 
558         rect.set(0, 0, 0, 0);
559         PrecomputedText.create(ssb, param).getBounds(2, 3, rect);
560         assertEquals(new Rect(0, -5, 5, 0), rect);
561 
562         rect.set(0, 0, 0, 0);
563         PrecomputedText.create(ssb, param).getBounds(0, 2, rect);
564         assertEquals(new Rect(0, -25, 26, 0), rect);
565 
566         rect.set(0, 0, 0, 0);
567         PrecomputedText.create(ssb, param).getBounds(1, 3, rect);
568         assertEquals(new Rect(0, -25, 30, 0), rect);
569 
570         rect.set(0, 0, 0, 0);
571         PrecomputedText.create(ssb, param).getBounds(0, 3, rect);
572         assertEquals(new Rect(0, -25, 31, 0), rect);
573     }
574 
575     @Test(expected = IllegalArgumentException.class)
testGetBounds_negative_start_offset()576     public void testGetBounds_negative_start_offset() {
577         final Rect rect = new Rect();
578         final Params param = new Params.Builder(PAINT).build();
579         PrecomputedText.create("a", param).getBounds(-1, 0, rect);
580     }
581 
582     @Test(expected = IllegalArgumentException.class)
testGetBounds_negative_end_offset()583     public void testGetBounds_negative_end_offset() {
584         final Rect rect = new Rect();
585         final Params param = new Params.Builder(PAINT).build();
586         PrecomputedText.create("a", param).getBounds(0, -1, rect);
587     }
588 
589     @Test(expected = IllegalArgumentException.class)
testGetBounds_index_out_of_bounds_start_offset()590     public void testGetBounds_index_out_of_bounds_start_offset() {
591         final Rect rect = new Rect();
592         final Params param = new Params.Builder(PAINT).build();
593         PrecomputedText.create("a", param).getBounds(2, 2, rect);
594     }
595 
596     @Test(expected = IllegalArgumentException.class)
testGetBounds_index_out_of_bounds_end_offset()597     public void testGetBounds_index_out_of_bounds_end_offset() {
598         final Rect rect = new Rect();
599         final Params param = new Params.Builder(PAINT).build();
600         PrecomputedText.create("a", param).getBounds(0, 2, rect);
601     }
602 
603     @Test(expected = IllegalArgumentException.class)
testGetBounds_reverse_offset()604     public void testGetBounds_reverse_offset() {
605         final Rect rect = new Rect();
606         final Params param = new Params.Builder(PAINT).build();
607         PrecomputedText.create("a", param).getBounds(1, 0, rect);
608     }
609 
610     @Test(expected = NullPointerException.class)
testGetBounds_null_rect()611     public void testGetBounds_null_rect() {
612         final Params param = new Params.Builder(PAINT).build();
613         PrecomputedText.create("a", param).getBounds(0, 1, null);
614     }
615 
616     @Test(expected = IllegalArgumentException.class)
testGetBounds_across_paragraph_boundary()617     public void testGetBounds_across_paragraph_boundary() {
618         final Rect rect = new Rect();
619         final Params param = new Params.Builder(PAINT).build();
620         PrecomputedText.create("a\nb", param).getBounds(0, 3, rect);
621     }
622 
623 }
624