Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2006 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;
     18 
     19 import android.graphics.Paint;
     20 import android.text.style.UpdateLayout;
     21 import android.text.style.WrapTogetherSpan;
     22 
     23 import com.android.internal.util.ArrayUtils;
     24 
     25 import java.lang.ref.WeakReference;
     26 
     27 /**
     28  * DynamicLayout is a text layout that updates itself as the text is edited.
     29  * <p>This is used by widgets to control text layout. You should not need
     30  * to use this class directly unless you are implementing your own widget
     31  * or custom display object, or need to call
     32  * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
     33  *  Canvas.drawText()} directly.</p>
     34  */
     35 public class DynamicLayout extends Layout
     36 {
     37     private static final int PRIORITY = 128;
     38     private static final int BLOCK_MINIMUM_CHARACTER_LENGTH = 400;
     39 
     40     /**
     41      * Make a layout for the specified text that will be updated as
     42      * the text is changed.
     43      */
     44     public DynamicLayout(CharSequence base,
     45                          TextPaint paint,
     46                          int width, Alignment align,
     47                          float spacingmult, float spacingadd,
     48                          boolean includepad) {
     49         this(base, base, paint, width, align, spacingmult, spacingadd,
     50              includepad);
     51     }
     52 
     53     /**
     54      * Make a layout for the transformed text (password transformation
     55      * being the primary example of a transformation)
     56      * that will be updated as the base text is changed.
     57      */
     58     public DynamicLayout(CharSequence base, CharSequence display,
     59                          TextPaint paint,
     60                          int width, Alignment align,
     61                          float spacingmult, float spacingadd,
     62                          boolean includepad) {
     63         this(base, display, paint, width, align, spacingmult, spacingadd,
     64              includepad, null, 0);
     65     }
     66 
     67     /**
     68      * Make a layout for the transformed text (password transformation
     69      * being the primary example of a transformation)
     70      * that will be updated as the base text is changed.
     71      * If ellipsize is non-null, the Layout will ellipsize the text
     72      * down to ellipsizedWidth.
     73      */
     74     public DynamicLayout(CharSequence base, CharSequence display,
     75                          TextPaint paint,
     76                          int width, Alignment align,
     77                          float spacingmult, float spacingadd,
     78                          boolean includepad,
     79                          TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
     80         this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
     81                 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth);
     82     }
     83 
     84     /**
     85      * Make a layout for the transformed text (password transformation
     86      * being the primary example of a transformation)
     87      * that will be updated as the base text is changed.
     88      * If ellipsize is non-null, the Layout will ellipsize the text
     89      * down to ellipsizedWidth.
     90      * *
     91      * *@hide
     92      */
     93     public DynamicLayout(CharSequence base, CharSequence display,
     94                          TextPaint paint,
     95                          int width, Alignment align, TextDirectionHeuristic textDir,
     96                          float spacingmult, float spacingadd,
     97                          boolean includepad,
     98                          TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
     99         super((ellipsize == null)
    100                 ? display
    101                 : (display instanceof Spanned)
    102                     ? new SpannedEllipsizer(display)
    103                     : new Ellipsizer(display),
    104               paint, width, align, textDir, spacingmult, spacingadd);
    105 
    106         mBase = base;
    107         mDisplay = display;
    108 
    109         if (ellipsize != null) {
    110             mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
    111             mEllipsizedWidth = ellipsizedWidth;
    112             mEllipsizeAt = ellipsize;
    113         } else {
    114             mInts = new PackedIntVector(COLUMNS_NORMAL);
    115             mEllipsizedWidth = width;
    116             mEllipsizeAt = null;
    117         }
    118 
    119         mObjects = new PackedObjectVector<Directions>(1);
    120 
    121         mIncludePad = includepad;
    122 
    123         /*
    124          * This is annoying, but we can't refer to the layout until
    125          * superclass construction is finished, and the superclass
    126          * constructor wants the reference to the display text.
    127          *
    128          * This will break if the superclass constructor ever actually
    129          * cares about the content instead of just holding the reference.
    130          */
    131         if (ellipsize != null) {
    132             Ellipsizer e = (Ellipsizer) getText();
    133 
    134             e.mLayout = this;
    135             e.mWidth = ellipsizedWidth;
    136             e.mMethod = ellipsize;
    137             mEllipsize = true;
    138         }
    139 
    140         // Initial state is a single line with 0 characters (0 to 0),
    141         // with top at 0 and bottom at whatever is natural, and
    142         // undefined ellipsis.
    143 
    144         int[] start;
    145 
    146         if (ellipsize != null) {
    147             start = new int[COLUMNS_ELLIPSIZE];
    148             start[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
    149         } else {
    150             start = new int[COLUMNS_NORMAL];
    151         }
    152 
    153         Directions[] dirs = new Directions[] { DIRS_ALL_LEFT_TO_RIGHT };
    154 
    155         Paint.FontMetricsInt fm = paint.getFontMetricsInt();
    156         int asc = fm.ascent;
    157         int desc = fm.descent;
    158 
    159         start[DIR] = DIR_LEFT_TO_RIGHT << DIR_SHIFT;
    160         start[TOP] = 0;
    161         start[DESCENT] = desc;
    162         mInts.insertAt(0, start);
    163 
    164         start[TOP] = desc - asc;
    165         mInts.insertAt(1, start);
    166 
    167         mObjects.insertAt(0, dirs);
    168 
    169         // Update from 0 characters to whatever the real text is
    170         reflow(base, 0, 0, base.length());
    171 
    172         if (base instanceof Spannable) {
    173             if (mWatcher == null)
    174                 mWatcher = new ChangeWatcher(this);
    175 
    176             // Strip out any watchers for other DynamicLayouts.
    177             Spannable sp = (Spannable) base;
    178             ChangeWatcher[] spans = sp.getSpans(0, sp.length(), ChangeWatcher.class);
    179             for (int i = 0; i < spans.length; i++)
    180                 sp.removeSpan(spans[i]);
    181 
    182             sp.setSpan(mWatcher, 0, base.length(),
    183                        Spannable.SPAN_INCLUSIVE_INCLUSIVE |
    184                        (PRIORITY << Spannable.SPAN_PRIORITY_SHIFT));
    185         }
    186     }
    187 
    188     private void reflow(CharSequence s, int where, int before, int after) {
    189         if (s != mBase)
    190             return;
    191 
    192         CharSequence text = mDisplay;
    193         int len = text.length();
    194 
    195         // seek back to the start of the paragraph
    196 
    197         int find = TextUtils.lastIndexOf(text, '\n', where - 1);
    198         if (find < 0)
    199             find = 0;
    200         else
    201             find = find + 1;
    202 
    203         {
    204             int diff = where - find;
    205             before += diff;
    206             after += diff;
    207             where -= diff;
    208         }
    209 
    210         // seek forward to the end of the paragraph
    211 
    212         int look = TextUtils.indexOf(text, '\n', where + after);
    213         if (look < 0)
    214             look = len;
    215         else
    216             look++; // we want the index after the \n
    217 
    218         int change = look - (where + after);
    219         before += change;
    220         after += change;
    221 
    222         // seek further out to cover anything that is forced to wrap together
    223 
    224         if (text instanceof Spanned) {
    225             Spanned sp = (Spanned) text;
    226             boolean again;
    227 
    228             do {
    229                 again = false;
    230 
    231                 Object[] force = sp.getSpans(where, where + after,
    232                                              WrapTogetherSpan.class);
    233 
    234                 for (int i = 0; i < force.length; i++) {
    235                     int st = sp.getSpanStart(force[i]);
    236                     int en = sp.getSpanEnd(force[i]);
    237 
    238                     if (st < where) {
    239                         again = true;
    240 
    241                         int diff = where - st;
    242                         before += diff;
    243                         after += diff;
    244                         where -= diff;
    245                     }
    246 
    247                     if (en > where + after) {
    248                         again = true;
    249 
    250                         int diff = en - (where + after);
    251                         before += diff;
    252                         after += diff;
    253                     }
    254                 }
    255             } while (again);
    256         }
    257 
    258         // find affected region of old layout
    259 
    260         int startline = getLineForOffset(where);
    261         int startv = getLineTop(startline);
    262 
    263         int endline = getLineForOffset(where + before);
    264         if (where + after == len)
    265             endline = getLineCount();
    266         int endv = getLineTop(endline);
    267         boolean islast = (endline == getLineCount());
    268 
    269         // generate new layout for affected text
    270 
    271         StaticLayout reflowed;
    272 
    273         synchronized (sLock) {
    274             reflowed = sStaticLayout;
    275             sStaticLayout = null;
    276         }
    277 
    278         if (reflowed == null) {
    279             reflowed = new StaticLayout(null);
    280         } else {
    281             reflowed.prepare();
    282         }
    283 
    284         reflowed.generate(text, where, where + after,
    285                 getPaint(), getWidth(), getTextDirectionHeuristic(), getSpacingMultiplier(),
    286                 getSpacingAdd(), false,
    287                 true, mEllipsizedWidth, mEllipsizeAt);
    288         int n = reflowed.getLineCount();
    289 
    290         // If the new layout has a blank line at the end, but it is not
    291         // the very end of the buffer, then we already have a line that
    292         // starts there, so disregard the blank line.
    293 
    294         if (where + after != len && reflowed.getLineStart(n - 1) == where + after)
    295             n--;
    296 
    297         // remove affected lines from old layout
    298         mInts.deleteAt(startline, endline - startline);
    299         mObjects.deleteAt(startline, endline - startline);
    300 
    301         // adjust offsets in layout for new height and offsets
    302 
    303         int ht = reflowed.getLineTop(n);
    304         int toppad = 0, botpad = 0;
    305 
    306         if (mIncludePad && startline == 0) {
    307             toppad = reflowed.getTopPadding();
    308             mTopPadding = toppad;
    309             ht -= toppad;
    310         }
    311         if (mIncludePad && islast) {
    312             botpad = reflowed.getBottomPadding();
    313             mBottomPadding = botpad;
    314             ht += botpad;
    315         }
    316 
    317         mInts.adjustValuesBelow(startline, START, after - before);
    318         mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
    319 
    320         // insert new layout
    321 
    322         int[] ints;
    323 
    324         if (mEllipsize) {
    325             ints = new int[COLUMNS_ELLIPSIZE];
    326             ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
    327         } else {
    328             ints = new int[COLUMNS_NORMAL];
    329         }
    330 
    331         Directions[] objects = new Directions[1];
    332 
    333         for (int i = 0; i < n; i++) {
    334             ints[START] = reflowed.getLineStart(i) |
    335                           (reflowed.getParagraphDirection(i) << DIR_SHIFT) |
    336                           (reflowed.getLineContainsTab(i) ? TAB_MASK : 0);
    337 
    338             int top = reflowed.getLineTop(i) + startv;
    339             if (i > 0)
    340                 top -= toppad;
    341             ints[TOP] = top;
    342 
    343             int desc = reflowed.getLineDescent(i);
    344             if (i == n - 1)
    345                 desc += botpad;
    346 
    347             ints[DESCENT] = desc;
    348             objects[0] = reflowed.getLineDirections(i);
    349 
    350             if (mEllipsize) {
    351                 ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
    352                 ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
    353             }
    354 
    355             mInts.insertAt(startline + i, ints);
    356             mObjects.insertAt(startline + i, objects);
    357         }
    358 
    359         updateBlocks(startline, endline - 1, n);
    360 
    361         synchronized (sLock) {
    362             sStaticLayout = reflowed;
    363             reflowed.finish();
    364         }
    365     }
    366 
    367     /**
    368      * Create the initial block structure, cutting the text into blocks of at least
    369      * BLOCK_MINIMUM_CHARACTER_SIZE characters, aligned on the ends of paragraphs.
    370      */
    371     private void createBlocks() {
    372         int offset = BLOCK_MINIMUM_CHARACTER_LENGTH;
    373         mNumberOfBlocks = 0;
    374         final CharSequence text = mDisplay;
    375 
    376         while (true) {
    377             offset = TextUtils.indexOf(text, '\n', offset);
    378             if (offset < 0) {
    379                 addBlockAtOffset(text.length());
    380                 break;
    381             } else {
    382                 addBlockAtOffset(offset);
    383                 offset += BLOCK_MINIMUM_CHARACTER_LENGTH;
    384             }
    385         }
    386 
    387         // mBlockIndices and mBlockEndLines should have the same length
    388         mBlockIndices = new int[mBlockEndLines.length];
    389         for (int i = 0; i < mBlockEndLines.length; i++) {
    390             mBlockIndices[i] = INVALID_BLOCK_INDEX;
    391         }
    392     }
    393 
    394     /**
    395      * Create a new block, ending at the specified character offset.
    396      * A block will actually be created only if has at least one line, i.e. this offset is
    397      * not on the end line of the previous block.
    398      */
    399     private void addBlockAtOffset(int offset) {
    400         final int line = getLineForOffset(offset);
    401 
    402         if (mBlockEndLines == null) {
    403             // Initial creation of the array, no test on previous block ending line
    404             mBlockEndLines = new int[ArrayUtils.idealIntArraySize(1)];
    405             mBlockEndLines[mNumberOfBlocks] = line;
    406             mNumberOfBlocks++;
    407             return;
    408         }
    409 
    410         final int previousBlockEndLine = mBlockEndLines[mNumberOfBlocks - 1];
    411         if (line > previousBlockEndLine) {
    412             if (mNumberOfBlocks == mBlockEndLines.length) {
    413                 // Grow the array if needed
    414                 int[] blockEndLines = new int[ArrayUtils.idealIntArraySize(mNumberOfBlocks + 1)];
    415                 System.arraycopy(mBlockEndLines, 0, blockEndLines, 0, mNumberOfBlocks);
    416                 mBlockEndLines = blockEndLines;
    417             }
    418             mBlockEndLines[mNumberOfBlocks] = line;
    419             mNumberOfBlocks++;
    420         }
    421     }
    422 
    423     /**
    424      * This method is called every time the layout is reflowed after an edition.
    425      * It updates the internal block data structure. The text is split in blocks
    426      * of contiguous lines, with at least one block for the entire text.
    427      * When a range of lines is edited, new blocks (from 0 to 3 depending on the
    428      * overlap structure) will replace the set of overlapping blocks.
    429      * Blocks are listed in order and are represented by their ending line number.
    430      * An index is associated to each block (which will be used by display lists),
    431      * this class simply invalidates the index of blocks overlapping a modification.
    432      *
    433      * This method is package private and not private so that it can be tested.
    434      *
    435      * @param startLine the first line of the range of modified lines
    436      * @param endLine the last line of the range, possibly equal to startLine, lower
    437      * than getLineCount()
    438      * @param newLineCount the number of lines that will replace the range, possibly 0
    439      *
    440      * @hide
    441      */
    442     void updateBlocks(int startLine, int endLine, int newLineCount) {
    443         if (mBlockEndLines == null) {
    444             createBlocks();
    445             return;
    446         }
    447 
    448         int firstBlock = -1;
    449         int lastBlock = -1;
    450         for (int i = 0; i < mNumberOfBlocks; i++) {
    451             if (mBlockEndLines[i] >= startLine) {
    452                 firstBlock = i;
    453                 break;
    454             }
    455         }
    456         for (int i = firstBlock; i < mNumberOfBlocks; i++) {
    457             if (mBlockEndLines[i] >= endLine) {
    458                 lastBlock = i;
    459                 break;
    460             }
    461         }
    462         final int lastBlockEndLine = mBlockEndLines[lastBlock];
    463 
    464         boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 :
    465                 mBlockEndLines[firstBlock - 1] + 1);
    466         boolean createBlock = newLineCount > 0;
    467         boolean createBlockAfter = endLine < mBlockEndLines[lastBlock];
    468 
    469         int numAddedBlocks = 0;
    470         if (createBlockBefore) numAddedBlocks++;
    471         if (createBlock) numAddedBlocks++;
    472         if (createBlockAfter) numAddedBlocks++;
    473 
    474         final int numRemovedBlocks = lastBlock - firstBlock + 1;
    475         final int newNumberOfBlocks = mNumberOfBlocks + numAddedBlocks - numRemovedBlocks;
    476 
    477         if (newNumberOfBlocks == 0) {
    478             // Even when text is empty, there is actually one line and hence one block
    479             mBlockEndLines[0] = 0;
    480             mBlockIndices[0] = INVALID_BLOCK_INDEX;
    481             mNumberOfBlocks = 1;
    482             return;
    483         }
    484 
    485         if (newNumberOfBlocks > mBlockEndLines.length) {
    486             final int newSize = ArrayUtils.idealIntArraySize(newNumberOfBlocks);
    487             int[] blockEndLines = new int[newSize];
    488             int[] blockIndices = new int[newSize];
    489             System.arraycopy(mBlockEndLines, 0, blockEndLines, 0, firstBlock);
    490             System.arraycopy(mBlockIndices, 0, blockIndices, 0, firstBlock);
    491             System.arraycopy(mBlockEndLines, lastBlock + 1,
    492                     blockEndLines, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
    493             System.arraycopy(mBlockIndices, lastBlock + 1,
    494                     blockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
    495             mBlockEndLines = blockEndLines;
    496             mBlockIndices = blockIndices;
    497         } else {
    498             System.arraycopy(mBlockEndLines, lastBlock + 1,
    499                     mBlockEndLines, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
    500             System.arraycopy(mBlockIndices, lastBlock + 1,
    501                     mBlockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
    502         }
    503 
    504         mNumberOfBlocks = newNumberOfBlocks;
    505         final int deltaLines = newLineCount - (endLine - startLine + 1);
    506         if (deltaLines != 0) {
    507             // Display list whose index is >= mIndexFirstChangedBlock is valid
    508             // but it needs to update its drawing location.
    509             mIndexFirstChangedBlock = firstBlock + numAddedBlocks;
    510             for (int i = mIndexFirstChangedBlock; i < mNumberOfBlocks; i++) {
    511                 mBlockEndLines[i] += deltaLines;
    512             }
    513         } else {
    514             mIndexFirstChangedBlock = mNumberOfBlocks;
    515         }
    516 
    517         int blockIndex = firstBlock;
    518         if (createBlockBefore) {
    519             mBlockEndLines[blockIndex] = startLine - 1;
    520             mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
    521             blockIndex++;
    522         }
    523 
    524         if (createBlock) {
    525             mBlockEndLines[blockIndex] = startLine + newLineCount - 1;
    526             mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
    527             blockIndex++;
    528         }
    529 
    530         if (createBlockAfter) {
    531             mBlockEndLines[blockIndex] = lastBlockEndLine + deltaLines;
    532             mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
    533         }
    534     }
    535 
    536     /**
    537      * This package private method is used for test purposes only
    538      * @hide
    539      */
    540     void setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks) {
    541         mBlockEndLines = new int[blockEndLines.length];
    542         mBlockIndices = new int[blockIndices.length];
    543         System.arraycopy(blockEndLines, 0, mBlockEndLines, 0, blockEndLines.length);
    544         System.arraycopy(blockIndices, 0, mBlockIndices, 0, blockIndices.length);
    545         mNumberOfBlocks = numberOfBlocks;
    546     }
    547 
    548     /**
    549      * @hide
    550      */
    551     public int[] getBlockEndLines() {
    552         return mBlockEndLines;
    553     }
    554 
    555     /**
    556      * @hide
    557      */
    558     public int[] getBlockIndices() {
    559         return mBlockIndices;
    560     }
    561 
    562     /**
    563      * @hide
    564      */
    565     public int getNumberOfBlocks() {
    566         return mNumberOfBlocks;
    567     }
    568 
    569     /**
    570      * @hide
    571      */
    572     public int getIndexFirstChangedBlock() {
    573         return mIndexFirstChangedBlock;
    574     }
    575 
    576     /**
    577      * @hide
    578      */
    579     public void setIndexFirstChangedBlock(int i) {
    580         mIndexFirstChangedBlock = i;
    581     }
    582 
    583     @Override
    584     public int getLineCount() {
    585         return mInts.size() - 1;
    586     }
    587 
    588     @Override
    589     public int getLineTop(int line) {
    590         return mInts.getValue(line, TOP);
    591     }
    592 
    593     @Override
    594     public int getLineDescent(int line) {
    595         return mInts.getValue(line, DESCENT);
    596     }
    597 
    598     @Override
    599     public int getLineStart(int line) {
    600         return mInts.getValue(line, START) & START_MASK;
    601     }
    602 
    603     @Override
    604     public boolean getLineContainsTab(int line) {
    605         return (mInts.getValue(line, TAB) & TAB_MASK) != 0;
    606     }
    607 
    608     @Override
    609     public int getParagraphDirection(int line) {
    610         return mInts.getValue(line, DIR) >> DIR_SHIFT;
    611     }
    612 
    613     @Override
    614     public final Directions getLineDirections(int line) {
    615         return mObjects.getValue(line, 0);
    616     }
    617 
    618     @Override
    619     public int getTopPadding() {
    620         return mTopPadding;
    621     }
    622 
    623     @Override
    624     public int getBottomPadding() {
    625         return mBottomPadding;
    626     }
    627 
    628     @Override
    629     public int getEllipsizedWidth() {
    630         return mEllipsizedWidth;
    631     }
    632 
    633     private static class ChangeWatcher implements TextWatcher, SpanWatcher {
    634         public ChangeWatcher(DynamicLayout layout) {
    635             mLayout = new WeakReference<DynamicLayout>(layout);
    636         }
    637 
    638         private void reflow(CharSequence s, int where, int before, int after) {
    639             DynamicLayout ml = mLayout.get();
    640 
    641             if (ml != null)
    642                 ml.reflow(s, where, before, after);
    643             else if (s instanceof Spannable)
    644                 ((Spannable) s).removeSpan(this);
    645         }
    646 
    647         public void beforeTextChanged(CharSequence s, int where, int before, int after) {
    648             // Intentionally empty
    649         }
    650 
    651         public void onTextChanged(CharSequence s, int where, int before, int after) {
    652             reflow(s, where, before, after);
    653         }
    654 
    655         public void afterTextChanged(Editable s) {
    656             // Intentionally empty
    657         }
    658 
    659         public void onSpanAdded(Spannable s, Object o, int start, int end) {
    660             if (o instanceof UpdateLayout)
    661                 reflow(s, start, end - start, end - start);
    662         }
    663 
    664         public void onSpanRemoved(Spannable s, Object o, int start, int end) {
    665             if (o instanceof UpdateLayout)
    666                 reflow(s, start, end - start, end - start);
    667         }
    668 
    669         public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) {
    670             if (o instanceof UpdateLayout) {
    671                 reflow(s, start, end - start, end - start);
    672                 reflow(s, nstart, nend - nstart, nend - nstart);
    673             }
    674         }
    675 
    676         private WeakReference<DynamicLayout> mLayout;
    677     }
    678 
    679     @Override
    680     public int getEllipsisStart(int line) {
    681         if (mEllipsizeAt == null) {
    682             return 0;
    683         }
    684 
    685         return mInts.getValue(line, ELLIPSIS_START);
    686     }
    687 
    688     @Override
    689     public int getEllipsisCount(int line) {
    690         if (mEllipsizeAt == null) {
    691             return 0;
    692         }
    693 
    694         return mInts.getValue(line, ELLIPSIS_COUNT);
    695     }
    696 
    697     private CharSequence mBase;
    698     private CharSequence mDisplay;
    699     private ChangeWatcher mWatcher;
    700     private boolean mIncludePad;
    701     private boolean mEllipsize;
    702     private int mEllipsizedWidth;
    703     private TextUtils.TruncateAt mEllipsizeAt;
    704 
    705     private PackedIntVector mInts;
    706     private PackedObjectVector<Directions> mObjects;
    707 
    708     /**
    709      * Value used in mBlockIndices when a block has been created or recycled and indicating that its
    710      * display list needs to be re-created.
    711      * @hide
    712      */
    713     public static final int INVALID_BLOCK_INDEX = -1;
    714     // Stores the line numbers of the last line of each block (inclusive)
    715     private int[] mBlockEndLines;
    716     // The indices of this block's display list in TextView's internal display list array or
    717     // INVALID_BLOCK_INDEX if this block has been invalidated during an edition
    718     private int[] mBlockIndices;
    719     // Number of items actually currently being used in the above 2 arrays
    720     private int mNumberOfBlocks;
    721     // The first index of the blocks whose locations are changed
    722     private int mIndexFirstChangedBlock;
    723 
    724     private int mTopPadding, mBottomPadding;
    725 
    726     private static StaticLayout sStaticLayout = new StaticLayout(null);
    727 
    728     private static final Object[] sLock = new Object[0];
    729 
    730     private static final int START = 0;
    731     private static final int DIR = START;
    732     private static final int TAB = START;
    733     private static final int TOP = 1;
    734     private static final int DESCENT = 2;
    735     private static final int COLUMNS_NORMAL = 3;
    736 
    737     private static final int ELLIPSIS_START = 3;
    738     private static final int ELLIPSIS_COUNT = 4;
    739     private static final int COLUMNS_ELLIPSIZE = 5;
    740 
    741     private static final int START_MASK = 0x1FFFFFFF;
    742     private static final int DIR_SHIFT  = 30;
    743     private static final int TAB_MASK   = 0x20000000;
    744 
    745     private static final int ELLIPSIS_UNDEFINED = 0x80000000;
    746 }
    747