TextLine.java revision 53145635e4fd724208e01db1ef6187a2212d6090
1e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/* 2e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Copyright (C) 2010 The Android Open Source Project 3e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 4e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Licensed under the Apache License, Version 2.0 (the "License"); 5e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * you may not use this file except in compliance with the License. 6e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * You may obtain a copy of the License at 7e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 8e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * http://www.apache.org/licenses/LICENSE-2.0 9e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 10e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Unless required by applicable law or agreed to in writing, software 11e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * distributed under the License is distributed on an "AS IS" BASIS, 12e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * See the License for the specific language governing permissions and 14e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * limitations under the License. 15e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 16e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 17e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltpackage android.text; 18e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 19538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport android.annotation.NonNull; 20538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport android.annotation.Nullable; 21e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Canvas; 22e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint; 23e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.graphics.Paint.FontMetricsInt; 24e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.Layout.Directions; 25c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Feltimport android.text.Layout.TabStops; 26e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.CharacterStyle; 27e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.MetricAffectingSpan; 28e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.text.style.ReplacementSpan; 29e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Feltimport android.util.Log; 30e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 313630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournaderimport com.android.internal.annotations.VisibleForTesting; 32dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunneimport com.android.internal.util.ArrayUtils; 33dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne 34538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournaderimport java.util.ArrayList; 35538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 36e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt/** 37e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Represents a line of styled text, for measuring in visual order and 38e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * for rendering. 39e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 40e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Get a new instance using obtain(), and when finished with it, return it 41e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * to the pool using recycle(). 42e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 43e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>Call set to prepare the instance for use, then either draw, measure, 44e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * metrics, or caretToLeftRightOf. 45e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 46e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @hide 47e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 483630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 493630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournaderpublic class TextLine { 50bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private static final boolean DEBUG = false; 51bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy 52e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private TextPaint mPaint; 53e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private CharSequence mText; 54e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private int mStart; 55e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private int mLen; 56e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private int mDir; 57e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private Directions mDirections; 58e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private boolean mHasTabs; 59c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt private TabStops mTabs; 60e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private char[] mChars; 61e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private boolean mCharsValid; 62e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private Spanned mSpanned; 63beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka private PrecomputedText mComputed; 6409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 6509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // Additional width of whitespace for justification. This value is per whitespace, thus 6609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // the line width will increase by mAddedWidth x (number of stretchable whitespaces). 6709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka private float mAddedWidth; 68538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 69345cb03315a0813ec57e44f97fc3fa4af6b3c309Gilles Debunne private final TextPaint mWorkPaint = new TextPaint(); 70538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader private final TextPaint mActivePaint = new TextPaint(); 71c1f44830809f0a8526855f13822702ea756214faGilles Debunne private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet = 72c1f44830809f0a8526855f13822702ea756214faGilles Debunne new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class); 73c1f44830809f0a8526855f13822702ea756214faGilles Debunne private final SpanSet<CharacterStyle> mCharacterStyleSpanSet = 74c1f44830809f0a8526855f13822702ea756214faGilles Debunne new SpanSet<CharacterStyle>(CharacterStyle.class); 75c1f44830809f0a8526855f13822702ea756214faGilles Debunne private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet = 76c1f44830809f0a8526855f13822702ea756214faGilles Debunne new SpanSet<ReplacementSpan>(ReplacementSpan.class); 77e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 781378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader private final DecorationInfo mDecorationInfo = new DecorationInfo(); 79a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir private final ArrayList<DecorationInfo> mDecorations = new ArrayList<>(); 80538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 81bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private static final TextLine[] sCached = new TextLine[3]; 82e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 83e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 84e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns a new TextLine from the shared pool. 85e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 86e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return an uninitialized TextLine 87e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 883630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 893630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader public static TextLine obtain() { 90e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt TextLine tl; 91bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy synchronized (sCached) { 92bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy for (int i = sCached.length; --i >= 0;) { 93bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy if (sCached[i] != null) { 94bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy tl = sCached[i]; 95bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy sCached[i] = null; 96e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return tl; 97e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 98e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 99e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 100e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt tl = new TextLine(); 101bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy if (DEBUG) { 102bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy Log.v("TLINE", "new: " + tl); 103bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy } 104e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return tl; 105e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 106e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 107e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 108e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Puts a TextLine back into the shared pool. Do not use this TextLine once 109e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * it has been returned. 110e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param tl the textLine 111e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return null, as a convenience from clearing references to the provided 112e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * TextLine 113e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 1143630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 1153630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader public static TextLine recycle(TextLine tl) { 116e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt tl.mText = null; 117e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt tl.mPaint = null; 118e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt tl.mDirections = null; 119893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov tl.mSpanned = null; 120893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov tl.mTabs = null; 121893d6fe48d37f71e683f722457bea646994a10bfSvet Ganov tl.mChars = null; 122beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka tl.mComputed = null; 123c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne 124c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne tl.mMetricAffectingSpanSpanSet.recycle(); 125c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne tl.mCharacterStyleSpanSet.recycle(); 126c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne tl.mReplacementSpanSpanSet.recycle(); 127c3fb7a11ad72c5e51ff93e1ad6958f843af0d5b1Gilles Debunne 128bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy synchronized(sCached) { 129bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy for (int i = 0; i < sCached.length; ++i) { 130bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy if (sCached[i] == null) { 131bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy sCached[i] = tl; 132f902d7bc49797ec277b4576c921dfffa15d741ddGilles Debunne break; 133e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 134e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 135e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 136e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return null; 137e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 138e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 139e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 140e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Initializes a TextLine and prepares it for use. 141e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 142e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param paint the base paint for the line 143e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param text the text, can be Styled 144e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the start of the line relative to the text 145e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the limit of the line relative to the text 146e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param dir the paragraph direction of this line 147e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param directions the directions information of this line 148112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader * @param hasTabs true if the line might contain tabs 149c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt * @param tabStops the tabStops. Can be null. 150e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 1513630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 1524e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka public void set(TextPaint paint, CharSequence text, int start, int limit, int dir, 1534e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka Directions directions, boolean hasTabs, TabStops tabStops) { 154e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mPaint = paint; 155e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mText = text; 156e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mStart = start; 157e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mLen = limit - start; 158e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mDir = dir; 159e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mDirections = directions; 1608059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio if (mDirections == null) { 1618059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio throw new IllegalArgumentException("Directions cannot be null"); 1628059e0903e36cbb5cf8b5c5d5d653acc9bbc8402Fabrice Di Meglio } 163e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mHasTabs = hasTabs; 164e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mSpanned = null; 165e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 166e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean hasReplacement = false; 167e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (text instanceof Spanned) { 168e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt mSpanned = (Spanned) text; 169c1f44830809f0a8526855f13822702ea756214faGilles Debunne mReplacementSpanSpanSet.init(mSpanned, start, limit); 170c1f44830809f0a8526855f13822702ea756214faGilles Debunne hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0; 171e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 172e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 173beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka mComputed = null; 174beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka if (text instanceof PrecomputedText) { 17553145635e4fd724208e01db1ef6187a2212d6090Seigo Nonaka // Here, no need to check line break strategy or hyphenation frequency since there is no 17653145635e4fd724208e01db1ef6187a2212d6090Seigo Nonaka // line break concept here. 177beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka mComputed = (PrecomputedText) text; 17853145635e4fd724208e01db1ef6187a2212d6090Seigo Nonaka if (!mComputed.getParams().getTextPaint().equalsForTextMeasurement(paint)) { 17953145635e4fd724208e01db1ef6187a2212d6090Seigo Nonaka mComputed = null; 18053145635e4fd724208e01db1ef6187a2212d6090Seigo Nonaka } 1814e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka } 1824e90fa262d57c1c1ee72166e2ddfce391696ca24Seigo Nonaka 1831e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT; 184e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 185e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mCharsValid) { 186e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mChars == null || mChars.length < mLen) { 187776abc24cdd18610232a50b997cce3cffa74609bAdam Lesinski mChars = ArrayUtils.newUnpaddedCharArray(mLen); 188e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 189e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt TextUtils.getChars(text, start, limit, mChars, 0); 1900c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (hasReplacement) { 1910c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // Handle these all at once so we don't have to do it as we go. 1920c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // Replace the first character of each replacement run with the 1930c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // object-replacement character and the remainder with zero width 1940c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // non-break space aka BOM. Cursor movement code skips these 1950c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // zero-width characters. 1960c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt char[] chars = mChars; 1970c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt for (int i = start, inext; i < limit; i = inext) { 198c1f44830809f0a8526855f13822702ea756214faGilles Debunne inext = mReplacementSpanSpanSet.getNextTransition(i, limit); 199c1f44830809f0a8526855f13822702ea756214faGilles Debunne if (mReplacementSpanSpanSet.hasSpansIntersecting(i, inext)) { 2001e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne // transition into a span 2010c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt chars[i - start] = '\ufffc'; 2020c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt for (int j = i - start + 1, e = inext - start; j < e; ++j) { 2030c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip 2040c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 2050c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 2060c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 2070c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 208e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 209c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt mTabs = tabStops; 21009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka mAddedWidth = 0; 21109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 21209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 21309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka /** 21409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka * Justify the line to the given width. 21509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka */ 2163630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 2173630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader public void justify(float justifyWidth) { 21809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka int end = mLen; 21909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) { 22009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka end--; 22109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 22209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka final int spaces = countStretchableSpaces(0, end); 22309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka if (spaces == 0) { 22409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // There are no stretchable spaces, so we can't help the justification by adding any 22509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // width. 22609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka return; 22709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 22809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka final float width = Math.abs(measure(end, false, null)); 22909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka mAddedWidth = (justifyWidth - width) / spaces; 230e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 231e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 232e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 233e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Renders the TextLine. 234e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 235e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas to render on 236e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the leading margin position 237e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 238e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 239e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 240e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 241e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt void draw(Canvas c, float x, int top, int y, int bottom) { 242e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (!mHasTabs) { 243e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) { 244bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy drawRun(c, 0, mLen, false, x, top, y, bottom, false); 245e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return; 246e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 247e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) { 248bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy drawRun(c, 0, mLen, true, x, top, y, bottom, false); 249e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return; 250e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 251e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 252e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 253e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float h = 0; 254e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int[] runs = mDirections.mDirections; 255e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 256e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int lastRunIndex = runs.length - 2; 257e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (int i = 0; i < runs.length; i += 2) { 258e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runStart = runs[i]; 259e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK); 260e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runLimit > mLen) { 261e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = mLen; 262e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 263e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0; 264e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 265e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int segstart = runStart; 266e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { 267e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int codept = 0; 268e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mHasTabs && j < runLimit) { 269e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt codept = mChars[j]; 270112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) { 271e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt codept = Character.codePointAt(mChars, j); 272112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (codept > 0xFFFF) { 273e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt ++j; 274e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt continue; 275e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 276e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 277e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 278e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 279112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (j == runLimit || codept == '\t') { 280bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom, 281e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt i != lastRunIndex || j != mLen); 282e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 283e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (codept == '\t') { 284e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt h = mDir * nextTab(h * mDir); 285e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 286e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt segstart = j + 1; 287e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 288e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 289e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 290e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 291e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 292e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 293e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns metrics information for the entire line. 294e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 295e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives font metrics information, can be null 296e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the line 297e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 2983630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 2993630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader public float metrics(FontMetricsInt fmi) { 300e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return measure(mLen, false, fmi); 301e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 302e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 303e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 304e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns information about a position on the line. 305e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 306e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param offset the line-relative character offset, between 0 and the 307e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * line length, inclusive 308e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param trailing true to measure the trailing edge of the character 309e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * before offset, false to measure the leading edge of the character 310e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * at offset. 311e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information about the requested 312e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * character, can be null. 313e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed offset from the leading margin to the requested 314e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * character edge. 315e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 316e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float measure(int offset, boolean trailing, FontMetricsInt fmi) { 317e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int target = trailing ? offset - 1 : offset; 318e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (target < 0) { 319e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return 0; 320e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 321e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 322e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float h = 0; 323e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 324e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (!mHasTabs) { 325e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) { 326bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return measureRun(0, offset, mLen, false, fmi); 327e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 328e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) { 329bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return measureRun(0, offset, mLen, true, fmi); 330e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 331e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 332e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 333e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt char[] chars = mChars; 334e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int[] runs = mDirections.mDirections; 335e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (int i = 0; i < runs.length; i += 2) { 336e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runStart = runs[i]; 337e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK); 338e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runLimit > mLen) { 339e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = mLen; 340e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 341e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0; 342e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 343e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int segstart = runStart; 344e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { 345e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int codept = 0; 346e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mHasTabs && j < runLimit) { 347e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt codept = chars[j]; 348112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) { 349e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt codept = Character.codePointAt(chars, j); 350112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (codept > 0xFFFF) { 351e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt ++j; 352e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt continue; 353e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 354e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 355e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 356e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 357112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader if (j == runLimit || codept == '\t') { 358e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean inSegment = target >= segstart && target < j; 359e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 360e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; 361e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (inSegment && advance) { 362a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir return h + measureRun(segstart, offset, j, runIsRtl, fmi); 363e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 364e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 365bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy float w = measureRun(segstart, j, j, runIsRtl, fmi); 366e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt h += advance ? w : -w; 367e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 368e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (inSegment) { 369a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir return h + measureRun(segstart, offset, j, runIsRtl, null); 370e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 371e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 372e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (codept == '\t') { 373e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (offset == j) { 374e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return h; 375e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 376e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt h = mDir * nextTab(h * mDir); 377e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (target == j) { 378e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return h; 379e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 380e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 381e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 382e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt segstart = j + 1; 383e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 384e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 385e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 386e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 387e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return h; 388e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 389e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 390e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 391e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Draws a unidirectional (but possibly multi-styled) run of text. 392e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 393bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy * 394e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas to draw on 395e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the line-relative start 396e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the line-relative limit 397e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 398e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the position of the run that is closest to the leading margin 399e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 400e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 401e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 402e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param needWidth true if the width value is required. 403e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the run, based on the paragraph direction. 404e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Only valid if needWidth is true. 405e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 406bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private float drawRun(Canvas c, int start, 407e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int limit, boolean runIsRtl, float x, int top, int y, int bottom, 408e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean needWidth) { 409e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 410e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { 411bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy float w = -measureRun(start, limit, limit, runIsRtl, null); 412bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy handleRun(start, limit, limit, runIsRtl, c, x + w, top, 4130c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt y, bottom, null, false); 414e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return w; 415e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 416e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 417bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return handleRun(start, limit, limit, runIsRtl, c, x, top, 4180c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt y, bottom, null, needWidth); 419e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 420e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 421e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 422e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Measures a unidirectional (but possibly multi-styled) run of text. 423e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 424bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy * 425e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the line-relative start of the run 426e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param offset the offset to measure to, between start and limit inclusive 427e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the line-relative limit of the run 428e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 429e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information about the requested 430e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * run, can be null. 431e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width from the start of the run to the leading edge 432e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * of the character at offset, based on the run (not paragraph) direction 433e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 434bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private float measureRun(int start, int offset, int limit, boolean runIsRtl, 435bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy FontMetricsInt fmi) { 436bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true); 437e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 438e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 439e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 440e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Walk the cursor through this line, skipping conjuncts and 441e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * zero-width characters. 442e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 443e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * <p>This function cannot properly walk the cursor off the ends of the line 444e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * since it does not know about any shaping on the previous/following line 445e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * that might affect the cursor position. Callers must either avoid these 446e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * situations or handle the result specially. 447e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 448e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param cursor the starting position of the cursor, between 0 and the 449e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * length of the line, inclusive 450e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param toLeft true if the caret is moving to the left. 451e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the new offset. If it is less than 0 or greater than the length 452e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * of the line, the previous/following line should be examined to get the 453e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * actual offset. 454e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 455e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int getOffsetToLeftRightOf(int cursor, boolean toLeft) { 456e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // 1) The caret marks the leading edge of a character. The character 457e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // logically before it might be on a different level, and the active caret 458e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // position is on the character at the lower level. If that character 459e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // was the previous character, the caret is on its trailing edge. 460e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // 2) Take this character/edge and move it in the indicated direction. 461e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // This gives you a new character and a new edge. 462e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // 3) This position is between two visually adjacent characters. One of 463e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // these might be at a lower level. The active position is on the 464e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // character at the lower level. 465e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // 4) If the active position is on the trailing edge of the character, 466e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // the new caret position is the following logical character, else it 467e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // is the character. 468e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 469e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int lineStart = 0; 470e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int lineEnd = mLen; 471e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean paraIsRtl = mDir == -1; 472e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int[] runs = mDirections.mDirections; 473e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 474e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1; 475e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean trailing = false; 476e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 477e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor == lineStart) { 478e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runIndex = -2; 479e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } else if (cursor == lineEnd) { 480e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runIndex = runs.length; 481e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } else { 482e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // First, get information about the run containing the character with 483e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // the active caret. 484e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (runIndex = 0; runIndex < runs.length; runIndex += 2) { 485e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runStart = lineStart + runs[runIndex]; 486e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor >= runStart) { 487e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK); 488e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runLimit > lineEnd) { 489e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = lineEnd; 490e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 491e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor < runLimit) { 492e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) & 493e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt Layout.RUN_LEVEL_MASK; 494e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor == runStart) { 495e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // The caret is on a run boundary, see if we should 496e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // use the position on the trailing edge of the previous 497e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // logical character instead. 498e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit; 499e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int pos = cursor - 1; 500e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) { 501e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt prevRunStart = lineStart + runs[prevRunIndex]; 502e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (pos >= prevRunStart) { 503e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt prevRunLimit = prevRunStart + 504e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK); 505e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (prevRunLimit > lineEnd) { 506e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt prevRunLimit = lineEnd; 507e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 508e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (pos < prevRunLimit) { 509e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) 510e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt & Layout.RUN_LEVEL_MASK; 511e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (prevRunLevel < runLevel) { 512e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Start from logically previous character. 513e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runIndex = prevRunIndex; 514e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLevel = prevRunLevel; 515e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runStart = prevRunStart; 516e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLimit = prevRunLimit; 517e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt trailing = true; 518e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 519e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 520e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 521e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 522e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 523e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 524e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 525e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 526e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 527e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 528e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 529e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // caret might be == lineEnd. This is generally a space or paragraph 530e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // separator and has an associated run, but might be the end of 531e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // text, in which case it doesn't. If that happens, we ran off the 532e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // end of the run list, and runIndex == runs.length. In this case, 533e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // we are at a run boundary so we skip the below test. 534e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runIndex != runs.length) { 535e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean runIsRtl = (runLevel & 0x1) != 0; 536e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean advance = toLeft == runIsRtl; 537e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (cursor != (advance ? runLimit : runStart) || advance != trailing) { 538e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Moving within or into the run, so we can move logically. 5390c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit, 5400c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt runIsRtl, cursor, advance); 541e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // If the new position is internal to the run, we're at the strong 542e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // position already so we're finished. 543e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret != (advance ? runLimit : runStart)) { 544e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return newCaret; 545e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 546e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 547e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 548e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 549e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 550e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // If newCaret is -1, we're starting at a run boundary and crossing 551e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // into another run. Otherwise we've arrived at a run boundary, and 552e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // need to figure out which character to attach to. Note we might 553e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // need to run this twice, if we cross a run boundary and end up at 554e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // another run boundary. 555e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt while (true) { 556e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean advance = toLeft == paraIsRtl; 557e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int otherRunIndex = runIndex + (advance ? 2 : -2); 558e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (otherRunIndex >= 0 && otherRunIndex < runs.length) { 559e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int otherRunStart = lineStart + runs[otherRunIndex]; 560e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int otherRunLimit = otherRunStart + 561e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK); 562e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (otherRunLimit > lineEnd) { 563e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt otherRunLimit = lineEnd; 564e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 565e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) & 566e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt Layout.RUN_LEVEL_MASK; 567e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt boolean otherRunIsRtl = (otherRunLevel & 1) != 0; 568e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 569e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt advance = toLeft == otherRunIsRtl; 570e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret == -1) { 5710c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart, 5720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt otherRunLimit, otherRunIsRtl, 573e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt advance ? otherRunStart : otherRunLimit, advance); 574e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret == (advance ? otherRunLimit : otherRunStart)) { 575e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Crossed and ended up at a new boundary, 576e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // repeat a second and final time. 577e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runIndex = otherRunIndex; 578e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt runLevel = otherRunLevel; 579e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt continue; 580e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 581e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 582e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 583e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 584e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // The new caret is at a boundary. 585e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (otherRunLevel < runLevel) { 586e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // The strong character is in the other run. 587e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt newCaret = advance ? otherRunStart : otherRunLimit; 588e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 589e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 590e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 591e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 592e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret == -1) { 593e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // We're walking off the end of the line. The paragraph 594e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // level is always equal to or lower than any internal level, so 595e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // the boundaries get the strong caret. 5960c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt newCaret = advance ? mLen + 1 : -1; 597e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 598e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 599e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 600e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Else we've arrived at the end of the line. That's a strong position. 601e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // We might have arrived here by crossing over a run with no internal 602e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // breaks and dropping out of the above loop before advancing one final 603e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // time, so reset the caret. 604e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Note, we use '<=' below to handle a situation where the only run 605e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // on the line is a counter-directional run. If we're not advancing, 606e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // we can end up at the 'lineEnd' position but the caret we want is at 607e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // the lineStart. 608e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (newCaret <= lineEnd) { 609e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt newCaret = advance ? lineEnd : lineStart; 610e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 611e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt break; 612e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 613e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 614e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return newCaret; 615e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 616e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 617e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 618e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns the next valid offset within this directional run, skipping 619e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * conjuncts and zero-width characters. This should not be called to walk 620cc3ec6cdb2b892eb29513e72d8b205acbe997b25Gilles Debunne * off the end of the line, since the returned values might not be valid 6210c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * on neighboring lines. If the returned offset is less than zero or 6220c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * greater than the line length, the offset should be recomputed on the 6230c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * preceding or following line, respectively. 624e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 625e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIndex the run index 6260c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param runStart the start of the run 6270c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param runLimit the limit of the run 6280c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param runIsRtl true if the run is right-to-left 629e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param offset the offset 630e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param after true if the new offset should logically follow the provided 631e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * offset 632e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the new offset 633e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 6340c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit, 6350c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt boolean runIsRtl, int offset, boolean after) { 636e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 6370c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (runIndex < 0 || offset == (after ? mLen : 0)) { 6380c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // Walking off end of line. Since we don't know 6390c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // what cursor positions are available on other lines, we can't 6400c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // return accurate values. These are a guess. 641e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (after) { 6420c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart; 6430c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6440c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart; 6450c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6460c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6470c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt TextPaint wp = mWorkPaint; 6480c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt wp.set(mPaint); 64909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka wp.setWordSpacing(mAddedWidth); 6500c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6510c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int spanStart = runStart; 6520c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int spanLimit; 6530c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (mSpanned == null) { 6540c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt spanLimit = runLimit; 6550c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } else { 6560c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int target = after ? offset + 1 : offset; 6570c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int limit = mStart + runLimit; 6580c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt while (true) { 6590c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit, 6600c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt MetricAffectingSpan.class) - mStart; 6610c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (spanLimit >= target) { 6620c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt break; 663e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 6640c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt spanStart = spanLimit; 6650c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6660c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6670c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart, 6680c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt mStart + spanLimit, MetricAffectingSpan.class); 6691e3ac18e7ad03e02819f3e1a89d6a80a2bb7645fGilles Debunne spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class); 6700c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6710c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (spans.length > 0) { 6720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt ReplacementSpan replacement = null; 6730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt for (int j = 0; j < spans.length; j++) { 6740c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt MetricAffectingSpan span = spans[j]; 6750c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (span instanceof ReplacementSpan) { 6760c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt replacement = (ReplacementSpan)span; 6770c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } else { 6780c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt span.updateMeasureState(wp); 6790c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6800c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 6810c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 6820c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (replacement != null) { 6830c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // If we have a replacement span, we're moving either to 6840c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // the start or end of this span. 6850c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return after ? spanLimit : spanStart; 686e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 687e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 688e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 689e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 690051910b9f998030dacb8a0722588cc715813fde1Raph Levien int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; 6910c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE; 6920c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (mCharsValid) { 6930c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart, 694051910b9f998030dacb8a0722588cc715813fde1Raph Levien dir, offset, cursorOpt); 6950c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } else { 6960c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt return wp.getTextRunCursor(mText, mStart + spanStart, 697051910b9f998030dacb8a0722588cc715813fde1Raph Levien mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart; 698e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 699e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 700e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 701e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 7020bb000931bb841e75903d655552d1626ae158707Gilles Debunne * @param wp 7030bb000931bb841e75903d655552d1626ae158707Gilles Debunne */ 7040bb000931bb841e75903d655552d1626ae158707Gilles Debunne private static void expandMetricsFromPaint(FontMetricsInt fmi, TextPaint wp) { 7050bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousTop = fmi.top; 7060bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousAscent = fmi.ascent; 7070bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousDescent = fmi.descent; 7080bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousBottom = fmi.bottom; 7090bb000931bb841e75903d655552d1626ae158707Gilles Debunne final int previousLeading = fmi.leading; 7100bb000931bb841e75903d655552d1626ae158707Gilles Debunne 7110bb000931bb841e75903d655552d1626ae158707Gilles Debunne wp.getFontMetricsInt(fmi); 7120bb000931bb841e75903d655552d1626ae158707Gilles Debunne 7138a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom, 7148a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousLeading); 7158a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio } 7168a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 7178a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent, 7188a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousDescent, int previousBottom, int previousLeading) { 7190bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.top = Math.min(fmi.top, previousTop); 7200bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.ascent = Math.min(fmi.ascent, previousAscent); 7210bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.descent = Math.max(fmi.descent, previousDescent); 7220bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.bottom = Math.max(fmi.bottom, previousBottom); 7230bb000931bb841e75903d655552d1626ae158707Gilles Debunne fmi.leading = Math.max(fmi.leading, previousLeading); 7240bb000931bb841e75903d655552d1626ae158707Gilles Debunne } 7250bb000931bb841e75903d655552d1626ae158707Gilles Debunne 7261378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader private static void drawStroke(TextPaint wp, Canvas c, int color, float position, 7271378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader float thickness, float xleft, float xright, float baseline) { 7281378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final float strokeTop = baseline + wp.baselineShift + position; 729538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 730538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final int previousColor = wp.getColor(); 731538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final Paint.Style previousStyle = wp.getStyle(); 732538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final boolean previousAntiAlias = wp.isAntiAlias(); 733538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 734538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setStyle(Paint.Style.FILL); 735538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setAntiAlias(true); 736538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 737538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setColor(color); 7381378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader c.drawRect(xleft, strokeTop, xright, strokeTop + thickness, wp); 739538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 740538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setStyle(previousStyle); 741538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setColor(previousColor); 742538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader wp.setAntiAlias(previousAntiAlias); 743538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 744538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 745538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd, 746538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader boolean runIsRtl, int offset) { 747538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (mCharsValid) { 748538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset); 749538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else { 750538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final int delta = mStart; 751beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka if (mComputed == null) { 752783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka // TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text. 753783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka return wp.getRunAdvance(mText, delta + start, delta + end, 754783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka delta + contextStart, delta + contextEnd, runIsRtl, delta + offset); 755783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka } else { 756beafa1f9d2845ee9b5ca352087de03ed0afe7db7Seigo Nonaka return mComputed.getWidth(start + delta, end + delta); 757783f961d2fa6f916009844dafeaa08ffaf96a4d3Seigo Nonaka } 758538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 759538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 760538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 7610bb000931bb841e75903d655552d1626ae158707Gilles Debunne /** 762e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Utility function for measuring and rendering text. The text must 763112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader * not include a tab. 764e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 765e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param wp the working paint 766e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the start of the text 7670c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param end the end of the text 768e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 769e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas, can be null if rendering is not needed 770e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the edge of the run closest to the leading margin 771e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 772e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 773e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 774e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information, can be null 775e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param needWidth true if the width of the run is needed 776909c7bca570b6f50650d0872e2037389b29252e3Raph Levien * @param offset the offset for the purpose of measuring 7771378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader * @param decorations the list of locations and paremeters for drawing decorations 778e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the run based on the run direction; only 779e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * valid if needWidth is true 780e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 7810c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt private float handleText(TextPaint wp, int start, int end, 7820c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int contextStart, int contextEnd, boolean runIsRtl, 7830c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt Canvas c, float x, int top, int y, int bottom, 784538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader FontMetricsInt fmi, boolean needWidth, int offset, 7851378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader @Nullable ArrayList<DecorationInfo> decorations) { 786e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 78709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka wp.setWordSpacing(mAddedWidth); 788850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio // Get metrics first (even for empty strings or "0" width runs) 789850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio if (fmi != null) { 790850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio expandMetricsFromPaint(fmi, wp); 791850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio } 792e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 793850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio // No need to do anything if the run width is "0" 794538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (end == start) { 795850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio return 0f; 796850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio } 797850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio 798538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader float totalWidth = 0; 799850dffa01ba9111799f24800ae8550eca457d757Fabrice Di Meglio 8001378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final int numDecorations = decorations == null ? 0 : decorations.size(); 8011378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader if (needWidth || (c != null && (wp.bgColor != 0 || numDecorations != 0 || runIsRtl))) { 802538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); 803e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 804e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 805e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (c != null) { 806538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final float leftX, rightX; 807e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (runIsRtl) { 808538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader leftX = x - totalWidth; 809538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader rightX = x; 810538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else { 811538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader leftX = x; 812538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader rightX = x + totalWidth; 813e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 814e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 815e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (wp.bgColor != 0) { 816dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne int previousColor = wp.getColor(); 817dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne Paint.Style previousStyle = wp.getStyle(); 818dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne 819e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt wp.setColor(wp.bgColor); 820e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt wp.setStyle(Paint.Style.FILL); 821538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader c.drawRect(leftX, top, rightX, bottom, wp); 822e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 823dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne wp.setStyle(previousStyle); 824dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne wp.setColor(previousColor); 825dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne } 826dd8f5ed79c7baed35b3f04e4778aff7867653255Gilles Debunne 8271378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader if (numDecorations != 0) { 8281378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader for (int i = 0; i < numDecorations; i++) { 8291378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final DecorationInfo info = decorations.get(i); 8301378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader 8311378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final int decorationStart = Math.max(info.start, start); 8321378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final int decorationEnd = Math.min(info.end, offset); 8331378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader float decorationStartAdvance = getRunAdvance( 8341378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader wp, start, end, contextStart, contextEnd, runIsRtl, decorationStart); 8351378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader float decorationEndAdvance = getRunAdvance( 8361378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader wp, start, end, contextStart, contextEnd, runIsRtl, decorationEnd); 8371378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final float decorationXLeft, decorationXRight; 838538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (runIsRtl) { 8391378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader decorationXLeft = rightX - decorationEndAdvance; 8401378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader decorationXRight = rightX - decorationStartAdvance; 841538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else { 8421378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader decorationXLeft = leftX + decorationStartAdvance; 8431378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader decorationXRight = leftX + decorationEndAdvance; 844538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 845702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir 846538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // Theoretically, there could be cases where both Paint's and TextPaint's 847538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // setUnderLineText() are called. For backward compatibility, we need to draw 848538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // both underlines, the one with custom color first. 849538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (info.underlineColor != 0) { 8501378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader drawStroke(wp, c, info.underlineColor, wp.getUnderlinePosition(), 8511378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader info.underlineThickness, decorationXLeft, decorationXRight, y); 852538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 853538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (info.isUnderlineText) { 854ca8a04a36640eb227a556ad9ced925c48ced2495Roozbeh Pournader final float thickness = 855a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir Math.max(wp.getUnderlineThickness(), 1.0f); 8561378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness, 8571378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader decorationXLeft, decorationXRight, y); 8581378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader } 8591378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader 8601378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader if (info.isStrikeThruText) { 8611378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final float thickness = 862a273a70ecbb0d1aaf9aeceec7aa91591290a4871Siyamed Sinir Math.max(wp.getStrikeThruThickness(), 1.0f); 8631378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness, 8641378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader decorationXLeft, decorationXRight, y); 865538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 866538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 867e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 868e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 869da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl, 870538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader leftX, y + wp.baselineShift); 871e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 872e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 873538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader return runIsRtl ? -totalWidth : totalWidth; 874e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 875e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 876e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 877e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Utility function for measuring and rendering a replacement. 878e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 879bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy * 880e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param replacement the replacement 881e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param wp the work paint 882e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the start of the run 883e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the limit of the run 884e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 885e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas, can be null if not rendering 886e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the edge of the replacement closest to the leading margin 887e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 888e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 889e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 890e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information, can be null 891e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param needWidth true if the width of the replacement is needed 892e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the run based on the run direction; only 893e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * valid if needWidth is true 894e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 895e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private float handleReplacement(ReplacementSpan replacement, TextPaint wp, 896bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy int start, int limit, boolean runIsRtl, Canvas c, 897e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float x, int top, int y, int bottom, FontMetricsInt fmi, 8980c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt boolean needWidth) { 899e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 900e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float ret = 0; 901e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 9020c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int textStart = mStart + start; 9030c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int textLimit = mStart + limit; 904e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 9050c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (needWidth || (c != null && runIsRtl)) { 9068a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousTop = 0; 9078a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousAscent = 0; 9088a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousDescent = 0; 9098a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousBottom = 0; 9108a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio int previousLeading = 0; 9118a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 9128a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio boolean needUpdateMetrics = (fmi != null); 9138a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 9148a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio if (needUpdateMetrics) { 9158a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousTop = fmi.top; 9168a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousAscent = fmi.ascent; 9178a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousDescent = fmi.descent; 9188a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousBottom = fmi.bottom; 9198a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousLeading = fmi.leading; 9208a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio } 9218a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 9220c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt ret = replacement.getSize(wp, mText, textStart, textLimit, fmi); 9238a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio 9248a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio if (needUpdateMetrics) { 9258a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom, 9268a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio previousLeading); 9278a5137a5aeba39cbc2c57c83ef79241b446d0cb7Fabrice Di Meglio } 9280c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt } 929e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 9300c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (c != null) { 9310c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt if (runIsRtl) { 9320c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt x -= ret; 933e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 9340c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt replacement.draw(c, mText, textStart, textLimit, 9350c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt x, top, y, bottom, wp); 936e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 937e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 938e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt return runIsRtl ? -ret : ret; 939e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 940945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 94146c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader private int adjustHyphenEdit(int start, int limit, int hyphenEdit) { 94246c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader int result = hyphenEdit; 94346c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader // Only draw hyphens on first or last run in line. Disable them otherwise. 94446c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader if (start > 0) { // not the first run 94546c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader result &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE; 94646c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader } 94746c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader if (limit < mLen) { // not the last run 94846c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader result &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE; 94946c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader } 95046c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader return result; 95146c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader } 95246c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader 9531378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader private static final class DecorationInfo { 9541378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader public boolean isStrikeThruText; 955538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public boolean isUnderlineText; 956538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public int underlineColor; 957538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public float underlineThickness; 958538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public int start = -1; 959538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader public int end = -1; 960538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 9611378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader public boolean hasDecoration() { 9621378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader return isStrikeThruText || isUnderlineText || underlineColor != 0; 963538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 964538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 965538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // Copies the info, but not the start and end range. 9661378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader public DecorationInfo copyInfo() { 9671378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final DecorationInfo copy = new DecorationInfo(); 9681378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader copy.isStrikeThruText = isStrikeThruText; 969538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.isUnderlineText = isUnderlineText; 970538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.underlineColor = underlineColor; 971538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.underlineThickness = underlineThickness; 972538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader return copy; 973538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 974538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 975538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 9761378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader private void extractDecorationInfo(@NonNull TextPaint paint, @NonNull DecorationInfo info) { 9771378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader info.isStrikeThruText = paint.isStrikeThruText(); 9781378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader if (info.isStrikeThruText) { 9791378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader paint.setStrikeThruText(false); 9801378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader } 981538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader info.isUnderlineText = paint.isUnderlineText(); 982538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (info.isUnderlineText) { 983538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader paint.setUnderlineText(false); 984538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 985538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader info.underlineColor = paint.underlineColor; 986538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader info.underlineThickness = paint.underlineThickness; 987538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader paint.setUnderlineText(0, 0.0f); 988538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 989538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 990e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 991e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Utility function for handling a unidirectional run. The run must not 992112d9c7f116bec0a52badde81bd778e59e88cb63Roozbeh Pournader * contain tabs but can contain styles. 993e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 994bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy * 995e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param start the line-relative start of the run 9960c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param measureLimit the offset to measure to, between start and limit inclusive 997e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param limit the limit of the run 998e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param runIsRtl true if the run is right-to-left 999e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas, can be null 1000e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the end of the run closest to the leading margin 1001e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param top the top of the line 1002e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline 1003e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param bottom the bottom of the line 1004e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param fmi receives metrics information, can be null 1005e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param needWidth true if the width is required 1006e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the signed width of the run based on the run direction; only 1007e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * valid if needWidth is true 1008e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 1009bc7cdb6783d059249133b1c0baf52c305c6b4a33Romain Guy private float handleRun(int start, int measureLimit, 1010e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt int limit, boolean runIsRtl, Canvas c, float x, int top, int y, 10110c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int bottom, FontMetricsInt fmi, boolean needWidth) { 1012e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 10139f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir if (measureLimit < start || measureLimit > limit) { 10149f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of " 10159f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir + "start (" + start + ") and limit (" + limit + ") bounds"); 10169f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir } 10179f3958cc2ba3da1406caac64650620d226bf8562Siyamed Sinir 1018f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne // Case of an empty line, make sure we update fmi according to mPaint 1019f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne if (start == measureLimit) { 1020538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final TextPaint wp = mWorkPaint; 1021f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne wp.set(mPaint); 102215c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio if (fmi != null) { 102315c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio expandMetricsFromPaint(fmi, wp); 102415c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio } 102515c097a1c23105cdc0dd66dd5605ff35467d7118Fabrice Di Meglio return 0f; 1026f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne } 1027f483e514d4ed3b93cc5ba22beb9c85efcda75535Gilles Debunne 102808836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader final boolean needsSpanMeasurement; 1029945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne if (mSpanned == null) { 103008836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader needsSpanMeasurement = false; 103108836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader } else { 103208836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit); 103308836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit); 103408836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader needsSpanMeasurement = mMetricAffectingSpanSpanSet.numberOfSpans != 0 103508836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader || mCharacterStyleSpanSet.numberOfSpans != 0; 103608836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader } 103708836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader 103808836d4ca5e1dab422575f1f0a1ad617a59e6ba0Roozbeh Pournader if (!needsSpanMeasurement) { 1039538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final TextPaint wp = mWorkPaint; 1040945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne wp.set(mPaint); 104146c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader wp.setHyphenEdit(adjustHyphenEdit(start, limit, wp.getHyphenEdit())); 1042909c7bca570b6f50650d0872e2037389b29252e3Raph Levien return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top, 1043538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader y, bottom, fmi, needWidth, measureLimit, null); 1044945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne } 1045945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 1046e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // Shaping needs to take into account context up to metric boundaries, 1047e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt // but rendering needs to take into account character style boundaries. 10480c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // So we iterate through metric runs to get metric bounds, 10490c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // then within each metric run iterate through character style runs 10500c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt // for the run bounds. 1051945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne final float originalX = x; 10520c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt for (int i = start, inext; i < measureLimit; i = inext) { 1053538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final TextPaint wp = mWorkPaint; 1054e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt wp.set(mPaint); 1055e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1056c1f44830809f0a8526855f13822702ea756214faGilles Debunne inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) - 1057c1f44830809f0a8526855f13822702ea756214faGilles Debunne mStart; 1058945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne int mlimit = Math.min(inext, measureLimit); 1059945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 1060945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne ReplacementSpan replacement = null; 1061945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 1062c1f44830809f0a8526855f13822702ea756214faGilles Debunne for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) { 1063945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT 1064945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne // empty by construction. This special case in getSpans() explains the >= & <= tests 1065c1f44830809f0a8526855f13822702ea756214faGilles Debunne if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) || 1066c1f44830809f0a8526855f13822702ea756214faGilles Debunne (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue; 1067538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j]; 1068945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne if (span instanceof ReplacementSpan) { 1069945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne replacement = (ReplacementSpan)span; 1070945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne } else { 1071945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne // We might have a replacement that uses the draw 1072945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne // state, otherwise measure state would suffice. 1073945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne span.updateDrawState(wp); 1074e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1075e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1076e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1077945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne if (replacement != null) { 1078945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y, 1079945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne bottom, fmi, needWidth || mlimit < measureLimit); 1080945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne continue; 1081945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne } 1082945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne 1083538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final TextPaint activePaint = mActivePaint; 1084538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.set(mPaint); 1085538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader int activeStart = i; 1086538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader int activeEnd = mlimit; 10871378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final DecorationInfo decorationInfo = mDecorationInfo; 10881378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader mDecorations.clear(); 108942ef515d185d4fc038d602172789cc264f1d9960Raph Levien for (int j = i, jnext; j < mlimit; j = jnext) { 1090702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) - 1091702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir mStart; 109242ef515d185d4fc038d602172789cc264f1d9960Raph Levien 1093538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final int offset = Math.min(jnext, mlimit); 109442ef515d185d4fc038d602172789cc264f1d9960Raph Levien wp.set(mPaint); 109542ef515d185d4fc038d602172789cc264f1d9960Raph Levien for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) { 109642ef515d185d4fc038d602172789cc264f1d9960Raph Levien // Intentionally using >= and <= as explained above 1097702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) || 1098702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue; 10990c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt 1100538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader final CharacterStyle span = mCharacterStyleSpanSet.spans[k]; 1101702c9f92fcb1fbdfdca07a2627d5f75dbca7b557Siyamed Sinir span.updateDrawState(wp); 1102e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 110342ef515d185d4fc038d602172789cc264f1d9960Raph Levien 11041378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader extractDecorationInfo(wp, decorationInfo); 1105538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 1106538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader if (j == i) { 1107538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // First chunk of text. We can't handle it yet, since we may need to merge it 1108538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // with the next chunk. So we just save the TextPaint for future comparisons 1109538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // and use. 1110538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.set(wp); 1111538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else if (!wp.hasEqualAttributes(activePaint)) { 1112538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // The style of the present chunk of text is substantially different from the 1113538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // style of the previous chunk. We need to handle the active piece of text 1114538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // and restart with the present chunk. 1115538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.setHyphenEdit(adjustHyphenEdit( 1116538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activeStart, activeEnd, mPaint.getHyphenEdit())); 1117538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, 1118538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader top, y, bottom, fmi, needWidth || activeEnd < measureLimit, 11191378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader Math.min(activeEnd, mlimit), mDecorations); 1120538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader 1121538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activeStart = j; 1122538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.set(wp); 11231378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader mDecorations.clear(); 1124538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } else { 1125538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // The present TextPaint is substantially equal to the last TextPaint except 11261378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader // perhaps for decorations. We just need to expand the active piece of text to 1127538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // include the present chunk, which we always do anyway. We don't need to save 1128538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // wp to activePaint, since they are already equal. 1129538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 113046c6f4c5ea7846fee4e6ef40c035ef2bee1adcbbRoozbeh Pournader 1131538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activeEnd = jnext; 11321378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader if (decorationInfo.hasDecoration()) { 11331378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader final DecorationInfo copy = decorationInfo.copyInfo(); 1134538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.start = j; 1135538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader copy.end = jnext; 11361378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader mDecorations.add(copy); 1137538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader } 1138e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1139538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader // Handle the final piece of text. 1140538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activePaint.setHyphenEdit(adjustHyphenEdit( 1141538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader activeStart, activeEnd, mPaint.getHyphenEdit())); 1142538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, 1143538de5bb3b3662c51c17cd9dd24e660c85e88b78Roozbeh Pournader top, y, bottom, fmi, needWidth || activeEnd < measureLimit, 11441378a9d72cb4a9f7a939f0a28a78fcfb87fb7879Roozbeh Pournader Math.min(activeEnd, mlimit), mDecorations); 1145e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1146e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1147945ee9b1661e60e0074d4f16f61fc147c728c6bfGilles Debunne return x - originalX; 1148e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1149e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1150e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 1151e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Render a text run with the set-up paint. 1152e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 1153e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param c the canvas 1154e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param wp the paint used to render the text 11550c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param start the start of the run 11560c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param end the end of the run 11570c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param contextStart the start of context for the run 11580c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt * @param contextEnd the end of the context for the run 1159da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio * @param runIsRtl true if the run is right-to-left 1160e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param x the x position of the left edge of the run 1161e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param y the baseline of the run 1162e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 11630c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt private void drawTextRun(Canvas c, TextPaint wp, int start, int end, 1164da12f389eb4be0c08ca3fa9ca7663f4977858df5Fabrice Di Meglio int contextStart, int contextEnd, boolean runIsRtl, float x, int y) { 1165e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1166e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt if (mCharsValid) { 11670c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int count = end - start; 11680c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int contextCount = contextEnd - contextStart; 11690c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt c.drawTextRun(mChars, start, count, contextStart, contextCount, 1170051910b9f998030dacb8a0722588cc715813fde1Raph Levien x, y, runIsRtl, wp); 1171e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } else { 11720c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt int delta = mStart; 11730c702b88c5d0d4380930b920f5be6e66dd95a0d8Doug Felt c.drawTextRun(mText, delta + start, delta + end, 1174051910b9f998030dacb8a0722588cc715813fde1Raph Levien delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp); 1175e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1176e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1177e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 1178e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt /** 1179e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * Returns the next tab position. 1180e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * 1181e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @param h the (unsigned) offset from the leading margin 1182e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt * @return the (unsigned) tab position after this offset 1183e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt */ 1184e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt float nextTab(float h) { 1185c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt if (mTabs != null) { 1186c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt return mTabs.nextTab(h); 1187e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1188c982f60e982c1d2df9f115ed9a5c3ef3643d0892Doug Felt return TabStops.nextDefaultStop(h, TAB_INCREMENT); 1189e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt } 1190e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt 119109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka private boolean isStretchableWhitespace(int ch) { 11923630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader // TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709). 11933630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader return ch == 0x0020; 119409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 119509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 119609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka /* Return the number of spaces in the text line, for the purpose of justification */ 119709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka private int countStretchableSpaces(int start, int end) { 119809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka int count = 0; 11993630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader for (int i = start; i < end; i++) { 12003630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart); 12013630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader if (isStretchableWhitespace(c)) { 12023630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader count++; 12033630d0861bc703cef843d5c196726235d72f1e80Roozbeh Pournader } 120409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 120509da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka return count; 120609da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 120709da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 120809da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace() 120909da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka public static boolean isLineEndSpace(char ch) { 121009da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka return ch == ' ' || ch == '\t' || ch == 0x1680 121109da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka || (0x2000 <= ch && ch <= 0x200A && ch != 0x2007) 121209da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka || ch == 0x205F || ch == 0x3000; 121309da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka } 121409da71a6dcfe07e0efdc35933322fba16091f555Seigo Nonaka 1215e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt private static final int TAB_INCREMENT = 20; 1216e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7aDoug Felt} 1217