TextLine.java revision 4e90fa262d57c1c1ee72166e2ddfce391696ca24
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
313630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournaderimport com.android.internal.annotations.VisibleForTesting;
32dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunneimport com.android.internal.util.ArrayUtils;
33dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
34538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport java.util.ArrayList;
35538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
36e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/**
37e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Represents a line of styled text, for measuring in visual order and
38e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * for rendering.
39e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
40e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Get a new instance using obtain(), and when finished with it, return it
41e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * to the pool using recycle().
42e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
43e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Call set to prepare the instance for use, then either draw, measure,
44e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * metrics, or caretToLeftRightOf.
45e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
46e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @hide
47e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */
483630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
493630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournaderpublic class TextLine {
50bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private static final boolean DEBUG = false;
51bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy
52e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private TextPaint mPaint;
53e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private CharSequence mText;
54e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mStart;
55e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mLen;
56e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mDir;
57e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private Directions mDirections;
58e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private boolean mHasTabs;
59c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt    private TabStops mTabs;
60e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private char[] mChars;
61e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private boolean mCharsValid;
62e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private Spanned mSpanned;
634e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka    private MeasuredText mMeasured;
6409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
6509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    // Additional width of whitespace for justification. This value is per whitespace, thus
6609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    // the line width will increase by mAddedWidth x (number of stretchable whitespaces).
6709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    private float mAddedWidth;
68538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
69345cb03315a0813ec57e44f97fc3fa4af6b3c309Gilles Debunne    private final TextPaint mWorkPaint = new TextPaint();
70538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    private final TextPaint mActivePaint = new TextPaint();
71c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
72c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
73c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
74c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<CharacterStyle>(CharacterStyle.class);
75c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
76c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<ReplacementSpan>(ReplacementSpan.class);
77e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
781378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader    private final DecorationInfo mDecorationInfo = new DecorationInfo();
79a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir    private final ArrayList<DecorationInfo> mDecorations = new ArrayList<>();
80538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
81bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private static final TextLine[] sCached = new TextLine[3];
82e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
83e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
84e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns a new TextLine from the shared pool.
85e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
86e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return an uninitialized TextLine
87e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
883630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
893630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader    public static TextLine obtain() {
90e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        TextLine tl;
91bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        synchronized (sCached) {
92bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            for (int i = sCached.length; --i >= 0;) {
93bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                if (sCached[i] != null) {
94bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    tl = sCached[i];
95bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    sCached[i] = null;
96e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    return tl;
97e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
98e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
99e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
100e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl = new TextLine();
101bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        if (DEBUG) {
102bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            Log.v("TLINE", "new: " + tl);
103bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        }
104e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return tl;
105e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
106e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
107e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
108e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Puts a TextLine back into the shared pool. Do not use this TextLine once
109e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * it has been returned.
110e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param tl the textLine
111e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return null, as a convenience from clearing references to the provided
112e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * TextLine
113e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
1143630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
1153630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader    public static TextLine recycle(TextLine tl) {
116e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mText = null;
117e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mPaint = null;
118e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mDirections = null;
119893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov        tl.mSpanned = null;
120893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov        tl.mTabs = null;
121893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov        tl.mChars = null;
1224e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka        tl.mMeasured = null;
123c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne
124c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mMetricAffectingSpanSpanSet.recycle();
125c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mCharacterStyleSpanSet.recycle();
126c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mReplacementSpanSpanSet.recycle();
127c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne
128bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        synchronized(sCached) {
129bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            for (int i = 0; i < sCached.length; ++i) {
130bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                if (sCached[i] == null) {
131bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    sCached[i] = tl;
132f902d7bc49797ec277b4576c921dfffa15d741ddGilles Debunne                    break;
133e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
134e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
135e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
136e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return null;
137e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
138e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
139e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
140e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Initializes a TextLine and prepares it for use.
141e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
142e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param paint the base paint for the line
143e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param text the text, can be Styled
144e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the line relative to the text
145e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the line relative to the text
146e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param dir the paragraph direction of this line
147e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param directions the directions information of this line
148112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader     * @param hasTabs true if the line might contain tabs
149c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt     * @param tabStops the tabStops. Can be null.
150e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
1513630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
1524e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka    public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
1534e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka            Directions directions, boolean hasTabs, TabStops tabStops) {
154e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mPaint = paint;
155e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mText = text;
156e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mStart = start;
157e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mLen = limit - start;
158e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mDir = dir;
159e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mDirections = directions;
1608059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio        if (mDirections == null) {
1618059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio            throw new IllegalArgumentException("Directions cannot be null");
1628059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio        }
163e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mHasTabs = hasTabs;
164e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mSpanned = null;
165e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
166e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean hasReplacement = false;
167e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (text instanceof Spanned) {
168e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            mSpanned = (Spanned) text;
169c1f44830809f0a8526855f13822702ea756214faGilles Debunne            mReplacementSpanSpanSet.init(mSpanned, start, limit);
170c1f44830809f0a8526855f13822702ea756214faGilles Debunne            hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
171e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
172e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1734e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka        mMeasured = null;
1744e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka        if (text instanceof MeasuredText) {
1754e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka            MeasuredText mt = (MeasuredText) text;
1764e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka            if (mt.canUseMeasuredResult(paint)) {
1774e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka                mMeasured = mt;
1784e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka            }
1794e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka        }
1804e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka
1811e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne        mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
182e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
183e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (mCharsValid) {
184e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mChars == null || mChars.length < mLen) {
185776abc24cdd18610232a50b997cce3cffa74609bAdam Lesinski                mChars = ArrayUtils.newUnpaddedCharArray(mLen);
186e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
187e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            TextUtils.getChars(text, start, limit, mChars, 0);
1880c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (hasReplacement) {
1890c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // Handle these all at once so we don't have to do it as we go.
1900c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // Replace the first character of each replacement run with the
1910c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // object-replacement character and the remainder with zero width
1920c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // non-break space aka BOM.  Cursor movement code skips these
1930c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // zero-width characters.
1940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                char[] chars = mChars;
1950c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                for (int i = start, inext; i < limit; i = inext) {
196c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    inext = mReplacementSpanSpanSet.getNextTransition(i, limit);
197c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    if (mReplacementSpanSpanSet.hasSpansIntersecting(i, inext)) {
1981e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne                        // transition into a span
1990c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        chars[i - start] = '\ufffc';
2000c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        for (int j = i - start + 1, e = inext - start; j < e; ++j) {
2010c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                            chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
2020c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        }
2030c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    }
2040c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                }
2050c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
206e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
207c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        mTabs = tabStops;
20809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        mAddedWidth = 0;
20909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    }
21009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
21109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    /**
21209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka     * Justify the line to the given width.
21309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka     */
2143630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2153630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader    public void justify(float justifyWidth) {
21609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        int end = mLen;
21709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
21809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            end--;
21909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        }
22009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        final int spaces = countStretchableSpaces(0, end);
22109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        if (spaces == 0) {
22209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            // There are no stretchable spaces, so we can't help the justification by adding any
22309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            // width.
22409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka            return;
22509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        }
22609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        final float width = Math.abs(measure(end, false, null));
22709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        mAddedWidth = (justifyWidth - width) / spaces;
228e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
229e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
230e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
231e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Renders the TextLine.
232e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
233e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas to render on
234e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the leading margin position
235e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
236e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
237e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
238e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
239e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    void draw(Canvas c, float x, int top, int y, int bottom) {
240e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (!mHasTabs) {
241e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
242bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                drawRun(c, 0, mLen, false, x, top, y, bottom, false);
243e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                return;
244e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
245e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
246bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                drawRun(c, 0, mLen, true, x, top, y, bottom, false);
247e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                return;
248e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
249e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
250e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
251e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float h = 0;
252e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
253e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
254e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lastRunIndex = runs.length - 2;
255e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        for (int i = 0; i < runs.length; i += 2) {
256e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runStart = runs[i];
257e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
258e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runLimit > mLen) {
259e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLimit = mLen;
260e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
261e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
262e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
263e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int segstart = runStart;
264e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
265e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                int codept = 0;
266e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (mHasTabs && j < runLimit) {
267e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    codept = mChars[j];
268112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
269e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        codept = Character.codePointAt(mChars, j);
270112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                        if (codept > 0xFFFF) {
271e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            ++j;
272e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            continue;
273e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
274e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
275e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
276e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
277112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                if (j == runLimit || codept == '\t') {
278bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
279e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            i != lastRunIndex || j != mLen);
280e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
281e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept == '\t') {
282e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h = mDir * nextTab(h * mDir);
283e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
284e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    segstart = j + 1;
285e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
286e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
287e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
288e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
289e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
290e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
291e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns metrics information for the entire line.
292e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
293e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives font metrics information, can be null
294e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the line
295e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
2963630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2973630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader    public float metrics(FontMetricsInt fmi) {
298e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return measure(mLen, false, fmi);
299e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
300e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
301e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
302e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns information about a position on the line.
303e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
304e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the line-relative character offset, between 0 and the
305e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * line length, inclusive
306e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param trailing true to measure the trailing edge of the character
307e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * before offset, false to measure the leading edge of the character
308e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * at offset.
309e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information about the requested
310e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * character, can be null.
311e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed offset from the leading margin to the requested
312e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * character edge.
313e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
314e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float measure(int offset, boolean trailing, FontMetricsInt fmi) {
315e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int target = trailing ? offset - 1 : offset;
316e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (target < 0) {
317e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return 0;
318e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
319e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
320e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float h = 0;
321e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
322e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (!mHasTabs) {
323e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
324bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                return measureRun(0, offset, mLen, false, fmi);
325e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
326e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
327bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                return measureRun(0, offset, mLen, true, fmi);
328e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
329e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
330e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
331e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        char[] chars = mChars;
332e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
333e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        for (int i = 0; i < runs.length; i += 2) {
334e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runStart = runs[i];
335e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
336e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runLimit > mLen) {
337e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLimit = mLen;
338e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
339e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
340e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
341e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int segstart = runStart;
342e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
343e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                int codept = 0;
344e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (mHasTabs && j < runLimit) {
345e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    codept = chars[j];
346112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                    if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
347e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        codept = Character.codePointAt(chars, j);
348112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                        if (codept > 0xFFFF) {
349e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            ++j;
350e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            continue;
351e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
352e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
353e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
354e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
355112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader                if (j == runLimit || codept == '\t') {
356e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    boolean inSegment = target >= segstart && target < j;
357e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
358e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
359e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (inSegment && advance) {
360a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir                        return h + measureRun(segstart, offset, j, runIsRtl, fmi);
361e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
362e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
363bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    float w = measureRun(segstart, j, j, runIsRtl, fmi);
364e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    h += advance ? w : -w;
365e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
366e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (inSegment) {
367a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir                        return h + measureRun(segstart, offset, j, runIsRtl, null);
368e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
369e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
370e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept == '\t') {
371e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (offset == j) {
372e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            return h;
373e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
374e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h = mDir * nextTab(h * mDir);
375e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (target == j) {
376e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            return h;
377e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
378e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
379e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
380e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    segstart = j + 1;
381e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
382e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
383e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
384e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
385e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return h;
386e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
387e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
388e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
389e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Draws a unidirectional (but possibly multi-styled) run of text.
390e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
391bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
392e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas to draw on
393e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start
394e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the line-relative limit
395e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
396e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the position of the run that is closest to the leading margin
397e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
398e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
399e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
400e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width value is required.
401e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run, based on the paragraph direction.
402e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Only valid if needWidth is true.
403e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
404bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float drawRun(Canvas c, int start,
405e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int limit, boolean runIsRtl, float x, int top, int y, int bottom,
406e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean needWidth) {
407e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
408e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
409bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            float w = -measureRun(start, limit, limit, runIsRtl, null);
410bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            handleRun(start, limit, limit, runIsRtl, c, x + w, top,
4110c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    y, bottom, null, false);
412e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return w;
413e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
414e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
415bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        return handleRun(start, limit, limit, runIsRtl, c, x, top,
4160c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                y, bottom, null, needWidth);
417e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
418e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
419e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
420e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Measures a unidirectional (but possibly multi-styled) run of text.
421e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
422bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
423e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start of the run
424e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the offset to measure to, between start and limit inclusive
425e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the line-relative limit of the run
426e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
427e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information about the requested
428e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * run, can be null.
429e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width from the start of the run to the leading edge
430e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * of the character at offset, based on the run (not paragraph) direction
431e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
432bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float measureRun(int start, int offset, int limit, boolean runIsRtl,
433bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            FontMetricsInt fmi) {
434bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
435e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
436e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
437e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
438e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Walk the cursor through this line, skipping conjuncts and
439e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * zero-width characters.
440e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
441e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * <p>This function cannot properly walk the cursor off the ends of the line
442e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * since it does not know about any shaping on the previous/following line
443e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * that might affect the cursor position. Callers must either avoid these
444e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * situations or handle the result specially.
445e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
446e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param cursor the starting position of the cursor, between 0 and the
447e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * length of the line, inclusive
448e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param toLeft true if the caret is moving to the left.
449e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the new offset.  If it is less than 0 or greater than the length
450e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * of the line, the previous/following line should be examined to get the
451e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * actual offset.
452e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
453e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
454e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 1) The caret marks the leading edge of a character. The character
455e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // logically before it might be on a different level, and the active caret
456e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // position is on the character at the lower level. If that character
457e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // was the previous character, the caret is on its trailing edge.
458e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 2) Take this character/edge and move it in the indicated direction.
459e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // This gives you a new character and a new edge.
460e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 3) This position is between two visually adjacent characters.  One of
461e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // these might be at a lower level.  The active position is on the
462e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // character at the lower level.
463e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 4) If the active position is on the trailing edge of the character,
464e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // the new caret position is the following logical character, else it
465e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // is the character.
466e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
467e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lineStart = 0;
468e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lineEnd = mLen;
469e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean paraIsRtl = mDir == -1;
470e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
471e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
472e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
473e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean trailing = false;
474e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
475e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (cursor == lineStart) {
476e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runIndex = -2;
477e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else if (cursor == lineEnd) {
478e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runIndex = runs.length;
479e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else {
480e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // First, get information about the run containing the character with
481e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // the active caret.
482e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
483e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runStart = lineStart + runs[runIndex];
484e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (cursor >= runStart) {
485e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
486e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (runLimit > lineEnd) {
487e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  runLimit = lineEnd;
488e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
489e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (cursor < runLimit) {
490e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
491e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    Layout.RUN_LEVEL_MASK;
492e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (cursor == runStart) {
493e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // The caret is on a run boundary, see if we should
494e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // use the position on the trailing edge of the previous
495e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // logical character instead.
496e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
497e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  int pos = cursor - 1;
498e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
499e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    prevRunStart = lineStart + runs[prevRunIndex];
500e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (pos >= prevRunStart) {
501e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      prevRunLimit = prevRunStart +
502e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
503e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      if (prevRunLimit > lineEnd) {
504e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          prevRunLimit = lineEnd;
505e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      }
506e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      if (pos < prevRunLimit) {
507e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
508e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            & Layout.RUN_LEVEL_MASK;
509e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (prevRunLevel < runLevel) {
510e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          // Start from logically previous character.
511e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runIndex = prevRunIndex;
512e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runLevel = prevRunLevel;
513e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runStart = prevRunStart;
514e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runLimit = prevRunLimit;
515e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          trailing = true;
516e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          break;
517e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
518e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      }
519e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
520e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  }
521e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
522e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                break;
523e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
524e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
525e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
526e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
527e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // caret might be == lineEnd.  This is generally a space or paragraph
528e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // separator and has an associated run, but might be the end of
529e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // text, in which case it doesn't.  If that happens, we ran off the
530e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // end of the run list, and runIndex == runs.length.  In this case,
531e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // we are at a run boundary so we skip the below test.
532e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (runIndex != runs.length) {
533e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              boolean runIsRtl = (runLevel & 0x1) != 0;
534e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              boolean advance = toLeft == runIsRtl;
535e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
536e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // Moving within or into the run, so we can move logically.
5370c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                  newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
5380c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                          runIsRtl, cursor, advance);
539e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // If the new position is internal to the run, we're at the strong
540e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // position already so we're finished.
541e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  if (newCaret != (advance ? runLimit : runStart)) {
542e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      return newCaret;
543e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  }
544e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
545e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
546e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
547e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
548e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // If newCaret is -1, we're starting at a run boundary and crossing
549e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // into another run. Otherwise we've arrived at a run boundary, and
550e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // need to figure out which character to attach to.  Note we might
551e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // need to run this twice, if we cross a run boundary and end up at
552e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // another run boundary.
553e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        while (true) {
554e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          boolean advance = toLeft == paraIsRtl;
555e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          int otherRunIndex = runIndex + (advance ? 2 : -2);
556e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
557e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunStart = lineStart + runs[otherRunIndex];
558e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunLimit = otherRunStart +
559e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
560e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (otherRunLimit > lineEnd) {
561e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                otherRunLimit = lineEnd;
562e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
563e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
564e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                Layout.RUN_LEVEL_MASK;
565e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
566e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
567e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            advance = toLeft == otherRunIsRtl;
568e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (newCaret == -1) {
5690c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
5700c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        otherRunLimit, otherRunIsRtl,
571e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        advance ? otherRunStart : otherRunLimit, advance);
572e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
573e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    // Crossed and ended up at a new boundary,
574e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    // repeat a second and final time.
575e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    runIndex = otherRunIndex;
576e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    runLevel = otherRunLevel;
577e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    continue;
578e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
579e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                break;
580e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
581e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
582e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            // The new caret is at a boundary.
583e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (otherRunLevel < runLevel) {
584e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // The strong character is in the other run.
585e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              newCaret = advance ? otherRunStart : otherRunLimit;
586e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
587e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            break;
588e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
589e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
590e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (newCaret == -1) {
591e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // We're walking off the end of the line.  The paragraph
592e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // level is always equal to or lower than any internal level, so
593e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // the boundaries get the strong caret.
5940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt              newCaret = advance ? mLen + 1 : -1;
595e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              break;
596e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
597e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
598e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // Else we've arrived at the end of the line.  That's a strong position.
599e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // We might have arrived here by crossing over a run with no internal
600e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // breaks and dropping out of the above loop before advancing one final
601e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // time, so reset the caret.
602e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // Note, we use '<=' below to handle a situation where the only run
603e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // on the line is a counter-directional run.  If we're not advancing,
604e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // we can end up at the 'lineEnd' position but the caret we want is at
605e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // the lineStart.
606e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (newCaret <= lineEnd) {
607e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              newCaret = advance ? lineEnd : lineStart;
608e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
609e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          break;
610e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
611e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
612e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return newCaret;
613e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
614e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
615e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
616e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns the next valid offset within this directional run, skipping
617e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * conjuncts and zero-width characters.  This should not be called to walk
618cc3ec6cdb2b892eb29513e72d8b205acbe997b25Gilles Debunne     * off the end of the line, since the returned values might not be valid
6190c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * on neighboring lines.  If the returned offset is less than zero or
6200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * greater than the line length, the offset should be recomputed on the
6210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * preceding or following line, respectively.
622e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
623e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIndex the run index
6240c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runStart the start of the run
6250c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runLimit the limit of the run
6260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runIsRtl true if the run is right-to-left
627e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the offset
628e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param after true if the new offset should logically follow the provided
629e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * offset
630e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the new offset
631e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
6320c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
6330c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            boolean runIsRtl, int offset, boolean after) {
634e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
6350c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (runIndex < 0 || offset == (after ? mLen : 0)) {
6360c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // Walking off end of line.  Since we don't know
6370c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // what cursor positions are available on other lines, we can't
6380c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // return accurate values.  These are a guess.
639e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (after) {
6400c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
6410c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
6420c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
6430c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        }
6440c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6450c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        TextPaint wp = mWorkPaint;
6460c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        wp.set(mPaint);
64709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        wp.setWordSpacing(mAddedWidth);
6480c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6490c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int spanStart = runStart;
6500c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int spanLimit;
6510c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (mSpanned == null) {
6520c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            spanLimit = runLimit;
6530c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        } else {
6540c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int target = after ? offset + 1 : offset;
6550c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int limit = mStart + runLimit;
6560c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            while (true) {
6570c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
6580c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        MetricAffectingSpan.class) - mStart;
6590c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                if (spanLimit >= target) {
6600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    break;
661e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
6620c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                spanStart = spanLimit;
6630c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
6640c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6650c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
6660c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    mStart + spanLimit, MetricAffectingSpan.class);
6671e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne            spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
6680c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6690c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (spans.length > 0) {
6700c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                ReplacementSpan replacement = null;
6710c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                for (int j = 0; j < spans.length; j++) {
6720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    MetricAffectingSpan span = spans[j];
6730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    if (span instanceof ReplacementSpan) {
6740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        replacement = (ReplacementSpan)span;
6750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    } else {
6760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        span.updateMeasureState(wp);
6770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    }
6780c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                }
6790c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6800c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                if (replacement != null) {
6810c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    // If we have a replacement span, we're moving either to
6820c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    // the start or end of this span.
6830c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    return after ? spanLimit : spanStart;
684e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
685e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
686e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
687e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
688051910b9f998030dacb8a0722588cc715813fde1Raph Levien        int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
6890c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
6900c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (mCharsValid) {
6910c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
692051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    dir, offset, cursorOpt);
6930c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        } else {
6940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return wp.getTextRunCursor(mText, mStart + spanStart,
695051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart;
696e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
697e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
698e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
699e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
7000bb000931bb841e75903d655552d1626ae158707Gilles Debunne     * @param wp
7010bb000931bb841e75903d655552d1626ae158707Gilles Debunne     */
7020bb000931bb841e75903d655552d1626ae158707Gilles Debunne    private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
7030bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousTop     = fmi.top;
7040bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousAscent  = fmi.ascent;
7050bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousDescent = fmi.descent;
7060bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousBottom  = fmi.bottom;
7070bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousLeading = fmi.leading;
7080bb000931bb841e75903d655552d1626ae158707Gilles Debunne
7090bb000931bb841e75903d655552d1626ae158707Gilles Debunne        wp.getFontMetricsInt(fmi);
7100bb000931bb841e75903d655552d1626ae158707Gilles Debunne
7118a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio        updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
7128a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousLeading);
7138a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio    }
7148a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
7158a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio    static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent,
7168a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousDescent, int previousBottom, int previousLeading) {
7170bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.top     = Math.min(fmi.top,     previousTop);
7180bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.ascent  = Math.min(fmi.ascent,  previousAscent);
7190bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.descent = Math.max(fmi.descent, previousDescent);
7200bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.bottom  = Math.max(fmi.bottom,  previousBottom);
7210bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.leading = Math.max(fmi.leading, previousLeading);
7220bb000931bb841e75903d655552d1626ae158707Gilles Debunne    }
7230bb000931bb841e75903d655552d1626ae158707Gilles Debunne
7241378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader    private static void drawStroke(TextPaint wp, Canvas c, int color, float position,
7251378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            float thickness, float xleft, float xright, float baseline) {
7261378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        final float strokeTop = baseline + wp.baselineShift + position;
727538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
728538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        final int previousColor = wp.getColor();
729538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        final Paint.Style previousStyle = wp.getStyle();
730538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        final boolean previousAntiAlias = wp.isAntiAlias();
731538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
732538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setStyle(Paint.Style.FILL);
733538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setAntiAlias(true);
734538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
735538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setColor(color);
7361378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        c.drawRect(xleft, strokeTop, xright, strokeTop + thickness, wp);
737538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
738538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setStyle(previousStyle);
739538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setColor(previousColor);
740538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        wp.setAntiAlias(previousAntiAlias);
741538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    }
742538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
743538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd,
744538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            boolean runIsRtl, int offset) {
745538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        if (mCharsValid) {
746538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset);
747538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        } else {
748538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final int delta = mStart;
7494e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka            if (mMeasured == null) {
750783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka                // TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text.
751783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka                return wp.getRunAdvance(mText, delta + start, delta + end,
752783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka                        delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
753783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka            } else {
7544e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka                return mMeasured.getWidth(start + delta, end + delta);
755783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka            }
756538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        }
757538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    }
758538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
7590bb000931bb841e75903d655552d1626ae158707Gilles Debunne    /**
760e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for measuring and rendering text.  The text must
761112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader     * not include a tab.
762e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
763e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the working paint
764e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the text
7650c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param end the end of the text
766e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
767e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null if rendering is not needed
768e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the edge of the run closest to the leading margin
769e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
770e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
771e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
772e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
773e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width of the run is needed
774909c7bca570b6f50650d0872e2037389b29252e3Raph Levien     * @param offset the offset for the purpose of measuring
7751378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader     * @param decorations the list of locations and paremeters for drawing decorations
776e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
777e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
778e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
7790c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private float handleText(TextPaint wp, int start, int end,
7800c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int contextStart, int contextEnd, boolean runIsRtl,
7810c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            Canvas c, float x, int top, int y, int bottom,
782538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            FontMetricsInt fmi, boolean needWidth, int offset,
7831378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            @Nullable ArrayList<DecorationInfo> decorations) {
784e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
78509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        wp.setWordSpacing(mAddedWidth);
786850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        // Get metrics first (even for empty strings or "0" width runs)
787850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        if (fmi != null) {
788850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio            expandMetricsFromPaint(fmi, wp);
789850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        }
790e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
791850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        // No need to do anything if the run width is "0"
792538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        if (end == start) {
793850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio            return 0f;
794850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        }
795850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio
796538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        float totalWidth = 0;
797850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio
7981378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        final int numDecorations = decorations == null ? 0 : decorations.size();
7991378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        if (needWidth || (c != null && (wp.bgColor != 0 || numDecorations != 0 || runIsRtl))) {
800538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
801e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
802e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
803e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (c != null) {
804538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final float leftX, rightX;
805e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runIsRtl) {
806538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                leftX = x - totalWidth;
807538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                rightX = x;
808538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            } else {
809538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                leftX = x;
810538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                rightX = x + totalWidth;
811e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
812e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
813e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (wp.bgColor != 0) {
814dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                int previousColor = wp.getColor();
815dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                Paint.Style previousStyle = wp.getStyle();
816dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
817e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                wp.setColor(wp.bgColor);
818e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                wp.setStyle(Paint.Style.FILL);
819538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                c.drawRect(leftX, top, rightX, bottom, wp);
820e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
821dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setStyle(previousStyle);
822dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setColor(previousColor);
823dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne            }
824dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
8251378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            if (numDecorations != 0) {
8261378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                for (int i = 0; i < numDecorations; i++) {
8271378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final DecorationInfo info = decorations.get(i);
8281378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader
8291378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final int decorationStart = Math.max(info.start, start);
8301378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final int decorationEnd = Math.min(info.end, offset);
8311378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    float decorationStartAdvance = getRunAdvance(
8321378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                            wp, start, end, contextStart, contextEnd, runIsRtl, decorationStart);
8331378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    float decorationEndAdvance = getRunAdvance(
8341378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                            wp, start, end, contextStart, contextEnd, runIsRtl, decorationEnd);
8351378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final float decorationXLeft, decorationXRight;
836538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    if (runIsRtl) {
8371378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        decorationXLeft = rightX - decorationEndAdvance;
8381378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        decorationXRight = rightX - decorationStartAdvance;
839538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    } else {
8401378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        decorationXLeft = leftX + decorationStartAdvance;
8411378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        decorationXRight = leftX + decorationEndAdvance;
842538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    }
843702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir
844538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // Theoretically, there could be cases where both Paint's and TextPaint's
845538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // setUnderLineText() are called. For backward compatibility, we need to draw
846538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // both underlines, the one with custom color first.
847538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    if (info.underlineColor != 0) {
8481378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        drawStroke(wp, c, info.underlineColor, wp.getUnderlinePosition(),
8491378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                                info.underlineThickness, decorationXLeft, decorationXRight, y);
850538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    }
851538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    if (info.isUnderlineText) {
852ca8a04a36640eb227a556ad9ced925c48ced2495Roozbeh Pournader                        final float thickness =
853a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir                                Math.max(wp.getUnderlineThickness(), 1.0f);
8541378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness,
8551378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                                decorationXLeft, decorationXRight, y);
8561378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    }
8571378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader
8581378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    if (info.isStrikeThruText) {
8591378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        final float thickness =
860a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir                                Math.max(wp.getStrikeThruThickness(), 1.0f);
8611378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                        drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness,
8621378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                                decorationXLeft, decorationXRight, y);
863538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    }
864538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                }
865e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
866e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
867da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio            drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
868538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    leftX, y + wp.baselineShift);
869e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
870e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
871538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        return runIsRtl ? -totalWidth : totalWidth;
872e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
873e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
874e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
875e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for measuring and rendering a replacement.
876e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
877bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
878e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param replacement the replacement
879e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the work paint
880e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the run
881e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the run
882e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
883e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null if not rendering
884e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the edge of the replacement closest to the leading margin
885e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
886e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
887e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
888e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
889e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width of the replacement is needed
890e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
891e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
892e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
893e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
894bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            int start, int limit, boolean runIsRtl, Canvas c,
895e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            float x, int top, int y, int bottom, FontMetricsInt fmi,
8960c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            boolean needWidth) {
897e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
898e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float ret = 0;
899e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
9000c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int textStart = mStart + start;
9010c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int textLimit = mStart + limit;
902e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
9030c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (needWidth || (c != null && runIsRtl)) {
9048a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousTop = 0;
9058a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousAscent = 0;
9068a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousDescent = 0;
9078a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousBottom = 0;
9088a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousLeading = 0;
9098a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
9108a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            boolean needUpdateMetrics = (fmi != null);
9118a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
9128a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            if (needUpdateMetrics) {
9138a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousTop     = fmi.top;
9148a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousAscent  = fmi.ascent;
9158a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousDescent = fmi.descent;
9168a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousBottom  = fmi.bottom;
9178a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousLeading = fmi.leading;
9188a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            }
9198a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
9200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
9218a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
9228a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            if (needUpdateMetrics) {
9238a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
9248a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                        previousLeading);
9258a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            }
9260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        }
927e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
9280c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (c != null) {
9290c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (runIsRtl) {
9300c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                x -= ret;
931e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
9320c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            replacement.draw(c, mText, textStart, textLimit,
9330c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    x, top, y, bottom, wp);
934e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
935e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
936e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return runIsRtl ? -ret : ret;
937e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
938945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
93946c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader    private int adjustHyphenEdit(int start, int limit, int hyphenEdit) {
94046c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        int result = hyphenEdit;
94146c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        // Only draw hyphens on first or last run in line. Disable them otherwise.
94246c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        if (start > 0) { // not the first run
94346c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader            result &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
94446c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        }
94546c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        if (limit < mLen) { // not the last run
94646c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader            result &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
94746c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        }
94846c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader        return result;
94946c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader    }
95046c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader
9511378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader    private static final class DecorationInfo {
9521378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        public boolean isStrikeThruText;
953538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public boolean isUnderlineText;
954538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public int underlineColor;
955538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public float underlineThickness;
956538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public int start = -1;
957538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        public int end = -1;
958538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
9591378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        public boolean hasDecoration() {
9601378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            return isStrikeThruText || isUnderlineText || underlineColor != 0;
961538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        }
962538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
963538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        // Copies the info, but not the start and end range.
9641378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        public DecorationInfo copyInfo() {
9651378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            final DecorationInfo copy = new DecorationInfo();
9661378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            copy.isStrikeThruText = isStrikeThruText;
967538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            copy.isUnderlineText = isUnderlineText;
968538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            copy.underlineColor = underlineColor;
969538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            copy.underlineThickness = underlineThickness;
970538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            return copy;
971538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        }
972538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    }
973538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
9741378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader    private void extractDecorationInfo(@NonNull TextPaint paint, @NonNull DecorationInfo info) {
9751378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        info.isStrikeThruText = paint.isStrikeThruText();
9761378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        if (info.isStrikeThruText) {
9771378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            paint.setStrikeThruText(false);
9781378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader        }
979538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        info.isUnderlineText = paint.isUnderlineText();
980538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        if (info.isUnderlineText) {
981538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            paint.setUnderlineText(false);
982538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        }
983538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        info.underlineColor = paint.underlineColor;
984538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        info.underlineThickness = paint.underlineThickness;
985538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader        paint.setUnderlineText(0, 0.0f);
986538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader    }
987538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
988e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
989e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for handling a unidirectional run.  The run must not
990112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader     * contain tabs but can contain styles.
991e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
992bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
993e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start of the run
9940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param measureLimit the offset to measure to, between start and limit inclusive
995e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the run
996e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
997e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null
998e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the end of the run closest to the leading margin
999e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
1000e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
1001e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
1002e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
1003e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width is required
1004e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
1005e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
1006e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
1007bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float handleRun(int start, int measureLimit,
1008e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
10090c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int bottom, FontMetricsInt fmi, boolean needWidth) {
1010e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
10119f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir        if (measureLimit < start || measureLimit > limit) {
10129f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir            throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
10139f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir                    + "start (" + start + ") and limit (" + limit + ") bounds");
10149f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir        }
10159f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir
1016f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        // Case of an empty line, make sure we update fmi according to mPaint
1017f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        if (start == measureLimit) {
1018538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final TextPaint wp = mWorkPaint;
1019f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne            wp.set(mPaint);
102015c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            if (fmi != null) {
102115c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio                expandMetricsFromPaint(fmi, wp);
102215c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            }
102315c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            return 0f;
1024f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        }
1025f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne
102608836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader        final boolean needsSpanMeasurement;
1027945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        if (mSpanned == null) {
102808836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader            needsSpanMeasurement = false;
102908836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader        } else {
103008836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader            mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
103108836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader            mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
103208836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader            needsSpanMeasurement = mMetricAffectingSpanSpanSet.numberOfSpans != 0
103308836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader                    || mCharacterStyleSpanSet.numberOfSpans != 0;
103408836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader        }
103508836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader
103608836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader        if (!needsSpanMeasurement) {
1037538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final TextPaint wp = mWorkPaint;
1038945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            wp.set(mPaint);
103946c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader            wp.setHyphenEdit(adjustHyphenEdit(start, limit, wp.getHyphenEdit()));
1040909c7bca570b6f50650d0872e2037389b29252e3Raph Levien            return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top,
1041538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    y, bottom, fmi, needWidth, measureLimit, null);
1042945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        }
1043945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
1044e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // Shaping needs to take into account context up to metric boundaries,
1045e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // but rendering needs to take into account character style boundaries.
10460c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // So we iterate through metric runs to get metric bounds,
10470c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // then within each metric run iterate through character style runs
10480c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // for the run bounds.
1049945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        final float originalX = x;
10500c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        for (int i = start, inext; i < measureLimit; i = inext) {
1051538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final TextPaint wp = mWorkPaint;
1052e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            wp.set(mPaint);
1053e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1054c1f44830809f0a8526855f13822702ea756214faGilles Debunne            inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
1055c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    mStart;
1056945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            int mlimit = Math.min(inext, measureLimit);
1057945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
1058945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            ReplacementSpan replacement = null;
1059945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
1060c1f44830809f0a8526855f13822702ea756214faGilles Debunne            for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
1061945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
1062945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                // empty by construction. This special case in getSpans() explains the >= & <= tests
1063c1f44830809f0a8526855f13822702ea756214faGilles Debunne                if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
1064c1f44830809f0a8526855f13822702ea756214faGilles Debunne                        (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
1065538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                final MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
1066945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                if (span instanceof ReplacementSpan) {
1067945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    replacement = (ReplacementSpan)span;
1068945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                } else {
1069945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    // We might have a replacement that uses the draw
1070945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    // state, otherwise measure state would suffice.
1071945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    span.updateDrawState(wp);
1072e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
1073e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
1074e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1075945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            if (replacement != null) {
1076945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
1077945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                        bottom, fmi, needWidth || mlimit < measureLimit);
1078945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                continue;
1079945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            }
1080945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
1081538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            final TextPaint activePaint = mActivePaint;
1082538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            activePaint.set(mPaint);
1083538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            int activeStart = i;
1084538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            int activeEnd = mlimit;
10851378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            final DecorationInfo decorationInfo = mDecorationInfo;
10861378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader            mDecorations.clear();
108742ef515d185d4fc038d602172789cc264f1d9960Raph Levien            for (int j = i, jnext; j < mlimit; j = jnext) {
1088702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) -
1089702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                        mStart;
109042ef515d185d4fc038d602172789cc264f1d9960Raph Levien
1091538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                final int offset = Math.min(jnext, mlimit);
109242ef515d185d4fc038d602172789cc264f1d9960Raph Levien                wp.set(mPaint);
109342ef515d185d4fc038d602172789cc264f1d9960Raph Levien                for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
109442ef515d185d4fc038d602172789cc264f1d9960Raph Levien                    // Intentionally using >= and <= as explained above
1095702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                    if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) ||
1096702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                            (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
10970c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
1098538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    final CharacterStyle span = mCharacterStyleSpanSet.spans[k];
1099702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir                    span.updateDrawState(wp);
1100e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
110142ef515d185d4fc038d602172789cc264f1d9960Raph Levien
11021378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                extractDecorationInfo(wp, decorationInfo);
1103538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
1104538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                if (j == i) {
1105538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // First chunk of text. We can't handle it yet, since we may need to merge it
1106538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // with the next chunk. So we just save the TextPaint for future comparisons
1107538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // and use.
1108538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activePaint.set(wp);
1109538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                } else if (!wp.hasEqualAttributes(activePaint)) {
1110538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // The style of the present chunk of text is substantially different from the
1111538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // style of the previous chunk. We need to handle the active piece of text
1112538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // and restart with the present chunk.
1113538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activePaint.setHyphenEdit(adjustHyphenEdit(
1114538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                            activeStart, activeEnd, mPaint.getHyphenEdit()));
1115538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
1116538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                            top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
11171378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                            Math.min(activeEnd, mlimit), mDecorations);
1118538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader
1119538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activeStart = j;
1120538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activePaint.set(wp);
11211378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    mDecorations.clear();
1122538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                } else {
1123538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // The present TextPaint is substantially equal to the last TextPaint except
11241378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    // perhaps for decorations. We just need to expand the active piece of text to
1125538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // include the present chunk, which we always do anyway. We don't need to save
1126538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    // wp to activePaint, since they are already equal.
1127538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                }
112846c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader
1129538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                activeEnd = jnext;
11301378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                if (decorationInfo.hasDecoration()) {
11311378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    final DecorationInfo copy = decorationInfo.copyInfo();
1132538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    copy.start = j;
1133538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    copy.end = jnext;
11341378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    mDecorations.add(copy);
1135538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                }
1136e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
1137538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            // Handle the final piece of text.
1138538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            activePaint.setHyphenEdit(adjustHyphenEdit(
1139538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    activeStart, activeEnd, mPaint.getHyphenEdit()));
1140538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader            x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
1141538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader                    top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
11421378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader                    Math.min(activeEnd, mlimit), mDecorations);
1143e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1144e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1145945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        return x - originalX;
1146e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
1147e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1148e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
1149e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Render a text run with the set-up paint.
1150e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
1151e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas
1152e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the paint used to render the text
11530c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param start the start of the run
11540c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param end the end of the run
11550c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param contextStart the start of context for the run
11560c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param contextEnd the end of the context for the run
1157da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio     * @param runIsRtl true if the run is right-to-left
1158e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the x position of the left edge of the run
1159e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline of the run
1160e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
11610c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
1162da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
1163e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1164e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (mCharsValid) {
11650c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int count = end - start;
11660c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int contextCount = contextEnd - contextStart;
11670c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            c.drawTextRun(mChars, start, count, contextStart, contextCount,
1168051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    x, y, runIsRtl, wp);
1169e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else {
11700c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int delta = mStart;
11710c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            c.drawTextRun(mText, delta + start, delta + end,
1172051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp);
1173e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1174e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
1175e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1176e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
1177e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns the next tab position.
1178e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
1179e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param h the (unsigned) offset from the leading margin
1180e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the (unsigned) tab position after this offset
1181e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
1182e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float nextTab(float h) {
1183c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        if (mTabs != null) {
1184c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt            return mTabs.nextTab(h);
1185e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1186c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        return TabStops.nextDefaultStop(h, TAB_INCREMENT);
1187e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
1188e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
118909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    private boolean isStretchableWhitespace(int ch) {
11903630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader        // TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709).
11913630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader        return ch == 0x0020;
119209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    }
119309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
119409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    /* Return the number of spaces in the text line, for the purpose of justification */
119509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    private int countStretchableSpaces(int start, int end) {
119609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        int count = 0;
11973630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader        for (int i = start; i < end; i++) {
11983630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader            final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
11993630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader            if (isStretchableWhitespace(c)) {
12003630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader                count++;
12013630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader            }
120209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        }
120309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        return count;
120409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    }
120509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
120609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
120709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    public static boolean isLineEndSpace(char ch) {
120809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka        return ch == ' ' || ch == '\t' || ch == 0x1680
120909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka                || (0x2000 <= ch && ch <= 0x200A && ch != 0x2007)
121009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka                || ch == 0x205F || ch == 0x3000;
121109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka    }
121209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka
1213e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private static final int TAB_INCREMENT = 20;
1214e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt}
1215