TextLine.java revision 538de5bb3b3662c51c17cd9dd24e660c85e88b78
1e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/* 2e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Copyright (C) 2010 The Android Open Source Project 3e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 4e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Licensed under the Apache License, Version 2.0 (the "License"); 5e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * you may not use this file except in compliance with the License. 6e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * You may obtain a copy of the License at 7e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 8e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * http://www.apache.org/licenses/LICENSE-2.0 9e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 10e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Unless required by applicable law or agreed to in writing, software 11e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * distributed under the License is distributed on an "AS IS" BASIS, 12e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * See the License for the specific language governing permissions and 14e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * limitations under the License. 15e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 16e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 17e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltpackage android.text; 18e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 19538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport android.annotation.NonNull; 20538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport android.annotation.Nullable; 21e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Canvas; 22e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint; 23e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint.FontMetricsInt; 24e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.Layout.Directions; 25c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Feltimport android.text.Layout.TabStops; 26e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.CharacterStyle; 27e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.MetricAffectingSpan; 28e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.ReplacementSpan; 29e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.util.Log; 30e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 31dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunneimport com.android.internal.util.ArrayUtils; 32dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne 33538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport java.util.ArrayList; 34538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 35e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/** 36e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Represents a line of styled text, for measuring in visual order and 37e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * for rendering. 38e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 39e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Get a new instance using obtain(), and when finished with it, return it 40e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * to the pool using recycle(). 41e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 42e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Call set to prepare the instance for use, then either draw, measure, 43e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * metrics, or caretToLeftRightOf. 44e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 45e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @hide 46e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 47e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltclass TextLine { 48bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private static final boolean DEBUG = false; 49bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy 50e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private TextPaint mPaint; 51e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private CharSequence mText; 52e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private int mStart; 53e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private int mLen; 54e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private int mDir; 55e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private Directions mDirections; 56e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private boolean mHasTabs; 57c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt private TabStops mTabs; 58e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private char[] mChars; 59e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private boolean mCharsValid; 60e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private Spanned mSpanned; 6109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 6209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // Additional width of whitespace for justification. This value is per whitespace, thus 6309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // the line width will increase by mAddedWidth x (number of stretchable whitespaces). 6409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka private float mAddedWidth; 65538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 66345cb03315a0813ec57e44f97fc3fa4af6b3c309Gilles Debunne private final TextPaint mWorkPaint = new TextPaint(); 67538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader private final TextPaint mActivePaint = new TextPaint(); 68c1f44830809f0a8526855f13822702ea756214faGilles Debunne private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet = 69c1f44830809f0a8526855f13822702ea756214faGilles Debunne new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class); 70c1f44830809f0a8526855f13822702ea756214faGilles Debunne private final SpanSet<CharacterStyle> mCharacterStyleSpanSet = 71c1f44830809f0a8526855f13822702ea756214faGilles Debunne new SpanSet<CharacterStyle>(CharacterStyle.class); 72c1f44830809f0a8526855f13822702ea756214faGilles Debunne private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet = 73c1f44830809f0a8526855f13822702ea756214faGilles Debunne new SpanSet<ReplacementSpan>(ReplacementSpan.class); 74e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 75538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader private final UnderlineInfo mUnderlineInfo = new UnderlineInfo(); 76538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader private final ArrayList<UnderlineInfo> mUnderlines = new ArrayList(); 77538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 78bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private static final TextLine[] sCached = new TextLine[3]; 79e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 80e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 81e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns a new TextLine from the shared pool. 82e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 83e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return an uninitialized TextLine 84e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 85e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt static TextLine obtain() { 86e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt TextLine tl; 87bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy synchronized (sCached) { 88bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy for (int i = sCached.length; --i >= 0;) { 89bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy if (sCached[i] != null) { 90bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy tl = sCached[i]; 91bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy sCached[i] = null; 92e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return tl; 93e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 94e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 95e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 96e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt tl = new TextLine(); 97bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy if (DEBUG) { 98bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy Log.v("TLINE", "new: " + tl); 99bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy } 100e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return tl; 101e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 102e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 103e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 104e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Puts a TextLine back into the shared pool. Do not use this TextLine once 105e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * it has been returned. 106e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param tl the textLine 107e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return null, as a convenience from clearing references to the provided 108e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * TextLine 109e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 110e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt static TextLine recycle(TextLine tl) { 111e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt tl.mText = null; 112e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt tl.mPaint = null; 113e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt tl.mDirections = null; 114893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov tl.mSpanned = null; 115893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov tl.mTabs = null; 116893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov tl.mChars = null; 117c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne 118c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne tl.mMetricAffectingSpanSpanSet.recycle(); 119c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne tl.mCharacterStyleSpanSet.recycle(); 120c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne tl.mReplacementSpanSpanSet.recycle(); 121c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne 122bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy synchronized(sCached) { 123bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy for (int i = 0; i < sCached.length; ++i) { 124bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy if (sCached[i] == null) { 125bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy sCached[i] = tl; 126f902d7bc49797ec277b4576c921dfffa15d741ddGilles Debunne break; 127e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 128e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 129e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 130e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return null; 131e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 132e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 133e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 134e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Initializes a TextLine and prepares it for use. 135e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 136e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param paint the base paint for the line 137e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param text the text, can be Styled 138e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the start of the line relative to the text 139e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the limit of the line relative to the text 140e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param dir the paragraph direction of this line 141e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param directions the directions information of this line 142112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader * @param hasTabs true if the line might contain tabs 143c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt * @param tabStops the tabStops. Can be null. 144e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 145e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt void set(TextPaint paint, CharSequence text, int start, int limit, int dir, 146c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt Directions directions, boolean hasTabs, TabStops tabStops) { 147e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mPaint = paint; 148e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mText = text; 149e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mStart = start; 150e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mLen = limit - start; 151e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mDir = dir; 152e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mDirections = directions; 1538059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio if (mDirections == null) { 1548059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio throw new IllegalArgumentException("Directions cannot be null"); 1558059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio } 156e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mHasTabs = hasTabs; 157e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mSpanned = null; 158e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 159e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean hasReplacement = false; 160e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (text instanceof Spanned) { 161e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mSpanned = (Spanned) text; 162c1f44830809f0a8526855f13822702ea756214faGilles Debunne mReplacementSpanSpanSet.init(mSpanned, start, limit); 163c1f44830809f0a8526855f13822702ea756214faGilles Debunne hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0; 164e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 165e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1661e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT; 167e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 168e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mCharsValid) { 169e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mChars == null || mChars.length < mLen) { 170776abc24cdd18610232a50b997cce3cffa74609bAdam Lesinski mChars = ArrayUtils.newUnpaddedCharArray(mLen); 171e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 172e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt TextUtils.getChars(text, start, limit, mChars, 0); 1730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (hasReplacement) { 1740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // Handle these all at once so we don't have to do it as we go. 1750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // Replace the first character of each replacement run with the 1760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // object-replacement character and the remainder with zero width 1770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // non-break space aka BOM. Cursor movement code skips these 1780c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // zero-width characters. 1790c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt char[] chars = mChars; 1800c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt for (int i = start, inext; i < limit; i = inext) { 181c1f44830809f0a8526855f13822702ea756214faGilles Debunne inext = mReplacementSpanSpanSet.getNextTransition(i, limit); 182c1f44830809f0a8526855f13822702ea756214faGilles Debunne if (mReplacementSpanSpanSet.hasSpansIntersecting(i, inext)) { 1831e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne // transition into a span 1840c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt chars[i - start] = '\ufffc'; 1850c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt for (int j = i - start + 1, e = inext - start; j < e; ++j) { 1860c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip 1870c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 1880c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 1890c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 1900c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 191e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 192c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt mTabs = tabStops; 19309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka mAddedWidth = 0; 19409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 19509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 19609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka /** 19709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka * Justify the line to the given width. 19809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka */ 19909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka void justify(float justifyWidth) { 20009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka int end = mLen; 20109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) { 20209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka end--; 20309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 20409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka final int spaces = countStretchableSpaces(0, end); 20509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka if (spaces == 0) { 20609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // There are no stretchable spaces, so we can't help the justification by adding any 20709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // width. 20809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka return; 20909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 21009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka final float width = Math.abs(measure(end, false, null)); 21109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka mAddedWidth = (justifyWidth - width) / spaces; 212e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 213e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 214e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 215e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Renders the TextLine. 216e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 217e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas to render on 218e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the leading margin position 219e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 220e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 221e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 222e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 223e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt void draw(Canvas c, float x, int top, int y, int bottom) { 224e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (!mHasTabs) { 225e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) { 226bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy drawRun(c, 0, mLen, false, x, top, y, bottom, false); 227e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return; 228e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 229e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) { 230bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy drawRun(c, 0, mLen, true, x, top, y, bottom, false); 231e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return; 232e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 233e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 234e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 235e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float h = 0; 236e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int[] runs = mDirections.mDirections; 237e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 238e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int lastRunIndex = runs.length - 2; 239e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (int i = 0; i < runs.length; i += 2) { 240e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runStart = runs[i]; 241e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK); 242e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runLimit > mLen) { 243e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = mLen; 244e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 245e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0; 246e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 247e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int segstart = runStart; 248e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { 249e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int codept = 0; 250e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mHasTabs && j < runLimit) { 251e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt codept = mChars[j]; 252112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) { 253e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt codept = Character.codePointAt(mChars, j); 254112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (codept > 0xFFFF) { 255e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt ++j; 256e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt continue; 257e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 258e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 259e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 260e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 261112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (j == runLimit || codept == '\t') { 262bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom, 263e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt i != lastRunIndex || j != mLen); 264e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 265e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (codept == '\t') { 266e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt h = mDir * nextTab(h * mDir); 267e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 268e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt segstart = j + 1; 269e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 270e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 271e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 272e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 273e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 274e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 275e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns metrics information for the entire line. 276e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 277e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives font metrics information, can be null 278e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the line 279e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 280e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float metrics(FontMetricsInt fmi) { 281e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return measure(mLen, false, fmi); 282e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 283e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 284e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 285e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns information about a position on the line. 286e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 287e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param offset the line-relative character offset, between 0 and the 288e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * line length, inclusive 289e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param trailing true to measure the trailing edge of the character 290e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * before offset, false to measure the leading edge of the character 291e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * at offset. 292e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information about the requested 293e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * character, can be null. 294e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed offset from the leading margin to the requested 295e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * character edge. 296e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 297e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float measure(int offset, boolean trailing, FontMetricsInt fmi) { 298e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int target = trailing ? offset - 1 : offset; 299e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (target < 0) { 300e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return 0; 301e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 302e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 303e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float h = 0; 304e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 305e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (!mHasTabs) { 306e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) { 307bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return measureRun(0, offset, mLen, false, fmi); 308e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 309e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) { 310bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return measureRun(0, offset, mLen, true, fmi); 311e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 312e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 313e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 314e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt char[] chars = mChars; 315e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int[] runs = mDirections.mDirections; 316e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (int i = 0; i < runs.length; i += 2) { 317e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runStart = runs[i]; 318e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK); 319e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runLimit > mLen) { 320e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = mLen; 321e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 322e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0; 323e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 324e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int segstart = runStart; 325e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { 326e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int codept = 0; 327e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mHasTabs && j < runLimit) { 328e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt codept = chars[j]; 329112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) { 330e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt codept = Character.codePointAt(chars, j); 331112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (codept > 0xFFFF) { 332e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt ++j; 333e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt continue; 334e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 335e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 336e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 337e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 338112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (j == runLimit || codept == '\t') { 339e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean inSegment = target >= segstart && target < j; 340e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 341e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; 342e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (inSegment && advance) { 343bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return h += measureRun(segstart, offset, j, runIsRtl, fmi); 344e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 345e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 346bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy float w = measureRun(segstart, j, j, runIsRtl, fmi); 347e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt h += advance ? w : -w; 348e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 349e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (inSegment) { 350bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return h += measureRun(segstart, offset, j, runIsRtl, null); 351e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 352e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 353e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (codept == '\t') { 354e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (offset == j) { 355e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return h; 356e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 357e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt h = mDir * nextTab(h * mDir); 358e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (target == j) { 359e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return h; 360e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 361e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 362e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 363e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt segstart = j + 1; 364e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 365e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 366e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 367e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 368e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return h; 369e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 370e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 371e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 372e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Draws a unidirectional (but possibly multi-styled) run of text. 373e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 374bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy * 375e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas to draw on 376e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the line-relative start 377e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the line-relative limit 378e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 379e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the position of the run that is closest to the leading margin 380e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 381e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 382e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 383e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param needWidth true if the width value is required. 384e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the run, based on the paragraph direction. 385e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Only valid if needWidth is true. 386e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 387bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private float drawRun(Canvas c, int start, 388e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int limit, boolean runIsRtl, float x, int top, int y, int bottom, 389e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean needWidth) { 390e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 391e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { 392bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy float w = -measureRun(start, limit, limit, runIsRtl, null); 393bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy handleRun(start, limit, limit, runIsRtl, c, x + w, top, 3940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt y, bottom, null, false); 395e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return w; 396e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 397e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 398bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return handleRun(start, limit, limit, runIsRtl, c, x, top, 3990c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt y, bottom, null, needWidth); 400e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 401e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 402e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 403e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Measures a unidirectional (but possibly multi-styled) run of text. 404e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 405bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy * 406e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the line-relative start of the run 407e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param offset the offset to measure to, between start and limit inclusive 408e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the line-relative limit of the run 409e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 410e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information about the requested 411e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * run, can be null. 412e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width from the start of the run to the leading edge 413e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * of the character at offset, based on the run (not paragraph) direction 414e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 415bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private float measureRun(int start, int offset, int limit, boolean runIsRtl, 416bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy FontMetricsInt fmi) { 417bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true); 418e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 419e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 420e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 421e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Walk the cursor through this line, skipping conjuncts and 422e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * zero-width characters. 423e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 424e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>This function cannot properly walk the cursor off the ends of the line 425e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * since it does not know about any shaping on the previous/following line 426e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * that might affect the cursor position. Callers must either avoid these 427e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * situations or handle the result specially. 428e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 429e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param cursor the starting position of the cursor, between 0 and the 430e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * length of the line, inclusive 431e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param toLeft true if the caret is moving to the left. 432e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the new offset. If it is less than 0 or greater than the length 433e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * of the line, the previous/following line should be examined to get the 434e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * actual offset. 435e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 436e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int getOffsetToLeftRightOf(int cursor, boolean toLeft) { 437e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // 1) The caret marks the leading edge of a character. The character 438e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // logically before it might be on a different level, and the active caret 439e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // position is on the character at the lower level. If that character 440e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // was the previous character, the caret is on its trailing edge. 441e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // 2) Take this character/edge and move it in the indicated direction. 442e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // This gives you a new character and a new edge. 443e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // 3) This position is between two visually adjacent characters. One of 444e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // these might be at a lower level. The active position is on the 445e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // character at the lower level. 446e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // 4) If the active position is on the trailing edge of the character, 447e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // the new caret position is the following logical character, else it 448e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // is the character. 449e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 450e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int lineStart = 0; 451e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int lineEnd = mLen; 452e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean paraIsRtl = mDir == -1; 453e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int[] runs = mDirections.mDirections; 454e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 455e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1; 456e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean trailing = false; 457e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 458e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor == lineStart) { 459e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runIndex = -2; 460e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } else if (cursor == lineEnd) { 461e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runIndex = runs.length; 462e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } else { 463e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // First, get information about the run containing the character with 464e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // the active caret. 465e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (runIndex = 0; runIndex < runs.length; runIndex += 2) { 466e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runStart = lineStart + runs[runIndex]; 467e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor >= runStart) { 468e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK); 469e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runLimit > lineEnd) { 470e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = lineEnd; 471e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 472e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor < runLimit) { 473e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) & 474e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt Layout.RUN_LEVEL_MASK; 475e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor == runStart) { 476e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // The caret is on a run boundary, see if we should 477e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // use the position on the trailing edge of the previous 478e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // logical character instead. 479e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit; 480e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int pos = cursor - 1; 481e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) { 482e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt prevRunStart = lineStart + runs[prevRunIndex]; 483e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (pos >= prevRunStart) { 484e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt prevRunLimit = prevRunStart + 485e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK); 486e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (prevRunLimit > lineEnd) { 487e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt prevRunLimit = lineEnd; 488e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 489e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (pos < prevRunLimit) { 490e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) 491e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt & Layout.RUN_LEVEL_MASK; 492e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (prevRunLevel < runLevel) { 493e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Start from logically previous character. 494e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runIndex = prevRunIndex; 495e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLevel = prevRunLevel; 496e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runStart = prevRunStart; 497e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = prevRunLimit; 498e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt trailing = true; 499e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 500e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 501e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 502e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 503e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 504e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 505e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 506e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 507e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 508e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 509e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 510e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // caret might be == lineEnd. This is generally a space or paragraph 511e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // separator and has an associated run, but might be the end of 512e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // text, in which case it doesn't. If that happens, we ran off the 513e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // end of the run list, and runIndex == runs.length. In this case, 514e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // we are at a run boundary so we skip the below test. 515e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runIndex != runs.length) { 516e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean runIsRtl = (runLevel & 0x1) != 0; 517e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean advance = toLeft == runIsRtl; 518e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor != (advance ? runLimit : runStart) || advance != trailing) { 519e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Moving within or into the run, so we can move logically. 5200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit, 5210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt runIsRtl, cursor, advance); 522e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // If the new position is internal to the run, we're at the strong 523e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // position already so we're finished. 524e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret != (advance ? runLimit : runStart)) { 525e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return newCaret; 526e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 527e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 528e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 529e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 530e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 531e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // If newCaret is -1, we're starting at a run boundary and crossing 532e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // into another run. Otherwise we've arrived at a run boundary, and 533e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // need to figure out which character to attach to. Note we might 534e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // need to run this twice, if we cross a run boundary and end up at 535e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // another run boundary. 536e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt while (true) { 537e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean advance = toLeft == paraIsRtl; 538e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int otherRunIndex = runIndex + (advance ? 2 : -2); 539e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (otherRunIndex >= 0 && otherRunIndex < runs.length) { 540e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int otherRunStart = lineStart + runs[otherRunIndex]; 541e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int otherRunLimit = otherRunStart + 542e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK); 543e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (otherRunLimit > lineEnd) { 544e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt otherRunLimit = lineEnd; 545e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 546e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) & 547e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt Layout.RUN_LEVEL_MASK; 548e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean otherRunIsRtl = (otherRunLevel & 1) != 0; 549e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 550e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt advance = toLeft == otherRunIsRtl; 551e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret == -1) { 5520c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart, 5530c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt otherRunLimit, otherRunIsRtl, 554e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt advance ? otherRunStart : otherRunLimit, advance); 555e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret == (advance ? otherRunLimit : otherRunStart)) { 556e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Crossed and ended up at a new boundary, 557e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // repeat a second and final time. 558e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runIndex = otherRunIndex; 559e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLevel = otherRunLevel; 560e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt continue; 561e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 562e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 563e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 564e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 565e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // The new caret is at a boundary. 566e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (otherRunLevel < runLevel) { 567e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // The strong character is in the other run. 568e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt newCaret = advance ? otherRunStart : otherRunLimit; 569e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 570e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 571e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 572e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 573e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret == -1) { 574e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // We're walking off the end of the line. The paragraph 575e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // level is always equal to or lower than any internal level, so 576e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // the boundaries get the strong caret. 5770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt newCaret = advance ? mLen + 1 : -1; 578e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 579e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 580e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 581e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Else we've arrived at the end of the line. That's a strong position. 582e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // We might have arrived here by crossing over a run with no internal 583e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // breaks and dropping out of the above loop before advancing one final 584e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // time, so reset the caret. 585e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Note, we use '<=' below to handle a situation where the only run 586e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // on the line is a counter-directional run. If we're not advancing, 587e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // we can end up at the 'lineEnd' position but the caret we want is at 588e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // the lineStart. 589e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret <= lineEnd) { 590e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt newCaret = advance ? lineEnd : lineStart; 591e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 592e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 593e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 594e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 595e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return newCaret; 596e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 597e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 598e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 599e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns the next valid offset within this directional run, skipping 600e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * conjuncts and zero-width characters. This should not be called to walk 601cc3ec6cdb2b892eb29513e72d8b205acbe997b25Gilles Debunne * off the end of the line, since the returned values might not be valid 6020c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * on neighboring lines. If the returned offset is less than zero or 6030c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * greater than the line length, the offset should be recomputed on the 6040c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * preceding or following line, respectively. 605e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 606e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIndex the run index 6070c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param runStart the start of the run 6080c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param runLimit the limit of the run 6090c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param runIsRtl true if the run is right-to-left 610e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param offset the offset 611e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param after true if the new offset should logically follow the provided 612e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * offset 613e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the new offset 614e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 6150c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit, 6160c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt boolean runIsRtl, int offset, boolean after) { 617e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 6180c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (runIndex < 0 || offset == (after ? mLen : 0)) { 6190c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // Walking off end of line. Since we don't know 6200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // what cursor positions are available on other lines, we can't 6210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // return accurate values. These are a guess. 622e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (after) { 6230c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart; 6240c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6250c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart; 6260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6270c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6280c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt TextPaint wp = mWorkPaint; 6290c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt wp.set(mPaint); 63009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka wp.setWordSpacing(mAddedWidth); 6310c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6320c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int spanStart = runStart; 6330c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int spanLimit; 6340c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (mSpanned == null) { 6350c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt spanLimit = runLimit; 6360c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } else { 6370c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int target = after ? offset + 1 : offset; 6380c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int limit = mStart + runLimit; 6390c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt while (true) { 6400c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit, 6410c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt MetricAffectingSpan.class) - mStart; 6420c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (spanLimit >= target) { 6430c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt break; 644e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 6450c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt spanStart = spanLimit; 6460c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6470c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6480c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart, 6490c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt mStart + spanLimit, MetricAffectingSpan.class); 6501e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class); 6510c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6520c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (spans.length > 0) { 6530c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt ReplacementSpan replacement = null; 6540c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt for (int j = 0; j < spans.length; j++) { 6550c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt MetricAffectingSpan span = spans[j]; 6560c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (span instanceof ReplacementSpan) { 6570c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt replacement = (ReplacementSpan)span; 6580c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } else { 6590c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt span.updateMeasureState(wp); 6600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6610c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6620c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6630c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (replacement != null) { 6640c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // If we have a replacement span, we're moving either to 6650c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // the start or end of this span. 6660c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return after ? spanLimit : spanStart; 667e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 668e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 669e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 670e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 671051910b9f998030dacb8a0722588cc715813fde1Raph Levien int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; 6720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE; 6730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (mCharsValid) { 6740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart, 675051910b9f998030dacb8a0722588cc715813fde1Raph Levien dir, offset, cursorOpt); 6760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } else { 6770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return wp.getTextRunCursor(mText, mStart + spanStart, 678051910b9f998030dacb8a0722588cc715813fde1Raph Levien mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart; 679e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 680e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 681e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 682e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 6830bb000931bb841e75903d655552d1626ae158707Gilles Debunne * @param wp 6840bb000931bb841e75903d655552d1626ae158707Gilles Debunne */ 6850bb000931bb841e75903d655552d1626ae158707Gilles Debunne private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) { 6860bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousTop = fmi.top; 6870bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousAscent = fmi.ascent; 6880bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousDescent = fmi.descent; 6890bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousBottom = fmi.bottom; 6900bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousLeading = fmi.leading; 6910bb000931bb841e75903d655552d1626ae158707Gilles Debunne 6920bb000931bb841e75903d655552d1626ae158707Gilles Debunne wp.getFontMetricsInt(fmi); 6930bb000931bb841e75903d655552d1626ae158707Gilles Debunne 6948a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom, 6958a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousLeading); 6968a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio } 6978a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 6988a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent, 6998a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousDescent, int previousBottom, int previousLeading) { 7000bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.top = Math.min(fmi.top, previousTop); 7010bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.ascent = Math.min(fmi.ascent, previousAscent); 7020bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.descent = Math.max(fmi.descent, previousDescent); 7030bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.bottom = Math.max(fmi.bottom, previousBottom); 7040bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.leading = Math.max(fmi.leading, previousLeading); 7050bb000931bb841e75903d655552d1626ae158707Gilles Debunne } 7060bb000931bb841e75903d655552d1626ae158707Gilles Debunne 707538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader private static void drawUnderline(TextPaint wp, Canvas c, int color, float thickness, 708538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader float xleft, float xright, int baseline) { 709538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h 710538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final float underlineTop = baseline + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize(); 711538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 712538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final int previousColor = wp.getColor(); 713538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final Paint.Style previousStyle = wp.getStyle(); 714538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final boolean previousAntiAlias = wp.isAntiAlias(); 715538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 716538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setStyle(Paint.Style.FILL); 717538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setAntiAlias(true); 718538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 719538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setColor(color); 720538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader c.drawRect(xleft, underlineTop, xright, underlineTop + thickness, wp); 721538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 722538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setStyle(previousStyle); 723538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setColor(previousColor); 724538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setAntiAlias(previousAntiAlias); 725538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 726538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 727538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd, 728538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader boolean runIsRtl, int offset) { 729538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (mCharsValid) { 730538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset); 731538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else { 732538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final int delta = mStart; 733538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader return wp.getRunAdvance(mText, delta + start, delta + end, 734538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader delta + contextStart, delta + contextEnd, runIsRtl, delta + offset); 735538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 736538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 737538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 7380bb000931bb841e75903d655552d1626ae158707Gilles Debunne /** 739e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Utility function for measuring and rendering text. The text must 740112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader * not include a tab. 741e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 742e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param wp the working paint 743e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the start of the text 7440c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param end the end of the text 745e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 746e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas, can be null if rendering is not needed 747e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the edge of the run closest to the leading margin 748e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 749e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 750e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 751e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information, can be null 752e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param needWidth true if the width of the run is needed 753909c7bca570b6f50650d0872e2037389b29252e3Raph Levien * @param offset the offset for the purpose of measuring 754538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader * @param underlines the list of locations and paremeters for drawing underlines 755e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the run based on the run direction; only 756e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * valid if needWidth is true 757e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 7580c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt private float handleText(TextPaint wp, int start, int end, 7590c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int contextStart, int contextEnd, boolean runIsRtl, 7600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt Canvas c, float x, int top, int y, int bottom, 761538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader FontMetricsInt fmi, boolean needWidth, int offset, 762538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader @Nullable ArrayList<UnderlineInfo> underlines) { 763e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 76409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka wp.setWordSpacing(mAddedWidth); 765850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio // Get metrics first (even for empty strings or "0" width runs) 766850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio if (fmi != null) { 767850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio expandMetricsFromPaint(fmi, wp); 768850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio } 769e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 770850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio // No need to do anything if the run width is "0" 771538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (end == start) { 772850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio return 0f; 773850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio } 774850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio 775538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader float totalWidth = 0; 776850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio 777538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final int numUnderlines = underlines == null ? 0 : underlines.size(); 778538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (needWidth || (c != null && (wp.bgColor != 0 || numUnderlines != 0 || runIsRtl))) { 779538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); 780e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 781e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 782e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (c != null) { 783538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final float leftX, rightX; 784e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runIsRtl) { 785538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader leftX = x - totalWidth; 786538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader rightX = x; 787538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else { 788538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader leftX = x; 789538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader rightX = x + totalWidth; 790e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 791e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 792e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (wp.bgColor != 0) { 793dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne int previousColor = wp.getColor(); 794dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne Paint.Style previousStyle = wp.getStyle(); 795dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne 796e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt wp.setColor(wp.bgColor); 797e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt wp.setStyle(Paint.Style.FILL); 798538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader c.drawRect(leftX, top, rightX, bottom, wp); 799e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 800dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne wp.setStyle(previousStyle); 801dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne wp.setColor(previousColor); 802dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne } 803dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne 804538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (numUnderlines != 0) { 805538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // kStdUnderline_Thickness = 1/18, defined in SkTextFormatParams.h 806538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final float defaultThickness = (1.0f / 18.0f) * wp.getTextSize(); 807538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader for (int i = 0; i < numUnderlines; i++) { 808538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final UnderlineInfo info = underlines.get(i); 809538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 810538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final int underlineStart = Math.max(info.start, start); 811538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final int underlineEnd = Math.min(info.end, offset); 812538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader float underlineStartAdvance = getRunAdvance( 813538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp, start, end, contextStart, contextEnd, runIsRtl, underlineStart); 814538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader float underlineEndAdvance = getRunAdvance( 815538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp, start, end, contextStart, contextEnd, runIsRtl, underlineEnd); 816538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final float underlineXLeft, underlineXRight; 817538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (runIsRtl) { 818538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader underlineXLeft = rightX - underlineEndAdvance; 819538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader underlineXRight = rightX - underlineStartAdvance; 820538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else { 821538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader underlineXLeft = leftX + underlineStartAdvance; 822538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader underlineXRight = leftX + underlineEndAdvance; 823538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 824702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir 825538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // Theoretically, there could be cases where both Paint's and TextPaint's 826538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // setUnderLineText() are called. For backward compatibility, we need to draw 827538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // both underlines, the one with custom color first. 828538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (info.underlineColor != 0) { 829538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader drawUnderline(wp, c, wp.underlineColor, wp.underlineThickness, 830538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader underlineXLeft, underlineXRight, y); 831538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 832538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (info.isUnderlineText) { 833538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader drawUnderline(wp, c, wp.getColor(), defaultThickness, 834538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader underlineXLeft, underlineXRight, y); 835538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 836538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 837e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 838e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 839da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl, 840538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader leftX, y + wp.baselineShift); 841e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 842e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 843538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader return runIsRtl ? -totalWidth : totalWidth; 844e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 845e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 846e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 847e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Utility function for measuring and rendering a replacement. 848e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 849bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy * 850e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param replacement the replacement 851e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param wp the work paint 852e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the start of the run 853e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the limit of the run 854e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 855e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas, can be null if not rendering 856e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the edge of the replacement closest to the leading margin 857e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 858e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 859e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 860e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information, can be null 861e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param needWidth true if the width of the replacement is needed 862e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the run based on the run direction; only 863e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * valid if needWidth is true 864e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 865e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private float handleReplacement(ReplacementSpan replacement, TextPaint wp, 866bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy int start, int limit, boolean runIsRtl, Canvas c, 867e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float x, int top, int y, int bottom, FontMetricsInt fmi, 8680c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt boolean needWidth) { 869e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 870e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float ret = 0; 871e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 8720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int textStart = mStart + start; 8730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int textLimit = mStart + limit; 874e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 8750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (needWidth || (c != null && runIsRtl)) { 8768a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousTop = 0; 8778a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousAscent = 0; 8788a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousDescent = 0; 8798a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousBottom = 0; 8808a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousLeading = 0; 8818a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 8828a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio boolean needUpdateMetrics = (fmi != null); 8838a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 8848a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio if (needUpdateMetrics) { 8858a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousTop = fmi.top; 8868a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousAscent = fmi.ascent; 8878a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousDescent = fmi.descent; 8888a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousBottom = fmi.bottom; 8898a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousLeading = fmi.leading; 8908a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio } 8918a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 8920c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt ret = replacement.getSize(wp, mText, textStart, textLimit, fmi); 8938a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 8948a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio if (needUpdateMetrics) { 8958a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom, 8968a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousLeading); 8978a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio } 8980c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 899e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 9000c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (c != null) { 9010c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (runIsRtl) { 9020c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt x -= ret; 903e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 9040c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt replacement.draw(c, mText, textStart, textLimit, 9050c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt x, top, y, bottom, wp); 906e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 907e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 908e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return runIsRtl ? -ret : ret; 909e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 910945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 91146c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader private int adjustHyphenEdit(int start, int limit, int hyphenEdit) { 91246c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader int result = hyphenEdit; 91346c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader // Only draw hyphens on first or last run in line. Disable them otherwise. 91446c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader if (start > 0) { // not the first run 91546c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader result &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE; 91646c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader } 91746c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader if (limit < mLen) { // not the last run 91846c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader result &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE; 91946c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader } 92046c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader return result; 92146c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader } 92246c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader 923538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader private static final class UnderlineInfo { 924538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public boolean isUnderlineText; 925538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public int underlineColor; 926538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public float underlineThickness; 927538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public int start = -1; 928538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public int end = -1; 929538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 930538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public boolean hasUnderline() { 931538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader return isUnderlineText || underlineColor != 0; 932538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 933538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 934538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // Copies the info, but not the start and end range. 935538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public UnderlineInfo copyInfo() { 936538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final UnderlineInfo copy = new UnderlineInfo(); 937538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.isUnderlineText = isUnderlineText; 938538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.underlineColor = underlineColor; 939538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.underlineThickness = underlineThickness; 940538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader return copy; 941538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 942538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 943538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 944538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader private void extractUnderlineInfo(@NonNull TextPaint paint, @NonNull UnderlineInfo info) { 945538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader info.isUnderlineText = paint.isUnderlineText(); 946538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (info.isUnderlineText) { 947538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader paint.setUnderlineText(false); 948538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 949538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader info.underlineColor = paint.underlineColor; 950538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader info.underlineThickness = paint.underlineThickness; 951538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader paint.setUnderlineText(0, 0.0f); 952538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 953538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 954e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 955e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Utility function for handling a unidirectional run. The run must not 956112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader * contain tabs but can contain styles. 957e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 958bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy * 959e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the line-relative start of the run 9600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param measureLimit the offset to measure to, between start and limit inclusive 961e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the limit of the run 962e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 963e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas, can be null 964e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the end of the run closest to the leading margin 965e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 966e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 967e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 968e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information, can be null 969e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param needWidth true if the width is required 970e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the run based on the run direction; only 971e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * valid if needWidth is true 972e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 973bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private float handleRun(int start, int measureLimit, 974e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int limit, boolean runIsRtl, Canvas c, float x, int top, int y, 9750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int bottom, FontMetricsInt fmi, boolean needWidth) { 976e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 9779f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir if (measureLimit < start || measureLimit > limit) { 9789f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of " 9799f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir + "start (" + start + ") and limit (" + limit + ") bounds"); 9809f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir } 9819f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir 982f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne // Case of an empty line, make sure we update fmi according to mPaint 983f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne if (start == measureLimit) { 984538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final TextPaint wp = mWorkPaint; 985f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne wp.set(mPaint); 98615c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio if (fmi != null) { 98715c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio expandMetricsFromPaint(fmi, wp); 98815c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio } 98915c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio return 0f; 990f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne } 991f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne 992945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne if (mSpanned == null) { 993538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final TextPaint wp = mWorkPaint; 994945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne wp.set(mPaint); 99546c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader wp.setHyphenEdit(adjustHyphenEdit(start, limit, wp.getHyphenEdit())); 996909c7bca570b6f50650d0872e2037389b29252e3Raph Levien return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top, 997538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader y, bottom, fmi, needWidth, measureLimit, null); 998945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne } 999945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 1000c1f44830809f0a8526855f13822702ea756214faGilles Debunne mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit); 1001c1f44830809f0a8526855f13822702ea756214faGilles Debunne mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit); 1002945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 1003e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Shaping needs to take into account context up to metric boundaries, 1004e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // but rendering needs to take into account character style boundaries. 10050c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // So we iterate through metric runs to get metric bounds, 10060c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // then within each metric run iterate through character style runs 10070c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // for the run bounds. 1008945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne final float originalX = x; 10090c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt for (int i = start, inext; i < measureLimit; i = inext) { 1010538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final TextPaint wp = mWorkPaint; 1011e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt wp.set(mPaint); 1012e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1013c1f44830809f0a8526855f13822702ea756214faGilles Debunne inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) - 1014c1f44830809f0a8526855f13822702ea756214faGilles Debunne mStart; 1015945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne int mlimit = Math.min(inext, measureLimit); 1016945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 1017945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne ReplacementSpan replacement = null; 1018945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 1019c1f44830809f0a8526855f13822702ea756214faGilles Debunne for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) { 1020945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT 1021945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne // empty by construction. This special case in getSpans() explains the >= & <= tests 1022c1f44830809f0a8526855f13822702ea756214faGilles Debunne if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) || 1023c1f44830809f0a8526855f13822702ea756214faGilles Debunne (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue; 1024538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j]; 1025945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne if (span instanceof ReplacementSpan) { 1026945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne replacement = (ReplacementSpan)span; 1027945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne } else { 1028945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne // We might have a replacement that uses the draw 1029945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne // state, otherwise measure state would suffice. 1030945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne span.updateDrawState(wp); 1031e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1032e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1033e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1034945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne if (replacement != null) { 1035945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y, 1036945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne bottom, fmi, needWidth || mlimit < measureLimit); 1037945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne continue; 1038945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne } 1039945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 1040538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final TextPaint activePaint = mActivePaint; 1041538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.set(mPaint); 1042538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader int activeStart = i; 1043538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader int activeEnd = mlimit; 1044538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final UnderlineInfo underlineInfo = mUnderlineInfo; 1045538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader mUnderlines.clear(); 104642ef515d185d4fc038d602172789cc264f1d9960Raph Levien for (int j = i, jnext; j < mlimit; j = jnext) { 1047702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) - 1048702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir mStart; 104942ef515d185d4fc038d602172789cc264f1d9960Raph Levien 1050538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final int offset = Math.min(jnext, mlimit); 105142ef515d185d4fc038d602172789cc264f1d9960Raph Levien wp.set(mPaint); 105242ef515d185d4fc038d602172789cc264f1d9960Raph Levien for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) { 105342ef515d185d4fc038d602172789cc264f1d9960Raph Levien // Intentionally using >= and <= as explained above 1054702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) || 1055702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue; 10560c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 1057538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final CharacterStyle span = mCharacterStyleSpanSet.spans[k]; 1058702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir span.updateDrawState(wp); 1059e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 106042ef515d185d4fc038d602172789cc264f1d9960Raph Levien 1061538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader extractUnderlineInfo(wp, underlineInfo); 1062538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 1063538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (j == i) { 1064538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // First chunk of text. We can't handle it yet, since we may need to merge it 1065538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // with the next chunk. So we just save the TextPaint for future comparisons 1066538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // and use. 1067538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.set(wp); 1068538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else if (!wp.hasEqualAttributes(activePaint)) { 1069538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // The style of the present chunk of text is substantially different from the 1070538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // style of the previous chunk. We need to handle the active piece of text 1071538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // and restart with the present chunk. 1072538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.setHyphenEdit(adjustHyphenEdit( 1073538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activeStart, activeEnd, mPaint.getHyphenEdit())); 1074538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, 1075538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader top, y, bottom, fmi, needWidth || activeEnd < measureLimit, 1076538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader Math.min(activeEnd, mlimit), mUnderlines); 1077538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 1078538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activeStart = j; 1079538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.set(wp); 1080538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader mUnderlines.clear(); 1081538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else { 1082538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // The present TextPaint is substantially equal to the last TextPaint except 1083538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // perhaps for underlines. We just need to expand the active piece of text to 1084538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // include the present chunk, which we always do anyway. We don't need to save 1085538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // wp to activePaint, since they are already equal. 1086538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 108746c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader 1088538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activeEnd = jnext; 1089538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (underlineInfo.hasUnderline()) { 1090538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final UnderlineInfo copy = underlineInfo.copyInfo(); 1091538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.start = j; 1092538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.end = jnext; 1093538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader mUnderlines.add(copy); 1094538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 1095e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1096538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // Handle the final piece of text. 1097538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.setHyphenEdit(adjustHyphenEdit( 1098538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activeStart, activeEnd, mPaint.getHyphenEdit())); 1099538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, 1100538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader top, y, bottom, fmi, needWidth || activeEnd < measureLimit, 1101538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader Math.min(activeEnd, mlimit), mUnderlines); 1102e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1103e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1104945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne return x - originalX; 1105e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1106e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1107e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 1108e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Render a text run with the set-up paint. 1109e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 1110e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas 1111e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param wp the paint used to render the text 11120c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param start the start of the run 11130c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param end the end of the run 11140c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param contextStart the start of context for the run 11150c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param contextEnd the end of the context for the run 1116da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio * @param runIsRtl true if the run is right-to-left 1117e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the x position of the left edge of the run 1118e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline of the run 1119e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 11200c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt private void drawTextRun(Canvas c, TextPaint wp, int start, int end, 1121da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio int contextStart, int contextEnd, boolean runIsRtl, float x, int y) { 1122e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1123e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mCharsValid) { 11240c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int count = end - start; 11250c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int contextCount = contextEnd - contextStart; 11260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt c.drawTextRun(mChars, start, count, contextStart, contextCount, 1127051910b9f998030dacb8a0722588cc715813fde1Raph Levien x, y, runIsRtl, wp); 1128e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } else { 11290c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int delta = mStart; 11300c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt c.drawTextRun(mText, delta + start, delta + end, 1131051910b9f998030dacb8a0722588cc715813fde1Raph Levien delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp); 1132e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1133e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1134e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1135e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 1136e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns the next tab position. 1137e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 1138e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param h the (unsigned) offset from the leading margin 1139e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the (unsigned) tab position after this offset 1140e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 1141e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float nextTab(float h) { 1142c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt if (mTabs != null) { 1143c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt return mTabs.nextTab(h); 1144e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1145c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt return TabStops.nextDefaultStop(h, TAB_INCREMENT); 1146e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1147e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 114809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka private boolean isStretchableWhitespace(int ch) { 114909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // TODO: Support other stretchable whitespace. (Bug: 34013491) 115009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka return ch == 0x0020 || ch == 0x00A0; 115109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 115209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 115309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka private int nextStretchableSpace(int start, int end) { 115409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka for (int i = start; i < end; i++) { 115509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart); 115609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka if (isStretchableWhitespace(c)) return i; 115709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 115809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka return end; 115909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 116009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 116109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka /* Return the number of spaces in the text line, for the purpose of justification */ 116209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka private int countStretchableSpaces(int start, int end) { 116309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka int count = 0; 116409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) { 116509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka count++; 116609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 116709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka return count; 116809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 116909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 117009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace() 117109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka public static boolean isLineEndSpace(char ch) { 117209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka return ch == ' ' || ch == '\t' || ch == 0x1680 117309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka || (0x2000 <= ch && ch <= 0x200A && ch != 0x2007) 117409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka || ch == 0x205F || ch == 0x3000; 117509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 117609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 1177e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private static final int TAB_INCREMENT = 20; 1178e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt} 1179