summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorSeigo Nonaka <nona@google.com>2017-12-04 18:25:03 +0000
committerSeigo Nonaka <nona@google.com>2017-12-04 18:25:03 +0000
commit4674e642a54e1db2028c4485383994fff2af559d (patch)
treea00a4567d22add6c5cc14ba3ffc6f6328e81278e /core/java/android
parent75492afb1b6566c889025e9ca0a15a0d0fe422aa (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.java374
-rw-r--r--core/java/android/text/Layout.java29
-rw-r--r--core/java/android/text/MeasuredText.java629
-rw-r--r--core/java/android/text/StaticLayout.java110
-rw-r--r--core/java/android/text/TextUtils.java67
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.