Loading api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -15724,6 +15724,7 @@ package android.graphics.text { public static class MeasuredText.Builder { ctor public MeasuredText.Builder(char[]); ctor public MeasuredText.Builder(android.graphics.text.MeasuredText); method public android.graphics.text.MeasuredText.Builder appendReplacementRun(android.graphics.Paint, int, float); method public android.graphics.text.MeasuredText.Builder appendStyleRun(android.graphics.Paint, int, boolean); method public android.graphics.text.MeasuredText build(); Loading Loading @@ -45266,6 +45267,7 @@ package android.text { public static class PrecomputedText.Params.Builder { ctor public PrecomputedText.Params.Builder(android.text.TextPaint); ctor public PrecomputedText.Params.Builder(android.text.PrecomputedText.Params); method public android.text.PrecomputedText.Params build(); method public android.text.PrecomputedText.Params.Builder setBreakStrategy(int); method public android.text.PrecomputedText.Params.Builder setHyphenationFrequency(int); core/java/android/text/MeasuredParagraph.java +12 −3 Original line number Diff line number Diff line Loading @@ -377,6 +377,9 @@ public class MeasuredParagraph { * @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 computeHyphenation true if need to compute hyphenation, otherwise false * @param computeLayout true if need to compute full layout, otherwise false. * @param hint pass if you already have measured paragraph. * @param recycle pass existing MeasuredParagraph if you want to recycle it. * * @return measured text Loading @@ -389,12 +392,18 @@ public class MeasuredParagraph { @NonNull TextDirectionHeuristic textDir, boolean computeHyphenation, boolean computeLayout, @Nullable MeasuredParagraph hint, @Nullable MeasuredParagraph recycle) { final MeasuredParagraph mt = recycle == null ? obtain() : recycle; mt.resetAndAnalyzeBidi(text, start, end, textDir); final MeasuredText.Builder builder = new MeasuredText.Builder(mt.mCopiedBuffer); builder.setComputeHyphenation(computeHyphenation); builder.setComputeLayout(computeLayout); final MeasuredText.Builder builder; if (hint == null) { builder = new MeasuredText.Builder(mt.mCopiedBuffer) .setComputeHyphenation(computeHyphenation) .setComputeLayout(computeLayout); } else { builder = new MeasuredText.Builder(hint.mMeasuredText); } if (mt.mTextLength == 0) { // Need to build empty native measured text for StaticLayout. // TODO: Stop creating empty measured text for empty lines. Loading core/java/android/text/PrecomputedText.java +105 −16 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.text; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -25,6 +26,8 @@ import android.text.style.MetricAffectingSpan; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; Loading Loading @@ -118,6 +121,16 @@ public class PrecomputedText implements Spannable { mPaint = paint; } /** * Builder constructor from existing params. */ public Builder(@NonNull Params params) { mPaint = params.mPaint; mTextDir = params.mTextDir; mBreakStrategy = params.mBreakStrategy; mHyphenationFrequency = params.mHyphenationFrequency; } /** * Set the line break strategy. * Loading Loading @@ -220,13 +233,41 @@ public class PrecomputedText implements Spannable { } /** @hide */ public boolean isSameTextMetricsInternal(@NonNull TextPaint paint, @IntDef(value = { UNUSABLE, NEED_RECOMPUTE, USABLE }) @Retention(RetentionPolicy.SOURCE) public @interface CheckResultUsableResult {} /** * Constant for returning value of checkResultUsable indicating that given parameter is not * compatible. * @hide */ public static final int UNUSABLE = 0; /** * Constant for returning value of checkResultUsable indicating that given parameter is not * compatible but partially usable for creating new PrecomputedText. * @hide */ public static final int NEED_RECOMPUTE = 1; /** * Constant for returning value of checkResultUsable indicating that given parameter is * compatible. * @hide */ public static final int USABLE = 2; /** @hide */ public @CheckResultUsableResult int checkResultUsable(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { return mTextDir == textDir && mBreakStrategy == strategy && mHyphenationFrequency == frequency && mPaint.equalsForTextMeasurement(paint); if (mBreakStrategy == strategy && mHyphenationFrequency == frequency && mPaint.equalsForTextMeasurement(paint)) { return mTextDir == textDir ? USABLE : NEED_RECOMPUTE; } else { return UNUSABLE; } } /** Loading @@ -243,8 +284,8 @@ public class PrecomputedText implements Spannable { return false; } Params param = (Params) o; return isSameTextMetricsInternal(param.mPaint, param.mTextDir, param.mBreakStrategy, param.mHyphenationFrequency); return checkResultUsable(param.mPaint, param.mTextDir, param.mBreakStrategy, param.mHyphenationFrequency) == Params.USABLE; } @Override Loading Loading @@ -321,11 +362,55 @@ public class PrecomputedText implements Spannable { * @return A {@link PrecomputedText} */ public static PrecomputedText create(@NonNull CharSequence text, @NonNull Params params) { ParagraphInfo[] paraInfo = createMeasuredParagraphs( ParagraphInfo[] paraInfo = null; if (text instanceof PrecomputedText) { final PrecomputedText hintPct = (PrecomputedText) text; final PrecomputedText.Params hintParams = hintPct.getParams(); final @Params.CheckResultUsableResult int checkResult = hintParams.checkResultUsable(params.mPaint, params.mTextDir, params.mBreakStrategy, params.mHyphenationFrequency); switch (checkResult) { case Params.USABLE: return hintPct; case Params.NEED_RECOMPUTE: // To be able to use PrecomputedText for new params, at least break strategy and // hyphenation frequency must be the same. if (params.getBreakStrategy() == hintParams.getBreakStrategy() && params.getHyphenationFrequency() == hintParams.getHyphenationFrequency()) { paraInfo = createMeasuredParagraphsFromPrecomputedText( hintPct, params, true /* compute layout */); } break; case Params.UNUSABLE: // Unable to use anything in PrecomputedText. Create PrecomputedText as the // normal text input. } } if (paraInfo == null) { paraInfo = createMeasuredParagraphs( text, params, 0, text.length(), true /* computeLayout */); } return new PrecomputedText(text, 0, text.length(), params, paraInfo); } private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText( @NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) { final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE; ArrayList<ParagraphInfo> result = new ArrayList<>(); for (int i = 0; i < pct.getParagraphCount(); ++i) { final int paraStart = pct.getParagraphStart(i); final int paraEnd = pct.getParagraphEnd(i); result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( params.getTextPaint(), pct, paraStart, paraEnd, params.getTextDirection(), needHyphenation, computeLayout, pct.getMeasuredParagraph(i), null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } /** @hide */ public static ParagraphInfo[] createMeasuredParagraphs( @NonNull CharSequence text, @NonNull Params params, Loading @@ -350,7 +435,8 @@ public class PrecomputedText implements Spannable { result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(), needHyphenation, computeLayout, null /* no recycle */))); needHyphenation, computeLayout, null /* no hint */, null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } Loading Loading @@ -434,12 +520,15 @@ public class PrecomputedText implements Spannable { * Returns true if the given TextPaint gives the same result of text layout for this text. * @hide */ public boolean canUseMeasuredResult(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint, @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { return mStart == start && mEnd == end && mParams.isSameTextMetricsInternal(paint, textDir, strategy, frequency); public @Params.CheckResultUsableResult int checkResultUsable(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint, @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { if (mStart != start || mEnd != end) { return Params.UNUSABLE; } else { return mParams.checkResultUsable(paint, textDir, strategy, frequency); } } /** @hide */ Loading core/java/android/text/StaticLayout.java +20 −4 Original line number Diff line number Diff line Loading @@ -650,10 +650,26 @@ public class StaticLayout extends Layout { final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null; if (source instanceof PrecomputedText) { PrecomputedText precomputed = (PrecomputedText) source; if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint, b.mBreakStrategy, b.mHyphenationFrequency)) { final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.checkResultUsable(bufStart, bufEnd, textDir, paint, b.mBreakStrategy, b.mHyphenationFrequency); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: break; case PrecomputedText.Params.NEED_RECOMPUTE: final PrecomputedText.Params newParams = new PrecomputedText.Params.Builder(paint) .setBreakStrategy(b.mBreakStrategy) .setHyphenationFrequency(b.mHyphenationFrequency) .setTextDirection(textDir) .build(); precomputed = PrecomputedText.create(precomputed, newParams); paragraphInfo = precomputed.getParagraphInfo(); break; case PrecomputedText.Params.USABLE: // Some parameters are different from the ones when measured text is created. paragraphInfo = precomputed.getParagraphInfo(); break; } } Loading core/java/android/widget/TextView.java +11 −3 Original line number Diff line number Diff line Loading @@ -6028,14 +6028,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } if (!precomputed.getParams().isSameTextMetricsInternal( getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency)) { final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: throw new IllegalArgumentException( "PrecomputedText's Parameters don't match the parameters of this TextView." + "Consider using setTextMetricsParams(precomputedText.getParams()) " + "to override the settings of this TextView: " + "PrecomputedText: " + precomputed.getParams() + "TextView: " + getTextMetricsParams()); case PrecomputedText.Params.NEED_RECOMPUTE: precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); break; case PrecomputedText.Params.USABLE: // pass through } } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); Loading Loading
api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -15724,6 +15724,7 @@ package android.graphics.text { public static class MeasuredText.Builder { ctor public MeasuredText.Builder(char[]); ctor public MeasuredText.Builder(android.graphics.text.MeasuredText); method public android.graphics.text.MeasuredText.Builder appendReplacementRun(android.graphics.Paint, int, float); method public android.graphics.text.MeasuredText.Builder appendStyleRun(android.graphics.Paint, int, boolean); method public android.graphics.text.MeasuredText build(); Loading Loading @@ -45266,6 +45267,7 @@ package android.text { public static class PrecomputedText.Params.Builder { ctor public PrecomputedText.Params.Builder(android.text.TextPaint); ctor public PrecomputedText.Params.Builder(android.text.PrecomputedText.Params); method public android.text.PrecomputedText.Params build(); method public android.text.PrecomputedText.Params.Builder setBreakStrategy(int); method public android.text.PrecomputedText.Params.Builder setHyphenationFrequency(int);
core/java/android/text/MeasuredParagraph.java +12 −3 Original line number Diff line number Diff line Loading @@ -377,6 +377,9 @@ public class MeasuredParagraph { * @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 computeHyphenation true if need to compute hyphenation, otherwise false * @param computeLayout true if need to compute full layout, otherwise false. * @param hint pass if you already have measured paragraph. * @param recycle pass existing MeasuredParagraph if you want to recycle it. * * @return measured text Loading @@ -389,12 +392,18 @@ public class MeasuredParagraph { @NonNull TextDirectionHeuristic textDir, boolean computeHyphenation, boolean computeLayout, @Nullable MeasuredParagraph hint, @Nullable MeasuredParagraph recycle) { final MeasuredParagraph mt = recycle == null ? obtain() : recycle; mt.resetAndAnalyzeBidi(text, start, end, textDir); final MeasuredText.Builder builder = new MeasuredText.Builder(mt.mCopiedBuffer); builder.setComputeHyphenation(computeHyphenation); builder.setComputeLayout(computeLayout); final MeasuredText.Builder builder; if (hint == null) { builder = new MeasuredText.Builder(mt.mCopiedBuffer) .setComputeHyphenation(computeHyphenation) .setComputeLayout(computeLayout); } else { builder = new MeasuredText.Builder(hint.mMeasuredText); } if (mt.mTextLength == 0) { // Need to build empty native measured text for StaticLayout. // TODO: Stop creating empty measured text for empty lines. Loading
core/java/android/text/PrecomputedText.java +105 −16 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.text; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -25,6 +26,8 @@ import android.text.style.MetricAffectingSpan; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; Loading Loading @@ -118,6 +121,16 @@ public class PrecomputedText implements Spannable { mPaint = paint; } /** * Builder constructor from existing params. */ public Builder(@NonNull Params params) { mPaint = params.mPaint; mTextDir = params.mTextDir; mBreakStrategy = params.mBreakStrategy; mHyphenationFrequency = params.mHyphenationFrequency; } /** * Set the line break strategy. * Loading Loading @@ -220,13 +233,41 @@ public class PrecomputedText implements Spannable { } /** @hide */ public boolean isSameTextMetricsInternal(@NonNull TextPaint paint, @IntDef(value = { UNUSABLE, NEED_RECOMPUTE, USABLE }) @Retention(RetentionPolicy.SOURCE) public @interface CheckResultUsableResult {} /** * Constant for returning value of checkResultUsable indicating that given parameter is not * compatible. * @hide */ public static final int UNUSABLE = 0; /** * Constant for returning value of checkResultUsable indicating that given parameter is not * compatible but partially usable for creating new PrecomputedText. * @hide */ public static final int NEED_RECOMPUTE = 1; /** * Constant for returning value of checkResultUsable indicating that given parameter is * compatible. * @hide */ public static final int USABLE = 2; /** @hide */ public @CheckResultUsableResult int checkResultUsable(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { return mTextDir == textDir && mBreakStrategy == strategy && mHyphenationFrequency == frequency && mPaint.equalsForTextMeasurement(paint); if (mBreakStrategy == strategy && mHyphenationFrequency == frequency && mPaint.equalsForTextMeasurement(paint)) { return mTextDir == textDir ? USABLE : NEED_RECOMPUTE; } else { return UNUSABLE; } } /** Loading @@ -243,8 +284,8 @@ public class PrecomputedText implements Spannable { return false; } Params param = (Params) o; return isSameTextMetricsInternal(param.mPaint, param.mTextDir, param.mBreakStrategy, param.mHyphenationFrequency); return checkResultUsable(param.mPaint, param.mTextDir, param.mBreakStrategy, param.mHyphenationFrequency) == Params.USABLE; } @Override Loading Loading @@ -321,11 +362,55 @@ public class PrecomputedText implements Spannable { * @return A {@link PrecomputedText} */ public static PrecomputedText create(@NonNull CharSequence text, @NonNull Params params) { ParagraphInfo[] paraInfo = createMeasuredParagraphs( ParagraphInfo[] paraInfo = null; if (text instanceof PrecomputedText) { final PrecomputedText hintPct = (PrecomputedText) text; final PrecomputedText.Params hintParams = hintPct.getParams(); final @Params.CheckResultUsableResult int checkResult = hintParams.checkResultUsable(params.mPaint, params.mTextDir, params.mBreakStrategy, params.mHyphenationFrequency); switch (checkResult) { case Params.USABLE: return hintPct; case Params.NEED_RECOMPUTE: // To be able to use PrecomputedText for new params, at least break strategy and // hyphenation frequency must be the same. if (params.getBreakStrategy() == hintParams.getBreakStrategy() && params.getHyphenationFrequency() == hintParams.getHyphenationFrequency()) { paraInfo = createMeasuredParagraphsFromPrecomputedText( hintPct, params, true /* compute layout */); } break; case Params.UNUSABLE: // Unable to use anything in PrecomputedText. Create PrecomputedText as the // normal text input. } } if (paraInfo == null) { paraInfo = createMeasuredParagraphs( text, params, 0, text.length(), true /* computeLayout */); } return new PrecomputedText(text, 0, text.length(), params, paraInfo); } private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText( @NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) { final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE; ArrayList<ParagraphInfo> result = new ArrayList<>(); for (int i = 0; i < pct.getParagraphCount(); ++i) { final int paraStart = pct.getParagraphStart(i); final int paraEnd = pct.getParagraphEnd(i); result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( params.getTextPaint(), pct, paraStart, paraEnd, params.getTextDirection(), needHyphenation, computeLayout, pct.getMeasuredParagraph(i), null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } /** @hide */ public static ParagraphInfo[] createMeasuredParagraphs( @NonNull CharSequence text, @NonNull Params params, Loading @@ -350,7 +435,8 @@ public class PrecomputedText implements Spannable { result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(), needHyphenation, computeLayout, null /* no recycle */))); needHyphenation, computeLayout, null /* no hint */, null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } Loading Loading @@ -434,12 +520,15 @@ public class PrecomputedText implements Spannable { * Returns true if the given TextPaint gives the same result of text layout for this text. * @hide */ public boolean canUseMeasuredResult(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint, @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { return mStart == start && mEnd == end && mParams.isSameTextMetricsInternal(paint, textDir, strategy, frequency); public @Params.CheckResultUsableResult int checkResultUsable(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint, @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { if (mStart != start || mEnd != end) { return Params.UNUSABLE; } else { return mParams.checkResultUsable(paint, textDir, strategy, frequency); } } /** @hide */ Loading
core/java/android/text/StaticLayout.java +20 −4 Original line number Diff line number Diff line Loading @@ -650,10 +650,26 @@ public class StaticLayout extends Layout { final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null; if (source instanceof PrecomputedText) { PrecomputedText precomputed = (PrecomputedText) source; if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint, b.mBreakStrategy, b.mHyphenationFrequency)) { final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.checkResultUsable(bufStart, bufEnd, textDir, paint, b.mBreakStrategy, b.mHyphenationFrequency); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: break; case PrecomputedText.Params.NEED_RECOMPUTE: final PrecomputedText.Params newParams = new PrecomputedText.Params.Builder(paint) .setBreakStrategy(b.mBreakStrategy) .setHyphenationFrequency(b.mHyphenationFrequency) .setTextDirection(textDir) .build(); precomputed = PrecomputedText.create(precomputed, newParams); paragraphInfo = precomputed.getParagraphInfo(); break; case PrecomputedText.Params.USABLE: // Some parameters are different from the ones when measured text is created. paragraphInfo = precomputed.getParagraphInfo(); break; } } Loading
core/java/android/widget/TextView.java +11 −3 Original line number Diff line number Diff line Loading @@ -6028,14 +6028,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } if (!precomputed.getParams().isSameTextMetricsInternal( getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency)) { final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: throw new IllegalArgumentException( "PrecomputedText's Parameters don't match the parameters of this TextView." + "Consider using setTextMetricsParams(precomputedText.getParams()) " + "to override the settings of this TextView: " + "PrecomputedText: " + precomputed.getParams() + "TextView: " + getTextMetricsParams()); case PrecomputedText.Params.NEED_RECOMPUTE: precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); break; case PrecomputedText.Params.USABLE: // pass through } } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); Loading