diff options
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/text/PremeasuredText.java | 272 | ||||
| -rw-r--r-- | core/java/android/text/StaticLayout.java | 53 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 10 |
3 files changed, 312 insertions, 23 deletions
diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java new file mode 100644 index 000000000000..465314dd21ac --- /dev/null +++ b/core/java/android/text/PremeasuredText.java @@ -0,0 +1,272 @@ +/* + * 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 android.util.IntArray; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; + +/** + * A text which has already been measured. + * + * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc. + */ +public class PremeasuredText implements Spanned { + private static final char LINE_FEED = '\n'; + + // The original text. + private final @NonNull CharSequence mText; + + // The inclusive start offset of the measuring target. + private final @IntRange(from = 0) int mStart; + + // The exclusive end offset of the measuring target. + private final @IntRange(from = 0) int mEnd; + + // The TextPaint used for measurement. + private final @NonNull TextPaint mPaint; + + // The requested text direction. + private final @NonNull TextDirectionHeuristic mTextDir; + + // The measured paragraph texts. + private final @NonNull MeasuredText[] mMeasuredTexts; + + // The sorted paragraph end offsets. + private final @NonNull int[] mParagraphBreakPoints; + + /** + * Build PremeasuredText from the text. + * + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @return The measured text. + */ + public static @NonNull PremeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir) { + return PremeasuredText.build(text, paint, textDir, 0, text.length()); + } + + /** + * Build PremeasuredText from the specific range of the text.. + * + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @param start The inclusive start offset of the text. + * @param end The exclusive start offset of the text. + * @return The measured text. + */ + public static @NonNull PremeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(paint); + Preconditions.checkNotNull(textDir); + Preconditions.checkArgumentInRange(start, 0, text.length(), "start"); + Preconditions.checkArgumentInRange(end, 0, text.length(), "end"); + + final IntArray paragraphEnds = new IntArray(); + final ArrayList<MeasuredText> measuredTexts = new ArrayList<>(); + + int paraEnd = 0; + for (int paraStart = start; paraStart < end; paraStart = paraEnd) { + paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); + if (paraEnd < 0) { + // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end. + paraEnd = end; + } else { + paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. + } + + paragraphEnds.add(paraEnd); + measuredTexts.add(MeasuredText.buildForStaticLayout( + paint, text, paraStart, paraEnd, textDir, null /* no recycle */)); + } + + return new PremeasuredText(text, start, end, paint, textDir, + measuredTexts.toArray(new MeasuredText[measuredTexts.size()]), + paragraphEnds.toArray()); + } + + // Use PremeasuredText.build instead. + private PremeasuredText(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @NonNull MeasuredText[] measuredTexts, + @NonNull int[] paragraphBreakPoints) { + mText = text; + mStart = start; + mEnd = end; + mPaint = paint; + mMeasuredTexts = measuredTexts; + mParagraphBreakPoints = paragraphBreakPoints; + mTextDir = textDir; + } + + /** + * Return the underlying text. + */ + public @NonNull CharSequence getText() { + return mText; + } + + /** + * Returns the inclusive start offset of measured region. + */ + public @IntRange(from = 0) int getStart() { + return mStart; + } + + /** + * Returns the exclusive end offset of measured region. + */ + public @IntRange(from = 0) int getEnd() { + return mEnd; + } + + /** + * Returns the text direction associated with char sequence. + */ + public @NonNull TextDirectionHeuristic getTextDir() { + return mTextDir; + } + + /** + * Returns the paint used to measure this text. + */ + public @NonNull TextPaint getPaint() { + return mPaint; + } + + /** + * Returns the length of the paragraph of this text. + */ + public @IntRange(from = 0) int getParagraphCount() { + return mParagraphBreakPoints.length; + } + + /** + * Returns the paragraph start offset of the text. + */ + public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; + } + + /** + * Returns the paragraph end offset of the text. + */ + public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return mParagraphBreakPoints[paraIndex]; + } + + /** @hide */ + public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) { + return mMeasuredTexts[paraIndex]; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Spanned overrides + // + // Just proxy for underlying mText if appropriate. + + @Override + public <T> T[] getSpans(int start, int end, Class<T> type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpans(start, end, type); + } else { + return ArrayUtils.emptyArray(type); + } + } + + @Override + public int getSpanStart(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanStart(tag); + } else { + return -1; + } + } + + @Override + public int getSpanEnd(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanEnd(tag); + } else { + return -1; + } + } + + @Override + public int getSpanFlags(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanFlags(tag); + } else { + return 0; + } + } + + @Override + public int nextSpanTransition(int start, int limit, Class type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).nextSpanTransition(start, limit, type); + } else { + return mText.length(); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // CharSequence overrides. + // + // Just proxy for underlying mText. + + @Override + public int length() { + return mText.length(); + } + + @Override + public char charAt(int index) { + // TODO: Should this be index + mStart ? + return mText.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + // TODO: return PremeasuredText. + // TODO: Should this be index + mStart, end + mStart ? + return mText.subSequence(start, end); + } + + @Override + public String toString() { + return mText.toString(); + } +} diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 53ddd1699d68..2e10fe8d4267 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -610,8 +610,8 @@ public class StaticLayout extends Layout { /* package */ void generate(Builder b, boolean includepad, boolean trackpad) { final CharSequence source = b.mText; - int bufStart = b.mStart; - int bufEnd = b.mEnd; + final int bufStart = b.mStart; + final int bufEnd = b.mEnd; TextPaint paint = b.mPaint; int outerWidth = b.mWidth; TextDirectionHeuristic textDir = b.mTextDir; @@ -634,10 +634,6 @@ public class StaticLayout extends Layout { Paint.FontMetricsInt fm = b.mFontMetricsInt; int[] chooseHtv = null; - Spanned spanned = null; - if (source instanceof Spanned) - spanned = (Spanned) source; - final int[] indents; if (mLeftIndents != null || mRightIndents != null) { final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length; @@ -660,16 +656,34 @@ public class StaticLayout extends Layout { b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLeftPaddings, mRightPaddings); - MeasuredText measured = null; + PremeasuredText premeasured = null; + final Spanned spanned; + if (source instanceof PremeasuredText) { + premeasured = (PremeasuredText) source; + + final CharSequence original = premeasured.getText(); + spanned = (original instanceof Spanned) ? (Spanned) original : null; + + if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) { + // The buffer position has changed. Re-measure here. + premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd); + } else { + // We can use premeasured information. + + // Overwrite with the one when premeasured. + // TODO: Give an option for developer not to overwrite and measure again here? + textDir = premeasured.getTextDir(); + paint = premeasured.getPaint(); + } + } else { + premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd); + spanned = (source instanceof Spanned) ? (Spanned) source : null; + } + try { - int paraEnd; - for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) { - paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd); - if (paraEnd < 0) { - paraEnd = bufEnd; - } else { - paraEnd++; - } + for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) { + final int paraStart = premeasured.getParagraphStart(paraIndex); + final int paraEnd = premeasured.getParagraphEnd(paraIndex); int firstWidthLineCount = 1; int firstWidth = outerWidth; @@ -735,8 +749,7 @@ public class StaticLayout extends Layout { } } - measured = MeasuredText.buildForStaticLayout( - paint, source, paraStart, paraEnd, textDir, measured); + final MeasuredText measured = premeasured.getMeasuredText(paraIndex); final char[] chs = measured.getChars(); final int[] spanEndCache = measured.getSpanEndCache().getRawArray(); final int[] fmCache = measured.getFontMetrics().getRawArray(); @@ -887,7 +900,8 @@ public class StaticLayout extends Layout { if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { - measured = MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, measured); + final MeasuredText measured = + MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null); paint.getFontMetricsInt(fm); v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, @@ -901,9 +915,6 @@ public class StaticLayout extends Layout { ellipsizedWidth, 0, paint, false); } } finally { - if (measured != null) { - measured.recycle(); - } nFinish(nativePtr); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d9bc51fffd6a..f8083babef29 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -77,6 +77,7 @@ import android.text.InputFilter; import android.text.InputType; import android.text.Layout; import android.text.ParcelableSpan; +import android.text.PremeasuredText; import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; @@ -5326,7 +5327,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) imm.restartInput(this); } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); - } else if (!(text instanceof CharWrapper)) { + } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) { text = TextUtils.stringOrSpannedString(text); } @@ -5610,10 +5611,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener spannable = (Spannable) text; } else { spannable = mSpannableFactory.newSpannable(text); - text = spannable; } SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); + if (spans.length == 0) { + return text; + } else { + text = spannable; + } + for (int i = 0; i < spans.length; i++) { spannable.removeSpan(spans[i]); } |
