TextLine.java revision 9f3958cc2ba3da1406caac64650620d226bf8562
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
19e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Canvas;
20e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint;
21e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint.FontMetricsInt;
22e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.Layout.Directions;
23c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Feltimport android.text.Layout.TabStops;
24e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.CharacterStyle;
25e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.MetricAffectingSpan;
26e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.ReplacementSpan;
27e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.util.Log;
28e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
29dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunneimport com.android.internal.util.ArrayUtils;
30dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
31e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/**
32e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Represents a line of styled text, for measuring in visual order and
33e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * for rendering.
34e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
35e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Get a new instance using obtain(), and when finished with it, return it
36e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * to the pool using recycle().
37e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
38e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Call set to prepare the instance for use, then either draw, measure,
39e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * metrics, or caretToLeftRightOf.
40e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
41e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @hide
42e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */
43e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltclass TextLine {
44bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private static final boolean DEBUG = false;
45bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy
46e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private TextPaint mPaint;
47e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private CharSequence mText;
48e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mStart;
49e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mLen;
50e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mDir;
51e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private Directions mDirections;
52e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private boolean mHasTabs;
53c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt    private TabStops mTabs;
54e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private char[] mChars;
55e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private boolean mCharsValid;
56e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private Spanned mSpanned;
57345cb03315a0813ec57e44f97fc3fa4af6b3c309Gilles Debunne    private final TextPaint mWorkPaint = new TextPaint();
58c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
59c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
60c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
61c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<CharacterStyle>(CharacterStyle.class);
62c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
63c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<ReplacementSpan>(ReplacementSpan.class);
64e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
65bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private static final TextLine[] sCached = new TextLine[3];
66e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
67e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
68e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns a new TextLine from the shared pool.
69e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
70e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return an uninitialized TextLine
71e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
72e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    static TextLine obtain() {
73e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        TextLine tl;
74bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        synchronized (sCached) {
75bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            for (int i = sCached.length; --i >= 0;) {
76bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                if (sCached[i] != null) {
77bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    tl = sCached[i];
78bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    sCached[i] = null;
79e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    return tl;
80e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
81e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
82e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
83e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl = new TextLine();
84bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        if (DEBUG) {
85bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            Log.v("TLINE", "new: " + tl);
86bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        }
87e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return tl;
88e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
89e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
90e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
91e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Puts a TextLine back into the shared pool. Do not use this TextLine once
92e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * it has been returned.
93e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param tl the textLine
94e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return null, as a convenience from clearing references to the provided
95e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * TextLine
96e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
97e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    static TextLine recycle(TextLine tl) {
98e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mText = null;
99e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mPaint = null;
100e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mDirections = null;
101893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov        tl.mSpanned = null;
102893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov        tl.mTabs = null;
103893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov        tl.mChars = null;
104c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne
105c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mMetricAffectingSpanSpanSet.recycle();
106c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mCharacterStyleSpanSet.recycle();
107c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mReplacementSpanSpanSet.recycle();
108c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne
109bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        synchronized(sCached) {
110bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            for (int i = 0; i < sCached.length; ++i) {
111bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                if (sCached[i] == null) {
112bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    sCached[i] = tl;
113f902d7bc49797ec277b4576c921dfffa15d741ddGilles Debunne                    break;
114e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
115e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
116e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
117e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return null;
118e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
119e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
120e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
121e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Initializes a TextLine and prepares it for use.
122e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
123e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param paint the base paint for the line
124e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param text the text, can be Styled
125e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the line relative to the text
126e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the line relative to the text
127e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param dir the paragraph direction of this line
128e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param directions the directions information of this line
129112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader     * @param hasTabs true if the line might contain tabs
130c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt     * @param tabStops the tabStops. Can be null.
131e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
132e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
133c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt            Directions directions, boolean hasTabs, TabStops tabStops) {
134e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mPaint = paint;
135e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mText = text;
136e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mStart = start;
137e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mLen = limit - start;
138e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mDir = dir;
139e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mDirections = directions;
1408059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio        if (mDirections == null) {
1418059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio            throw new IllegalArgumentException("Directions cannot be null");
1428059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio        }
143e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mHasTabs = hasTabs;
144e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mSpanned = null;
145e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
146e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean hasReplacement = false;
147e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (text instanceof Spanned) {
148e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            mSpanned = (Spanned) text;
149c1f44830809f0a8526855f13822702ea756214faGilles Debunne            mReplacementSpanSpanSet.init(mSpanned, start, limit);
150c1f44830809f0a8526855f13822702ea756214faGilles Debunne            hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
151e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
152e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1531e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne        mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
154e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
155e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (mCharsValid) {
156e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mChars == null || mChars.length < mLen) {
157776abc24cdd18610232a50b997cce3cffa74609bAdam Lesinski                mChars = ArrayUtils.newUnpaddedCharArray(mLen);
158e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
159e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            TextUtils.getChars(text, start, limit, mChars, 0);
1600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (hasReplacement) {
1610c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // Handle these all at once so we don't have to do it as we go.
1620c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // Replace the first character of each replacement run with the
1630c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // object-replacement character and the remainder with zero width
1640c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // non-break space aka BOM.  Cursor movement code skips these
1650c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // zero-width characters.
1660c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                char[] chars = mChars;
1670c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                for (int i = start, inext; i < limit; i = inext) {
168c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    inext = mReplacementSpanSpanSet.getNextTransition(i, limit);
169c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    if (mReplacementSpanSpanSet.hasSpansIntersecting(i, inext)) {
1701e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne                        // transition into a span
1710c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        chars[i - start] = '\ufffc';
1720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        for (int j = i - start + 1, e = inext - start; j < e; ++j) {
1730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                            chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
1740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        }
1750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    }
1760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                }
1770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
178e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
179c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        mTabs = tabStops;
180e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
181e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
182e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
183e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Renders the TextLine.
184e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
185e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas to render on
186e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the leading margin position
187e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
188e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
189e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
190e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
191e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    void draw(Canvas c, float x, int top, int y, int bottom) {
192e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (!mHasTabs) {
193e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
194bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                drawRun(c, 0, mLen, false, x, top, y, bottom, false);
195e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                return;
196e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
197e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
198bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                drawRun(c, 0, mLen, true, x, top, y, bottom, false);
199e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                return;
200e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
201e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
202e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
203e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float h = 0;
204e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
205e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
206e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lastRunIndex = runs.length - 2;
207e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        for (int i = 0; i < runs.length; i += 2) {
208e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runStart = runs[i];
209e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
210e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runLimit > mLen) {
211e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLimit = mLen;
212e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
213e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
214e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
215e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int segstart = runStart;
216e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
217e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                int codept = 0;
218e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (mHasTabs && j < runLimit) {
219e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    codept = mChars[j];
220112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
221e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        codept = Character.codePointAt(mChars, j);
222112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                        if (codept > 0xFFFF) {
223e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            ++j;
224e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            continue;
225e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
226e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
227e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
228e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
229112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                if (j == runLimit || codept == '\t') {
230bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
231e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            i != lastRunIndex || j != mLen);
232e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
233e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept == '\t') {
234e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h = mDir * nextTab(h * mDir);
235e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
236e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    segstart = j + 1;
237e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
238e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
239e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
240e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
241e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
242e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
243e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns metrics information for the entire line.
244e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
245e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives font metrics information, can be null
246e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the line
247e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
248e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float metrics(FontMetricsInt fmi) {
249e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return measure(mLen, false, fmi);
250e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
251e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
252e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
253e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns information about a position on the line.
254e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
255e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the line-relative character offset, between 0 and the
256e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * line length, inclusive
257e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param trailing true to measure the trailing edge of the character
258e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * before offset, false to measure the leading edge of the character
259e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * at offset.
260e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information about the requested
261e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * character, can be null.
262e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed offset from the leading margin to the requested
263e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * character edge.
264e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
265e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float measure(int offset, boolean trailing, FontMetricsInt fmi) {
266e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int target = trailing ? offset - 1 : offset;
267e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (target < 0) {
268e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return 0;
269e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
270e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
271e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float h = 0;
272e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
273e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (!mHasTabs) {
274e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
275bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                return measureRun(0, offset, mLen, false, fmi);
276e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
277e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
278bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                return measureRun(0, offset, mLen, true, fmi);
279e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
280e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
281e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
282e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        char[] chars = mChars;
283e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
284e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        for (int i = 0; i < runs.length; i += 2) {
285e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runStart = runs[i];
286e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
287e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runLimit > mLen) {
288e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLimit = mLen;
289e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
290e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
291e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
292e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int segstart = runStart;
293e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
294e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                int codept = 0;
295e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (mHasTabs && j < runLimit) {
296e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    codept = chars[j];
297112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
298e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        codept = Character.codePointAt(chars, j);
299112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                        if (codept > 0xFFFF) {
300e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            ++j;
301e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            continue;
302e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
303e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
304e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
305e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
306112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                if (j == runLimit || codept == '\t') {
307e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    boolean inSegment = target >= segstart && target < j;
308e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
309e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
310e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (inSegment && advance) {
311bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                        return h += measureRun(segstart, offset, j, runIsRtl, fmi);
312e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
313e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
314bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    float w = measureRun(segstart, j, j, runIsRtl, fmi);
315e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    h += advance ? w : -w;
316e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
317e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (inSegment) {
318bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                        return h += measureRun(segstart, offset, j, runIsRtl, null);
319e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
320e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
321e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept == '\t') {
322e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (offset == j) {
323e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            return h;
324e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
325e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h = mDir * nextTab(h * mDir);
326e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (target == j) {
327e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            return h;
328e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
329e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
330e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
331e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    segstart = j + 1;
332e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
333e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
334e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
335e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
336e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return h;
337e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
338e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
339e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
340e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Draws a unidirectional (but possibly multi-styled) run of text.
341e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
342bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
343e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas to draw on
344e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start
345e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the line-relative limit
346e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
347e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the position of the run that is closest to the leading margin
348e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
349e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
350e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
351e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width value is required.
352e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run, based on the paragraph direction.
353e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Only valid if needWidth is true.
354e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
355bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float drawRun(Canvas c, int start,
356e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int limit, boolean runIsRtl, float x, int top, int y, int bottom,
357e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean needWidth) {
358e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
359e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
360bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            float w = -measureRun(start, limit, limit, runIsRtl, null);
361bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            handleRun(start, limit, limit, runIsRtl, c, x + w, top,
3620c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    y, bottom, null, false);
363e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return w;
364e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
365e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
366bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        return handleRun(start, limit, limit, runIsRtl, c, x, top,
3670c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                y, bottom, null, needWidth);
368e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
369e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
370e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
371e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Measures a unidirectional (but possibly multi-styled) run of text.
372e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
373bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
374e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start of the run
375e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the offset to measure to, between start and limit inclusive
376e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the line-relative limit of the run
377e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
378e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information about the requested
379e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * run, can be null.
380e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width from the start of the run to the leading edge
381e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * of the character at offset, based on the run (not paragraph) direction
382e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
383bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float measureRun(int start, int offset, int limit, boolean runIsRtl,
384bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            FontMetricsInt fmi) {
385bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
386e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
387e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
388e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
389e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Walk the cursor through this line, skipping conjuncts and
390e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * zero-width characters.
391e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
392e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * <p>This function cannot properly walk the cursor off the ends of the line
393e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * since it does not know about any shaping on the previous/following line
394e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * that might affect the cursor position. Callers must either avoid these
395e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * situations or handle the result specially.
396e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
397e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param cursor the starting position of the cursor, between 0 and the
398e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * length of the line, inclusive
399e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param toLeft true if the caret is moving to the left.
400e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the new offset.  If it is less than 0 or greater than the length
401e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * of the line, the previous/following line should be examined to get the
402e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * actual offset.
403e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
404e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
405e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 1) The caret marks the leading edge of a character. The character
406e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // logically before it might be on a different level, and the active caret
407e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // position is on the character at the lower level. If that character
408e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // was the previous character, the caret is on its trailing edge.
409e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 2) Take this character/edge and move it in the indicated direction.
410e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // This gives you a new character and a new edge.
411e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 3) This position is between two visually adjacent characters.  One of
412e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // these might be at a lower level.  The active position is on the
413e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // character at the lower level.
414e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 4) If the active position is on the trailing edge of the character,
415e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // the new caret position is the following logical character, else it
416e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // is the character.
417e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
418e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lineStart = 0;
419e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lineEnd = mLen;
420e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean paraIsRtl = mDir == -1;
421e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
422e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
423e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
424e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean trailing = false;
425e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
426e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (cursor == lineStart) {
427e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runIndex = -2;
428e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else if (cursor == lineEnd) {
429e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runIndex = runs.length;
430e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else {
431e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // First, get information about the run containing the character with
432e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // the active caret.
433e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
434e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runStart = lineStart + runs[runIndex];
435e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (cursor >= runStart) {
436e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
437e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (runLimit > lineEnd) {
438e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  runLimit = lineEnd;
439e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
440e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (cursor < runLimit) {
441e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
442e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    Layout.RUN_LEVEL_MASK;
443e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (cursor == runStart) {
444e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // The caret is on a run boundary, see if we should
445e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // use the position on the trailing edge of the previous
446e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // logical character instead.
447e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
448e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  int pos = cursor - 1;
449e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
450e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    prevRunStart = lineStart + runs[prevRunIndex];
451e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (pos >= prevRunStart) {
452e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      prevRunLimit = prevRunStart +
453e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
454e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      if (prevRunLimit > lineEnd) {
455e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          prevRunLimit = lineEnd;
456e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      }
457e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      if (pos < prevRunLimit) {
458e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
459e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            & Layout.RUN_LEVEL_MASK;
460e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (prevRunLevel < runLevel) {
461e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          // Start from logically previous character.
462e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runIndex = prevRunIndex;
463e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runLevel = prevRunLevel;
464e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runStart = prevRunStart;
465e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runLimit = prevRunLimit;
466e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          trailing = true;
467e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          break;
468e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
469e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      }
470e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
471e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  }
472e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
473e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                break;
474e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
475e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
476e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
477e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
478e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // caret might be == lineEnd.  This is generally a space or paragraph
479e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // separator and has an associated run, but might be the end of
480e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // text, in which case it doesn't.  If that happens, we ran off the
481e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // end of the run list, and runIndex == runs.length.  In this case,
482e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // we are at a run boundary so we skip the below test.
483e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (runIndex != runs.length) {
484e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              boolean runIsRtl = (runLevel & 0x1) != 0;
485e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              boolean advance = toLeft == runIsRtl;
486e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
487e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // Moving within or into the run, so we can move logically.
4880c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                  newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
4890c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                          runIsRtl, cursor, advance);
490e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // If the new position is internal to the run, we're at the strong
491e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // position already so we're finished.
492e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  if (newCaret != (advance ? runLimit : runStart)) {
493e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      return newCaret;
494e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  }
495e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
496e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
497e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
498e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
499e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // If newCaret is -1, we're starting at a run boundary and crossing
500e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // into another run. Otherwise we've arrived at a run boundary, and
501e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // need to figure out which character to attach to.  Note we might
502e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // need to run this twice, if we cross a run boundary and end up at
503e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // another run boundary.
504e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        while (true) {
505e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          boolean advance = toLeft == paraIsRtl;
506e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          int otherRunIndex = runIndex + (advance ? 2 : -2);
507e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
508e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunStart = lineStart + runs[otherRunIndex];
509e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunLimit = otherRunStart +
510e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
511e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (otherRunLimit > lineEnd) {
512e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                otherRunLimit = lineEnd;
513e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
514e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
515e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                Layout.RUN_LEVEL_MASK;
516e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
517e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
518e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            advance = toLeft == otherRunIsRtl;
519e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (newCaret == -1) {
5200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
5210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        otherRunLimit, otherRunIsRtl,
522e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        advance ? otherRunStart : otherRunLimit, advance);
523e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
524e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    // Crossed and ended up at a new boundary,
525e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    // repeat a second and final time.
526e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    runIndex = otherRunIndex;
527e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    runLevel = otherRunLevel;
528e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    continue;
529e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
530e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                break;
531e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
532e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
533e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            // The new caret is at a boundary.
534e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (otherRunLevel < runLevel) {
535e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // The strong character is in the other run.
536e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              newCaret = advance ? otherRunStart : otherRunLimit;
537e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
538e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            break;
539e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
540e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
541e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (newCaret == -1) {
542e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // We're walking off the end of the line.  The paragraph
543e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // level is always equal to or lower than any internal level, so
544e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // the boundaries get the strong caret.
5450c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt              newCaret = advance ? mLen + 1 : -1;
546e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              break;
547e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
548e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
549e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // Else we've arrived at the end of the line.  That's a strong position.
550e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // We might have arrived here by crossing over a run with no internal
551e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // breaks and dropping out of the above loop before advancing one final
552e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // time, so reset the caret.
553e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // Note, we use '<=' below to handle a situation where the only run
554e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // on the line is a counter-directional run.  If we're not advancing,
555e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // we can end up at the 'lineEnd' position but the caret we want is at
556e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // the lineStart.
557e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (newCaret <= lineEnd) {
558e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              newCaret = advance ? lineEnd : lineStart;
559e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
560e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          break;
561e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
562e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
563e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return newCaret;
564e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
565e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
566e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
567e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns the next valid offset within this directional run, skipping
568e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * conjuncts and zero-width characters.  This should not be called to walk
569cc3ec6cdb2b892eb29513e72d8b205acbe997b25Gilles Debunne     * off the end of the line, since the returned values might not be valid
5700c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * on neighboring lines.  If the returned offset is less than zero or
5710c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * greater than the line length, the offset should be recomputed on the
5720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * preceding or following line, respectively.
573e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
574e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIndex the run index
5750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runStart the start of the run
5760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runLimit the limit of the run
5770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runIsRtl true if the run is right-to-left
578e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the offset
579e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param after true if the new offset should logically follow the provided
580e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * offset
581e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the new offset
582e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
5830c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
5840c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            boolean runIsRtl, int offset, boolean after) {
585e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
5860c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (runIndex < 0 || offset == (after ? mLen : 0)) {
5870c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // Walking off end of line.  Since we don't know
5880c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // what cursor positions are available on other lines, we can't
5890c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // return accurate values.  These are a guess.
590e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (after) {
5910c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
5920c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
5930c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
5940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        }
5950c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
5960c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        TextPaint wp = mWorkPaint;
5970c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        wp.set(mPaint);
5980c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
5990c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int spanStart = runStart;
6000c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int spanLimit;
6010c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (mSpanned == null) {
6020c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            spanLimit = runLimit;
6030c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        } else {
6040c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int target = after ? offset + 1 : offset;
6050c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int limit = mStart + runLimit;
6060c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            while (true) {
6070c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
6080c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        MetricAffectingSpan.class) - mStart;
6090c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                if (spanLimit >= target) {
6100c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    break;
611e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
6120c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                spanStart = spanLimit;
6130c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
6140c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6150c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
6160c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    mStart + spanLimit, MetricAffectingSpan.class);
6171e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne            spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
6180c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6190c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (spans.length > 0) {
6200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                ReplacementSpan replacement = null;
6210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                for (int j = 0; j < spans.length; j++) {
6220c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    MetricAffectingSpan span = spans[j];
6230c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    if (span instanceof ReplacementSpan) {
6240c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        replacement = (ReplacementSpan)span;
6250c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    } else {
6260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        span.updateMeasureState(wp);
6270c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    }
6280c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                }
6290c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6300c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                if (replacement != null) {
6310c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    // If we have a replacement span, we're moving either to
6320c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    // the start or end of this span.
6330c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    return after ? spanLimit : spanStart;
634e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
635e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
636e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
637e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
638051910b9f998030dacb8a0722588cc715813fde1Raph Levien        int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
6390c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
6400c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (mCharsValid) {
6410c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
642051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    dir, offset, cursorOpt);
6430c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        } else {
6440c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return wp.getTextRunCursor(mText, mStart + spanStart,
645051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart;
646e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
647e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
648e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
649e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
6500bb000931bb841e75903d655552d1626ae158707Gilles Debunne     * @param wp
6510bb000931bb841e75903d655552d1626ae158707Gilles Debunne     */
6520bb000931bb841e75903d655552d1626ae158707Gilles Debunne    private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
6530bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousTop     = fmi.top;
6540bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousAscent  = fmi.ascent;
6550bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousDescent = fmi.descent;
6560bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousBottom  = fmi.bottom;
6570bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousLeading = fmi.leading;
6580bb000931bb841e75903d655552d1626ae158707Gilles Debunne
6590bb000931bb841e75903d655552d1626ae158707Gilles Debunne        wp.getFontMetricsInt(fmi);
6600bb000931bb841e75903d655552d1626ae158707Gilles Debunne
6618a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio        updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
6628a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousLeading);
6638a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio    }
6648a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
6658a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio    static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent,
6668a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousDescent, int previousBottom, int previousLeading) {
6670bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.top     = Math.min(fmi.top,     previousTop);
6680bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.ascent  = Math.min(fmi.ascent,  previousAscent);
6690bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.descent = Math.max(fmi.descent, previousDescent);
6700bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.bottom  = Math.max(fmi.bottom,  previousBottom);
6710bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.leading = Math.max(fmi.leading, previousLeading);
6720bb000931bb841e75903d655552d1626ae158707Gilles Debunne    }
6730bb000931bb841e75903d655552d1626ae158707Gilles Debunne
6740bb000931bb841e75903d655552d1626ae158707Gilles Debunne    /**
675e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for measuring and rendering text.  The text must
676112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader     * not include a tab.
677e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
678e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the working paint
679e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the text
6800c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param end the end of the text
681e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
682e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null if rendering is not needed
683e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the edge of the run closest to the leading margin
684e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
685e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
686e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
687e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
688e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width of the run is needed
689909c7bca570b6f50650d0872e2037389b29252e3Raph Levien     * @param offset the offset for the purpose of measuring
690e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
691e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
692e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
6930c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private float handleText(TextPaint wp, int start, int end,
6940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int contextStart, int contextEnd, boolean runIsRtl,
6950c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            Canvas c, float x, int top, int y, int bottom,
696909c7bca570b6f50650d0872e2037389b29252e3Raph Levien            FontMetricsInt fmi, boolean needWidth, int offset) {
697e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
698850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        // Get metrics first (even for empty strings or "0" width runs)
699850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        if (fmi != null) {
700850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio            expandMetricsFromPaint(fmi, wp);
701850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        }
702e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
7030c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int runLen = end - start;
704850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        // No need to do anything if the run width is "0"
705850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        if (runLen == 0) {
706850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio            return 0f;
707850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        }
708850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio
709850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        float ret = 0;
710850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio
711c9fd978da60f76c0576150c55629a034e1fa19fbGilles Debunne        if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
712e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mCharsValid) {
713ea2b4a40f7236172398ebcaa273612e00340d847Raph Levien                ret = wp.getRunAdvance(mChars, start, end, contextStart, contextEnd,
714909c7bca570b6f50650d0872e2037389b29252e3Raph Levien                        runIsRtl, offset);
715e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            } else {
7160c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                int delta = mStart;
717ea2b4a40f7236172398ebcaa273612e00340d847Raph Levien                ret = wp.getRunAdvance(mText, delta + start, delta + end,
718909c7bca570b6f50650d0872e2037389b29252e3Raph Levien                        delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
719e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
720e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
721e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
722e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (c != null) {
723e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runIsRtl) {
724e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                x -= ret;
725e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
726e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
727e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (wp.bgColor != 0) {
728dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                int previousColor = wp.getColor();
729dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                Paint.Style previousStyle = wp.getStyle();
730dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
731e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                wp.setColor(wp.bgColor);
732e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                wp.setStyle(Paint.Style.FILL);
733e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                c.drawRect(x, top, x + ret, bottom, wp);
734e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
735dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setStyle(previousStyle);
736dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setColor(previousColor);
737dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne            }
738dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
739c9fd978da60f76c0576150c55629a034e1fa19fbGilles Debunne            if (wp.underlineColor != 0) {
740dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
741e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin                float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();
742dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
743dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                int previousColor = wp.getColor();
744dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                Paint.Style previousStyle = wp.getStyle();
745e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin                boolean previousAntiAlias = wp.isAntiAlias();
746dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
747dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setStyle(Paint.Style.FILL);
748e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin                wp.setAntiAlias(true);
749e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin
750c9fd978da60f76c0576150c55629a034e1fa19fbGilles Debunne                wp.setColor(wp.underlineColor);
751c9fd978da60f76c0576150c55629a034e1fa19fbGilles Debunne                c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp);
752dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
753dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setStyle(previousStyle);
754dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setColor(previousColor);
755e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin                wp.setAntiAlias(previousAntiAlias);
756e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
757e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
758da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio            drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
759da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio                    x, y + wp.baselineShift);
760e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
761e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
762e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return runIsRtl ? -ret : ret;
763e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
764e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
765e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
766e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for measuring and rendering a replacement.
767e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
768bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
769e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param replacement the replacement
770e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the work paint
771e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the run
772e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the run
773e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
774e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null if not rendering
775e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the edge of the replacement closest to the leading margin
776e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
777e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
778e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
779e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
780e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width of the replacement is needed
781e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
782e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
783e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
784e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
785bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            int start, int limit, boolean runIsRtl, Canvas c,
786e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            float x, int top, int y, int bottom, FontMetricsInt fmi,
7870c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            boolean needWidth) {
788e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
789e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float ret = 0;
790e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
7910c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int textStart = mStart + start;
7920c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int textLimit = mStart + limit;
793e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
7940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (needWidth || (c != null && runIsRtl)) {
7958a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousTop = 0;
7968a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousAscent = 0;
7978a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousDescent = 0;
7988a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousBottom = 0;
7998a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousLeading = 0;
8008a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8018a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            boolean needUpdateMetrics = (fmi != null);
8028a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8038a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            if (needUpdateMetrics) {
8048a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousTop     = fmi.top;
8058a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousAscent  = fmi.ascent;
8068a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousDescent = fmi.descent;
8078a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousBottom  = fmi.bottom;
8088a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousLeading = fmi.leading;
8098a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            }
8108a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8110c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
8128a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8138a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            if (needUpdateMetrics) {
8148a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
8158a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                        previousLeading);
8168a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            }
8170c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        }
818e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
8190c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (c != null) {
8200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (runIsRtl) {
8210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                x -= ret;
822e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
8230c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            replacement.draw(c, mText, textStart, textLimit,
8240c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    x, top, y, bottom, wp);
825e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
826e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
827e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return runIsRtl ? -ret : ret;
828e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
829945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
830e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
831e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for handling a unidirectional run.  The run must not
832112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader     * contain tabs but can contain styles.
833e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
834bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
835e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start of the run
8360c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param measureLimit the offset to measure to, between start and limit inclusive
837e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the run
838e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
839e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null
840e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the end of the run closest to the leading margin
841e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
842e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
843e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
844e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
845e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width is required
846e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
847e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
848e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
849bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float handleRun(int start, int measureLimit,
850e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
8510c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int bottom, FontMetricsInt fmi, boolean needWidth) {
852e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
8539f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir        if (measureLimit < start || measureLimit > limit) {
8549f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir            throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
8559f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir                    + "start (" + start + ") and limit (" + limit + ") bounds");
8569f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir        }
8579f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir
858f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        // Case of an empty line, make sure we update fmi according to mPaint
859f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        if (start == measureLimit) {
860f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne            TextPaint wp = mWorkPaint;
861f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne            wp.set(mPaint);
86215c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            if (fmi != null) {
86315c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio                expandMetricsFromPaint(fmi, wp);
86415c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            }
86515c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            return 0f;
866f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        }
867f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne
868945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        if (mSpanned == null) {
869945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            TextPaint wp = mWorkPaint;
870945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            wp.set(mPaint);
871945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            final int mlimit = measureLimit;
872909c7bca570b6f50650d0872e2037389b29252e3Raph Levien            return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top,
873909c7bca570b6f50650d0872e2037389b29252e3Raph Levien                    y, bottom, fmi, needWidth || mlimit < measureLimit, mlimit);
874945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        }
875945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
876c1f44830809f0a8526855f13822702ea756214faGilles Debunne        mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
877c1f44830809f0a8526855f13822702ea756214faGilles Debunne        mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
878945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
879e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // Shaping needs to take into account context up to metric boundaries,
880e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // but rendering needs to take into account character style boundaries.
8810c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // So we iterate through metric runs to get metric bounds,
8820c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // then within each metric run iterate through character style runs
8830c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // for the run bounds.
884945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        final float originalX = x;
8850c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        for (int i = start, inext; i < measureLimit; i = inext) {
886e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            TextPaint wp = mWorkPaint;
887e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            wp.set(mPaint);
888e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
889c1f44830809f0a8526855f13822702ea756214faGilles Debunne            inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
890c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    mStart;
891945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            int mlimit = Math.min(inext, measureLimit);
892945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
893945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            ReplacementSpan replacement = null;
894945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
895c1f44830809f0a8526855f13822702ea756214faGilles Debunne            for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
896945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
897945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                // empty by construction. This special case in getSpans() explains the >= & <= tests
898c1f44830809f0a8526855f13822702ea756214faGilles Debunne                if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
899c1f44830809f0a8526855f13822702ea756214faGilles Debunne                        (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
900c1f44830809f0a8526855f13822702ea756214faGilles Debunne                MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
901945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                if (span instanceof ReplacementSpan) {
902945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    replacement = (ReplacementSpan)span;
903945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                } else {
904945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    // We might have a replacement that uses the draw
905945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    // state, otherwise measure state would suffice.
906945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    span.updateDrawState(wp);
907e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
908e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
909e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
910945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            if (replacement != null) {
911945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
912945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                        bottom, fmi, needWidth || mlimit < measureLimit);
913945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                continue;
914945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            }
915945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
91642ef515d185d4fc038d602172789cc264f1d9960Raph Levien            for (int j = i, jnext; j < mlimit; j = jnext) {
917909c7bca570b6f50650d0872e2037389b29252e3Raph Levien                jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) -
91842ef515d185d4fc038d602172789cc264f1d9960Raph Levien                        mStart;
919909c7bca570b6f50650d0872e2037389b29252e3Raph Levien                int offset = Math.min(jnext, mlimit);
92042ef515d185d4fc038d602172789cc264f1d9960Raph Levien
92142ef515d185d4fc038d602172789cc264f1d9960Raph Levien                wp.set(mPaint);
92242ef515d185d4fc038d602172789cc264f1d9960Raph Levien                for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
92342ef515d185d4fc038d602172789cc264f1d9960Raph Levien                    // Intentionally using >= and <= as explained above
924909c7bca570b6f50650d0872e2037389b29252e3Raph Levien                    if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) ||
92542ef515d185d4fc038d602172789cc264f1d9960Raph Levien                            (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
9260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
92742ef515d185d4fc038d602172789cc264f1d9960Raph Levien                    CharacterStyle span = mCharacterStyleSpanSet.spans[k];
92842ef515d185d4fc038d602172789cc264f1d9960Raph Levien                    span.updateDrawState(wp);
929e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
93042ef515d185d4fc038d602172789cc264f1d9960Raph Levien
93126d443aee4ee5a8791417b4ca09e8c78ba8dc78bRaph Levien                // Only draw hyphen on last run in line
93226d443aee4ee5a8791417b4ca09e8c78ba8dc78bRaph Levien                if (jnext < mLen) {
93326d443aee4ee5a8791417b4ca09e8c78ba8dc78bRaph Levien                    wp.setHyphenEdit(0);
93426d443aee4ee5a8791417b4ca09e8c78ba8dc78bRaph Levien                }
93542ef515d185d4fc038d602172789cc264f1d9960Raph Levien                x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
936909c7bca570b6f50650d0872e2037389b29252e3Raph Levien                        top, y, bottom, fmi, needWidth || jnext < measureLimit, offset);
937e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
938e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
939e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
940945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        return x - originalX;
941e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
942e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
943e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
944e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Render a text run with the set-up paint.
945e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
946e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas
947e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the paint used to render the text
9480c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param start the start of the run
9490c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param end the end of the run
9500c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param contextStart the start of context for the run
9510c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param contextEnd the end of the context for the run
952da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio     * @param runIsRtl true if the run is right-to-left
953e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the x position of the left edge of the run
954e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline of the run
955e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
9560c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
957da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
958e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
959e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (mCharsValid) {
9600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int count = end - start;
9610c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int contextCount = contextEnd - contextStart;
9620c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            c.drawTextRun(mChars, start, count, contextStart, contextCount,
963051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    x, y, runIsRtl, wp);
964e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else {
9650c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int delta = mStart;
9660c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            c.drawTextRun(mText, delta + start, delta + end,
967051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp);
968e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
969e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
970e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
971e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
972e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns the next tab position.
973e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
974e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param h the (unsigned) offset from the leading margin
975e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the (unsigned) tab position after this offset
976e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
977e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float nextTab(float h) {
978c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        if (mTabs != null) {
979c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt            return mTabs.nextTab(h);
980e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
981c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        return TabStops.nextDefaultStop(h, TAB_INCREMENT);
982e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
983e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
984e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private static final int TAB_INCREMENT = 20;
985e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt}
986