Loading apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +82 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.text; import static android.text.TextDirectionHeuristics.LTR; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; Loading Loading @@ -182,4 +184,84 @@ public class StaticLayoutPerfTest { .build(); } } @Test public void testCreate_MeasuredText_NoStyled_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .build(); } } @Test public void testCreate_MeasuredText_NoStyled_Greedy_Hyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .build(); } } @Test public void testCreate_MeasuredText_NoStyled_Balanced_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .build(); } } @Test public void testCreate_MeasuredText_NoStyled_Balanced_Hyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .build(); } } @Test public void testCreate_MeasuredText_Styled_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .build(); } } } api/current.txt +21 −0 Original line number Diff line number Diff line Loading @@ -42277,6 +42277,27 @@ package android.text { method public abstract int getSpanTypeId(); } public class PremeasuredText implements android.text.Spanned { method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); method public char charAt(int); method public int getEnd(); method public android.text.TextPaint getPaint(); method public int getParagraphCount(); method public int getParagraphEnd(int); method public int getParagraphStart(int); method public int getSpanEnd(java.lang.Object); method public int getSpanFlags(java.lang.Object); method public int getSpanStart(java.lang.Object); method public <T> T[] getSpans(int, int, java.lang.Class<T>); method public int getStart(); method public java.lang.CharSequence getText(); method public android.text.TextDirectionHeuristic getTextDir(); method public int length(); method public int nextSpanTransition(int, int, java.lang.Class); method public java.lang.CharSequence subSequence(int, int); } public class Selection { method public static boolean extendDown(android.text.Spannable, android.text.Layout); method public static boolean extendLeft(android.text.Spannable, android.text.Layout); core/java/android/text/PremeasuredText.java 0 → 100644 +272 −0 Original line number Diff line number Diff line /* * 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(); } } core/java/android/text/StaticLayout.java +32 −21 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -660,17 +656,35 @@ 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) { paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd); if (paraEnd < 0) { paraEnd = bufEnd; 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 { paraEnd++; premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd); spanned = (source instanceof Spanned) ? (Spanned) source : null; } try { 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; int restWidth = outerWidth; Loading Loading @@ -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(); Loading Loading @@ -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, Loading @@ -901,9 +915,6 @@ public class StaticLayout extends Layout { ellipsizedWidth, 0, paint, false); } } finally { if (measured != null) { measured.recycle(); } nFinish(nativePtr); } } Loading core/java/android/widget/TextView.java +8 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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]); } Loading Loading
apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +82 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.text; import static android.text.TextDirectionHeuristics.LTR; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; Loading Loading @@ -182,4 +184,84 @@ public class StaticLayoutPerfTest { .build(); } } @Test public void testCreate_MeasuredText_NoStyled_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .build(); } } @Test public void testCreate_MeasuredText_NoStyled_Greedy_Hyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .build(); } } @Test public void testCreate_MeasuredText_NoStyled_Balanced_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .build(); } } @Test public void testCreate_MeasuredText_NoStyled_Balanced_Hyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .build(); } } @Test public void testCreate_MeasuredText_Styled_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); final PremeasuredText text = PremeasuredText.build( generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .build(); } } }
api/current.txt +21 −0 Original line number Diff line number Diff line Loading @@ -42277,6 +42277,27 @@ package android.text { method public abstract int getSpanTypeId(); } public class PremeasuredText implements android.text.Spanned { method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); method public char charAt(int); method public int getEnd(); method public android.text.TextPaint getPaint(); method public int getParagraphCount(); method public int getParagraphEnd(int); method public int getParagraphStart(int); method public int getSpanEnd(java.lang.Object); method public int getSpanFlags(java.lang.Object); method public int getSpanStart(java.lang.Object); method public <T> T[] getSpans(int, int, java.lang.Class<T>); method public int getStart(); method public java.lang.CharSequence getText(); method public android.text.TextDirectionHeuristic getTextDir(); method public int length(); method public int nextSpanTransition(int, int, java.lang.Class); method public java.lang.CharSequence subSequence(int, int); } public class Selection { method public static boolean extendDown(android.text.Spannable, android.text.Layout); method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
core/java/android/text/PremeasuredText.java 0 → 100644 +272 −0 Original line number Diff line number Diff line /* * 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(); } }
core/java/android/text/StaticLayout.java +32 −21 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -660,17 +656,35 @@ 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) { paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd); if (paraEnd < 0) { paraEnd = bufEnd; 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 { paraEnd++; premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd); spanned = (source instanceof Spanned) ? (Spanned) source : null; } try { 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; int restWidth = outerWidth; Loading Loading @@ -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(); Loading Loading @@ -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, Loading @@ -901,9 +915,6 @@ public class StaticLayout extends Layout { ellipsizedWidth, 0, paint, false); } } finally { if (measured != null) { measured.recycle(); } nFinish(nativePtr); } } Loading
core/java/android/widget/TextView.java +8 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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]); } Loading