1 /* 2 * Copyright (C) 2008 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 com.android.compatibility.common.util.WidgetTestUtils.sameCharSequence; 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.assertSame; 26 import static org.junit.Assert.assertTrue; 27 import static org.mockito.Matchers.any; 28 import static org.mockito.Matchers.anyFloat; 29 import static org.mockito.Matchers.anyInt; 30 import static org.mockito.Matchers.eq; 31 import static org.mockito.Mockito.reset; 32 import static org.mockito.Mockito.spy; 33 import static org.mockito.Mockito.times; 34 import static org.mockito.Mockito.verify; 35 36 import android.graphics.Bitmap; 37 import android.graphics.Bitmap.Config; 38 import android.graphics.Canvas; 39 import android.graphics.Paint; 40 import android.os.LocaleList; 41 import android.text.BoringLayout; 42 import android.text.BoringLayout.Metrics; 43 import android.text.Layout; 44 import android.text.Layout.Alignment; 45 import android.text.PrecomputedText; 46 import android.text.TextDirectionHeuristic; 47 import android.text.TextDirectionHeuristics; 48 import android.text.TextPaint; 49 import android.text.TextUtils; 50 51 import androidx.test.filters.SmallTest; 52 import androidx.test.runner.AndroidJUnit4; 53 54 import org.junit.Before; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 58 @SmallTest 59 @RunWith(AndroidJUnit4.class) 60 public class BoringLayoutTest { 61 private static final float SPACING_MULT_NO_SCALE = 1.0f; 62 private static final float SPACING_ADD_NO_SCALE = 0.0f; 63 private static final int DEFAULT_OUTER_WIDTH = 100; 64 private static final int METRICS_TOP = 10; 65 private static final int METRICS_ASCENT = 20; 66 private static final int METRICS_DESCENT = 40; 67 private static final int METRICS_BOTTOM = 50; 68 private static final int METRICS_WIDTH = 50; 69 private static final int METRICS_LEADING = 50; 70 71 private static final CharSequence DEFAULT_CHAR_SEQUENCE = "default"; 72 private static final TextPaint DEFAULT_PAINT = new TextPaint(); 73 private static final Layout.Alignment DEFAULT_ALIGN = Layout.Alignment.ALIGN_CENTER; 74 private static final Metrics DEFAULT_METRICS = createMetrics( 75 METRICS_TOP, 76 METRICS_ASCENT, 77 METRICS_DESCENT, 78 METRICS_BOTTOM, 79 METRICS_WIDTH, 80 METRICS_LEADING); 81 82 private BoringLayout mBoringLayout; 83 84 @Before 85 public void setup() { 86 mBoringLayout = makeDefaultBoringLayout(); 87 } 88 89 @Test 90 public void testConstructors() { 91 new BoringLayout(DEFAULT_CHAR_SEQUENCE, 92 DEFAULT_PAINT, 93 DEFAULT_OUTER_WIDTH, 94 DEFAULT_ALIGN, 95 SPACING_MULT_NO_SCALE, 96 SPACING_ADD_NO_SCALE, 97 DEFAULT_METRICS, 98 true); 99 100 new BoringLayout(DEFAULT_CHAR_SEQUENCE, 101 DEFAULT_PAINT, 102 DEFAULT_OUTER_WIDTH, 103 DEFAULT_ALIGN, 104 SPACING_MULT_NO_SCALE, 105 SPACING_ADD_NO_SCALE, 106 DEFAULT_METRICS, 107 true, 108 TextUtils.TruncateAt.START, 109 DEFAULT_OUTER_WIDTH); 110 } 111 112 private void verifyMultAddScale(float spacingMult, float spacingAdd) { 113 final int height = METRICS_BOTTOM - METRICS_TOP; 114 115 BoringLayout boringLayout = makeBoringLayout(spacingMult, spacingAdd); 116 assertEquals(height, boringLayout.getHeight()); 117 assertEquals(height + METRICS_TOP, boringLayout.getLineDescent(0)); 118 } 119 120 @Test 121 public void testScale() { 122 // no scale 123 verifyMultAddScale(1.0f, 0.0f); 124 125 // test line spacing multiplier 126 verifyMultAddScale(2.0f, 0.0f); 127 verifyMultAddScale(0.5f, 0.0f); 128 129 // test line spacing add 130 verifyMultAddScale(1.0f, 1.5f); 131 verifyMultAddScale(1.0f, -1.6f); 132 verifyMultAddScale(1.0f, 1.4f); 133 verifyMultAddScale(1.0f, -1.4f); 134 verifyMultAddScale(1.0f, 3.0f); 135 verifyMultAddScale(1.0f, -3.0f); 136 } 137 138 @Test 139 public void testPreconditions() { 140 assertEquals(1, mBoringLayout.getLineCount()); 141 assertEquals(0, mBoringLayout.getLineTop(0)); 142 assertEquals(mBoringLayout.getHeight(), mBoringLayout.getLineTop(1)); 143 assertEquals(mBoringLayout.getHeight(), mBoringLayout.getLineTop(10)); 144 assertEquals(0, mBoringLayout.getLineStart(0)); 145 assertEquals(DEFAULT_CHAR_SEQUENCE.length(), mBoringLayout.getLineStart(1)); 146 assertEquals(DEFAULT_CHAR_SEQUENCE.length(), mBoringLayout.getLineStart(10)); 147 assertEquals(Layout.DIR_LEFT_TO_RIGHT, mBoringLayout.getParagraphDirection(0)); 148 assertFalse(mBoringLayout.getLineContainsTab(0)); 149 assertEquals((float) METRICS_WIDTH, mBoringLayout.getLineMax(0), 0.0f); 150 assertEquals(Layout.DIR_LEFT_TO_RIGHT, mBoringLayout.getParagraphDirection(0)); 151 assertEquals(0, mBoringLayout.getEllipsisCount(0)); 152 mBoringLayout.ellipsized(0, 1); 153 assertEquals(1, mBoringLayout.getEllipsisCount(0)); 154 mBoringLayout.ellipsized(1, 2); 155 assertEquals(1, mBoringLayout.getEllipsisStart(0)); 156 } 157 158 @Test 159 public void testReplaceOrMake() { 160 String source = "This is a SpannableString."; 161 BoringLayout layout_1 = mBoringLayout.replaceOrMake( 162 source, 163 DEFAULT_PAINT, 164 DEFAULT_OUTER_WIDTH, 165 DEFAULT_ALIGN, 166 SPACING_MULT_NO_SCALE, 167 SPACING_ADD_NO_SCALE, 168 DEFAULT_METRICS, 169 true); 170 assertSame(mBoringLayout, layout_1); 171 172 layout_1 = mBoringLayout.replaceOrMake( 173 source, 174 DEFAULT_PAINT, 175 DEFAULT_OUTER_WIDTH, 176 DEFAULT_ALIGN, 177 SPACING_MULT_NO_SCALE, 178 SPACING_ADD_NO_SCALE, 179 DEFAULT_METRICS, 180 true, 181 TextUtils.TruncateAt.START, 182 100); 183 assertSame(mBoringLayout, layout_1); 184 assertEquals(100, mBoringLayout.getEllipsizedWidth()); 185 } 186 187 188 @Test 189 public void testAlignment() { 190 BoringLayout boringLayout = makeBoringLayoutAlign(Layout.Alignment.ALIGN_NORMAL); 191 assertEquals(0.0f, boringLayout.getLineLeft(0), 0.0f); 192 assertEquals((float) DEFAULT_METRICS.width, boringLayout.getLineRight(0), 0.0f); 193 194 boringLayout = makeBoringLayoutAlign(Layout.Alignment.ALIGN_CENTER); 195 int expectedWidth = DEFAULT_OUTER_WIDTH - METRICS_WIDTH; 196 assertEquals((float) expectedWidth / 2, boringLayout.getLineLeft(0), 0.0f); 197 expectedWidth = DEFAULT_OUTER_WIDTH + METRICS_WIDTH; 198 assertEquals((float) expectedWidth / 2, boringLayout.getLineRight(0), 0.0f); 199 200 boringLayout = makeBoringLayoutAlign(Layout.Alignment.ALIGN_OPPOSITE); 201 expectedWidth = DEFAULT_OUTER_WIDTH - METRICS_WIDTH; 202 assertEquals((float) expectedWidth, boringLayout.getLineLeft(0), 0.0f); 203 assertEquals((float) DEFAULT_OUTER_WIDTH, boringLayout.getLineRight(0), 0.0f); 204 } 205 206 @Test 207 public void testGetLineDescent_withIncludePadding() { 208 final int height = METRICS_BOTTOM - METRICS_TOP; 209 assertEquals(height + METRICS_TOP, mBoringLayout.getLineDescent(0)); 210 } 211 212 @Test 213 public void testGetLineDescent_withoutIncludePadding() { 214 BoringLayout boringLayout = new BoringLayout( 215 DEFAULT_CHAR_SEQUENCE, 216 DEFAULT_PAINT, 217 DEFAULT_OUTER_WIDTH, 218 DEFAULT_ALIGN, 219 SPACING_MULT_NO_SCALE, 220 SPACING_ADD_NO_SCALE, 221 DEFAULT_METRICS, 222 false); 223 224 final int height = METRICS_DESCENT - METRICS_ASCENT; 225 assertEquals(height + METRICS_ASCENT, boringLayout.getLineDescent(0)); 226 } 227 228 @Test 229 public void testIncludePadding() { 230 assertEquals(METRICS_TOP - METRICS_ASCENT, mBoringLayout.getTopPadding()); 231 assertEquals(METRICS_BOTTOM - METRICS_DESCENT, mBoringLayout.getBottomPadding()); 232 assertEquals(METRICS_BOTTOM - METRICS_TOP, mBoringLayout.getHeight()); 233 234 BoringLayout boringLayout = new BoringLayout( 235 DEFAULT_CHAR_SEQUENCE, 236 DEFAULT_PAINT, 237 DEFAULT_OUTER_WIDTH, 238 DEFAULT_ALIGN, 239 SPACING_MULT_NO_SCALE, 240 SPACING_ADD_NO_SCALE, 241 DEFAULT_METRICS, 242 false); 243 244 assertEquals(0, boringLayout.getTopPadding()); 245 assertEquals(0, boringLayout.getBottomPadding()); 246 assertEquals(METRICS_DESCENT - METRICS_ASCENT, boringLayout.getHeight()); 247 } 248 249 @Test 250 public void testIsBoringString() { 251 TextPaint paint = new TextPaint(); 252 assertNotNull(BoringLayout.isBoring("hello android", paint)); 253 254 Metrics metrics = new Metrics(); 255 metrics.width = 100; 256 assertNotNull(BoringLayout.isBoring("hello android", paint, metrics)); 257 258 assertNull(BoringLayout.isBoring("\u0590 \u0591", paint)); 259 assertNull(BoringLayout.isBoring("hello \t android", paint)); 260 assertNull(BoringLayout.isBoring("hello \n android", paint)); 261 assertNull(BoringLayout.isBoring("hello \n\n\n android", paint)); 262 assertNull(BoringLayout.isBoring("\nhello \n android\n", paint)); 263 assertNull(BoringLayout.isBoring("hello android\n\n\n", paint)); 264 } 265 266 @Test 267 public void testIsBoring_resetsFontMetrics() { 268 int someInt = 100; 269 String text = "some text"; 270 271 TextPaint paint = new TextPaint(); 272 Paint.FontMetricsInt paintMetrics = paint.getFontMetricsInt(); 273 Metrics changedMetrics = new Metrics(); 274 changedMetrics.top = paintMetrics.top - someInt; 275 changedMetrics.ascent = paintMetrics.ascent - someInt; 276 changedMetrics.bottom = paintMetrics.bottom + someInt; 277 changedMetrics.descent = paintMetrics.descent + someInt; 278 changedMetrics.leading = paintMetrics.leading + someInt; 279 280 Metrics expectedMetrics = BoringLayout.isBoring(text, paint, (Metrics) null); 281 Metrics actualMetrics = BoringLayout.isBoring(text, paint, changedMetrics); 282 283 assertNotNull(actualMetrics); 284 assertNotNull(expectedMetrics); 285 assertEquals(expectedMetrics.top, actualMetrics.top); 286 assertEquals(expectedMetrics.ascent, actualMetrics.ascent); 287 assertEquals(expectedMetrics.bottom, actualMetrics.bottom); 288 assertEquals(expectedMetrics.descent, actualMetrics.descent); 289 assertEquals(expectedMetrics.leading, actualMetrics.leading); 290 } 291 292 @Test 293 public void testGetLineDirections() { 294 assertNotNull(mBoringLayout.getLineDirections(0)); 295 assertNotNull(mBoringLayout.getLineDirections(2)); 296 } 297 298 @Test 299 public void testMake() { 300 BoringLayout boringLayout = BoringLayout.make(DEFAULT_CHAR_SEQUENCE, 301 DEFAULT_PAINT, 302 DEFAULT_OUTER_WIDTH, 303 DEFAULT_ALIGN, 304 SPACING_MULT_NO_SCALE, 305 SPACING_ADD_NO_SCALE, 306 DEFAULT_METRICS, 307 true); 308 assertNotNull(boringLayout); 309 310 boringLayout = BoringLayout.make(DEFAULT_CHAR_SEQUENCE, 311 DEFAULT_PAINT, 312 DEFAULT_OUTER_WIDTH, 313 DEFAULT_ALIGN, 314 SPACING_MULT_NO_SCALE, 315 SPACING_ADD_NO_SCALE, 316 DEFAULT_METRICS, 317 true, 318 TextUtils.TruncateAt.START, 319 DEFAULT_OUTER_WIDTH); 320 assertNotNull(boringLayout); 321 } 322 323 @Test 324 public void testDraw() { 325 BoringLayout boringLayout = BoringLayout.make(DEFAULT_CHAR_SEQUENCE, 326 DEFAULT_PAINT, 327 DEFAULT_OUTER_WIDTH, 328 Alignment.ALIGN_NORMAL, 329 SPACING_MULT_NO_SCALE, 330 SPACING_ADD_NO_SCALE, 331 DEFAULT_METRICS, 332 true); 333 334 Bitmap mutableBitmap = Bitmap.createBitmap(10, 28, Config.ARGB_8888); 335 Canvas canvas = spy(new Canvas(mutableBitmap)); 336 boringLayout.draw(canvas, null, null, 0); 337 verify(canvas, times(1)).drawText(eq(DEFAULT_CHAR_SEQUENCE.toString()), 338 anyFloat(), anyFloat(), any(Paint.class)); 339 340 reset(canvas); 341 boringLayout = BoringLayout.make(DEFAULT_CHAR_SEQUENCE, 342 DEFAULT_PAINT, 343 DEFAULT_OUTER_WIDTH, 344 Alignment.ALIGN_OPPOSITE, 345 SPACING_MULT_NO_SCALE, 346 SPACING_ADD_NO_SCALE, 347 DEFAULT_METRICS, 348 true); 349 boringLayout.draw(canvas, null, null, 0); 350 verify(canvas, times(1)).drawText(sameCharSequence(DEFAULT_CHAR_SEQUENCE), 351 anyInt(), anyInt(), anyFloat(), anyFloat(), any(Paint.class)); 352 } 353 354 private static Bitmap drawToBitmap(Layout l) { 355 final Bitmap bmp = Bitmap.createBitmap(l.getWidth(), l.getHeight(), Bitmap.Config.RGB_565); 356 final Canvas c = new Canvas(bmp); 357 358 c.save(); 359 c.translate(0, 0); 360 l.draw(c); 361 c.restore(); 362 return bmp; 363 } 364 365 private static String textPaintToString(TextPaint p) { 366 return "{" 367 + "mTextSize=" + p.getTextSize() + ", " 368 + "mTextSkewX=" + p.getTextSkewX() + ", " 369 + "mTextScaleX=" + p.getTextScaleX() + ", " 370 + "mLetterSpacing=" + p.getLetterSpacing() + ", " 371 + "mFlags=" + p.getFlags() + ", " 372 + "mTextLocales=" + p.getTextLocales() + ", " 373 + "mFontVariationSettings=" + p.getFontVariationSettings() + ", " 374 + "mTypeface=" + p.getTypeface() + ", " 375 + "mFontFeatureSettings=" + p.getFontFeatureSettings() 376 + "}"; 377 } 378 379 private static String directionToString(TextDirectionHeuristic dir) { 380 if (dir == TextDirectionHeuristics.LTR) { 381 return "LTR"; 382 } else if (dir == TextDirectionHeuristics.RTL) { 383 return "RTL"; 384 } else if (dir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { 385 return "FIRSTSTRONG_LTR"; 386 } else if (dir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { 387 return "FIRSTSTRONG_RTL"; 388 } else if (dir == TextDirectionHeuristics.ANYRTL_LTR) { 389 return "ANYRTL_LTR"; 390 } else { 391 throw new RuntimeException("Unknown Direction"); 392 } 393 } 394 395 static class LayoutParam { 396 final int mStrategy; 397 final int mFrequency; 398 final TextPaint mPaint; 399 final TextDirectionHeuristic mDir; 400 401 LayoutParam(int strategy, int frequency, TextPaint paint, TextDirectionHeuristic dir) { 402 mStrategy = strategy; 403 mFrequency = frequency; 404 mPaint = new TextPaint(paint); 405 mDir = dir; 406 } 407 408 @Override 409 public String toString() { 410 return "{" 411 + "mStrategy=" + mStrategy + ", " 412 + "mFrequency=" + mFrequency + ", " 413 + "mPaint=" + textPaintToString(mPaint) + ", " 414 + "mDir=" + directionToString(mDir) 415 + "}"; 416 417 } 418 419 PrecomputedText getPrecomputedText(CharSequence text) { 420 PrecomputedText.Params param = new PrecomputedText.Params.Builder(mPaint) 421 .setBreakStrategy(mStrategy) 422 .setHyphenationFrequency(mFrequency) 423 .setTextDirection(mDir).build(); 424 return PrecomputedText.create(text, param); 425 } 426 }; 427 428 void assertSameDrawOutput( 429 CharSequence text, 430 LayoutParam measuredTextParam, 431 TextPaint layoutPaint) { 432 String msg = "BoringLayout#draw for String and PrecomputedText with " + measuredTextParam 433 + " must output the same BMP."; 434 435 final BoringLayout.Metrics metricsWithString = BoringLayout.isBoring(text, layoutPaint); 436 final Layout layoutWithString = BoringLayout.make( 437 text, layoutPaint, metricsWithString.width, Alignment.ALIGN_NORMAL, 438 SPACING_MULT_NO_SCALE, SPACING_ADD_NO_SCALE, DEFAULT_METRICS, 439 true /* include padding */); 440 441 final PrecomputedText precomputed = measuredTextParam.getPrecomputedText(text); 442 final BoringLayout.Metrics metricsWithPrecomputed = BoringLayout.isBoring( 443 precomputed, layoutPaint); 444 final Layout layoutWithPrecomputed = BoringLayout.make( 445 precomputed, layoutPaint, metricsWithPrecomputed.width, Alignment.ALIGN_NORMAL, 446 SPACING_MULT_NO_SCALE, SPACING_ADD_NO_SCALE, DEFAULT_METRICS, 447 true /* include padding */); 448 449 assertEquals(msg, layoutWithString.getHeight(), layoutWithPrecomputed.getHeight(), 0.0f); 450 451 final Bitmap expectedBMP = drawToBitmap(layoutWithString); 452 final Bitmap resultBMP = drawToBitmap(layoutWithPrecomputed); 453 454 assertTrue(msg, resultBMP.sameAs(expectedBMP)); 455 } 456 457 @Test 458 public void testPrecomputedText() { 459 int[] breaks = { 460 Layout.BREAK_STRATEGY_SIMPLE, 461 Layout.BREAK_STRATEGY_HIGH_QUALITY, 462 Layout.BREAK_STRATEGY_BALANCED, 463 }; 464 465 int[] frequencies = { 466 Layout.HYPHENATION_FREQUENCY_NORMAL, 467 Layout.HYPHENATION_FREQUENCY_FULL, 468 Layout.HYPHENATION_FREQUENCY_NONE, 469 }; 470 471 TextDirectionHeuristic[] dirs = { 472 TextDirectionHeuristics.LTR, 473 TextDirectionHeuristics.RTL, 474 TextDirectionHeuristics.FIRSTSTRONG_LTR, 475 TextDirectionHeuristics.FIRSTSTRONG_RTL, 476 TextDirectionHeuristics.ANYRTL_LTR, 477 }; 478 479 float[] textSizes = { 480 8.0f, 16.0f, 32.0f 481 }; 482 483 LocaleList[] locales = { 484 LocaleList.forLanguageTags("en-US"), 485 LocaleList.forLanguageTags("ja-JP"), 486 LocaleList.forLanguageTags("en-US,ja-JP"), 487 }; 488 489 String onelineText = "Hello, World."; 490 491 TextPaint paint = new TextPaint(); 492 493 // If the PrecomputedText is created with the same Paint, the draw result must be the same. 494 for (int b : breaks) { 495 for (int f : frequencies) { 496 for (TextDirectionHeuristic dir : dirs) { 497 for (float textSize : textSizes) { 498 for (LocaleList locale : locales) { 499 paint.setTextSize(textSize); 500 paint.setTextLocales(locale); 501 502 assertSameDrawOutput(onelineText, new LayoutParam(b, f, paint, dir), 503 paint); 504 } 505 } 506 } 507 } 508 } 509 510 // Even if the parameters are different, the output of the static layout must be 511 // same bitmap. 512 for (int bi = 0; bi < breaks.length; bi++) { 513 for (int fi = 0; fi < frequencies.length; fi++) { 514 for (int diri = 0; diri < dirs.length; diri++) { 515 for (int sizei = 0; sizei < textSizes.length; sizei++) { 516 for (int localei = 0; localei < locales.length; localei++) { 517 TextPaint precomputedPaint = new TextPaint(); 518 TextPaint layoutPaint = new TextPaint(); 519 520 precomputedPaint.setTextSize(textSizes[sizei]); 521 layoutPaint.setTextSize(textSizes[(sizei + 1) % textSizes.length]); 522 523 precomputedPaint.setTextLocales(locales[localei]); 524 layoutPaint.setTextLocales(locales[(localei + 1) % locales.length]); 525 526 int b = breaks[bi]; 527 528 int f = frequencies[fi]; 529 530 TextDirectionHeuristic dir = dirs[diri]; 531 532 assertSameDrawOutput(onelineText, 533 new LayoutParam(b, f, precomputedPaint, dir), 534 layoutPaint); 535 } 536 } 537 } 538 } 539 } 540 } 541 542 private static Metrics createMetrics( 543 final int top, 544 final int ascent, 545 final int descent, 546 final int bottom, 547 final int width, 548 final int leading) { 549 550 final Metrics metrics = new Metrics(); 551 552 metrics.top = top; 553 metrics.ascent = ascent; 554 metrics.descent = descent; 555 metrics.bottom = bottom; 556 metrics.width = width; 557 metrics.leading = leading; 558 559 return metrics; 560 } 561 562 private static BoringLayout makeDefaultBoringLayout() { 563 return new BoringLayout(DEFAULT_CHAR_SEQUENCE, 564 DEFAULT_PAINT, 565 DEFAULT_OUTER_WIDTH, 566 DEFAULT_ALIGN, 567 SPACING_MULT_NO_SCALE, 568 SPACING_ADD_NO_SCALE, 569 DEFAULT_METRICS, 570 true); 571 } 572 573 private static BoringLayout makeBoringLayout(float spacingMult,float spacingAdd) { 574 return new BoringLayout(DEFAULT_CHAR_SEQUENCE, 575 DEFAULT_PAINT, 576 DEFAULT_OUTER_WIDTH, 577 DEFAULT_ALIGN, 578 spacingMult, 579 spacingAdd, 580 DEFAULT_METRICS, 581 true); 582 } 583 584 private static BoringLayout makeBoringLayoutAlign(Alignment align) { 585 return new BoringLayout(DEFAULT_CHAR_SEQUENCE, 586 DEFAULT_PAINT, 587 DEFAULT_OUTER_WIDTH, 588 align, 589 SPACING_MULT_NO_SCALE, 590 SPACING_ADD_NO_SCALE, 591 DEFAULT_METRICS, 592 true); 593 } 594 } 595