1 /* 2 * Copyright (C) 2016 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 android.text.Layout.Alignment.ALIGN_NORMAL; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 import static org.junit.Assert.assertTrue; 26 27 import android.graphics.Canvas; 28 import android.graphics.Paint; 29 import android.platform.test.annotations.Presubmit; 30 import android.support.test.filters.SmallTest; 31 import android.support.test.runner.AndroidJUnit4; 32 import android.text.style.ReplacementSpan; 33 import android.util.ArraySet; 34 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 38 @Presubmit 39 @SmallTest 40 @RunWith(AndroidJUnit4.class) 41 public class DynamicLayoutTest { 42 private static final int WIDTH = 10000; 43 44 @Test 45 public void testGetBlocksAlwaysNeedToBeRedrawn_en() { 46 final SpannableStringBuilder builder = new SpannableStringBuilder(); 47 final DynamicLayout layout = new DynamicLayout(builder, new TextPaint(), WIDTH, 48 ALIGN_NORMAL, 0, 0, false); 49 50 assertNull(layout.getBlocksAlwaysNeedToBeRedrawn()); 51 52 builder.append("abcd efg\n"); 53 builder.append("hijk lmn\n"); 54 assertNull(layout.getBlocksAlwaysNeedToBeRedrawn()); 55 56 builder.delete(0, builder.length()); 57 assertNull(layout.getBlocksAlwaysNeedToBeRedrawn()); 58 } 59 60 private class MockReplacementSpan extends ReplacementSpan { 61 public int getSize(Paint paint, CharSequence text, int start, int end, 62 Paint.FontMetricsInt fm) { 63 return 10; 64 } 65 66 public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, 67 int y, int bottom, Paint paint) { } 68 } 69 70 @Test 71 public void testGetBlocksAlwaysNeedToBeRedrawn_replacementSpan() { 72 final SpannableStringBuilder builder = new SpannableStringBuilder(); 73 final DynamicLayout layout = new DynamicLayout(builder, new TextPaint(), WIDTH, 74 ALIGN_NORMAL, 0, 0, false); 75 76 assertNull(layout.getBlocksAlwaysNeedToBeRedrawn()); 77 78 builder.append("abcd efg\n"); 79 builder.append("hijk lmn\n"); 80 assertNull(layout.getBlocksAlwaysNeedToBeRedrawn()); 81 82 builder.setSpan(new MockReplacementSpan(), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 83 assertNotNull(layout.getBlocksAlwaysNeedToBeRedrawn()); 84 assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0)); 85 86 builder.setSpan(new MockReplacementSpan(), 9, 13, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 87 assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0)); 88 assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(1)); 89 90 builder.delete(9, 13); 91 assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0)); 92 assertFalse(layout.getBlocksAlwaysNeedToBeRedrawn().contains(1)); 93 94 builder.delete(0, 4); 95 assertFalse(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0)); 96 assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().isEmpty()); 97 } 98 99 @Test 100 public void testGetBlocksAlwaysNeedToBeRedrawn_thai() { 101 final SpannableStringBuilder builder = new SpannableStringBuilder(); 102 final DynamicLayout layout = new DynamicLayout(builder, new TextPaint(), WIDTH, 103 ALIGN_NORMAL, 0, 0, false); 104 105 assertNull(layout.getBlocksAlwaysNeedToBeRedrawn()); 106 107 builder.append("\u0E22\u0E34\u0E19\u0E14\u0E35\u0E15\u0E49\u0E2D\u0E19\u0E23\u0E31\u0E1A"); 108 builder.append("\u0E2A\u0E39\u0E48"); 109 assertNull(layout.getBlocksAlwaysNeedToBeRedrawn()); 110 111 builder.append("\u0E48\u0E48\u0E48\u0E48\u0E48"); 112 assertNotNull(layout.getBlocksAlwaysNeedToBeRedrawn()); 113 assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0)); 114 115 builder.delete(builder.length() -5, builder.length()); 116 assertFalse(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0)); 117 assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().isEmpty()); 118 } 119 120 @Test 121 public void testGetLineExtra_withoutLinespacing() { 122 final SpannableStringBuilder text = new SpannableStringBuilder("a\nb\nc"); 123 final TextPaint textPaint = new TextPaint(); 124 125 // create a StaticLayout to check against 126 final StaticLayout staticLayout = StaticLayout.Builder.obtain(text, 0, 127 text.length(), textPaint, WIDTH) 128 .setAlignment(ALIGN_NORMAL) 129 .setIncludePad(false) 130 .build(); 131 132 // create the DynamicLayout 133 final DynamicLayout dynamicLayout = new DynamicLayout(text, 134 textPaint, 135 WIDTH, 136 ALIGN_NORMAL, 137 1f /*spacingMultiplier*/, 138 0 /*spacingAdd*/, 139 false /*includepad*/); 140 141 final int lineCount = staticLayout.getLineCount(); 142 assertEquals(lineCount, dynamicLayout.getLineCount()); 143 for (int i = 0; i < lineCount; i++) { 144 assertEquals(staticLayout.getLineExtra(i), dynamicLayout.getLineExtra(i)); 145 } 146 } 147 148 @Test 149 public void testGetLineExtra_withLinespacing() { 150 final SpannableStringBuilder text = new SpannableStringBuilder("a\nb\nc"); 151 final TextPaint textPaint = new TextPaint(); 152 final float spacingMultiplier = 2f; 153 final float spacingAdd = 4; 154 155 // create a StaticLayout to check against 156 final StaticLayout staticLayout = StaticLayout.Builder.obtain(text, 0, 157 text.length(), textPaint, WIDTH) 158 .setAlignment(ALIGN_NORMAL) 159 .setIncludePad(false) 160 .setLineSpacing(spacingAdd, spacingMultiplier) 161 .build(); 162 163 // create the DynamicLayout 164 final DynamicLayout dynamicLayout = new DynamicLayout(text, 165 textPaint, 166 WIDTH, 167 ALIGN_NORMAL, 168 spacingMultiplier, 169 spacingAdd, 170 false /*includepad*/); 171 172 final int lineCount = staticLayout.getLineCount(); 173 assertEquals(lineCount, dynamicLayout.getLineCount()); 174 for (int i = 0; i < lineCount - 1; i++) { 175 assertEquals(staticLayout.getLineExtra(i), dynamicLayout.getLineExtra(i)); 176 } 177 } 178 179 @Test(expected = IndexOutOfBoundsException.class) 180 public void testGetLineExtra_withNegativeValue() { 181 final DynamicLayout layout = new DynamicLayout("", new TextPaint(), 10 /*width*/, 182 ALIGN_NORMAL, 1.0f /*spacingMultiplier*/, 0f /*spacingAdd*/, false /*includepad*/); 183 layout.getLineExtra(-1); 184 } 185 186 @Test(expected = IndexOutOfBoundsException.class) 187 public void testGetLineExtra_withParamGreaterThanLineCount() { 188 final DynamicLayout layout = new DynamicLayout("", new TextPaint(), 10 /*width*/, 189 ALIGN_NORMAL, 1.0f /*spacingMultiplier*/, 0f /*spacingAdd*/, false /*includepad*/); 190 layout.getLineExtra(100); 191 } 192 193 @Test 194 public void testReflow_afterSpannableEdit() { 195 final String text = "a\nb:\uD83C\uDF1A c \n\uD83C\uDF1A"; 196 final int length = text.length(); 197 final SpannableStringBuilder spannable = new SpannableStringBuilder(text); 198 spannable.setSpan(new MockReplacementSpan(), 4, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 199 spannable.setSpan(new MockReplacementSpan(), 10, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 200 201 final DynamicLayout layout = new DynamicLayout(spannable, new TextPaint(), WIDTH, 202 ALIGN_NORMAL, 1.0f /*spacingMultiplier*/, 0f /*spacingAdd*/, false /*includepad*/); 203 204 spannable.delete(8, 9); 205 spannable.replace(7, 8, "ch"); 206 207 layout.reflow(spannable, 0, length, length); 208 final ArraySet<Integer> blocks = layout.getBlocksAlwaysNeedToBeRedrawn(); 209 for (Integer value : blocks) { 210 assertTrue("Block index should not be negative", value >= 0); 211 } 212 } 213 214 @Test 215 public void testFallbackLineSpacing() { 216 // All glyphs in the fonts are 1em wide. 217 final String[] testFontFiles = { 218 // ascent == 1em, descent == 2em, only supports 'a' and space 219 "ascent1em-descent2em.ttf", 220 // ascent == 3em, descent == 4em, only supports 'b' 221 "ascent3em-descent4em.ttf" 222 }; 223 final String xml = "<?xml version='1.0' encoding='UTF-8'?>" 224 + "<familyset>" 225 + " <family name='sans-serif'>" 226 + " <font weight='400' style='normal'>ascent1em-descent2em.ttf</font>" 227 + " </family>" 228 + " <family>" 229 + " <font weight='400' style='normal'>ascent3em-descent4em.ttf</font>" 230 + " </family>" 231 + "</familyset>"; 232 233 try (FontFallbackSetup setup = 234 new FontFallbackSetup("DynamicLayout", testFontFiles, xml)) { 235 final TextPaint paint = setup.getPaintFor("sans-serif"); 236 final int textSize = 100; 237 paint.setTextSize(textSize); 238 assertEquals(-textSize, paint.ascent(), 0.0f); 239 assertEquals(2 * textSize, paint.descent(), 0.0f); 240 241 final int paraWidth = 5 * textSize; 242 final String text = "aaaaa aabaa aaaaa"; // This should result in three lines. 243 244 // Old line spacing. All lines should get their ascent and descents from the first font. 245 DynamicLayout layout = DynamicLayout.Builder 246 .obtain(text, paint, paraWidth) 247 .setIncludePad(false) 248 .setUseLineSpacingFromFallbacks(false) 249 .build(); 250 assertEquals(3, layout.getLineCount()); 251 assertEquals(-textSize, layout.getLineAscent(0)); 252 assertEquals(2 * textSize, layout.getLineDescent(0)); 253 assertEquals(-textSize, layout.getLineAscent(1)); 254 assertEquals(2 * textSize, layout.getLineDescent(1)); 255 assertEquals(-textSize, layout.getLineAscent(2)); 256 assertEquals(2 * textSize, layout.getLineDescent(2)); 257 258 // New line spacing. The second line has a 'b', so it needs more ascent and descent. 259 layout = DynamicLayout.Builder 260 .obtain(text, paint, paraWidth) 261 .setIncludePad(false) 262 .setUseLineSpacingFromFallbacks(true) 263 .build(); 264 assertEquals(3, layout.getLineCount()); 265 assertEquals(-textSize, layout.getLineAscent(0)); 266 assertEquals(2 * textSize, layout.getLineDescent(0)); 267 assertEquals(-3 * textSize, layout.getLineAscent(1)); 268 assertEquals(4 * textSize, layout.getLineDescent(1)); 269 assertEquals(-textSize, layout.getLineAscent(2)); 270 assertEquals(2 * textSize, layout.getLineDescent(2)); 271 272 // The default is the old line spacing, for backward compatibility. 273 layout = DynamicLayout.Builder 274 .obtain(text, paint, paraWidth) 275 .setIncludePad(false) 276 .build(); 277 assertEquals(3, layout.getLineCount()); 278 assertEquals(-textSize, layout.getLineAscent(0)); 279 assertEquals(2 * textSize, layout.getLineDescent(0)); 280 assertEquals(-textSize, layout.getLineAscent(1)); 281 assertEquals(2 * textSize, layout.getLineDescent(1)); 282 assertEquals(-textSize, layout.getLineAscent(2)); 283 assertEquals(2 * textSize, layout.getLineDescent(2)); 284 } 285 } 286 287 @Test 288 public void testBuilder_defaultTextDirection() { 289 final DynamicLayout.Builder builder = DynamicLayout.Builder 290 .obtain("", new TextPaint(), WIDTH); 291 final DynamicLayout layout = builder.build(); 292 assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, layout.getTextDirectionHeuristic()); 293 } 294 295 @Test 296 public void testBuilder_setTextDirection() { 297 final DynamicLayout.Builder builder = DynamicLayout.Builder 298 .obtain("", new TextPaint(), WIDTH) 299 .setTextDirection(TextDirectionHeuristics.ANYRTL_LTR); 300 final DynamicLayout layout = builder.build(); 301 assertEquals(TextDirectionHeuristics.ANYRTL_LTR, layout.getTextDirectionHeuristic()); 302 } 303 } 304