1e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/*
2e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Copyright (C) 2010 The Android Open Source Project
3e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
4e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Licensed under the Apache License, Version 2.0 (the "License");
5e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * you may not use this file except in compliance with the License.
6e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * You may obtain a copy of the License at
7e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
8e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *      http://www.apache.org/licenses/LICENSE-2.0
9e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
10e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Unless required by applicable law or agreed to in writing, software
11e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * distributed under the License is distributed on an "AS IS" BASIS,
12e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * See the License for the specific language governing permissions and
14e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * limitations under the License.
15e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */
16e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
17e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltpackage android.text;
18e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
19538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport android.annotation.NonNull;
20538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport android.annotation.Nullable;
21e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Canvas;
22e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint;
23e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint.FontMetricsInt;
24e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.Layout.Directions;
25c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Feltimport android.text.Layout.TabStops;
26e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.CharacterStyle;
27e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.MetricAffectingSpan;
28e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.ReplacementSpan;
29e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.util.Log;
30e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
31dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunneimport com.android.internal.util.ArrayUtils;
32dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
33538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport java.util.ArrayList;
34538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
35e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/**
36e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Represents a line of styled text, for measuring in visual order and
37e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * for rendering.
38e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
39e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Get a new instance using obtain(), and when finished with it, return it
40e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * to the pool using recycle().
41e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
42e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Call set to prepare the instance for use, then either draw, measure,
43e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * metrics, or caretToLeftRightOf.
44e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
45e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @hide
46e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */
47e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltclass TextLine {
48bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private static final boolean DEBUG = false;
49bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy
50e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private TextPaint mPaint;
51e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private CharSequence mText;
52e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mStart;
53e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mLen;
54e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mDir;
55e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private Directions mDirections;
56e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private boolean mHasTabs;
57c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt    private TabStops mTabs;
58e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private char[] mChars;
59e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private boolean mCharsValid;
60e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private Spanned mSpanned;
6109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
6209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    // Additional width of whitespace for justification. This value is per whitespace, thus
6309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    // the line width will increase by mAddedWidth x (number of stretchable whitespaces).
6409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    private float mAddedWidth;
65538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
66345cb03315a0813ec57e44f97fc3fa4af6b3c309Gilles Debunne    private final TextPaint mWorkPaint = new TextPaint();
67538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    private final TextPaint mActivePaint = new TextPaint();
68c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
69c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
70c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
71c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<CharacterStyle>(CharacterStyle.class);
72c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
73c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<ReplacementSpan>(ReplacementSpan.class);
74e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
751378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader    private final DecorationInfo mDecorationInfo = new DecorationInfo();
761378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader    private final ArrayList<DecorationInfo> mDecorations = new ArrayList();
77538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
78bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private static final TextLine[] sCached = new TextLine[3];
79e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
80e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
81e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns a new TextLine from the shared pool.
82e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
83e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return an uninitialized TextLine
84e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
85e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    static TextLine obtain() {
86e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        TextLine tl;
87bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        synchronized (sCached) {
88bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            for (int i = sCached.length; --i >= 0;) {
89bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                if (sCached[i] != null) {
90bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    tl = sCached[i];
91bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    sCached[i] = null;
92e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    return tl;
93e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
94e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
95e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
96e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl = new TextLine();
97bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        if (DEBUG) {
98bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            Log.v("TLINE", "new: " + tl);
99bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        }
100e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return tl;
101e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
102e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
103e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
104e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Puts a TextLine back into the shared pool. Do not use this TextLine once
105e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * it has been returned.
106e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param tl the textLine
107e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return null, as a convenience from clearing references to the provided
108e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * TextLine
109e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
110e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    static TextLine recycle(TextLine tl) {
111e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mText = null;
112e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mPaint = null;
113e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mDirections = null;
114893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov        tl.mSpanned = null;
115893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov        tl.mTabs = null;
116893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov        tl.mChars = null;
117c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne
118c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mMetricAffectingSpanSpanSet.recycle();
119c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mCharacterStyleSpanSet.recycle();
120c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mReplacementSpanSpanSet.recycle();
121c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne
122bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        synchronized(sCached) {
123bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            for (int i = 0; i < sCached.length; ++i) {
124bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                if (sCached[i] == null) {
125bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    sCached[i] = tl;
126f902d7bc49797ec277b4576c921dfffa15d741ddGilles Debunne                    break;
127e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
128e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
129e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
130e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return null;
131e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
132e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
133e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
134e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Initializes a TextLine and prepares it for use.
135e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
136e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param paint the base paint for the line
137e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param text the text, can be Styled
138e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the line relative to the text
139e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the line relative to the text
140e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param dir the paragraph direction of this line
141e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param directions the directions information of this line
142112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader     * @param hasTabs true if the line might contain tabs
143c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt     * @param tabStops the tabStops. Can be null.
144e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
145e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
146c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt            Directions directions, boolean hasTabs, TabStops tabStops) {
147e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mPaint = paint;
148e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mText = text;
149e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mStart = start;
150e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mLen = limit - start;
151e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mDir = dir;
152e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mDirections = directions;
1538059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio        if (mDirections == null) {
1548059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio            throw new IllegalArgumentException("Directions cannot be null");
1558059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio        }
156e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mHasTabs = hasTabs;
157e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mSpanned = null;
158e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
159e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean hasReplacement = false;
160e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (text instanceof Spanned) {
161e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            mSpanned = (Spanned) text;
162c1f44830809f0a8526855f13822702ea756214faGilles Debunne            mReplacementSpanSpanSet.init(mSpanned, start, limit);
163c1f44830809f0a8526855f13822702ea756214faGilles Debunne            hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
164e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
165e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1661e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne        mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
167e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
168e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (mCharsValid) {
169e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mChars == null || mChars.length < mLen) {
170776abc24cdd18610232a50b997cce3cffa74609bAdam Lesinski                mChars = ArrayUtils.newUnpaddedCharArray(mLen);
171e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
172e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            TextUtils.getChars(text, start, limit, mChars, 0);
1730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (hasReplacement) {
1740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // Handle these all at once so we don't have to do it as we go.
1750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // Replace the first character of each replacement run with the
1760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // object-replacement character and the remainder with zero width
1770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // non-break space aka BOM.  Cursor movement code skips these
1780c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // zero-width characters.
1790c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                char[] chars = mChars;
1800c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                for (int i = start, inext; i < limit; i = inext) {
181c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    inext = mReplacementSpanSpanSet.getNextTransition(i, limit);
182c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    if (mReplacementSpanSpanSet.hasSpansIntersecting(i, inext)) {
1831e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne                        // transition into a span
1840c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        chars[i - start] = '\ufffc';
1850c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        for (int j = i - start + 1, e = inext - start; j < e; ++j) {
1860c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                            chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
1870c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        }
1880c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    }
1890c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                }
1900c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
191e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
192c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        mTabs = tabStops;
19309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        mAddedWidth = 0;
19409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    }
19509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
19609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    /**
19709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka     * Justify the line to the given width.
19809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka     */
19909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    void justify(float justifyWidth) {
20009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        int end = mLen;
20109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
20209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            end--;
20309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        }
20409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        final int spaces = countStretchableSpaces(0, end);
20509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        if (spaces == 0) {
20609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            // There are no stretchable spaces, so we can't help the justification by adding any
20709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            // width.
20809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            return;
20909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        }
21009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        final float width = Math.abs(measure(end, false, null));
21109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        mAddedWidth = (justifyWidth - width) / spaces;
212e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
213e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
214e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
215e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Renders the TextLine.
216e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
217e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas to render on
218e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the leading margin position
219e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
220e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
221e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
222e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
223e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    void draw(Canvas c, float x, int top, int y, int bottom) {
224e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (!mHasTabs) {
225e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
226bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                drawRun(c, 0, mLen, false, x, top, y, bottom, false);
227e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                return;
228e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
229e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
230bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                drawRun(c, 0, mLen, true, x, top, y, bottom, false);
231e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                return;
232e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
233e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
234e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
235e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float h = 0;
236e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
237e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
238e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lastRunIndex = runs.length - 2;
239e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        for (int i = 0; i < runs.length; i += 2) {
240e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runStart = runs[i];
241e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
242e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runLimit > mLen) {
243e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLimit = mLen;
244e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
245e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
246e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
247e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int segstart = runStart;
248e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
249e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                int codept = 0;
250e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (mHasTabs && j < runLimit) {
251e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    codept = mChars[j];
252112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
253e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        codept = Character.codePointAt(mChars, j);
254112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                        if (codept > 0xFFFF) {
255e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            ++j;
256e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            continue;
257e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
258e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
259e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
260e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
261112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                if (j == runLimit || codept == '\t') {
262bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
263e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            i != lastRunIndex || j != mLen);
264e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
265e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept == '\t') {
266e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h = mDir * nextTab(h * mDir);
267e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
268e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    segstart = j + 1;
269e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
270e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
271e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
272e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
273e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
274e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
275e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns metrics information for the entire line.
276e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
277e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives font metrics information, can be null
278e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the line
279e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
280e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float metrics(FontMetricsInt fmi) {
281e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return measure(mLen, false, fmi);
282e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
283e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
284e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
285e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns information about a position on the line.
286e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
287e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the line-relative character offset, between 0 and the
288e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * line length, inclusive
289e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param trailing true to measure the trailing edge of the character
290e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * before offset, false to measure the leading edge of the character
291e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * at offset.
292e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information about the requested
293e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * character, can be null.
294e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed offset from the leading margin to the requested
295e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * character edge.
296e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
297e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float measure(int offset, boolean trailing, FontMetricsInt fmi) {
298e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int target = trailing ? offset - 1 : offset;
299e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (target < 0) {
300e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return 0;
301e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
302e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
303e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float h = 0;
304e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
305e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (!mHasTabs) {
306e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
307bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                return measureRun(0, offset, mLen, false, fmi);
308e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
309e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
310bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                return measureRun(0, offset, mLen, true, fmi);
311e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
312e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
313e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
314e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        char[] chars = mChars;
315e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
316e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        for (int i = 0; i < runs.length; i += 2) {
317e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runStart = runs[i];
318e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
319e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runLimit > mLen) {
320e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLimit = mLen;
321e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
322e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
323e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
324e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int segstart = runStart;
325e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
326e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                int codept = 0;
327e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (mHasTabs && j < runLimit) {
328e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    codept = chars[j];
329112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
330e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        codept = Character.codePointAt(chars, j);
331112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                        if (codept > 0xFFFF) {
332e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            ++j;
333e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            continue;
334e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
335e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
336e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
337e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
338112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                if (j == runLimit || codept == '\t') {
339e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    boolean inSegment = target >= segstart && target < j;
340e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
341e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
342e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (inSegment && advance) {
343bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                        return h += measureRun(segstart, offset, j, runIsRtl, fmi);
344e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
345e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
346bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    float w = measureRun(segstart, j, j, runIsRtl, fmi);
347e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    h += advance ? w : -w;
348e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
349e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (inSegment) {
350bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                        return h += measureRun(segstart, offset, j, runIsRtl, null);
351e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
352e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
353e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept == '\t') {
354e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (offset == j) {
355e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            return h;
356e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
357e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h = mDir * nextTab(h * mDir);
358e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (target == j) {
359e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            return h;
360e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
361e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
362e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
363e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    segstart = j + 1;
364e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
365e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
366e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
367e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
368e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return h;
369e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
370e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
371e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
372e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Draws a unidirectional (but possibly multi-styled) run of text.
373e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
374bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
375e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas to draw on
376e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start
377e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the line-relative limit
378e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
379e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the position of the run that is closest to the leading margin
380e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
381e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
382e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
383e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width value is required.
384e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run, based on the paragraph direction.
385e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Only valid if needWidth is true.
386e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
387bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float drawRun(Canvas c, int start,
388e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int limit, boolean runIsRtl, float x, int top, int y, int bottom,
389e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean needWidth) {
390e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
391e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
392bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            float w = -measureRun(start, limit, limit, runIsRtl, null);
393bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            handleRun(start, limit, limit, runIsRtl, c, x + w, top,
3940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    y, bottom, null, false);
395e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return w;
396e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
397e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
398bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        return handleRun(start, limit, limit, runIsRtl, c, x, top,
3990c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                y, bottom, null, needWidth);
400e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
401e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
402e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
403e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Measures a unidirectional (but possibly multi-styled) run of text.
404e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
405bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
406e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start of the run
407e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the offset to measure to, between start and limit inclusive
408e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the line-relative limit of the run
409e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
410e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information about the requested
411e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * run, can be null.
412e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width from the start of the run to the leading edge
413e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * of the character at offset, based on the run (not paragraph) direction
414e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
415bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float measureRun(int start, int offset, int limit, boolean runIsRtl,
416bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            FontMetricsInt fmi) {
417bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
418e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
419e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
420e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
421e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Walk the cursor through this line, skipping conjuncts and
422e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * zero-width characters.
423e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
424e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * <p>This function cannot properly walk the cursor off the ends of the line
425e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * since it does not know about any shaping on the previous/following line
426e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * that might affect the cursor position. Callers must either avoid these
427e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * situations or handle the result specially.
428e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
429e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param cursor the starting position of the cursor, between 0 and the
430e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * length of the line, inclusive
431e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param toLeft true if the caret is moving to the left.
432e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the new offset.  If it is less than 0 or greater than the length
433e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * of the line, the previous/following line should be examined to get the
434e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * actual offset.
435e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
436e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
437e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 1) The caret marks the leading edge of a character. The character
438e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // logically before it might be on a different level, and the active caret
439e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // position is on the character at the lower level. If that character
440e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // was the previous character, the caret is on its trailing edge.
441e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 2) Take this character/edge and move it in the indicated direction.
442e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // This gives you a new character and a new edge.
443e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 3) This position is between two visually adjacent characters.  One of
444e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // these might be at a lower level.  The active position is on the
445e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // character at the lower level.
446e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 4) If the active position is on the trailing edge of the character,
447e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // the new caret position is the following logical character, else it
448e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // is the character.
449e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
450e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lineStart = 0;
451e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lineEnd = mLen;
452e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean paraIsRtl = mDir == -1;
453e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
454e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
455e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
456e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean trailing = false;
457e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
458e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (cursor == lineStart) {
459e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runIndex = -2;
460e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else if (cursor == lineEnd) {
461e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runIndex = runs.length;
462e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else {
463e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // First, get information about the run containing the character with
464e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // the active caret.
465e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
466e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runStart = lineStart + runs[runIndex];
467e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (cursor >= runStart) {
468e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
469e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (runLimit > lineEnd) {
470e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  runLimit = lineEnd;
471e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
472e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (cursor < runLimit) {
473e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
474e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    Layout.RUN_LEVEL_MASK;
475e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (cursor == runStart) {
476e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // The caret is on a run boundary, see if we should
477e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // use the position on the trailing edge of the previous
478e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // logical character instead.
479e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
480e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  int pos = cursor - 1;
481e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
482e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    prevRunStart = lineStart + runs[prevRunIndex];
483e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (pos >= prevRunStart) {
484e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      prevRunLimit = prevRunStart +
485e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
486e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      if (prevRunLimit > lineEnd) {
487e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          prevRunLimit = lineEnd;
488e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      }
489e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      if (pos < prevRunLimit) {
490e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
491e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            & Layout.RUN_LEVEL_MASK;
492e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (prevRunLevel < runLevel) {
493e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          // Start from logically previous character.
494e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runIndex = prevRunIndex;
495e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runLevel = prevRunLevel;
496e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runStart = prevRunStart;
497e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runLimit = prevRunLimit;
498e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          trailing = true;
499e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          break;
500e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
501e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      }
502e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
503e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  }
504e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
505e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                break;
506e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
507e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
508e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
509e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
510e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // caret might be == lineEnd.  This is generally a space or paragraph
511e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // separator and has an associated run, but might be the end of
512e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // text, in which case it doesn't.  If that happens, we ran off the
513e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // end of the run list, and runIndex == runs.length.  In this case,
514e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // we are at a run boundary so we skip the below test.
515e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (runIndex != runs.length) {
516e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              boolean runIsRtl = (runLevel & 0x1) != 0;
517e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              boolean advance = toLeft == runIsRtl;
518e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
519e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // Moving within or into the run, so we can move logically.
5200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                  newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
5210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                          runIsRtl, cursor, advance);
522e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // If the new position is internal to the run, we're at the strong
523e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // position already so we're finished.
524e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  if (newCaret != (advance ? runLimit : runStart)) {
525e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      return newCaret;
526e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  }
527e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
528e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
529e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
530e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
531e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // If newCaret is -1, we're starting at a run boundary and crossing
532e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // into another run. Otherwise we've arrived at a run boundary, and
533e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // need to figure out which character to attach to.  Note we might
534e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // need to run this twice, if we cross a run boundary and end up at
535e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // another run boundary.
536e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        while (true) {
537e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          boolean advance = toLeft == paraIsRtl;
538e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          int otherRunIndex = runIndex + (advance ? 2 : -2);
539e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
540e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunStart = lineStart + runs[otherRunIndex];
541e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunLimit = otherRunStart +
542e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
543e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (otherRunLimit > lineEnd) {
544e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                otherRunLimit = lineEnd;
545e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
546e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
547e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                Layout.RUN_LEVEL_MASK;
548e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
549e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
550e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            advance = toLeft == otherRunIsRtl;
551e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (newCaret == -1) {
5520c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
5530c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        otherRunLimit, otherRunIsRtl,
554e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        advance ? otherRunStart : otherRunLimit, advance);
555e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
556e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    // Crossed and ended up at a new boundary,
557e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    // repeat a second and final time.
558e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    runIndex = otherRunIndex;
559e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    runLevel = otherRunLevel;
560e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    continue;
561e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
562e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                break;
563e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
564e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
565e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            // The new caret is at a boundary.
566e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (otherRunLevel < runLevel) {
567e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // The strong character is in the other run.
568e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              newCaret = advance ? otherRunStart : otherRunLimit;
569e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
570e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            break;
571e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
572e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
573e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (newCaret == -1) {
574e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // We're walking off the end of the line.  The paragraph
575e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // level is always equal to or lower than any internal level, so
576e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // the boundaries get the strong caret.
5770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt              newCaret = advance ? mLen + 1 : -1;
578e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              break;
579e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
580e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
581e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // Else we've arrived at the end of the line.  That's a strong position.
582e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // We might have arrived here by crossing over a run with no internal
583e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // breaks and dropping out of the above loop before advancing one final
584e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // time, so reset the caret.
585e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // Note, we use '<=' below to handle a situation where the only run
586e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // on the line is a counter-directional run.  If we're not advancing,
587e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // we can end up at the 'lineEnd' position but the caret we want is at
588e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // the lineStart.
589e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (newCaret <= lineEnd) {
590e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              newCaret = advance ? lineEnd : lineStart;
591e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
592e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          break;
593e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
594e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
595e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return newCaret;
596e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
597e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
598e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
599e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns the next valid offset within this directional run, skipping
600e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * conjuncts and zero-width characters.  This should not be called to walk
601cc3ec6cdb2b892eb29513e72d8b205acbe997b25Gilles Debunne     * off the end of the line, since the returned values might not be valid
6020c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * on neighboring lines.  If the returned offset is less than zero or
6030c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * greater than the line length, the offset should be recomputed on the
6040c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * preceding or following line, respectively.
605e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
606e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIndex the run index
6070c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runStart the start of the run
6080c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runLimit the limit of the run
6090c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runIsRtl true if the run is right-to-left
610e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the offset
611e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param after true if the new offset should logically follow the provided
612e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * offset
613e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the new offset
614e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
6150c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
6160c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            boolean runIsRtl, int offset, boolean after) {
617e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
6180c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (runIndex < 0 || offset == (after ? mLen : 0)) {
6190c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // Walking off end of line.  Since we don't know
6200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // what cursor positions are available on other lines, we can't
6210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // return accurate values.  These are a guess.
622e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (after) {
6230c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
6240c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
6250c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
6260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        }
6270c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6280c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        TextPaint wp = mWorkPaint;
6290c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        wp.set(mPaint);
63009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        wp.setWordSpacing(mAddedWidth);
6310c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6320c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int spanStart = runStart;
6330c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int spanLimit;
6340c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (mSpanned == null) {
6350c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            spanLimit = runLimit;
6360c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        } else {
6370c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int target = after ? offset + 1 : offset;
6380c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int limit = mStart + runLimit;
6390c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            while (true) {
6400c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
6410c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        MetricAffectingSpan.class) - mStart;
6420c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                if (spanLimit >= target) {
6430c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    break;
644e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
6450c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                spanStart = spanLimit;
6460c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
6470c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6480c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
6490c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    mStart + spanLimit, MetricAffectingSpan.class);
6501e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne            spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
6510c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6520c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (spans.length > 0) {
6530c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                ReplacementSpan replacement = null;
6540c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                for (int j = 0; j < spans.length; j++) {
6550c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    MetricAffectingSpan span = spans[j];
6560c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    if (span instanceof ReplacementSpan) {
6570c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        replacement = (ReplacementSpan)span;
6580c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    } else {
6590c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        span.updateMeasureState(wp);
6600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    }
6610c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                }
6620c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6630c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                if (replacement != null) {
6640c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    // If we have a replacement span, we're moving either to
6650c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    // the start or end of this span.
6660c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    return after ? spanLimit : spanStart;
667e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
668e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
669e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
670e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
671051910b9f998030dacb8a0722588cc715813fde1Raph Levien        int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
6720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
6730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (mCharsValid) {
6740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
675051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    dir, offset, cursorOpt);
6760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        } else {
6770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return wp.getTextRunCursor(mText, mStart + spanStart,
678051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart;
679e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
680e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
681e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
682e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
6830bb000931bb841e75903d655552d1626ae158707Gilles Debunne     * @param wp
6840bb000931bb841e75903d655552d1626ae158707Gilles Debunne     */
6850bb000931bb841e75903d655552d1626ae158707Gilles Debunne    private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
6860bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousTop     = fmi.top;
6870bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousAscent  = fmi.ascent;
6880bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousDescent = fmi.descent;
6890bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousBottom  = fmi.bottom;
6900bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousLeading = fmi.leading;
6910bb000931bb841e75903d655552d1626ae158707Gilles Debunne
6920bb000931bb841e75903d655552d1626ae158707Gilles Debunne        wp.getFontMetricsInt(fmi);
6930bb000931bb841e75903d655552d1626ae158707Gilles Debunne
6948a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio        updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
6958a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousLeading);
6968a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio    }
6978a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
6988a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio    static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent,
6998a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousDescent, int previousBottom, int previousLeading) {
7000bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.top     = Math.min(fmi.top,     previousTop);
7010bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.ascent  = Math.min(fmi.ascent,  previousAscent);
7020bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.descent = Math.max(fmi.descent, previousDescent);
7030bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.bottom  = Math.max(fmi.bottom,  previousBottom);
7040bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.leading = Math.max(fmi.leading, previousLeading);
7050bb000931bb841e75903d655552d1626ae158707Gilles Debunne    }
7060bb000931bb841e75903d655552d1626ae158707Gilles Debunne
7071378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader    private static void drawStroke(TextPaint wp, Canvas c, int color, float position,
7081378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            float thickness, float xleft, float xright, float baseline) {
7091378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        final float strokeTop = baseline + wp.baselineShift + position;
710538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
711538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        final int previousColor = wp.getColor();
712538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        final Paint.Style previousStyle = wp.getStyle();
713538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        final boolean previousAntiAlias = wp.isAntiAlias();
714538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
715538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setStyle(Paint.Style.FILL);
716538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setAntiAlias(true);
717538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
718538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setColor(color);
7191378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        c.drawRect(xleft, strokeTop, xright, strokeTop + thickness, wp);
720538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
721538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setStyle(previousStyle);
722538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setColor(previousColor);
723538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setAntiAlias(previousAntiAlias);
724538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    }
725538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
726538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd,
727538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            boolean runIsRtl, int offset) {
728538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        if (mCharsValid) {
729538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset);
730538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        } else {
731538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final int delta = mStart;
732538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            return wp.getRunAdvance(mText, delta + start, delta + end,
733538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
734538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        }
735538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    }
736538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
7370bb000931bb841e75903d655552d1626ae158707Gilles Debunne    /**
738e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for measuring and rendering text.  The text must
739112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader     * not include a tab.
740e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
741e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the working paint
742e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the text
7430c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param end the end of the text
744e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
745e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null if rendering is not needed
746e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the edge of the run closest to the leading margin
747e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
748e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
749e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
750e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
751e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width of the run is needed
752909c7bca570b6f50650d0872e2037389b29252e3Raph Levien     * @param offset the offset for the purpose of measuring
7531378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader     * @param decorations the list of locations and paremeters for drawing decorations
754e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
755e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
756e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
7570c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private float handleText(TextPaint wp, int start, int end,
7580c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int contextStart, int contextEnd, boolean runIsRtl,
7590c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            Canvas c, float x, int top, int y, int bottom,
760538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            FontMetricsInt fmi, boolean needWidth, int offset,
7611378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            @Nullable ArrayList<DecorationInfo> decorations) {
762e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
76309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        wp.setWordSpacing(mAddedWidth);
764850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        // Get metrics first (even for empty strings or "0" width runs)
765850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        if (fmi != null) {
766850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio            expandMetricsFromPaint(fmi, wp);
767850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        }
768e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
769850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        // No need to do anything if the run width is "0"
770538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        if (end == start) {
771850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio            return 0f;
772850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        }
773850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio
774538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        float totalWidth = 0;
775850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio
7761378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        final int numDecorations = decorations == null ? 0 : decorations.size();
7771378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        if (needWidth || (c != null && (wp.bgColor != 0 || numDecorations != 0 || runIsRtl))) {
778538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
779e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
780e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
781e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (c != null) {
782538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final float leftX, rightX;
783e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runIsRtl) {
784538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                leftX = x - totalWidth;
785538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                rightX = x;
786538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            } else {
787538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                leftX = x;
788538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                rightX = x + totalWidth;
789e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
790e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
791e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (wp.bgColor != 0) {
792dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                int previousColor = wp.getColor();
793dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                Paint.Style previousStyle = wp.getStyle();
794dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
795e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                wp.setColor(wp.bgColor);
796e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                wp.setStyle(Paint.Style.FILL);
797538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                c.drawRect(leftX, top, rightX, bottom, wp);
798e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
799dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setStyle(previousStyle);
800dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setColor(previousColor);
801dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne            }
802dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
8031378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            if (numDecorations != 0) {
8041378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                for (int i = 0; i < numDecorations; i++) {
8051378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final DecorationInfo info = decorations.get(i);
8061378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader
8071378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final int decorationStart = Math.max(info.start, start);
8081378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final int decorationEnd = Math.min(info.end, offset);
8091378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    float decorationStartAdvance = getRunAdvance(
8101378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                            wp, start, end, contextStart, contextEnd, runIsRtl, decorationStart);
8111378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    float decorationEndAdvance = getRunAdvance(
8121378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                            wp, start, end, contextStart, contextEnd, runIsRtl, decorationEnd);
8131378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final float decorationXLeft, decorationXRight;
814538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    if (runIsRtl) {
8151378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        decorationXLeft = rightX - decorationEndAdvance;
8161378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        decorationXRight = rightX - decorationStartAdvance;
817538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    } else {
8181378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        decorationXLeft = leftX + decorationStartAdvance;
8191378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        decorationXRight = leftX + decorationEndAdvance;
820538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    }
821702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir
822538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // Theoretically, there could be cases where both Paint's and TextPaint's
823538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // setUnderLineText() are called. For backward compatibility, we need to draw
824538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // both underlines, the one with custom color first.
825538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    if (info.underlineColor != 0) {
8261378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        drawStroke(wp, c, info.underlineColor, wp.getUnderlinePosition(),
8271378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                                info.underlineThickness, decorationXLeft, decorationXRight, y);
828538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    }
829538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    if (info.isUnderlineText) {
830ca8a04a36640eb227a556ad9ced925c48ced2495Roozbeh Pournader                        final float thickness =
831ca8a04a36640eb227a556ad9ced925c48ced2495Roozbeh Pournader                                Math.max(((Paint) wp).getUnderlineThickness(), 1.0f);
8321378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness,
8331378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                                decorationXLeft, decorationXRight, y);
8341378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    }
8351378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader
8361378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    if (info.isStrikeThruText) {
8371378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        final float thickness =
8381378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                                Math.max(((Paint) wp).getStrikeThruThickness(), 1.0f);
8391378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness,
8401378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                                decorationXLeft, decorationXRight, y);
841538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    }
842538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                }
843e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
844e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
845da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio            drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
846538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    leftX, y + wp.baselineShift);
847e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
848e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
849538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        return runIsRtl ? -totalWidth : totalWidth;
850e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
851e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
852e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
853e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for measuring and rendering a replacement.
854e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
855bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
856e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param replacement the replacement
857e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the work paint
858e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the run
859e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the run
860e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
861e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null if not rendering
862e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the edge of the replacement closest to the leading margin
863e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
864e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
865e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
866e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
867e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width of the replacement is needed
868e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
869e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
870e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
871e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
872bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            int start, int limit, boolean runIsRtl, Canvas c,
873e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            float x, int top, int y, int bottom, FontMetricsInt fmi,
8740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            boolean needWidth) {
875e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
876e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float ret = 0;
877e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
8780c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int textStart = mStart + start;
8790c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int textLimit = mStart + limit;
880e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
8810c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (needWidth || (c != null && runIsRtl)) {
8828a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousTop = 0;
8838a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousAscent = 0;
8848a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousDescent = 0;
8858a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousBottom = 0;
8868a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousLeading = 0;
8878a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8888a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            boolean needUpdateMetrics = (fmi != null);
8898a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8908a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            if (needUpdateMetrics) {
8918a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousTop     = fmi.top;
8928a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousAscent  = fmi.ascent;
8938a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousDescent = fmi.descent;
8948a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousBottom  = fmi.bottom;
8958a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousLeading = fmi.leading;
8968a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            }
8978a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8980c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
8998a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
9008a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            if (needUpdateMetrics) {
9018a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
9028a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                        previousLeading);
9038a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            }
9040c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        }
905e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
9060c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (c != null) {
9070c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (runIsRtl) {
9080c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                x -= ret;
909e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
9100c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            replacement.draw(c, mText, textStart, textLimit,
9110c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    x, top, y, bottom, wp);
912e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
913e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
914e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return runIsRtl ? -ret : ret;
915e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
916945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
91746c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader    private int adjustHyphenEdit(int start, int limit, int hyphenEdit) {
91846c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        int result = hyphenEdit;
91946c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        // Only draw hyphens on first or last run in line. Disable them otherwise.
92046c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        if (start > 0) { // not the first run
92146c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader            result &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
92246c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        }
92346c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        if (limit < mLen) { // not the last run
92446c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader            result &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
92546c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        }
92646c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        return result;
92746c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader    }
92846c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader
9291378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader    private static final class DecorationInfo {
9301378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        public boolean isStrikeThruText;
931538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public boolean isUnderlineText;
932538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public int underlineColor;
933538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public float underlineThickness;
934538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public int start = -1;
935538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public int end = -1;
936538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
9371378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        public boolean hasDecoration() {
9381378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            return isStrikeThruText || isUnderlineText || underlineColor != 0;
939538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        }
940538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
941538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        // Copies the info, but not the start and end range.
9421378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        public DecorationInfo copyInfo() {
9431378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            final DecorationInfo copy = new DecorationInfo();
9441378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            copy.isStrikeThruText = isStrikeThruText;
945538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            copy.isUnderlineText = isUnderlineText;
946538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            copy.underlineColor = underlineColor;
947538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            copy.underlineThickness = underlineThickness;
948538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            return copy;
949538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        }
950538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    }
951538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
9521378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader    private void extractDecorationInfo(@NonNull TextPaint paint, @NonNull DecorationInfo info) {
9531378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        info.isStrikeThruText = paint.isStrikeThruText();
9541378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        if (info.isStrikeThruText) {
9551378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            paint.setStrikeThruText(false);
9561378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        }
957538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        info.isUnderlineText = paint.isUnderlineText();
958538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        if (info.isUnderlineText) {
959538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            paint.setUnderlineText(false);
960538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        }
961538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        info.underlineColor = paint.underlineColor;
962538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        info.underlineThickness = paint.underlineThickness;
963538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        paint.setUnderlineText(0, 0.0f);
964538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    }
965538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
966e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
967e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for handling a unidirectional run.  The run must not
968112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader     * contain tabs but can contain styles.
969e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
970bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
971e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start of the run
9720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param measureLimit the offset to measure to, between start and limit inclusive
973e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the run
974e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
975e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null
976e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the end of the run closest to the leading margin
977e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
978e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
979e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
980e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
981e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width is required
982e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
983e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
984e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
985bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float handleRun(int start, int measureLimit,
986e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
9870c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int bottom, FontMetricsInt fmi, boolean needWidth) {
988e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
9899f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir        if (measureLimit < start || measureLimit > limit) {
9909f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir            throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
9919f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir                    + "start (" + start + ") and limit (" + limit + ") bounds");
9929f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir        }
9939f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir
994f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        // Case of an empty line, make sure we update fmi according to mPaint
995f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        if (start == measureLimit) {
996538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final TextPaint wp = mWorkPaint;
997f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne            wp.set(mPaint);
99815c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            if (fmi != null) {
99915c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio                expandMetricsFromPaint(fmi, wp);
100015c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            }
100115c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            return 0f;
1002f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        }
1003f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne
100408836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader        final boolean needsSpanMeasurement;
1005945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        if (mSpanned == null) {
100608836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader            needsSpanMeasurement = false;
100708836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader        } else {
100808836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader            mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
100908836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader            mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
101008836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader            needsSpanMeasurement = mMetricAffectingSpanSpanSet.numberOfSpans != 0
101108836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader                    || mCharacterStyleSpanSet.numberOfSpans != 0;
101208836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader        }
101308836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader
101408836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader        if (!needsSpanMeasurement) {
1015538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final TextPaint wp = mWorkPaint;
1016945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            wp.set(mPaint);
101746c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader            wp.setHyphenEdit(adjustHyphenEdit(start, limit, wp.getHyphenEdit()));
1018909c7bca570b6f50650d0872e2037389b29252e3Raph Levien            return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top,
1019538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    y, bottom, fmi, needWidth, measureLimit, null);
1020945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        }
1021945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
1022e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // Shaping needs to take into account context up to metric boundaries,
1023e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // but rendering needs to take into account character style boundaries.
10240c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // So we iterate through metric runs to get metric bounds,
10250c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // then within each metric run iterate through character style runs
10260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // for the run bounds.
1027945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        final float originalX = x;
10280c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        for (int i = start, inext; i < measureLimit; i = inext) {
1029538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final TextPaint wp = mWorkPaint;
1030e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            wp.set(mPaint);
1031e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1032c1f44830809f0a8526855f13822702ea756214faGilles Debunne            inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
1033c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    mStart;
1034945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            int mlimit = Math.min(inext, measureLimit);
1035945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
1036945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            ReplacementSpan replacement = null;
1037945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
1038c1f44830809f0a8526855f13822702ea756214faGilles Debunne            for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
1039945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
1040945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                // empty by construction. This special case in getSpans() explains the >= & <= tests
1041c1f44830809f0a8526855f13822702ea756214faGilles Debunne                if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
1042c1f44830809f0a8526855f13822702ea756214faGilles Debunne                        (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
1043538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                final MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
1044945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                if (span instanceof ReplacementSpan) {
1045945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    replacement = (ReplacementSpan)span;
1046945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                } else {
1047945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    // We might have a replacement that uses the draw
1048945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    // state, otherwise measure state would suffice.
1049945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    span.updateDrawState(wp);
1050e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
1051e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
1052e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1053945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            if (replacement != null) {
1054945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
1055945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                        bottom, fmi, needWidth || mlimit < measureLimit);
1056945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                continue;
1057945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            }
1058945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
1059538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final TextPaint activePaint = mActivePaint;
1060538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            activePaint.set(mPaint);
1061538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            int activeStart = i;
1062538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            int activeEnd = mlimit;
10631378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            final DecorationInfo decorationInfo = mDecorationInfo;
10641378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            mDecorations.clear();
106542ef515d185d4fc038d602172789cc264f1d9960Raph Levien            for (int j = i, jnext; j < mlimit; j = jnext) {
1066702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) -
1067702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                        mStart;
106842ef515d185d4fc038d602172789cc264f1d9960Raph Levien
1069538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                final int offset = Math.min(jnext, mlimit);
107042ef515d185d4fc038d602172789cc264f1d9960Raph Levien                wp.set(mPaint);
107142ef515d185d4fc038d602172789cc264f1d9960Raph Levien                for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
107242ef515d185d4fc038d602172789cc264f1d9960Raph Levien                    // Intentionally using >= and <= as explained above
1073702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                    if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) ||
1074702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                            (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
10750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
1076538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    final CharacterStyle span = mCharacterStyleSpanSet.spans[k];
1077702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                    span.updateDrawState(wp);
1078e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
107942ef515d185d4fc038d602172789cc264f1d9960Raph Levien
10801378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                extractDecorationInfo(wp, decorationInfo);
1081538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
1082538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                if (j == i) {
1083538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // First chunk of text. We can't handle it yet, since we may need to merge it
1084538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // with the next chunk. So we just save the TextPaint for future comparisons
1085538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // and use.
1086538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activePaint.set(wp);
1087538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                } else if (!wp.hasEqualAttributes(activePaint)) {
1088538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // The style of the present chunk of text is substantially different from the
1089538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // style of the previous chunk. We need to handle the active piece of text
1090538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // and restart with the present chunk.
1091538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activePaint.setHyphenEdit(adjustHyphenEdit(
1092538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                            activeStart, activeEnd, mPaint.getHyphenEdit()));
1093538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
1094538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                            top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
10951378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                            Math.min(activeEnd, mlimit), mDecorations);
1096538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
1097538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activeStart = j;
1098538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activePaint.set(wp);
10991378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    mDecorations.clear();
1100538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                } else {
1101538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // The present TextPaint is substantially equal to the last TextPaint except
11021378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    // perhaps for decorations. We just need to expand the active piece of text to
1103538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // include the present chunk, which we always do anyway. We don't need to save
1104538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // wp to activePaint, since they are already equal.
1105538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                }
110646c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader
1107538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                activeEnd = jnext;
11081378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                if (decorationInfo.hasDecoration()) {
11091378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final DecorationInfo copy = decorationInfo.copyInfo();
1110538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    copy.start = j;
1111538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    copy.end = jnext;
11121378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    mDecorations.add(copy);
1113538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                }
1114e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
1115538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            // Handle the final piece of text.
1116538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            activePaint.setHyphenEdit(adjustHyphenEdit(
1117538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activeStart, activeEnd, mPaint.getHyphenEdit()));
1118538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
1119538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
11201378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    Math.min(activeEnd, mlimit), mDecorations);
1121e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1122e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1123945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        return x - originalX;
1124e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
1125e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1126e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
1127e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Render a text run with the set-up paint.
1128e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
1129e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas
1130e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the paint used to render the text
11310c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param start the start of the run
11320c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param end the end of the run
11330c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param contextStart the start of context for the run
11340c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param contextEnd the end of the context for the run
1135da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio     * @param runIsRtl true if the run is right-to-left
1136e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the x position of the left edge of the run
1137e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline of the run
1138e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
11390c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
1140da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
1141e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1142e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (mCharsValid) {
11430c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int count = end - start;
11440c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int contextCount = contextEnd - contextStart;
11450c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            c.drawTextRun(mChars, start, count, contextStart, contextCount,
1146051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    x, y, runIsRtl, wp);
1147e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else {
11480c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int delta = mStart;
11490c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            c.drawTextRun(mText, delta + start, delta + end,
1150051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp);
1151e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1152e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
1153e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1154e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
1155e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns the next tab position.
1156e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
1157e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param h the (unsigned) offset from the leading margin
1158e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the (unsigned) tab position after this offset
1159e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
1160e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float nextTab(float h) {
1161c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        if (mTabs != null) {
1162c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt            return mTabs.nextTab(h);
1163e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1164c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        return TabStops.nextDefaultStop(h, TAB_INCREMENT);
1165e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
1166e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
116709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    private boolean isStretchableWhitespace(int ch) {
116809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        // TODO: Support other stretchable whitespace. (Bug: 34013491)
116909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        return ch == 0x0020 || ch == 0x00A0;
117009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    }
117109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
117209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    private int nextStretchableSpace(int start, int end) {
117309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        for (int i = start; i < end; i++) {
117409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
117509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            if (isStretchableWhitespace(c)) return i;
117609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        }
117709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        return end;
117809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    }
117909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
118009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    /* Return the number of spaces in the text line, for the purpose of justification */
118109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    private int countStretchableSpaces(int start, int end) {
118209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        int count = 0;
118309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) {
118409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            count++;
118509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        }
118609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        return count;
118709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    }
118809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
118909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
119009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    public static boolean isLineEndSpace(char ch) {
119109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        return ch == ' ' || ch == '\t' || ch == 0x1680
119209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka                || (0x2000 <= ch && ch <= 0x200A && ch != 0x2007)
119309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka                || ch == 0x205F || ch == 0x3000;
119409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    }
119509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
1196e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private static final int TAB_INCREMENT = 20;
1197e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt}
1198