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