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