TextLine.java revision 051910b9f998030dacb8a0722588cc715813fde1
1e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/*
2e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Copyright (C) 2010 The Android Open Source Project
3e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
4e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Licensed under the Apache License, Version 2.0 (the "License");
5e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * you may not use this file except in compliance with the License.
6e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * You may obtain a copy of the License at
7e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
8e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *      http://www.apache.org/licenses/LICENSE-2.0
9e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
10e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Unless required by applicable law or agreed to in writing, software
11e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * distributed under the License is distributed on an "AS IS" BASIS,
12e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * See the License for the specific language governing permissions and
14e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * limitations under the License.
15e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */
16e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
17e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltpackage android.text;
18e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
19e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Bitmap;
20e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Canvas;
21e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint;
22e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint.FontMetricsInt;
23cc3ec6cdb2b892eb29513e72d8b205acbe997b25Gilles Debunneimport android.graphics.RectF;
24e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.Layout.Directions;
25c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Feltimport android.text.Layout.TabStops;
26e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.CharacterStyle;
27e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.MetricAffectingSpan;
28e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.ReplacementSpan;
29e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.util.Log;
30e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
31dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunneimport com.android.internal.util.ArrayUtils;
32dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
33e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/**
34e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Represents a line of styled text, for measuring in visual order and
35e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * for rendering.
36e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
37e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Get a new instance using obtain(), and when finished with it, return it
38e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * to the pool using recycle().
39e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
40e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Call set to prepare the instance for use, then either draw, measure,
41e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * metrics, or caretToLeftRightOf.
42e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt *
43e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @hide
44e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */
45e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltclass TextLine {
46bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private static final boolean DEBUG = false;
47bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy
48e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private TextPaint mPaint;
49e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private CharSequence mText;
50e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mStart;
51e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mLen;
52e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private int mDir;
53e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private Directions mDirections;
54e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private boolean mHasTabs;
55c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt    private TabStops mTabs;
56e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private char[] mChars;
57e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private boolean mCharsValid;
58e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private Spanned mSpanned;
59345cb03315a0813ec57e44f97fc3fa4af6b3c309Gilles Debunne    private final TextPaint mWorkPaint = new TextPaint();
60c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
61c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
62c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
63c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<CharacterStyle>(CharacterStyle.class);
64c1f44830809f0a8526855f13822702ea756214faGilles Debunne    private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
65c1f44830809f0a8526855f13822702ea756214faGilles Debunne            new SpanSet<ReplacementSpan>(ReplacementSpan.class);
66e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
67bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private static final TextLine[] sCached = new TextLine[3];
68e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
69e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
70e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns a new TextLine from the shared pool.
71e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
72e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return an uninitialized TextLine
73e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
74e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    static TextLine obtain() {
75e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        TextLine tl;
76bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        synchronized (sCached) {
77bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            for (int i = sCached.length; --i >= 0;) {
78bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                if (sCached[i] != null) {
79bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    tl = sCached[i];
80bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    sCached[i] = null;
81e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    return tl;
82e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
83e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
84e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
85e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl = new TextLine();
86bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        if (DEBUG) {
87bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            Log.v("TLINE", "new: " + tl);
88bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        }
89e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return tl;
90e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
91e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
92e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
93e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Puts a TextLine back into the shared pool. Do not use this TextLine once
94e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * it has been returned.
95e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param tl the textLine
96e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return null, as a convenience from clearing references to the provided
97e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * TextLine
98e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
99e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    static TextLine recycle(TextLine tl) {
100e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mText = null;
101e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mPaint = null;
102e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        tl.mDirections = null;
103c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne
104c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mMetricAffectingSpanSpanSet.recycle();
105c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mCharacterStyleSpanSet.recycle();
106c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne        tl.mReplacementSpanSpanSet.recycle();
107c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne
108bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        synchronized(sCached) {
109bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            for (int i = 0; i < sCached.length; ++i) {
110bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                if (sCached[i] == null) {
111bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    sCached[i] = tl;
112f902d7bc49797ec277b4576c921dfffa15d741ddGilles Debunne                    break;
113e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
114e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
115e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
116e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return null;
117e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
118e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
119e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
120e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Initializes a TextLine and prepares it for use.
121e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
122e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param paint the base paint for the line
123e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param text the text, can be Styled
124e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the line relative to the text
125e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the line relative to the text
126e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param dir the paragraph direction of this line
127e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param directions the directions information of this line
128e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param hasTabs true if the line might contain tabs or emoji
129c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt     * @param tabStops the tabStops. Can be null.
130e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
131e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
132c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt            Directions directions, boolean hasTabs, TabStops tabStops) {
133e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mPaint = paint;
134e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mText = text;
135e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mStart = start;
136e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mLen = limit - start;
137e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mDir = dir;
138e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mDirections = directions;
1398059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio        if (mDirections == null) {
1408059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio            throw new IllegalArgumentException("Directions cannot be null");
1418059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio        }
142e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mHasTabs = hasTabs;
143e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        mSpanned = null;
144e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
145e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean hasReplacement = false;
146e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (text instanceof Spanned) {
147e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            mSpanned = (Spanned) text;
148c1f44830809f0a8526855f13822702ea756214faGilles Debunne            mReplacementSpanSpanSet.init(mSpanned, start, limit);
149c1f44830809f0a8526855f13822702ea756214faGilles Debunne            hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
150e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
151e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1521e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne        mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
153e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
154e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (mCharsValid) {
155e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mChars == null || mChars.length < mLen) {
156776abc24cdd18610232a50b997cce3cffa74609bAdam Lesinski                mChars = ArrayUtils.newUnpaddedCharArray(mLen);
157e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
158e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            TextUtils.getChars(text, start, limit, mChars, 0);
1590c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (hasReplacement) {
1600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // Handle these all at once so we don't have to do it as we go.
1610c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // Replace the first character of each replacement run with the
1620c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // object-replacement character and the remainder with zero width
1630c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // non-break space aka BOM.  Cursor movement code skips these
1640c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                // zero-width characters.
1650c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                char[] chars = mChars;
1660c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                for (int i = start, inext; i < limit; i = inext) {
167c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    inext = mReplacementSpanSpanSet.getNextTransition(i, limit);
168c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    if (mReplacementSpanSpanSet.hasSpansIntersecting(i, inext)) {
1691e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne                        // transition into a span
1700c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        chars[i - start] = '\ufffc';
1710c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        for (int j = i - start + 1, e = inext - start; j < e; ++j) {
1720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                            chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
1730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        }
1740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    }
1750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                }
1760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
177e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
178c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        mTabs = tabStops;
179e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
180e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
181e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
182e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Renders the TextLine.
183e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
184e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas to render on
185e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the leading margin position
186e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
187e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
188e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
189e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
190e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    void draw(Canvas c, float x, int top, int y, int bottom) {
191e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (!mHasTabs) {
192e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
193bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                drawRun(c, 0, mLen, false, x, top, y, bottom, false);
194e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                return;
195e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
196e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
197bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                drawRun(c, 0, mLen, true, x, top, y, bottom, false);
198e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                return;
199e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
200e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
201e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
202e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float h = 0;
203e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
204e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        RectF emojiRect = null;
205e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
206e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lastRunIndex = runs.length - 2;
207e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        for (int i = 0; i < runs.length; i += 2) {
208e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runStart = runs[i];
209e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
210e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runLimit > mLen) {
211e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLimit = mLen;
212e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
213e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
214e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
215e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int segstart = runStart;
216e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
217e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                int codept = 0;
218e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                Bitmap bm = null;
219e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
220e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (mHasTabs && j < runLimit) {
221e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    codept = mChars[j];
222e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
223e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        codept = Character.codePointAt(mChars, j);
224e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
225e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
226e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        } else if (codept > 0xffff) {
227e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            ++j;
228e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            continue;
229e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
230e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
231e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
232e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
233e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (j == runLimit || codept == '\t' || bm != null) {
234bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
235e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            i != lastRunIndex || j != mLen);
236e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
237e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept == '\t') {
238e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h = mDir * nextTab(h * mDir);
239e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    } else if (bm != null) {
240e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        float bmAscent = ascent(j);
241e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        float bitmapHeight = bm.getHeight();
242e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        float scale = -bmAscent / bitmapHeight;
243e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        float width = bm.getWidth() * scale;
244e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
245e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (emojiRect == null) {
246e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            emojiRect = new RectF();
247e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
248e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        emojiRect.set(x + h, y + bmAscent,
249e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                                x + h + width, y);
250e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        c.drawBitmap(bm, null, emojiRect, mPaint);
251e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h += width;
252e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        j++;
253e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
254e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    segstart = j + 1;
255e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
256e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
257e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
258e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
259e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
260e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
261e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns metrics information for the entire line.
262e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
263e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives font metrics information, can be null
264e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the line
265e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
266e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float metrics(FontMetricsInt fmi) {
267e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return measure(mLen, false, fmi);
268e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
269e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
270e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
271e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns information about a position on the line.
272e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
273e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the line-relative character offset, between 0 and the
274e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * line length, inclusive
275e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param trailing true to measure the trailing edge of the character
276e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * before offset, false to measure the leading edge of the character
277e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * at offset.
278e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information about the requested
279e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * character, can be null.
280e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed offset from the leading margin to the requested
281e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * character edge.
282e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
283e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float measure(int offset, boolean trailing, FontMetricsInt fmi) {
284e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int target = trailing ? offset - 1 : offset;
285e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (target < 0) {
286e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return 0;
287e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
288e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
289e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float h = 0;
290e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
291e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (!mHasTabs) {
292e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
293bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                return measureRun(0, offset, mLen, false, fmi);
294e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
295e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
296bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                return measureRun(0, offset, mLen, true, fmi);
297e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
298e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
299e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
300e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        char[] chars = mChars;
301e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
302e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        for (int i = 0; i < runs.length; i += 2) {
303e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runStart = runs[i];
304e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
305e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runLimit > mLen) {
306e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLimit = mLen;
307e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
308e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
309e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
310e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int segstart = runStart;
311e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
312e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                int codept = 0;
313e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                Bitmap bm = null;
314e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
315e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (mHasTabs && j < runLimit) {
316e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    codept = chars[j];
317e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
318e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        codept = Character.codePointAt(chars, j);
319e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
320e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
321e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        } else if (codept > 0xffff) {
322e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            ++j;
323e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            continue;
324e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
325e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
326e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
327e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
328e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (j == runLimit || codept == '\t' || bm != null) {
329e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    boolean inSegment = target >= segstart && target < j;
330e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
331e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
332e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (inSegment && advance) {
333bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                        return h += measureRun(segstart, offset, j, runIsRtl, fmi);
334e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
335e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
336bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                    float w = measureRun(segstart, j, j, runIsRtl, fmi);
337e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    h += advance ? w : -w;
338e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
339e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (inSegment) {
340bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy                        return h += measureRun(segstart, offset, j, runIsRtl, null);
341e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
342e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
343e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (codept == '\t') {
344e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (offset == j) {
345e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            return h;
346e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
347e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h = mDir * nextTab(h * mDir);
348e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (target == j) {
349e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            return h;
350e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
351e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
352e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
353e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (bm != null) {
354e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        float bmAscent = ascent(j);
355e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        float wid = bm.getWidth() * -bmAscent / bm.getHeight();
356e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        h += mDir * wid;
357e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        j++;
358e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
359e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
360e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    segstart = j + 1;
361e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
362e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
363e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
364e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
365e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return h;
366e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
367e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
368e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
369e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Draws a unidirectional (but possibly multi-styled) run of text.
370e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
371bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
372e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas to draw on
373e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start
374e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the line-relative limit
375e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
376e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the position of the run that is closest to the leading margin
377e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
378e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
379e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
380e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width value is required.
381e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run, based on the paragraph direction.
382e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Only valid if needWidth is true.
383e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
384bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float drawRun(Canvas c, int start,
385e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int limit, boolean runIsRtl, float x, int top, int y, int bottom,
386e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean needWidth) {
387e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
388e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
389bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            float w = -measureRun(start, limit, limit, runIsRtl, null);
390bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            handleRun(start, limit, limit, runIsRtl, c, x + w, top,
3910c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    y, bottom, null, false);
392e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return w;
393e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
394e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
395bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        return handleRun(start, limit, limit, runIsRtl, c, x, top,
3960c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                y, bottom, null, needWidth);
397e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
398e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
399e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
400e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Measures a unidirectional (but possibly multi-styled) run of text.
401e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
402bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
403e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start of the run
404e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the offset to measure to, between start and limit inclusive
405e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the line-relative limit of the run
406e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
407e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information about the requested
408e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * run, can be null.
409e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width from the start of the run to the leading edge
410e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * of the character at offset, based on the run (not paragraph) direction
411e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
412bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float measureRun(int start, int offset, int limit, boolean runIsRtl,
413bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            FontMetricsInt fmi) {
414bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy        return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
415e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
416e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
417e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
418e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Walk the cursor through this line, skipping conjuncts and
419e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * zero-width characters.
420e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
421e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * <p>This function cannot properly walk the cursor off the ends of the line
422e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * since it does not know about any shaping on the previous/following line
423e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * that might affect the cursor position. Callers must either avoid these
424e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * situations or handle the result specially.
425e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
426e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param cursor the starting position of the cursor, between 0 and the
427e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * length of the line, inclusive
428e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param toLeft true if the caret is moving to the left.
429e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the new offset.  If it is less than 0 or greater than the length
430e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * of the line, the previous/following line should be examined to get the
431e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * actual offset.
432e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
433e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
434e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 1) The caret marks the leading edge of a character. The character
435e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // logically before it might be on a different level, and the active caret
436e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // position is on the character at the lower level. If that character
437e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // was the previous character, the caret is on its trailing edge.
438e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 2) Take this character/edge and move it in the indicated direction.
439e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // This gives you a new character and a new edge.
440e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 3) This position is between two visually adjacent characters.  One of
441e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // these might be at a lower level.  The active position is on the
442e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // character at the lower level.
443e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // 4) If the active position is on the trailing edge of the character,
444e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // the new caret position is the following logical character, else it
445e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // is the character.
446e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
447e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lineStart = 0;
448e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int lineEnd = mLen;
449e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean paraIsRtl = mDir == -1;
450e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int[] runs = mDirections.mDirections;
451e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
452e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
453e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        boolean trailing = false;
454e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
455e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (cursor == lineStart) {
456e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runIndex = -2;
457e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else if (cursor == lineEnd) {
458e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runIndex = runs.length;
459e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else {
460e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // First, get information about the run containing the character with
461e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // the active caret.
462e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
463e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            runStart = lineStart + runs[runIndex];
464e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (cursor >= runStart) {
465e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
466e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (runLimit > lineEnd) {
467e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  runLimit = lineEnd;
468e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
469e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (cursor < runLimit) {
470e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
471e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    Layout.RUN_LEVEL_MASK;
472e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (cursor == runStart) {
473e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // The caret is on a run boundary, see if we should
474e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // use the position on the trailing edge of the previous
475e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // logical character instead.
476e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
477e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  int pos = cursor - 1;
478e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
479e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    prevRunStart = lineStart + runs[prevRunIndex];
480e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    if (pos >= prevRunStart) {
481e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      prevRunLimit = prevRunStart +
482e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
483e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      if (prevRunLimit > lineEnd) {
484e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          prevRunLimit = lineEnd;
485e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      }
486e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      if (pos < prevRunLimit) {
487e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
488e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                            & Layout.RUN_LEVEL_MASK;
489e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        if (prevRunLevel < runLevel) {
490e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          // Start from logically previous character.
491e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runIndex = prevRunIndex;
492e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runLevel = prevRunLevel;
493e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runStart = prevRunStart;
494e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          runLimit = prevRunLimit;
495e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          trailing = true;
496e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                          break;
497e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        }
498e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      }
499e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    }
500e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  }
501e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
502e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                break;
503e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
504e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
505e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
506e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
507e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // caret might be == lineEnd.  This is generally a space or paragraph
508e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // separator and has an associated run, but might be the end of
509e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // text, in which case it doesn't.  If that happens, we ran off the
510e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // end of the run list, and runIndex == runs.length.  In this case,
511e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // we are at a run boundary so we skip the below test.
512e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (runIndex != runs.length) {
513e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              boolean runIsRtl = (runLevel & 0x1) != 0;
514e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              boolean advance = toLeft == runIsRtl;
515e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
516e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // Moving within or into the run, so we can move logically.
5170c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                  newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
5180c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                          runIsRtl, cursor, advance);
519e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // If the new position is internal to the run, we're at the strong
520e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  // position already so we're finished.
521e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  if (newCaret != (advance ? runLimit : runStart)) {
522e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                      return newCaret;
523e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                  }
524e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              }
525e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
526e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
527e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
528e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // If newCaret is -1, we're starting at a run boundary and crossing
529e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // into another run. Otherwise we've arrived at a run boundary, and
530e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // need to figure out which character to attach to.  Note we might
531e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // need to run this twice, if we cross a run boundary and end up at
532e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // another run boundary.
533e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        while (true) {
534e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          boolean advance = toLeft == paraIsRtl;
535e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          int otherRunIndex = runIndex + (advance ? 2 : -2);
536e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
537e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunStart = lineStart + runs[otherRunIndex];
538e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunLimit = otherRunStart +
539e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
540e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (otherRunLimit > lineEnd) {
541e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                otherRunLimit = lineEnd;
542e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
543e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
544e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                Layout.RUN_LEVEL_MASK;
545e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
546e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
547e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            advance = toLeft == otherRunIsRtl;
548e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (newCaret == -1) {
5490c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
5500c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        otherRunLimit, otherRunIsRtl,
551e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                        advance ? otherRunStart : otherRunLimit, advance);
552e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
553e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    // Crossed and ended up at a new boundary,
554e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    // repeat a second and final time.
555e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    runIndex = otherRunIndex;
556e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    runLevel = otherRunLevel;
557e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                    continue;
558e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
559e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                break;
560e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
561e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
562e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            // The new caret is at a boundary.
563e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (otherRunLevel < runLevel) {
564e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // The strong character is in the other run.
565e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              newCaret = advance ? otherRunStart : otherRunLimit;
566e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
567e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            break;
568e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
569e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
570e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (newCaret == -1) {
571e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // We're walking off the end of the line.  The paragraph
572e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // level is always equal to or lower than any internal level, so
573e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              // the boundaries get the strong caret.
5740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt              newCaret = advance ? mLen + 1 : -1;
575e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              break;
576e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
577e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
578e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // Else we've arrived at the end of the line.  That's a strong position.
579e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // We might have arrived here by crossing over a run with no internal
580e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // breaks and dropping out of the above loop before advancing one final
581e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // time, so reset the caret.
582e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // Note, we use '<=' below to handle a situation where the only run
583e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // on the line is a counter-directional run.  If we're not advancing,
584e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // we can end up at the 'lineEnd' position but the caret we want is at
585e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          // the lineStart.
586e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          if (newCaret <= lineEnd) {
587e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt              newCaret = advance ? lineEnd : lineStart;
588e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          }
589e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt          break;
590e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
591e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
592e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return newCaret;
593e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
594e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
595e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
596e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns the next valid offset within this directional run, skipping
597e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * conjuncts and zero-width characters.  This should not be called to walk
598cc3ec6cdb2b892eb29513e72d8b205acbe997b25Gilles Debunne     * off the end of the line, since the returned values might not be valid
5990c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * on neighboring lines.  If the returned offset is less than zero or
6000c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * greater than the line length, the offset should be recomputed on the
6010c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * preceding or following line, respectively.
602e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
603e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIndex the run index
6040c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runStart the start of the run
6050c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runLimit the limit of the run
6060c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param runIsRtl true if the run is right-to-left
607e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param offset the offset
608e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param after true if the new offset should logically follow the provided
609e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * offset
610e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the new offset
611e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
6120c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
6130c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            boolean runIsRtl, int offset, boolean after) {
614e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
6150c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (runIndex < 0 || offset == (after ? mLen : 0)) {
6160c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // Walking off end of line.  Since we don't know
6170c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // what cursor positions are available on other lines, we can't
6180c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            // return accurate values.  These are a guess.
619e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (after) {
6200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
6210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
6220c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
6230c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        }
6240c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6250c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        TextPaint wp = mWorkPaint;
6260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        wp.set(mPaint);
6270c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6280c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int spanStart = runStart;
6290c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int spanLimit;
6300c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (mSpanned == null) {
6310c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            spanLimit = runLimit;
6320c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        } else {
6330c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int target = after ? offset + 1 : offset;
6340c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int limit = mStart + runLimit;
6350c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            while (true) {
6360c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
6370c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        MetricAffectingSpan.class) - mStart;
6380c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                if (spanLimit >= target) {
6390c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    break;
640e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
6410c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                spanStart = spanLimit;
6420c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            }
6430c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6440c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
6450c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    mStart + spanLimit, MetricAffectingSpan.class);
6461e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne            spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
6470c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6480c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (spans.length > 0) {
6490c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                ReplacementSpan replacement = null;
6500c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                for (int j = 0; j < spans.length; j++) {
6510c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    MetricAffectingSpan span = spans[j];
6520c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    if (span instanceof ReplacementSpan) {
6530c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        replacement = (ReplacementSpan)span;
6540c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    } else {
6550c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                        span.updateMeasureState(wp);
6560c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    }
6570c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                }
6580c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
6590c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                if (replacement != null) {
6600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    // If we have a replacement span, we're moving either to
6610c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    // the start or end of this span.
6620c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    return after ? spanLimit : spanStart;
663e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
664e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
665e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
666e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
667051910b9f998030dacb8a0722588cc715813fde1Raph Levien        int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
6680c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
6690c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (mCharsValid) {
6700c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
671051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    dir, offset, cursorOpt);
6720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        } else {
6730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            return wp.getTextRunCursor(mText, mStart + spanStart,
674051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart;
675e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
676e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
677e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
678e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
6790bb000931bb841e75903d655552d1626ae158707Gilles Debunne     * @param wp
6800bb000931bb841e75903d655552d1626ae158707Gilles Debunne     */
6810bb000931bb841e75903d655552d1626ae158707Gilles Debunne    private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) {
6820bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousTop     = fmi.top;
6830bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousAscent  = fmi.ascent;
6840bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousDescent = fmi.descent;
6850bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousBottom  = fmi.bottom;
6860bb000931bb841e75903d655552d1626ae158707Gilles Debunne        final int previousLeading = fmi.leading;
6870bb000931bb841e75903d655552d1626ae158707Gilles Debunne
6880bb000931bb841e75903d655552d1626ae158707Gilles Debunne        wp.getFontMetricsInt(fmi);
6890bb000931bb841e75903d655552d1626ae158707Gilles Debunne
6908a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio        updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
6918a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousLeading);
6928a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio    }
6938a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
6948a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio    static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent,
6958a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousDescent, int previousBottom, int previousLeading) {
6960bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.top     = Math.min(fmi.top,     previousTop);
6970bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.ascent  = Math.min(fmi.ascent,  previousAscent);
6980bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.descent = Math.max(fmi.descent, previousDescent);
6990bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.bottom  = Math.max(fmi.bottom,  previousBottom);
7000bb000931bb841e75903d655552d1626ae158707Gilles Debunne        fmi.leading = Math.max(fmi.leading, previousLeading);
7010bb000931bb841e75903d655552d1626ae158707Gilles Debunne    }
7020bb000931bb841e75903d655552d1626ae158707Gilles Debunne
7030bb000931bb841e75903d655552d1626ae158707Gilles Debunne    /**
704e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for measuring and rendering text.  The text must
705e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * not include a tab or emoji.
706e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
707e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the working paint
708e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the text
7090c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param end the end of the text
710e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
711e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null if rendering is not needed
712e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the edge of the run closest to the leading margin
713e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
714e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
715e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
716e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
717e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width of the run is needed
718e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
719e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
720e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
7210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private float handleText(TextPaint wp, int start, int end,
7220c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int contextStart, int contextEnd, boolean runIsRtl,
7230c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            Canvas c, float x, int top, int y, int bottom,
724e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            FontMetricsInt fmi, boolean needWidth) {
725e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
726850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        // Get metrics first (even for empty strings or "0" width runs)
727850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        if (fmi != null) {
728850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio            expandMetricsFromPaint(fmi, wp);
729850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        }
730e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
7310c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int runLen = end - start;
732850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        // No need to do anything if the run width is "0"
733850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        if (runLen == 0) {
734850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio            return 0f;
735850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        }
736850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio
737850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio        float ret = 0;
738850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio
7390c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int contextLen = contextEnd - contextStart;
740c9fd978da60f76c0576150c55629a034e1fa19fbGilles Debunne        if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
741e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (mCharsValid) {
7420c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                ret = wp.getTextRunAdvances(mChars, start, runLen,
743051910b9f998030dacb8a0722588cc715813fde1Raph Levien                        contextStart, contextLen, runIsRtl, null, 0);
744e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            } else {
7450c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                int delta = mStart;
746da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio                ret = wp.getTextRunAdvances(mText, delta + start,
747da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio                        delta + end, delta + contextStart, delta + contextEnd,
748051910b9f998030dacb8a0722588cc715813fde1Raph Levien                        runIsRtl, null, 0);
749e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
750e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
751e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
752e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (c != null) {
753e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (runIsRtl) {
754e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                x -= ret;
755e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
756e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
757e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            if (wp.bgColor != 0) {
758dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                int previousColor = wp.getColor();
759dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                Paint.Style previousStyle = wp.getStyle();
760dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
761e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                wp.setColor(wp.bgColor);
762e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                wp.setStyle(Paint.Style.FILL);
763e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                c.drawRect(x, top, x + ret, bottom, wp);
764e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
765dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setStyle(previousStyle);
766dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setColor(previousColor);
767dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne            }
768dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
769c9fd978da60f76c0576150c55629a034e1fa19fbGilles Debunne            if (wp.underlineColor != 0) {
770dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
771e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin                float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();
772dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
773dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                int previousColor = wp.getColor();
774dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                Paint.Style previousStyle = wp.getStyle();
775e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin                boolean previousAntiAlias = wp.isAntiAlias();
776dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
777dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setStyle(Paint.Style.FILL);
778e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin                wp.setAntiAlias(true);
779e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin
780c9fd978da60f76c0576150c55629a034e1fa19fbGilles Debunne                wp.setColor(wp.underlineColor);
781c9fd978da60f76c0576150c55629a034e1fa19fbGilles Debunne                c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp);
782dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne
783dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setStyle(previousStyle);
784dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne                wp.setColor(previousColor);
785e6d368218918f911b1954296dab25bf84147b4c6Luca Zanolin                wp.setAntiAlias(previousAntiAlias);
786e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
787e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
788da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio            drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
789da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio                    x, y + wp.baselineShift);
790e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
791e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
792e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return runIsRtl ? -ret : ret;
793e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
794e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
795e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
796e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for measuring and rendering a replacement.
797e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
798bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
799e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param replacement the replacement
800e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the work paint
801e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the start of the run
802e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the run
803e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
804e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null if not rendering
805e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the edge of the replacement closest to the leading margin
806e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
807e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
808e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
809e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
810e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width of the replacement is needed
811e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
812e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
813e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
814e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
815bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy            int start, int limit, boolean runIsRtl, Canvas c,
816e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            float x, int top, int y, int bottom, FontMetricsInt fmi,
8170c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            boolean needWidth) {
818e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
819e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        float ret = 0;
820e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
8210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int textStart = mStart + start;
8220c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        int textLimit = mStart + limit;
823e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
8240c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (needWidth || (c != null && runIsRtl)) {
8258a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousTop = 0;
8268a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousAscent = 0;
8278a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousDescent = 0;
8288a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousBottom = 0;
8298a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            int previousLeading = 0;
8308a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8318a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            boolean needUpdateMetrics = (fmi != null);
8328a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8338a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            if (needUpdateMetrics) {
8348a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousTop     = fmi.top;
8358a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousAscent  = fmi.ascent;
8368a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousDescent = fmi.descent;
8378a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousBottom  = fmi.bottom;
8388a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                previousLeading = fmi.leading;
8398a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            }
8408a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8410c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
8428a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio
8438a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            if (needUpdateMetrics) {
8448a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
8458a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio                        previousLeading);
8468a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio            }
8470c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        }
848e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
8490c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        if (c != null) {
8500c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            if (runIsRtl) {
8510c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                x -= ret;
852e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
8530c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            replacement.draw(c, mText, textStart, textLimit,
8540c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt                    x, top, y, bottom, wp);
855e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
856e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
857e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return runIsRtl ? -ret : ret;
858e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
859945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
860e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
861e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Utility function for handling a unidirectional run.  The run must not
862e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * contain tabs or emoji but can contain styles.
863e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
864bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy     *
865e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param start the line-relative start of the run
8660c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param measureLimit the offset to measure to, between start and limit inclusive
867e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param limit the limit of the run
868e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param runIsRtl true if the run is right-to-left
869e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas, can be null
870e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the end of the run closest to the leading margin
871e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param top the top of the line
872e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline
873e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param bottom the bottom of the line
874e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param fmi receives metrics information, can be null
875e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param needWidth true if the width is required
876e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the signed width of the run based on the run direction; only
877e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * valid if needWidth is true
878e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
879bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy    private float handleRun(int start, int measureLimit,
880e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
8810c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int bottom, FontMetricsInt fmi, boolean needWidth) {
882e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
883f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        // Case of an empty line, make sure we update fmi according to mPaint
884f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        if (start == measureLimit) {
885f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne            TextPaint wp = mWorkPaint;
886f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne            wp.set(mPaint);
88715c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            if (fmi != null) {
88815c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio                expandMetricsFromPaint(fmi, wp);
88915c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            }
89015c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio            return 0f;
891f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne        }
892f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne
893945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        if (mSpanned == null) {
894945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            TextPaint wp = mWorkPaint;
895945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            wp.set(mPaint);
896945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            final int mlimit = measureLimit;
897945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            return handleText(wp, start, mlimit, start, limit, runIsRtl, c, x, top,
898945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    y, bottom, fmi, needWidth || mlimit < measureLimit);
899945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        }
900945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
901c1f44830809f0a8526855f13822702ea756214faGilles Debunne        mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
902c1f44830809f0a8526855f13822702ea756214faGilles Debunne        mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
903945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
904e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // Shaping needs to take into account context up to metric boundaries,
905e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        // but rendering needs to take into account character style boundaries.
9060c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // So we iterate through metric runs to get metric bounds,
9070c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // then within each metric run iterate through character style runs
9080c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        // for the run bounds.
909945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        final float originalX = x;
9100c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt        for (int i = start, inext; i < measureLimit; i = inext) {
911e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            TextPaint wp = mWorkPaint;
912e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            wp.set(mPaint);
913e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
914c1f44830809f0a8526855f13822702ea756214faGilles Debunne            inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
915c1f44830809f0a8526855f13822702ea756214faGilles Debunne                    mStart;
916945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            int mlimit = Math.min(inext, measureLimit);
917945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
918945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            ReplacementSpan replacement = null;
919945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
920c1f44830809f0a8526855f13822702ea756214faGilles Debunne            for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
921945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
922945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                // empty by construction. This special case in getSpans() explains the >= & <= tests
923c1f44830809f0a8526855f13822702ea756214faGilles Debunne                if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
924c1f44830809f0a8526855f13822702ea756214faGilles Debunne                        (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
925c1f44830809f0a8526855f13822702ea756214faGilles Debunne                MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
926945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                if (span instanceof ReplacementSpan) {
927945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    replacement = (ReplacementSpan)span;
928945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                } else {
929945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    // We might have a replacement that uses the draw
930945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    // state, otherwise measure state would suffice.
931945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                    span.updateDrawState(wp);
932e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
933e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
934e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
935945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            if (replacement != null) {
936945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
937945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                        bottom, fmi, needWidth || mlimit < measureLimit);
938945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne                continue;
939945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne            }
940945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne
94142ef515d185d4fc038d602172789cc264f1d9960Raph Levien            for (int j = i, jnext; j < mlimit; j = jnext) {
94242ef515d185d4fc038d602172789cc264f1d9960Raph Levien                jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + mlimit) -
94342ef515d185d4fc038d602172789cc264f1d9960Raph Levien                        mStart;
94442ef515d185d4fc038d602172789cc264f1d9960Raph Levien
94542ef515d185d4fc038d602172789cc264f1d9960Raph Levien                wp.set(mPaint);
94642ef515d185d4fc038d602172789cc264f1d9960Raph Levien                for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
94742ef515d185d4fc038d602172789cc264f1d9960Raph Levien                    // Intentionally using >= and <= as explained above
94842ef515d185d4fc038d602172789cc264f1d9960Raph Levien                    if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + jnext) ||
94942ef515d185d4fc038d602172789cc264f1d9960Raph Levien                            (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
9500c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt
95142ef515d185d4fc038d602172789cc264f1d9960Raph Levien                    CharacterStyle span = mCharacterStyleSpanSet.spans[k];
95242ef515d185d4fc038d602172789cc264f1d9960Raph Levien                    span.updateDrawState(wp);
953e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt                }
95442ef515d185d4fc038d602172789cc264f1d9960Raph Levien
95542ef515d185d4fc038d602172789cc264f1d9960Raph Levien                x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
95642ef515d185d4fc038d602172789cc264f1d9960Raph Levien                        top, y, bottom, fmi, needWidth || jnext < measureLimit);
957e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            }
958e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
959e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
960945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        return x - originalX;
961e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
962e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
963e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
964e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Render a text run with the set-up paint.
965e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
966e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param c the canvas
967e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param wp the paint used to render the text
9680c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param start the start of the run
9690c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param end the end of the run
9700c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param contextStart the start of context for the run
9710c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt     * @param contextEnd the end of the context for the run
972da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio     * @param runIsRtl true if the run is right-to-left
973e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param x the x position of the left edge of the run
974e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param y the baseline of the run
975e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
9760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt    private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
977da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
978e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
979e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (mCharsValid) {
9800c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int count = end - start;
9810c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int contextCount = contextEnd - contextStart;
9820c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            c.drawTextRun(mChars, start, count, contextStart, contextCount,
983051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    x, y, runIsRtl, wp);
984e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        } else {
9850c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            int delta = mStart;
9860c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt            c.drawTextRun(mText, delta + start, delta + end,
987051910b9f998030dacb8a0722588cc715813fde1Raph Levien                    delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp);
988e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
989e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
990e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
991e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
992e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns the ascent of the text at start.  This is used for scaling
993e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * emoji.
994e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
995e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param pos the line-relative position
996e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the ascent of the text at start
997e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
998e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float ascent(int pos) {
999e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (mSpanned == null) {
1000e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return mPaint.ascent();
1001e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1002e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1003e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        pos += mStart;
1004945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne        MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class);
1005e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        if (spans.length == 0) {
1006e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            return mPaint.ascent();
1007e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1008e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1009e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        TextPaint wp = mWorkPaint;
1010e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        wp.set(mPaint);
1011e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        for (MetricAffectingSpan span : spans) {
1012e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt            span.updateMeasureState(wp);
1013e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1014e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        return wp.ascent();
1015e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
1016e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1017e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    /**
1018e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * Returns the next tab position.
1019e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     *
1020e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @param h the (unsigned) offset from the leading margin
1021e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     * @return the (unsigned) tab position after this offset
1022e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt     */
1023e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    float nextTab(float h) {
1024c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        if (mTabs != null) {
1025c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt            return mTabs.nextTab(h);
1026e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt        }
1027c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt        return TabStops.nextDefaultStop(h, TAB_INCREMENT);
1028e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    }
1029e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt
1030e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt    private static final int TAB_INCREMENT = 20;
1031e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt}
1032