Home | History | Annotate | Download | only in cts
      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 android.text.Layout.Alignment.ALIGN_NORMAL;
     20 import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
     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 import static org.mockito.Mockito.mock;
     28 
     29 import android.graphics.Paint.FontMetricsInt;
     30 import android.text.DynamicLayout;
     31 import android.text.Layout;
     32 import android.text.SpannableStringBuilder;
     33 import android.text.StaticLayout;
     34 import android.text.TextPaint;
     35 import android.text.TextUtils;
     36 import android.text.style.TypefaceSpan;
     37 
     38 import androidx.test.filters.SmallTest;
     39 import androidx.test.runner.AndroidJUnit4;
     40 
     41 import org.junit.Before;
     42 import org.junit.Test;
     43 import org.junit.runner.RunWith;
     44 
     45 @SmallTest
     46 @RunWith(AndroidJUnit4.class)
     47 public class DynamicLayoutTest {
     48 
     49     private static final float SPACING_MULT_NO_SCALE = 1.0f;
     50     private static final float SPACING_ADD_NO_SCALE = 0.0f;
     51     private static final int DEFAULT_OUTER_WIDTH = 150;
     52     private static final int ELLIPSIZE_WIDTH = 8;
     53     private static final CharSequence SINGLELINE_CHAR_SEQUENCE = "......";
     54     private static final String[] TEXT = {"CharSequence\n", "Char\tSequence\n", "CharSequence"};
     55     private static final CharSequence MULTLINE_CHAR_SEQUENCE = TEXT[0] + TEXT[1] + TEXT[2];
     56     private static final Layout.Alignment DEFAULT_ALIGN = Layout.Alignment.ALIGN_CENTER;
     57     private TextPaint mDefaultPaint;
     58 
     59     private static final int LINE0 = 0;
     60     private static final int LINE0_TOP = 0;
     61     private static final int LINE1 = 1;
     62     private static final int LINE2 = 2;
     63     private static final int LINE3 = 3;
     64 
     65     private static final int ELLIPSIS_UNDEFINED = 0x80000000;
     66 
     67     private DynamicLayout mDynamicLayout;
     68 
     69     @Before
     70     public void setup() {
     71         mDefaultPaint = new TextPaint();
     72         mDynamicLayout = new DynamicLayout(MULTLINE_CHAR_SEQUENCE,
     73                 mDefaultPaint,
     74                 DEFAULT_OUTER_WIDTH,
     75                 DEFAULT_ALIGN,
     76                 SPACING_MULT_NO_SCALE,
     77                 SPACING_ADD_NO_SCALE,
     78                 true);
     79     }
     80 
     81     @Test
     82     public void testConstructors() {
     83         new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
     84                 MULTLINE_CHAR_SEQUENCE,
     85                 mDefaultPaint,
     86                 DEFAULT_OUTER_WIDTH,
     87                 DEFAULT_ALIGN,
     88                 SPACING_MULT_NO_SCALE,
     89                 SPACING_ADD_NO_SCALE,
     90                 true);
     91         new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
     92                 MULTLINE_CHAR_SEQUENCE,
     93                 mDefaultPaint,
     94                 DEFAULT_OUTER_WIDTH,
     95                 DEFAULT_ALIGN,
     96                 SPACING_MULT_NO_SCALE,
     97                 SPACING_ADD_NO_SCALE,
     98                 true,
     99                 TextUtils.TruncateAt.START,
    100                 DEFAULT_OUTER_WIDTH);
    101         new DynamicLayout(MULTLINE_CHAR_SEQUENCE,
    102                 mDefaultPaint,
    103                 DEFAULT_OUTER_WIDTH,
    104                 DEFAULT_ALIGN,
    105                 SPACING_MULT_NO_SCALE,
    106                 SPACING_ADD_NO_SCALE,
    107                 true);
    108     }
    109 
    110     @Test
    111     public void testEllipsis() {
    112         final DynamicLayout dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
    113                 MULTLINE_CHAR_SEQUENCE,
    114                 mDefaultPaint,
    115                 DEFAULT_OUTER_WIDTH,
    116                 DEFAULT_ALIGN,
    117                 SPACING_MULT_NO_SCALE,
    118                 SPACING_ADD_NO_SCALE,
    119                 true,
    120                 TextUtils.TruncateAt.START,
    121                 DEFAULT_OUTER_WIDTH);
    122         assertEquals(0, dynamicLayout.getEllipsisCount(LINE1));
    123         assertEquals(ELLIPSIS_UNDEFINED, dynamicLayout.getEllipsisStart(LINE1));
    124         assertEquals(DEFAULT_OUTER_WIDTH, dynamicLayout.getEllipsizedWidth());
    125     }
    126 
    127     /*
    128      * Test whether include the padding to calculate the layout.
    129      * 1. Include padding while calculate the layout.
    130      * 2. Don't include padding while calculate the layout.
    131      */
    132     @Test
    133     public void testIncludePadding() {
    134         final FontMetricsInt fontMetricsInt = mDefaultPaint.getFontMetricsInt();
    135 
    136         DynamicLayout dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
    137                 mDefaultPaint,
    138                 DEFAULT_OUTER_WIDTH,
    139                 DEFAULT_ALIGN,
    140                 SPACING_MULT_NO_SCALE,
    141                 SPACING_ADD_NO_SCALE,
    142                 true);
    143         assertEquals(fontMetricsInt.top - fontMetricsInt.ascent, dynamicLayout.getTopPadding());
    144         assertEquals(fontMetricsInt.bottom - fontMetricsInt.descent,
    145                 dynamicLayout.getBottomPadding());
    146 
    147         dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
    148                 mDefaultPaint,
    149                 DEFAULT_OUTER_WIDTH,
    150                 DEFAULT_ALIGN,
    151                 SPACING_MULT_NO_SCALE,
    152                 SPACING_ADD_NO_SCALE,
    153                 false);
    154         assertEquals(0, dynamicLayout.getTopPadding());
    155         assertEquals(0, dynamicLayout.getBottomPadding());
    156     }
    157 
    158     /*
    159      * Test the line count and whether include the Tab the layout.
    160      * 1. Include Tab. 2. Don't include Tab Use the Y-coordinate to calculate the line number
    161      * Test the line top
    162      * 1. the Y-coordinate of line top.2. the Y-coordinate of baseline.
    163      */
    164     @Test
    165     public void testLineLayout() {
    166         assertEquals(TEXT.length, mDynamicLayout.getLineCount());
    167         assertFalse(mDynamicLayout.getLineContainsTab(LINE0));
    168         assertTrue(mDynamicLayout.getLineContainsTab(LINE1));
    169 
    170         assertEquals(LINE0_TOP, mDynamicLayout.getLineTop(LINE0));
    171 
    172         assertEquals(mDynamicLayout.getLineBottom(LINE0), mDynamicLayout.getLineTop(LINE1));
    173         assertEquals(mDynamicLayout.getLineBottom(LINE1), mDynamicLayout.getLineTop(LINE2));
    174         assertEquals(mDynamicLayout.getLineBottom(LINE2), mDynamicLayout.getLineTop(LINE3));
    175 
    176         try {
    177             assertEquals(mDynamicLayout.getLineBottom(mDynamicLayout.getLineCount()),
    178                     mDynamicLayout.getLineTop(mDynamicLayout.getLineCount() + 1));
    179             fail("Test DynamicLayout fail, should throw IndexOutOfBoundsException.");
    180         } catch (IndexOutOfBoundsException e) {
    181             // expected
    182         }
    183 
    184         assertEquals(mDynamicLayout.getLineDescent(LINE0) - mDynamicLayout.getLineAscent(LINE0),
    185                 mDynamicLayout.getLineBottom(LINE0));
    186 
    187         assertEquals(mDynamicLayout.getLineDescent(LINE1) - mDynamicLayout.getLineAscent(LINE1),
    188                 mDynamicLayout.getLineBottom(LINE1) - mDynamicLayout.getLineBottom(LINE0));
    189 
    190         assertEquals(mDynamicLayout.getLineDescent(LINE2) - mDynamicLayout.getLineAscent(LINE2),
    191                 mDynamicLayout.getLineBottom(LINE2) - mDynamicLayout.getLineBottom(LINE1));
    192 
    193         assertEquals(LINE0, mDynamicLayout.getLineForVertical(mDynamicLayout.getLineTop(LINE0)));
    194 
    195         assertNotNull(mDynamicLayout.getLineDirections(LINE0));
    196         assertEquals(Layout.DIR_LEFT_TO_RIGHT, mDynamicLayout.getParagraphDirection(LINE0));
    197 
    198         assertEquals(0, mDynamicLayout.getLineStart(LINE0));
    199         assertEquals(TEXT[0].length(), mDynamicLayout.getLineStart(LINE1));
    200         assertEquals(TEXT[0].length() + TEXT[1].length(), mDynamicLayout.getLineStart(LINE2));
    201     }
    202 
    203     @Test
    204     public void testLineSpacing() {
    205         SpannableStringBuilder text = new SpannableStringBuilder("a\nb\nc");
    206         final float spacingMultiplier = 2f;
    207         final float spacingAdd = 4;
    208         final int width = 1000;
    209         final TextPaint textPaint = new TextPaint();
    210         // create the DynamicLayout
    211         final DynamicLayout dynamicLayout = new DynamicLayout(text,
    212                 textPaint,
    213                 width,
    214                 ALIGN_NORMAL,
    215                 spacingMultiplier,
    216                 spacingAdd,
    217                 false /*includepad*/);
    218 
    219         // create a StaticLayout with same text, this will define the expectations
    220         Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
    221                 spacingMultiplier);
    222         assertLineSpecs(expected, dynamicLayout);
    223 
    224         // add a new line to the end, DynamicLayout will re-calculate
    225         text = text.append("\nd");
    226         expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
    227                 spacingMultiplier);
    228         assertLineSpecs(expected, dynamicLayout);
    229 
    230         // insert a next line and a char as the new second line
    231         text = text.insert(TextUtils.indexOf(text, '\n') + 1, "a1\n");
    232         expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
    233                 spacingMultiplier);
    234         assertLineSpecs(expected, dynamicLayout);
    235     }
    236 
    237     @Test
    238     public void testLineSpacing_textEndingWithNextLine() {
    239         final SpannableStringBuilder text = new SpannableStringBuilder("a\n");
    240         final float spacingMultiplier = 2f;
    241         final float spacingAdd = 4f;
    242         final int width = 1000;
    243         final TextPaint textPaint = new TextPaint();
    244         // create the DynamicLayout
    245         final DynamicLayout dynamicLayout = new DynamicLayout(text,
    246                 textPaint,
    247                 width,
    248                 ALIGN_NORMAL,
    249                 spacingMultiplier,
    250                 spacingAdd,
    251                 false /*includepad*/);
    252 
    253         // create a StaticLayout with same text, this will define the expectations
    254         final Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
    255                 spacingMultiplier);
    256         assertLineSpecs(expected, dynamicLayout);
    257     }
    258 
    259     private Layout createStaticLayout(CharSequence text, TextPaint textPaint, int width,
    260             float spacingAdd, float spacingMultiplier) {
    261         return StaticLayout.Builder.obtain(text, 0,
    262                 text.length(), textPaint, width)
    263                 .setAlignment(ALIGN_NORMAL)
    264                 .setIncludePad(false)
    265                 .setLineSpacing(spacingAdd, spacingMultiplier)
    266                 .build();
    267     }
    268 
    269     private void assertLineSpecs(Layout expected, DynamicLayout actual) {
    270         final int lineCount = expected.getLineCount();
    271         assertTrue(lineCount > 1);
    272         assertEquals(lineCount, actual.getLineCount());
    273 
    274         for (int i = 0; i < lineCount; i++) {
    275             assertEquals(expected.getLineTop(i), actual.getLineTop(i));
    276             assertEquals(expected.getLineDescent(i), actual.getLineDescent(i));
    277             assertEquals(expected.getLineBaseline(i), actual.getLineBaseline(i));
    278             assertEquals(expected.getLineBottom(i), actual.getLineBottom(i));
    279         }
    280     }
    281 
    282     @Test
    283     public void testLineSpacing_notAffectedByPreviousEllipsization() {
    284         // Create an ellipsized DynamicLayout, but throw it away.
    285         final String ellipsizedText = "Some arbitrary relatively long text";
    286         final DynamicLayout ellipsizedLayout = new DynamicLayout(
    287                 ellipsizedText,
    288                 ellipsizedText,
    289                 mDefaultPaint,
    290                 1 << 20 /* width */,
    291                 DEFAULT_ALIGN,
    292                 SPACING_MULT_NO_SCALE,
    293                 SPACING_ADD_NO_SCALE,
    294                 true /* include pad */,
    295                 TextUtils.TruncateAt.END,
    296                 2 * (int) mDefaultPaint.getTextSize() /* ellipsizedWidth */);
    297 
    298         // Now try to measure linespacing in a non-ellipsized DynamicLayout.
    299         final String text = "a\nb\nc";
    300         final float spacingMultiplier = 2f;
    301         final float spacingAdd = 4f;
    302         final int width = 1000;
    303         final TextPaint textPaint = new TextPaint();
    304         // create the DynamicLayout
    305         final DynamicLayout dynamicLayout = new DynamicLayout(text,
    306                 textPaint,
    307                 width,
    308                 ALIGN_NORMAL,
    309                 spacingMultiplier,
    310                 spacingAdd,
    311                 false /*includepad*/);
    312 
    313         // create a StaticLayout with same text, this will define the expectations
    314         Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
    315                 spacingMultiplier);
    316         assertLineSpecs(expected, dynamicLayout);
    317     }
    318 
    319     @Test
    320     public void testBuilder_obtain() {
    321         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
    322                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
    323         final DynamicLayout layout = builder.build();
    324         // Check values passed to obtain().
    325         assertEquals(MULTLINE_CHAR_SEQUENCE, layout.getText());
    326         assertEquals(mDefaultPaint, layout.getPaint());
    327         assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
    328         // Check default values.
    329         assertEquals(Layout.Alignment.ALIGN_NORMAL, layout.getAlignment());
    330         assertEquals(0.0f, layout.getSpacingAdd(), 0.0f);
    331         assertEquals(1.0f, layout.getSpacingMultiplier(), 0.0f);
    332         assertEquals(DEFAULT_OUTER_WIDTH, layout.getEllipsizedWidth());
    333     }
    334 
    335     @Test(expected = NullPointerException.class)
    336     public void testBuilder_obtainWithNullText() {
    337         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(null, mDefaultPaint, 0);
    338         final DynamicLayout layout = builder.build();
    339     }
    340 
    341     @Test(expected = NullPointerException.class)
    342     public void testBuilder_obtainWithNullPaint() {
    343         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
    344                 null, 0);
    345         final DynamicLayout layout = builder.build();
    346     }
    347 
    348     @Test
    349     public void testBuilder_setDisplayTest() {
    350         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
    351                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
    352         builder.setDisplayText(SINGLELINE_CHAR_SEQUENCE);
    353         final DynamicLayout layout = builder.build();
    354         assertEquals(SINGLELINE_CHAR_SEQUENCE, layout.getText());
    355     }
    356 
    357     @Test
    358     public void testBuilder_setAlignment() {
    359         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
    360                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
    361         builder.setAlignment(DEFAULT_ALIGN);
    362         final DynamicLayout layout = builder.build();
    363         assertEquals(DEFAULT_ALIGN, layout.getAlignment());
    364     }
    365 
    366     @Test
    367     public void testBuilder_setLineSpacing() {
    368         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
    369                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
    370         builder.setLineSpacing(1.0f, 2.0f);
    371         final DynamicLayout layout = builder.build();
    372         assertEquals(1.0f, layout.getSpacingAdd(), 0.0f);
    373         assertEquals(2.0f, layout.getSpacingMultiplier(), 0.0f);
    374     }
    375 
    376     @Test
    377     public void testBuilder_ellipsization() {
    378         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
    379                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
    380         builder.setEllipsize(TextUtils.TruncateAt.END)
    381                 .setEllipsizedWidth(ELLIPSIZE_WIDTH);
    382         final DynamicLayout layout = builder.build();
    383         assertEquals(ELLIPSIZE_WIDTH, layout.getEllipsizedWidth());
    384         assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
    385         for (int i = 0; i < TEXT.length; i++) {
    386             if (i == TEXT.length - 1) { // last line
    387                 assertTrue(layout.getEllipsisCount(i) > 0);
    388             } else {
    389                 assertEquals(0, layout.getEllipsisCount(i));
    390             }
    391         }
    392     }
    393 
    394     @Test
    395     public void testBuilder_otherSetters() {
    396         // Setter methods that cannot be directly tested.
    397         // setBreakStrategy, setHyphenationFrequency, setIncludePad, and setJustificationMode.
    398         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
    399                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
    400         builder.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
    401                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
    402                 .setIncludePad(true)
    403                 .setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD);
    404         final DynamicLayout layout = builder.build();
    405         assertNotNull(layout);
    406     }
    407 
    408     @Test
    409     public void testReflow_afterSpanChangedShouldNotThrowException() {
    410         final SpannableStringBuilder builder = new SpannableStringBuilder("crash crash crash!!");
    411 
    412         final TypefaceSpan span = mock(TypefaceSpan.class);
    413         builder.setSpan(span, 1, 4, SPAN_EXCLUSIVE_EXCLUSIVE);
    414 
    415         final DynamicLayout layout = DynamicLayout.Builder.obtain(builder,
    416                 new TextPaint(), Integer.MAX_VALUE).build();
    417         try {
    418             builder.insert(1, "Hello there\n\n");
    419         } catch (Throwable e) {
    420             throw new RuntimeException("Inserting text into DynamicLayout should not crash", e);
    421         }
    422     }
    423 
    424 }
    425