diff options
| author | Seigo Nonaka <nona@google.com> | 2017-12-04 18:25:03 +0000 |
|---|---|---|
| committer | Seigo Nonaka <nona@google.com> | 2017-12-04 18:25:03 +0000 |
| commit | 4674e642a54e1db2028c4485383994fff2af559d (patch) | |
| tree | a00a4567d22add6c5cc14ba3ffc6f6328e81278e /core/java/android | |
| parent | 75492afb1b6566c889025e9ca0a15a0d0fe422aa (diff) | |
Revert "Refactor MeasuredText"
This reverts commit 75492afb1b6566c889025e9ca0a15a0d0fe422aa.
Reason for revert: 70146381
Change-Id: Ibb6433b5e02608326ef51cc16d8d5d8efa86beec
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/text/AutoGrowArray.java | 374 | ||||
| -rw-r--r-- | core/java/android/text/Layout.java | 29 | ||||
| -rw-r--r-- | core/java/android/text/MeasuredText.java | 629 | ||||
| -rw-r--r-- | core/java/android/text/StaticLayout.java | 110 | ||||
| -rw-r--r-- | core/java/android/text/TextUtils.java | 67 |
5 files changed, 311 insertions, 898 deletions
diff --git a/core/java/android/text/AutoGrowArray.java b/core/java/android/text/AutoGrowArray.java deleted file mode 100644 index e428377a0a31..000000000000 --- a/core/java/android/text/AutoGrowArray.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.text; - -import android.annotation.IntRange; -import android.annotation.NonNull; - -import com.android.internal.util.ArrayUtils; - -import libcore.util.EmptyArray; - -/** - * Implements a growing array of int primitives. - * - * These arrays are NOT thread safe. - * - * @hide - */ -public final class AutoGrowArray { - private static final int MIN_CAPACITY_INCREMENT = 12; - private static final int MAX_CAPACITY_TO_BE_KEPT = 10000; - - /** - * Returns next capacity size. - * - * The returned capacity is larger than requested capacity. - */ - private static int computeNewCapacity(int currentSize, int requested) { - final int targetCapacity = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2) - ? MIN_CAPACITY_INCREMENT : currentSize >> 1); - return targetCapacity > requested ? targetCapacity : requested; - } - - /** - * An auto growing byte array. - */ - public static class ByteArray { - - private @NonNull byte[] mValues; - private @IntRange(from = 0) int mSize; - - /** - * Creates an empty ByteArray with the default initial capacity. - */ - public ByteArray() { - this(10); - } - - /** - * Creates an empty ByteArray with the specified initial capacity. - */ - public ByteArray(@IntRange(from = 0) int initialCapacity) { - if (initialCapacity == 0) { - mValues = EmptyArray.BYTE; - } else { - mValues = ArrayUtils.newUnpaddedByteArray(initialCapacity); - } - mSize = 0; - } - - /** - * Changes the size of this ByteArray. If this ByteArray is shrinked, the backing array - * capacity is unchanged. - */ - public void resize(@IntRange(from = 0) int newSize) { - if (newSize > mValues.length) { - ensureCapacity(newSize - mSize); - } - mSize = newSize; - } - - /** - * Appends the specified value to the end of this array. - */ - public void append(byte value) { - ensureCapacity(1); - mValues[mSize++] = value; - } - - /** - * Ensures capacity to append at least <code>count</code> values. - */ - private void ensureCapacity(@IntRange int count) { - final int requestedSize = mSize + count; - if (requestedSize >= mValues.length) { - final int newCapacity = computeNewCapacity(mSize, requestedSize); - final byte[] newValues = ArrayUtils.newUnpaddedByteArray(newCapacity); - System.arraycopy(mValues, 0, newValues, 0, mSize); - mValues = newValues; - } - } - - /** - * Removes all values from this array. - */ - public void clear() { - mSize = 0; - } - - /** - * Removes all values from this array and release the internal array object if it is too - * large. - */ - public void clearWithReleasingLargeArray() { - clear(); - if (mValues.length > MAX_CAPACITY_TO_BE_KEPT) { - mValues = EmptyArray.BYTE; - } - } - - /** - * Returns the value at the specified position in this array. - */ - public byte get(@IntRange(from = 0) int index) { - return mValues[index]; - } - - /** - * Sets the value at the specified position in this array. - */ - public void set(@IntRange(from = 0) int index, byte value) { - mValues[index] = value; - } - - /** - * Returns the number of values in this array. - */ - public @IntRange(from = 0) int size() { - return mSize; - } - - /** - * Returns internal raw array. - * - * Note that this array may have larger size than you requested. - * Use size() instead for getting the actual array size. - */ - public @NonNull byte[] getRawArray() { - return mValues; - } - } - - /** - * An auto growing int array. - */ - public static class IntArray { - - private @NonNull int[] mValues; - private @IntRange(from = 0) int mSize; - - /** - * Creates an empty IntArray with the default initial capacity. - */ - public IntArray() { - this(10); - } - - /** - * Creates an empty IntArray with the specified initial capacity. - */ - public IntArray(@IntRange(from = 0) int initialCapacity) { - if (initialCapacity == 0) { - mValues = EmptyArray.INT; - } else { - mValues = ArrayUtils.newUnpaddedIntArray(initialCapacity); - } - mSize = 0; - } - - /** - * Changes the size of this IntArray. If this IntArray is shrinked, the backing array - * capacity is unchanged. - */ - public void resize(@IntRange(from = 0) int newSize) { - if (newSize > mValues.length) { - ensureCapacity(newSize - mSize); - } - mSize = newSize; - } - - /** - * Appends the specified value to the end of this array. - */ - public void append(int value) { - ensureCapacity(1); - mValues[mSize++] = value; - } - - /** - * Ensures capacity to append at least <code>count</code> values. - */ - private void ensureCapacity(@IntRange(from = 0) int count) { - final int requestedSize = mSize + count; - if (requestedSize >= mValues.length) { - final int newCapacity = computeNewCapacity(mSize, requestedSize); - final int[] newValues = ArrayUtils.newUnpaddedIntArray(newCapacity); - System.arraycopy(mValues, 0, newValues, 0, mSize); - mValues = newValues; - } - } - - /** - * Removes all values from this array. - */ - public void clear() { - mSize = 0; - } - - /** - * Removes all values from this array and release the internal array object if it is too - * large. - */ - public void clearWithReleasingLargeArray() { - clear(); - if (mValues.length > MAX_CAPACITY_TO_BE_KEPT) { - mValues = EmptyArray.INT; - } - } - - /** - * Returns the value at the specified position in this array. - */ - public int get(@IntRange(from = 0) int index) { - return mValues[index]; - } - - /** - * Sets the value at the specified position in this array. - */ - public void set(@IntRange(from = 0) int index, int value) { - mValues[index] = value; - } - - /** - * Returns the number of values in this array. - */ - public @IntRange(from = 0) int size() { - return mSize; - } - - /** - * Returns internal raw array. - * - * Note that this array may have larger size than you requested. - * Use size() instead for getting the actual array size. - */ - public @NonNull int[] getRawArray() { - return mValues; - } - } - - /** - * An auto growing float array. - */ - public static class FloatArray { - - private @NonNull float[] mValues; - private @IntRange(from = 0) int mSize; - - /** - * Creates an empty FloatArray with the default initial capacity. - */ - public FloatArray() { - this(10); - } - - /** - * Creates an empty FloatArray with the specified initial capacity. - */ - public FloatArray(@IntRange(from = 0) int initialCapacity) { - if (initialCapacity == 0) { - mValues = EmptyArray.FLOAT; - } else { - mValues = ArrayUtils.newUnpaddedFloatArray(initialCapacity); - } - mSize = 0; - } - - /** - * Changes the size of this FloatArray. If this FloatArray is shrinked, the backing array - * capacity is unchanged. - */ - public void resize(@IntRange(from = 0) int newSize) { - if (newSize > mValues.length) { - ensureCapacity(newSize - mSize); - } - mSize = newSize; - } - - /** - * Appends the specified value to the end of this array. - */ - public void append(float value) { - ensureCapacity(1); - mValues[mSize++] = value; - } - - /** - * Ensures capacity to append at least <code>count</code> values. - */ - private void ensureCapacity(int count) { - final int requestedSize = mSize + count; - if (requestedSize >= mValues.length) { - final int newCapacity = computeNewCapacity(mSize, requestedSize); - final float[] newValues = ArrayUtils.newUnpaddedFloatArray(newCapacity); - System.arraycopy(mValues, 0, newValues, 0, mSize); - mValues = newValues; - } - } - - /** - * Removes all values from this array. - */ - public void clear() { - mSize = 0; - } - - /** - * Removes all values from this array and release the internal array object if it is too - * large. - */ - public void clearWithReleasingLargeArray() { - clear(); - if (mValues.length > MAX_CAPACITY_TO_BE_KEPT) { - mValues = EmptyArray.FLOAT; - } - } - - /** - * Returns the value at the specified position in this array. - */ - public float get(@IntRange(from = 0) int index) { - return mValues[index]; - } - - /** - * Sets the value at the specified position in this array. - */ - public void set(@IntRange(from = 0) int index, float value) { - mValues[index] = value; - } - - /** - * Returns the number of values in this array. - */ - public @IntRange(from = 0) int size() { - return mSize; - } - - /** - * Returns internal raw array. - * - * Note that this array may have larger size than you requested. - * Use size() instead for getting the actual array size. - */ - public @NonNull float[] getRawArray() { - return mValues; - } - } -} diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 2a693a1841e6..4d2a9629c83a 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1907,14 +1907,22 @@ public abstract class Layout { private static float measurePara(TextPaint paint, CharSequence text, int start, int end, TextDirectionHeuristic textDir) { - MeasuredText mt = null; + MeasuredText mt = MeasuredText.obtain(); TextLine tl = TextLine.obtain(); try { - mt = MeasuredText.buildForBidi(text, start, end, textDir, mt); - final char[] chars = mt.getChars(); - final int len = chars.length; - final Directions directions = mt.getDirections(0, len); - final int dir = mt.getParagraphDir(); + mt.setPara(text, start, end, textDir); + Directions directions; + int dir; + if (mt.mEasy) { + directions = DIRS_ALL_LEFT_TO_RIGHT; + dir = Layout.DIR_LEFT_TO_RIGHT; + } else { + directions = AndroidBidi.directions(mt.mDir, mt.mLevels, + 0, mt.mChars, 0, mt.mLen); + dir = mt.mDir; + } + char[] chars = mt.mChars; + int len = mt.mLen; boolean hasTabs = false; TabStops tabStops = null; // leading margins should be taken into account when measuring a paragraph @@ -1947,9 +1955,7 @@ public abstract class Layout { return margin + Math.abs(tl.metrics(null)); } finally { TextLine.recycle(tl); - if (mt != null) { - mt.recycle(); - } + MeasuredText.recycle(mt); } } @@ -2266,11 +2272,6 @@ public abstract class Layout { private SpanSet<LineBackgroundSpan> mLineBackgroundSpans; private int mJustificationMode; - /** @hide */ - @IntDef({DIR_LEFT_TO_RIGHT, DIR_RIGHT_TO_LEFT}) - @Retention(RetentionPolicy.SOURCE) - public @interface Direction {} - public static final int DIR_LEFT_TO_RIGHT = 1; public static final int DIR_RIGHT_TO_LEFT = -1; diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index ca311763b7bb..3d9fba71db74 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -16,384 +16,125 @@ package android.text; -import android.annotation.FloatRange; -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.Paint; -import android.text.AutoGrowArray.ByteArray; -import android.text.AutoGrowArray.FloatArray; -import android.text.AutoGrowArray.IntArray; -import android.text.Layout.Directions; import android.text.style.MetricAffectingSpan; import android.text.style.ReplacementSpan; -import android.util.Pools.SynchronizedPool; +import android.util.Log; -import java.util.Arrays; +import com.android.internal.util.ArrayUtils; /** - * MeasuredText provides text information for rendering purpose. - * - * The first motivation of this class is identify the text directions and retrieving individual - * character widths. However retrieving character widths is slower than identifying text directions. - * Thus, this class provides several builder methods for specific purposes. - * - * - buildForBidi: - * Compute only text directions. - * - buildForMeasurement: - * Compute text direction and all character widths. - * - buildForStaticLayout: - * This is bit special. StaticLayout also needs to know text direction and character widths for - * line breaking, but all things are done in native code. Similarly, text measurement is done - * in native code. So instead of storing result to Java array, this keeps the result in native - * code since there is no good reason to move the results to Java layer. - * - * In addition to the character widths, some additional information is computed for each purposes, - * e.g. whole text length for measurement or font metrics for static layout. - * - * MeasuredText is NOT a thread safe object. * @hide */ class MeasuredText { - private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; - - private MeasuredText() {} // Use build static functions instead. - - private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1); - - private static @NonNull MeasuredText obtain() { // Use build static functions instead. - final MeasuredText mt = sPool.acquire(); - return mt != null ? mt : new MeasuredText(); - } - - /** - * Recycle the MeasuredText. - * - * Do not call any methods after you call this method. - */ - public void recycle() { - release(); - sPool.release(this); - } - - // The casted original text. - // - // This may be null if the passed text is not a Spanned. - private @Nullable Spanned mSpanned; - - // The start offset of the target range in the original text (mSpanned); - private @IntRange(from = 0) int mTextStart; - - // The length of the target range in the original text. - private @IntRange(from = 0) int mTextLength; - - // The copied character buffer for measuring text. - // - // The length of this array is mTextLength. - private @Nullable char[] mCopiedBuffer; - - // The whole paragraph direction. - private @Layout.Direction int mParaDir; - - // True if the text is LTR direction and doesn't contain any bidi characters. - private boolean mLtrWithoutBidi; - - // The bidi level for individual characters. - // - // This is empty if mLtrWithoutBidi is true. - private @NonNull ByteArray mLevels = new ByteArray(); - - // The whole width of the text. - // See getWholeWidth comments. - private @FloatRange(from = 0.0f) float mWholeWidth; - - // Individual characters' widths. - // See getWidths comments. - private @Nullable FloatArray mWidths = new FloatArray(); - - // The span end positions. - // See getSpanEndCache comments. - private @Nullable IntArray mSpanEndCache = new IntArray(4); - - // The font metrics. - // See getFontMetrics comments. - private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); - - // Following two objects are for avoiding object allocation. - private @NonNull TextPaint mCachedPaint = new TextPaint(); - private @Nullable Paint.FontMetricsInt mCachedFm; - - /** - * Releases internal buffers. - */ - public void release() { - reset(); - mLevels.clearWithReleasingLargeArray(); - mWidths.clearWithReleasingLargeArray(); - mFontMetrics.clearWithReleasingLargeArray(); - mSpanEndCache.clearWithReleasingLargeArray(); - } - - /** - * Resets the internal state for starting new text. - */ - private void reset() { - mSpanned = null; - mCopiedBuffer = null; - mWholeWidth = 0; - mLevels.clear(); - mWidths.clear(); - mFontMetrics.clear(); - mSpanEndCache.clear(); - } - - /** - * Returns the characters to be measured. - * - * This is always available. - */ - public @NonNull char[] getChars() { - return mCopiedBuffer; + private static final boolean localLOGV = false; + CharSequence mText; + int mTextStart; + float[] mWidths; + char[] mChars; + byte[] mLevels; + int mDir; + boolean mEasy; + int mLen; + + private int mPos; + private TextPaint mWorkPaint; + + private MeasuredText() { + mWorkPaint = new TextPaint(); } - /** - * Returns the paragraph direction. - * - * This is always available. - */ - public @Layout.Direction int getParagraphDir() { - return mParaDir; - } - - /** - * Returns the directions. - * - * This is always available. - */ - public Directions getDirections(@IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end) { // exclusive - if (mLtrWithoutBidi) { - return Layout.DIRS_ALL_LEFT_TO_RIGHT; + private static final Object[] sLock = new Object[0]; + private static final MeasuredText[] sCached = new MeasuredText[3]; + + static MeasuredText obtain() { + MeasuredText mt; + synchronized (sLock) { + for (int i = sCached.length; --i >= 0;) { + if (sCached[i] != null) { + mt = sCached[i]; + sCached[i] = null; + return mt; + } + } + } + mt = new MeasuredText(); + if (localLOGV) { + Log.v("MEAS", "new: " + mt); } - - final int length = end - start; - return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start, - length); - } - - /** - * Returns the whole text width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns 0 in other cases. - */ - public @FloatRange(from = 0.0f) float getWholeWidth() { - return mWholeWidth; - } - - /** - * Returns the individual character's width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns empty array in other cases. - */ - public @NonNull FloatArray getWidths() { - return mWidths; - } - - /** - * Returns the MetricsAffectingSpan end indices. - * - * If the input text is not a spanned string, this has one value that is the length of the text. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. - */ - public @NonNull IntArray getSpanEndCache() { - return mSpanEndCache; - } - - /** - * Returns the int array which holds FontMetrics. - * - * This array holds the repeat of top, bottom, ascent, descent of font metrics value. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. - */ - public @NonNull IntArray getFontMetrics() { - return mFontMetrics; - } - - /** - * Generates new MeasuredText for Bidi computation. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text - */ - public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); return mt; } - /** - * Generates new MeasuredText for measuring texts. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text - */ - public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - - mt.mWidths.resize(mt.mTextLength); - if (mt.mTextLength == 0) { - return mt; - } - - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan( - paint, null /* spans */, start, end, 0 /* native static layout ptr */); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan( - paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); + static MeasuredText recycle(MeasuredText mt) { + mt.finish(); + synchronized(sLock) { + for (int i = 0; i < sCached.length; ++i) { + if (sCached[i] == null) { + sCached[i] = mt; + mt.mText = null; + break; + } } } - return mt; + return null; } - /** - * Generates new MeasuredText for StaticLayout. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param nativeStaticLayoutPtr the pointer to the native static layout object - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text - */ - public static @NonNull MeasuredText buildForStaticLayout( - @NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - /* Non-Zero */ long nativeStaticLayoutPtr, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - if (mt.mTextLength == 0) { - return mt; + void finish() { + mText = null; + if (mLen > 1000) { + mWidths = null; + mChars = null; + mLevels = null; } - - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, - nativeStaticLayoutPtr); - mt.mSpanEndCache.append(end); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, - nativeStaticLayoutPtr); - mt.mSpanEndCache.append(spanEnd); - } - } - - return mt; } /** - * Reset internal state and analyzes text for bidirectional runs. - * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction + * Analyzes text for bidirectional runs. Allocates working buffers. */ - private void resetAndAnalyzeBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end, // exclusive - @NonNull TextDirectionHeuristic textDir) { - reset(); - mSpanned = text instanceof Spanned ? (Spanned) text : null; + void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) { + mText = text; mTextStart = start; - mTextLength = end - start; - if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) { - mCopiedBuffer = new char[mTextLength]; + int len = end - start; + mLen = len; + mPos = 0; + + if (mWidths == null || mWidths.length < len) { + mWidths = ArrayUtils.newUnpaddedFloatArray(len); + } + if (mChars == null || mChars.length != len) { + mChars = new char[len]; } - TextUtils.getChars(text, start, end, mCopiedBuffer, 0); + TextUtils.getChars(text, start, end, mChars, 0); - // Replace characters associated with ReplacementSpan to U+FFFC. - if (mSpanned != null) { - ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class); + if (text instanceof Spanned) { + Spanned spanned = (Spanned) text; + ReplacementSpan[] spans = spanned.getSpans(start, end, + ReplacementSpan.class); for (int i = 0; i < spans.length; i++) { - int startInPara = mSpanned.getSpanStart(spans[i]) - start; - int endInPara = mSpanned.getSpanEnd(spans[i]) - start; - // The span interval may be larger and must be restricted to [start, end) + int startInPara = spanned.getSpanStart(spans[i]) - start; + int endInPara = spanned.getSpanEnd(spans[i]) - start; + // The span interval may be larger and must be restricted to [start, end[ if (startInPara < 0) startInPara = 0; - if (endInPara > mTextLength) endInPara = mTextLength; - Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER); + if (endInPara > len) endInPara = len; + for (int j = startInPara; j < endInPara; j++) { + mChars[j] = '\uFFFC'; // object replacement character + } } } if ((textDir == TextDirectionHeuristics.LTR || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR || textDir == TextDirectionHeuristics.ANYRTL_LTR) && - TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) { - mLevels.clear(); - mParaDir = Layout.DIR_LEFT_TO_RIGHT; - mLtrWithoutBidi = true; + TextUtils.doesNotNeedBidi(mChars, 0, len)) { + mDir = Layout.DIR_LEFT_TO_RIGHT; + mEasy = true; } else { - final int bidiRequest; + if (mLevels == null || mLevels.length < len) { + mLevels = ArrayUtils.newUnpaddedByteArray(len); + } + int bidiRequest; if (textDir == TextDirectionHeuristics.LTR) { bidiRequest = Layout.DIR_REQUEST_LTR; } else if (textDir == TextDirectionHeuristics.RTL) { @@ -403,146 +144,122 @@ class MeasuredText { } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; } else { - final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength); + boolean isRtl = textDir.isRtl(mChars, 0, len); bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; } - mLevels.resize(mTextLength); - mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray()); - mLtrWithoutBidi = false; + mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels); + mEasy = false; } } - private void applyReplacementRun(@NonNull ReplacementSpan replacement, - @IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeStaticLayoutPtr) { - // Use original text. Shouldn't matter. - // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for - // backward compatibility? or Should we initialize them for getFontMetricsInt? - final float width = replacement.getSize( - mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); - if (nativeStaticLayoutPtr == 0) { - // Assigns all width to the first character. This is the same behavior as minikin. - mWidths.set(start, width); - if (end > start + 1) { - Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f); - } - mWholeWidth += width; - } else { - StaticLayout.addReplacementRun(nativeStaticLayoutPtr, mCachedPaint, start, end, width); + /** + * Apply the style. + * + * If nativeStaticLayoutPtr is 0, this method measures the styled text width. + * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native + * code by calling StaticLayout.addstyleRun() and returns 0. + */ + float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm, + long nativeStaticLayoutPtr) { + if (fm != null) { + paint.getFontMetricsInt(fm); } - } - private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeStaticLayoutPtr) { - if (nativeStaticLayoutPtr != 0) { - mCachedPaint.getFontMetricsInt(mCachedFm); - } + final int p = mPos; + mPos = p + len; - if (mLtrWithoutBidi) { - // If the whole text is LTR direction, just apply whole region. + if (mEasy) { + final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT; if (nativeStaticLayoutPtr == 0) { - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, - mWidths.getRawArray(), start); + return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p); } else { - StaticLayout.addStyleRun(nativeStaticLayoutPtr, mCachedPaint, start, end, - false /* isRtl */); + StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl); + return 0.0f; // Builder.addStyleRun doesn't return the width. } - } else { - // If there is multiple bidi levels, split into individual bidi level and apply style. - byte level = mLevels.get(start); - // Note that the empty text or empty range won't reach this method. - // Safe to search from start + 1. - for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { - if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point - final boolean isRtl = (level & 0x1) != 0; - if (nativeStaticLayoutPtr == 0) { - final int levelLength = levelEnd - levelStart; - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, levelStart, levelLength, levelStart, levelEnd, isRtl, - mWidths.getRawArray(), levelStart); - } else { - StaticLayout.addStyleRun( - nativeStaticLayoutPtr, mCachedPaint, levelStart, levelEnd, isRtl); - } - if (levelEnd == end) { - break; - } - levelStart = levelEnd; - level = mLevels.get(levelEnd); + } + + float totalAdvance = 0; + int level = mLevels[p]; + for (int q = p, i = p + 1, e = p + len;; ++i) { + if (i == e || mLevels[i] != level) { + final boolean isRtl = (level & 0x1) != 0; + if (nativeStaticLayoutPtr == 0) { + totalAdvance += + paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q); + } else { + // Builder.addStyleRun doesn't return the width. + StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl); + } + if (i == e) { + break; } + q = i; + level = mLevels[i]; } } + return totalAdvance; // If nativeStaticLayoutPtr is 0, the result is zero. } - private void applyMetricsAffectingSpan( - @NonNull TextPaint paint, - @Nullable MetricAffectingSpan[] spans, - @IntRange(from = 0) int start, // inclusive, in original text buffer - @IntRange(from = 0) int end, // exclusive, in original text buffer - /* Maybe Zero */ long nativeStaticLayoutPtr) { - mCachedPaint.set(paint); - // XXX paint should not have a baseline shift, but... - mCachedPaint.baselineShift = 0; + float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) { + return addStyleRun(paint, len, fm, 0 /* native ptr */); + } - final boolean needFontMetrics = nativeStaticLayoutPtr != 0; + float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, + Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) { - if (needFontMetrics && mCachedFm == null) { - mCachedFm = new Paint.FontMetricsInt(); - } + TextPaint workPaint = mWorkPaint; + workPaint.set(paint); + // XXX paint should not have a baseline shift, but... + workPaint.baselineShift = 0; ReplacementSpan replacement = null; - if (spans != null) { - for (int i = 0; i < spans.length; i++) { - MetricAffectingSpan span = spans[i]; - if (span instanceof ReplacementSpan) { - // The last ReplacementSpan is effective for backward compatibility reasons. - replacement = (ReplacementSpan) span; - } else { - // TODO: No need to call updateMeasureState for ReplacementSpan as well? - span.updateMeasureState(mCachedPaint); - } + for (int i = 0; i < spans.length; i++) { + MetricAffectingSpan span = spans[i]; + if (span instanceof ReplacementSpan) { + replacement = (ReplacementSpan)span; + } else { + span.updateMeasureState(workPaint); } } - final int startInCopiedBuffer = start - mTextStart; - final int endInCopiedBuffer = end - mTextStart; - - if (replacement != null) { - applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, - nativeStaticLayoutPtr); + float wid; + if (replacement == null) { + wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr); } else { - applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeStaticLayoutPtr); + // Use original text. Shouldn't matter. + wid = replacement.getSize(workPaint, mText, mTextStart + mPos, + mTextStart + mPos + len, fm); + if (nativeStaticLayoutPtr == 0) { + float[] w = mWidths; + w[mPos] = wid; + for (int i = mPos + 1, e = mPos + len; i < e; i++) + w[i] = 0; + } else { + StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid); + } + mPos += len; } - if (needFontMetrics) { - if (mCachedPaint.baselineShift < 0) { - mCachedFm.ascent += mCachedPaint.baselineShift; - mCachedFm.top += mCachedPaint.baselineShift; + if (fm != null) { + if (workPaint.baselineShift < 0) { + fm.ascent += workPaint.baselineShift; + fm.top += workPaint.baselineShift; } else { - mCachedFm.descent += mCachedPaint.baselineShift; - mCachedFm.bottom += mCachedPaint.baselineShift; + fm.descent += workPaint.baselineShift; + fm.bottom += workPaint.baselineShift; } - - mFontMetrics.append(mCachedFm.top); - mFontMetrics.append(mCachedFm.bottom); - mFontMetrics.append(mCachedFm.ascent); - mFontMetrics.append(mCachedFm.descent); } + + return wid; } - /** - * Returns the maximum index that the accumulated width not exceeds the width. - * - * If forward=false is passed, returns the minimum index from the end instead. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { - float[] w = mWidths.getRawArray(); + float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, + Paint.FontMetricsInt fm) { + return addStyleRun(paint, spans, len, fm, 0 /* native ptr */); + } + + int breakText(int limit, boolean forwards, float width) { + float[] w = mWidths; if (forwards) { int i = 0; while (i < limit) { @@ -550,7 +267,7 @@ class MeasuredText { if (width < 0.0f) break; i++; } - while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--; + while (i > 0 && mChars[i - 1] == ' ') i--; return i; } else { int i = limit - 1; @@ -559,22 +276,16 @@ class MeasuredText { if (width < 0.0f) break; i--; } - while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) { + while (i < limit - 1 && (mChars[i + 1] == ' ' || w[i + 1] == 0.0f)) { i++; } return limit - i - 1; } } - /** - * Returns the length of the substring. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @FloatRange(from = 0.0f) float measure(int start, int limit) { + float measure(int start, int limit) { float width = 0; - float[] w = mWidths.getRawArray(); + float[] w = mWidths; for (int i = start; i < limit; ++i) { width += w[i]; } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 81c82c994ef4..c0fc44fd8ee1 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -21,10 +21,10 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Paint; -import android.text.AutoGrowArray.FloatArray; import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; import android.text.style.LineHeightSpan; +import android.text.style.MetricAffectingSpan; import android.text.style.TabStopSpan; import android.util.Log; import android.util.Pools.SynchronizedPool; @@ -99,6 +99,8 @@ public class StaticLayout extends Layout { b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; + + b.mMeasuredText = MeasuredText.obtain(); return b; } @@ -109,6 +111,8 @@ public class StaticLayout extends Layout { private static void recycle(@NonNull Builder b) { b.mPaint = null; b.mText = null; + MeasuredText.recycle(b.mMeasuredText); + b.mMeasuredText = null; b.mLeftIndents = null; b.mRightIndents = null; b.mLeftPaddings = null; @@ -124,6 +128,7 @@ public class StaticLayout extends Layout { mRightIndents = null; mLeftPaddings = null; mRightPaddings = null; + mMeasuredText.finish(); } public Builder setText(CharSequence source) { @@ -439,6 +444,9 @@ public class StaticLayout extends Layout { private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); + // This will go away and be subsumed by native builder code + private MeasuredText mMeasuredText; + private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3); } @@ -610,7 +618,11 @@ public class StaticLayout extends Layout { TextUtils.TruncateAt ellipsize = b.mEllipsize; final boolean addLastLineSpacing = b.mAddLastLineLineSpacing; LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs - FloatArray widths = new FloatArray(); + // store span end locations + int[] spanEndCache = new int[4]; + // store fontMetrics per span range + // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range) + int[] fmCache = new int[4 * 4]; mLineCount = 0; mEllipsized = false; @@ -622,6 +634,8 @@ public class StaticLayout extends Layout { Paint.FontMetricsInt fm = b.mFontMetricsInt; int[] chooseHtv = null; + MeasuredText measured = b.mMeasuredText; + Spanned spanned = null; if (source instanceof Spanned) spanned = (Spanned) source; @@ -648,7 +662,6 @@ public class StaticLayout extends Layout { b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLeftPaddings, mRightPaddings); - MeasuredText measured = null; try { int paraEnd; for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) { @@ -708,6 +721,13 @@ public class StaticLayout extends Layout { } } + measured.setPara(source, paraStart, paraEnd, textDir); + char[] chs = measured.mChars; + float[] widths = measured.mWidths; + byte[] chdirs = measured.mLevels; + int dir = measured.mDir; + boolean easy = measured.mEasy; + // tab stop locations int[] variableTabStops = null; if (spanned != null) { @@ -723,16 +743,50 @@ public class StaticLayout extends Layout { } } - measured = MeasuredText.buildForStaticLayout( - paint, source, paraStart, paraEnd, textDir, nativePtr, measured); - final char[] chs = measured.getChars(); - final int[] spanEndCache = measured.getSpanEndCache().getRawArray(); - final int[] fmCache = measured.getFontMetrics().getRawArray(); - widths.resize(chs.length); - // measurement has to be done before performing line breaking // but we don't want to recompute fontmetrics or span ranges the // second time, so we cache those and then use those stored values + int fmCacheCount = 0; + int spanEndCacheCount = 0; + for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + if (fmCacheCount * 4 >= fmCache.length) { + int[] grow = new int[fmCacheCount * 4 * 2]; + System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4); + fmCache = grow; + } + + if (spanEndCacheCount >= spanEndCache.length) { + int[] grow = new int[spanEndCacheCount * 2]; + System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); + spanEndCache = grow; + } + + if (spanned == null) { + spanEnd = paraEnd; + int spanLen = spanEnd - spanStart; + measured.addStyleRun(paint, spanLen, fm, nativePtr); + } else { + spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, + MetricAffectingSpan.class); + int spanLen = spanEnd - spanStart; + MetricAffectingSpan[] spans = + spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, spanned, + MetricAffectingSpan.class); + measured.addStyleRun(paint, spans, spanLen, fm, nativePtr); + } + + // the order of storage here (top, bottom, ascent, descent) has to match the + // code below where these values are retrieved + fmCache[fmCacheCount * 4 + 0] = fm.top; + fmCache[fmCacheCount * 4 + 1] = fm.bottom; + fmCache[fmCacheCount * 4 + 2] = fm.ascent; + fmCache[fmCacheCount * 4 + 3] = fm.descent; + fmCacheCount++; + + spanEndCache[spanEndCacheCount] = spanEnd; + spanEndCacheCount++; + } int breakCount = nComputeLineBreaks( nativePtr, @@ -755,7 +809,7 @@ public class StaticLayout extends Layout { lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags, - widths.getRawArray()); + widths); final int[] breaks = lineBreaks.breaks; final float[] lineWidths = lineBreaks.widths; @@ -778,7 +832,7 @@ public class StaticLayout extends Layout { width += lineWidths[i]; } else { for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) { - width += widths.get(j); + width += widths[j]; } } flag |= flags[i] & TAB_MASK; @@ -842,10 +896,10 @@ public class StaticLayout extends Layout { v = out(source, here, endPos, ascent, descent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, - flags[breakIndex], needMultiply, measured, bufEnd, - includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(), - paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], - paint, moreChars); + flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd, + includepad, trackpad, addLastLineSpacing, chs, widths, paraStart, + ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, + moreChars); if (endPos < spanEnd) { // preserve metrics for current span @@ -873,8 +927,7 @@ public class StaticLayout extends Layout { if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { - measured = MeasuredText.buildForStaticLayout( - paint, source, bufEnd, bufEnd, textDir, nativePtr, measured); + measured.setPara(source, bufEnd, bufEnd, textDir); paint.getFontMetricsInt(fm); @@ -884,15 +937,12 @@ public class StaticLayout extends Layout { v, spacingmult, spacingadd, null, null, fm, 0, - needMultiply, measured, bufEnd, + needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, includepad, trackpad, addLastLineSpacing, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false); } } finally { - if (measured != null) { - measured.recycle(); - } nFinish(nativePtr); } } @@ -902,8 +952,8 @@ public class StaticLayout extends Layout { private int out(final CharSequence text, final int start, final int end, int above, int below, int top, int bottom, int v, final float spacingmult, final float spacingadd, final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm, - final int flags, final boolean needMultiply, final MeasuredText measured, - final int bufEnd, final boolean includePad, final boolean trackPad, + final int flags, final boolean needMultiply, final byte[] chdirs, final int dir, + final boolean easy, final int bufEnd, final boolean includePad, final boolean trackPad, final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, final float textWidth, final TextPaint paint, final boolean moreChars) { @@ -911,7 +961,6 @@ public class StaticLayout extends Layout { final int off = j * mColumns; final int want = off + mColumns + TOP; int[] lines = mLines; - final int dir = measured.getParagraphDir(); if (want >= lines.length) { final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want)); @@ -937,8 +986,17 @@ public class StaticLayout extends Layout { // one bit for start field lines[off + TAB] |= flags & TAB_MASK; lines[off + HYPHEN] = flags; + lines[off + DIR] |= dir << DIR_SHIFT; - mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart); + // easy means all chars < the first RTL, so no emoji, no nothing + // XXX a run with no text or all spaces is easy but might be an empty + // RTL paragraph. Make sure easy is false if this is the case. + if (easy) { + mLineDirections[j] = DIRS_ALL_LEFT_TO_RIGHT; + } else { + mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs, + start - widthStart, end - start); + } final boolean firstLine = (j == 0); final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount); diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index af661575b447..cbdaa69b5aa0 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -42,6 +42,7 @@ import android.text.style.EasyEditSpan; import android.text.style.ForegroundColorSpan; import android.text.style.LeadingMarginSpan; import android.text.style.LocaleSpan; +import android.text.style.MetricAffectingSpan; import android.text.style.ParagraphStyle; import android.text.style.QuoteSpan; import android.text.style.RelativeSizeSpan; @@ -1250,11 +1251,10 @@ public class TextUtils { @NonNull String ellipsis) { final int len = text.length(); - MeasuredText mt = null; + final MeasuredText mt = MeasuredText.obtain(); MeasuredText resultMt = null; try { - mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt); - float width = mt.getWholeWidth(); + float width = setPara(mt, paint, text, 0, text.length(), textDir); if (width <= avail) { if (callback != null) { @@ -1263,6 +1263,7 @@ public class TextUtils { return text; } + resultMt = MeasuredText.obtain(); // First estimate of effective width of ellipsis. float ellipsisWidth = paint.measureText(ellipsis); int numberOfTries = 0; @@ -1289,7 +1290,7 @@ public class TextUtils { } } - final char[] buf = mt.getChars(); + final char[] buf = mt.mChars; final Spanned sp = text instanceof Spanned ? (Spanned) text : null; final int removed = end - start; @@ -1332,9 +1333,7 @@ public class TextUtils { if (remaining == 0) { // All text is gone. textFits = true; } else { - resultMt = MeasuredText.buildForMeasurement( - paint, result, 0, result.length(), textDir, resultMt); - width = resultMt.getWholeWidth(); + width = setPara(resultMt, paint, result, 0, result.length(), textDir); if (width <= avail) { textFits = true; } else { @@ -1358,11 +1357,9 @@ public class TextUtils { } return result; } finally { - if (mt != null) { - mt.recycle(); - } + MeasuredText.recycle(mt); if (resultMt != null) { - resultMt.recycle(); + MeasuredText.recycle(resultMt); } } } @@ -1479,17 +1476,15 @@ public class TextUtils { public static CharSequence commaEllipsize(CharSequence text, TextPaint p, float avail, String oneMore, String more, TextDirectionHeuristic textDir) { - MeasuredText mt = null; - MeasuredText tempMt = null; + MeasuredText mt = MeasuredText.obtain(); try { int len = text.length(); - mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt); - float width = mt.getWholeWidth(); + float width = setPara(mt, p, text, 0, len, textDir); if (width <= avail) { return text; } - char[] buf = mt.getChars(); + char[] buf = mt.mChars; int commaCount = 0; for (int i = 0; i < len; i++) { @@ -1505,8 +1500,9 @@ public class TextUtils { int w = 0; int count = 0; - float[] widths = mt.getWidths().getRawArray(); + float[] widths = mt.mWidths; + MeasuredText tempMt = MeasuredText.obtain(); for (int i = 0; i < len; i++) { w += widths[i]; @@ -1523,9 +1519,8 @@ public class TextUtils { } // XXX this is probably ok, but need to look at it more - tempMt = MeasuredText.buildForMeasurement( - p, format, 0, format.length(), textDir, tempMt); - float moreWid = tempMt.getWholeWidth(); + tempMt.setPara(format, 0, format.length(), textDir); + float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null); if (w + moreWid <= avail) { ok = i + 1; @@ -1533,18 +1528,40 @@ public class TextUtils { } } } + MeasuredText.recycle(tempMt); SpannableStringBuilder out = new SpannableStringBuilder(okFormat); out.insert(0, text, 0, ok); return out; } finally { - if (mt != null) { - mt.recycle(); - } - if (tempMt != null) { - tempMt.recycle(); + MeasuredText.recycle(mt); + } + } + + private static float setPara(MeasuredText mt, TextPaint paint, + CharSequence text, int start, int end, TextDirectionHeuristic textDir) { + + mt.setPara(text, start, end, textDir); + + float width; + Spanned sp = text instanceof Spanned ? (Spanned) text : null; + int len = end - start; + if (sp == null) { + width = mt.addStyleRun(paint, len, null); + } else { + width = 0; + int spanEnd; + for (int spanStart = 0; spanStart < len; spanStart = spanEnd) { + spanEnd = sp.nextSpanTransition(spanStart, len, + MetricAffectingSpan.class); + MetricAffectingSpan[] spans = sp.getSpans( + spanStart, spanEnd, MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, sp, MetricAffectingSpan.class); + width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null); } } + + return width; } // Returns true if the character's presence could affect RTL layout. |
