• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
DynamicLayout(CharSequence base, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad)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      */
DynamicLayout(CharSequence base, CharSequence display, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad)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      */
DynamicLayout(CharSequence base, CharSequence display, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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      */
DynamicLayout(CharSequence base, CharSequence display, TextPaint paint, int width, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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 
reflow(CharSequence s, int where, int before, int after)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      */
createBlocks()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      */
addBlockAtOffset(int offset)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      */
updateBlocks(int startLine, int endLine, int newLineCount)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         for (int i = firstBlock + numAddedBlocks; i < mNumberOfBlocks; i++) {
507             mBlockEndLines[i] += deltaLines;
508         }
509 
510         int blockIndex = firstBlock;
511         if (createBlockBefore) {
512             mBlockEndLines[blockIndex] = startLine - 1;
513             mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
514             blockIndex++;
515         }
516 
517         if (createBlock) {
518             mBlockEndLines[blockIndex] = startLine + newLineCount - 1;
519             mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
520             blockIndex++;
521         }
522 
523         if (createBlockAfter) {
524             mBlockEndLines[blockIndex] = lastBlockEndLine + deltaLines;
525             mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
526         }
527     }
528 
529     /**
530      * This package private method is used for test purposes only
531      * @hide
532      */
setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks)533     void setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks) {
534         mBlockEndLines = new int[blockEndLines.length];
535         mBlockIndices = new int[blockIndices.length];
536         System.arraycopy(blockEndLines, 0, mBlockEndLines, 0, blockEndLines.length);
537         System.arraycopy(blockIndices, 0, mBlockIndices, 0, blockIndices.length);
538         mNumberOfBlocks = numberOfBlocks;
539     }
540 
541     /**
542      * @hide
543      */
getBlockEndLines()544     public int[] getBlockEndLines() {
545         return mBlockEndLines;
546     }
547 
548     /**
549      * @hide
550      */
getBlockIndices()551     public int[] getBlockIndices() {
552         return mBlockIndices;
553     }
554 
555     /**
556      * @hide
557      */
getNumberOfBlocks()558     public int getNumberOfBlocks() {
559         return mNumberOfBlocks;
560     }
561 
562     @Override
getLineCount()563     public int getLineCount() {
564         return mInts.size() - 1;
565     }
566 
567     @Override
getLineTop(int line)568     public int getLineTop(int line) {
569         return mInts.getValue(line, TOP);
570     }
571 
572     @Override
getLineDescent(int line)573     public int getLineDescent(int line) {
574         return mInts.getValue(line, DESCENT);
575     }
576 
577     @Override
getLineStart(int line)578     public int getLineStart(int line) {
579         return mInts.getValue(line, START) & START_MASK;
580     }
581 
582     @Override
getLineContainsTab(int line)583     public boolean getLineContainsTab(int line) {
584         return (mInts.getValue(line, TAB) & TAB_MASK) != 0;
585     }
586 
587     @Override
getParagraphDirection(int line)588     public int getParagraphDirection(int line) {
589         return mInts.getValue(line, DIR) >> DIR_SHIFT;
590     }
591 
592     @Override
getLineDirections(int line)593     public final Directions getLineDirections(int line) {
594         return mObjects.getValue(line, 0);
595     }
596 
597     @Override
getTopPadding()598     public int getTopPadding() {
599         return mTopPadding;
600     }
601 
602     @Override
getBottomPadding()603     public int getBottomPadding() {
604         return mBottomPadding;
605     }
606 
607     @Override
getEllipsizedWidth()608     public int getEllipsizedWidth() {
609         return mEllipsizedWidth;
610     }
611 
612     private static class ChangeWatcher implements TextWatcher, SpanWatcher {
ChangeWatcher(DynamicLayout layout)613         public ChangeWatcher(DynamicLayout layout) {
614             mLayout = new WeakReference<DynamicLayout>(layout);
615         }
616 
reflow(CharSequence s, int where, int before, int after)617         private void reflow(CharSequence s, int where, int before, int after) {
618             DynamicLayout ml = mLayout.get();
619 
620             if (ml != null)
621                 ml.reflow(s, where, before, after);
622             else if (s instanceof Spannable)
623                 ((Spannable) s).removeSpan(this);
624         }
625 
beforeTextChanged(CharSequence s, int where, int before, int after)626         public void beforeTextChanged(CharSequence s, int where, int before, int after) {
627             // Intentionally empty
628         }
629 
onTextChanged(CharSequence s, int where, int before, int after)630         public void onTextChanged(CharSequence s, int where, int before, int after) {
631             reflow(s, where, before, after);
632         }
633 
afterTextChanged(Editable s)634         public void afterTextChanged(Editable s) {
635             // Intentionally empty
636         }
637 
onSpanAdded(Spannable s, Object o, int start, int end)638         public void onSpanAdded(Spannable s, Object o, int start, int end) {
639             if (o instanceof UpdateLayout)
640                 reflow(s, start, end - start, end - start);
641         }
642 
onSpanRemoved(Spannable s, Object o, int start, int end)643         public void onSpanRemoved(Spannable s, Object o, int start, int end) {
644             if (o instanceof UpdateLayout)
645                 reflow(s, start, end - start, end - start);
646         }
647 
onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend)648         public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) {
649             if (o instanceof UpdateLayout) {
650                 reflow(s, start, end - start, end - start);
651                 reflow(s, nstart, nend - nstart, nend - nstart);
652             }
653         }
654 
655         private WeakReference<DynamicLayout> mLayout;
656     }
657 
658     @Override
getEllipsisStart(int line)659     public int getEllipsisStart(int line) {
660         if (mEllipsizeAt == null) {
661             return 0;
662         }
663 
664         return mInts.getValue(line, ELLIPSIS_START);
665     }
666 
667     @Override
getEllipsisCount(int line)668     public int getEllipsisCount(int line) {
669         if (mEllipsizeAt == null) {
670             return 0;
671         }
672 
673         return mInts.getValue(line, ELLIPSIS_COUNT);
674     }
675 
676     private CharSequence mBase;
677     private CharSequence mDisplay;
678     private ChangeWatcher mWatcher;
679     private boolean mIncludePad;
680     private boolean mEllipsize;
681     private int mEllipsizedWidth;
682     private TextUtils.TruncateAt mEllipsizeAt;
683 
684     private PackedIntVector mInts;
685     private PackedObjectVector<Directions> mObjects;
686 
687     /**
688      * Value used in mBlockIndices when a block has been created or recycled and indicating that its
689      * display list needs to be re-created.
690      * @hide
691      */
692     public static final int INVALID_BLOCK_INDEX = -1;
693     // Stores the line numbers of the last line of each block (inclusive)
694     private int[] mBlockEndLines;
695     // The indices of this block's display list in TextView's internal display list array or
696     // INVALID_BLOCK_INDEX if this block has been invalidated during an edition
697     private int[] mBlockIndices;
698     // Number of items actually currently being used in the above 2 arrays
699     private int mNumberOfBlocks;
700 
701     private int mTopPadding, mBottomPadding;
702 
703     private static StaticLayout sStaticLayout = new StaticLayout(null);
704 
705     private static final Object[] sLock = new Object[0];
706 
707     private static final int START = 0;
708     private static final int DIR = START;
709     private static final int TAB = START;
710     private static final int TOP = 1;
711     private static final int DESCENT = 2;
712     private static final int COLUMNS_NORMAL = 3;
713 
714     private static final int ELLIPSIS_START = 3;
715     private static final int ELLIPSIS_COUNT = 4;
716     private static final int COLUMNS_ELLIPSIZE = 5;
717 
718     private static final int START_MASK = 0x1FFFFFFF;
719     private static final int DIR_SHIFT  = 30;
720     private static final int TAB_MASK   = 0x20000000;
721 
722     private static final int ELLIPSIS_UNDEFINED = 0x80000000;
723 }
724