Merge "Add setLineBreakConfig API"

This commit is contained in:
James Lin
2022-01-19 00:31:43 +00:00
committed by Android (Google) Code Review
11 changed files with 353 additions and 43 deletions

View File

@@ -949,6 +949,7 @@ package android {
field public static final int left = 16843181; // 0x10101ad
field public static final int letterSpacing = 16843958; // 0x10104b6
field public static final int level = 16844032; // 0x1010500
field public static final int lineBreakStyle = 16844365; // 0x101064d
field public static final int lineHeight = 16844159; // 0x101057f
field public static final int lineSpacingExtra = 16843287; // 0x1010217
field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -17576,6 +17577,17 @@ package android.graphics.pdf {
package android.graphics.text {
public final class LineBreakConfig {
ctor public LineBreakConfig();
method public int getLineBreakStyle();
method public void set(@Nullable android.graphics.text.LineBreakConfig);
method public void setLineBreakStyle(int);
field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
}
public class LineBreaker {
method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int);
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
@@ -17631,6 +17643,7 @@ package android.graphics.text {
ctor public MeasuredText.Builder(@NonNull android.graphics.text.MeasuredText);
method @NonNull public android.graphics.text.MeasuredText.Builder appendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float);
method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, boolean);
method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean);
method @NonNull public android.graphics.text.MeasuredText build();
method @Deprecated @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean);
method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(int);
@@ -45434,6 +45447,7 @@ package android.text {
public static final class PrecomputedText.Params {
method public int getBreakStrategy();
method public int getHyphenationFrequency();
method @Nullable public android.graphics.text.LineBreakConfig getLineBreakConfig();
method @NonNull public android.text.TextDirectionHeuristic getTextDirection();
method @NonNull public android.text.TextPaint getTextPaint();
}
@@ -45444,6 +45458,7 @@ package android.text {
method @NonNull public android.text.PrecomputedText.Params build();
method public android.text.PrecomputedText.Params.Builder setBreakStrategy(int);
method public android.text.PrecomputedText.Params.Builder setHyphenationFrequency(int);
method @NonNull public android.text.PrecomputedText.Params.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method public android.text.PrecomputedText.Params.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
}
@@ -45604,6 +45619,7 @@ package android.text {
method @NonNull public android.text.StaticLayout.Builder setIncludePad(boolean);
method @NonNull public android.text.StaticLayout.Builder setIndents(@Nullable int[], @Nullable int[]);
method @NonNull public android.text.StaticLayout.Builder setJustificationMode(int);
method @NonNull public android.text.StaticLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method @NonNull public android.text.StaticLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int);
method public android.text.StaticLayout.Builder setText(CharSequence);
@@ -57682,6 +57698,7 @@ package android.widget {
method public final android.text.Layout getLayout();
method public float getLetterSpacing();
method public int getLineBounds(int, android.graphics.Rect);
method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
method public int getLineCount();
method public int getLineHeight();
method public float getLineSpacingExtra();
@@ -57809,6 +57826,7 @@ package android.widget {
method public void setKeyListener(android.text.method.KeyListener);
method public void setLastBaselineToBottomHeight(@IntRange(from=0) @Px int);
method public void setLetterSpacing(float);
method public void setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method public void setLineHeight(@IntRange(from=0) @Px int);
method public void setLineSpacing(float, float);
method public void setLines(int);

View File

@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.MeasuredText;
import android.text.AutoGrowArray.ByteArray;
import android.text.AutoGrowArray.FloatArray;
@@ -124,7 +125,7 @@ public class MeasuredParagraph {
// The native MeasuredParagraph.
private @Nullable MeasuredText mMeasuredText;
// Following two objects are for avoiding object allocation.
// Following three objects are for avoiding object allocation.
private @NonNull TextPaint mCachedPaint = new TextPaint();
private @Nullable Paint.FontMetricsInt mCachedFm;
@@ -350,7 +351,8 @@ public class MeasuredParagraph {
if (mt.mSpanned == null) {
// No style change by MetricsAffectingSpan. Just measure all text.
mt.applyMetricsAffectingSpan(
paint, null /* spans */, start, end, null /* native builder ptr */);
paint, null /* lineBreakConfig */, null /* spans */, start, end,
null /* native builder ptr */);
} else {
// There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
int spanEnd;
@@ -360,7 +362,8 @@ public class MeasuredParagraph {
MetricAffectingSpan.class);
spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
mt.applyMetricsAffectingSpan(
paint, spans, spanStart, spanEnd, null /* native builder ptr */);
paint, null /* line break config */, spans, spanStart, spanEnd,
null /* native builder ptr */);
}
}
return mt;
@@ -373,6 +376,7 @@ public class MeasuredParagraph {
* result to recycle and returns recycle.
*
* @param paint the paint to be used for rendering the text.
* @param lineBreakConfig the line break configuration for text wrapping.
* @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
@@ -386,6 +390,7 @@ public class MeasuredParagraph {
*/
public static @NonNull MeasuredParagraph buildForStaticLayout(
@NonNull TextPaint paint,
@Nullable LineBreakConfig lineBreakConfig,
@NonNull CharSequence text,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@@ -411,7 +416,8 @@ public class MeasuredParagraph {
} else {
if (mt.mSpanned == null) {
// No style change by MetricsAffectingSpan. Just measure all text.
mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, builder);
mt.applyMetricsAffectingSpan(paint, lineBreakConfig, null /* spans */, start, end,
builder);
mt.mSpanEndCache.append(end);
} else {
// There may be a MetricsAffectingSpan. Split into span transitions and apply
@@ -424,7 +430,9 @@ public class MeasuredParagraph {
MetricAffectingSpan.class);
spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
MetricAffectingSpan.class);
mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, builder);
// TODO: Update line break config with spans.
mt.applyMetricsAffectingSpan(paint, lineBreakConfig, spans, spanStart, spanEnd,
builder);
mt.mSpanEndCache.append(spanEnd);
}
}
@@ -500,12 +508,13 @@ public class MeasuredParagraph {
private void applyReplacementRun(@NonNull ReplacementSpan replacement,
@IntRange(from = 0) int start, // inclusive, in copied buffer
@IntRange(from = 0) int end, // exclusive, in copied buffer
@NonNull TextPaint paint,
@Nullable MeasuredText.Builder builder) {
// 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);
paint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
if (builder == null) {
// Assigns all width to the first character. This is the same behavior as minikin.
mWidths.set(start, width);
@@ -514,22 +523,24 @@ public class MeasuredParagraph {
}
mWholeWidth += width;
} else {
builder.appendReplacementRun(mCachedPaint, end - start, width);
builder.appendReplacementRun(paint, end - start, width);
}
}
private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer
@IntRange(from = 0) int end, // exclusive, in copied buffer
@NonNull TextPaint paint,
@Nullable LineBreakConfig config,
@Nullable MeasuredText.Builder builder) {
if (mLtrWithoutBidi) {
// If the whole text is LTR direction, just apply whole region.
if (builder == null) {
mWholeWidth += mCachedPaint.getTextRunAdvances(
mWholeWidth += paint.getTextRunAdvances(
mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
mWidths.getRawArray(), start);
} else {
builder.appendStyleRun(mCachedPaint, end - start, false /* isRtl */);
builder.appendStyleRun(paint, config, end - start, false /* isRtl */);
}
} else {
// If there is multiple bidi levels, split into individual bidi level and apply style.
@@ -541,11 +552,11 @@ public class MeasuredParagraph {
final boolean isRtl = (level & 0x1) != 0;
if (builder == null) {
final int levelLength = levelEnd - levelStart;
mWholeWidth += mCachedPaint.getTextRunAdvances(
mWholeWidth += paint.getTextRunAdvances(
mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
isRtl, mWidths.getRawArray(), levelStart);
} else {
builder.appendStyleRun(mCachedPaint, levelEnd - levelStart, isRtl);
builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl);
}
if (levelEnd == end) {
break;
@@ -559,6 +570,7 @@ public class MeasuredParagraph {
private void applyMetricsAffectingSpan(
@NonNull TextPaint paint,
@Nullable LineBreakConfig lineBreakConfig,
@Nullable MetricAffectingSpan[] spans,
@IntRange(from = 0) int start, // inclusive, in original text buffer
@IntRange(from = 0) int end, // exclusive, in original text buffer
@@ -595,9 +607,11 @@ public class MeasuredParagraph {
}
if (replacement != null) {
applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, builder);
applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, mCachedPaint,
builder);
} else {
applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, builder);
applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, mCachedPaint,
lineBreakConfig, builder);
}
if (needFontMetrics) {

View File

@@ -22,6 +22,7 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.MeasuredText;
import android.text.style.MetricAffectingSpan;
@@ -96,6 +97,9 @@ public class PrecomputedText implements Spannable {
// The hyphenation frequency for this measured text.
private final @Layout.HyphenationFrequency int mHyphenationFrequency;
// The line break configuration for calculating text wrapping.
private final @Nullable LineBreakConfig mLineBreakConfig;
/**
* A builder for creating {@link Params}.
*/
@@ -113,6 +117,9 @@ public class PrecomputedText implements Spannable {
private @Layout.HyphenationFrequency int mHyphenationFrequency =
Layout.HYPHENATION_FREQUENCY_NORMAL;
// The line break configuration for calculating text wrapping.
private @Nullable LineBreakConfig mLineBreakConfig;
/**
* Builder constructor.
*
@@ -130,6 +137,7 @@ public class PrecomputedText implements Spannable {
mTextDir = params.mTextDir;
mBreakStrategy = params.mBreakStrategy;
mHyphenationFrequency = params.mHyphenationFrequency;
mLineBreakConfig = params.mLineBreakConfig;
}
/**
@@ -176,25 +184,42 @@ public class PrecomputedText implements Spannable {
return this;
}
/**
* Set the line break config for the text wrapping.
*
* @param lineBreakConfig the newly line break configuration.
* @return this builder, useful for chaining.
* @see StaticLayout.Builder#setLineBreakConfig
*/
public @NonNull Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
mLineBreakConfig = lineBreakConfig;
return this;
}
/**
* Build the {@link Params}.
*
* @return the layout parameter
*/
public @NonNull Params build() {
return new Params(mPaint, mTextDir, mBreakStrategy, mHyphenationFrequency);
return new Params(mPaint, mLineBreakConfig, mTextDir, mBreakStrategy,
mHyphenationFrequency);
}
}
// This is public hidden for internal use.
// For the external developers, use Builder instead.
/** @hide */
public Params(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir,
@Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) {
public Params(@NonNull TextPaint paint,
@Nullable LineBreakConfig lineBreakConfig,
@NonNull TextDirectionHeuristic textDir,
@Layout.BreakStrategy int strategy,
@Layout.HyphenationFrequency int frequency) {
mPaint = paint;
mTextDir = textDir;
mBreakStrategy = strategy;
mHyphenationFrequency = frequency;
mLineBreakConfig = lineBreakConfig;
}
/**
@@ -233,6 +258,15 @@ public class PrecomputedText implements Spannable {
return mHyphenationFrequency;
}
/**
* Return the line break configuration for this text.
*
* @return the current line break configuration, null if no line break configuration is set.
*/
public @Nullable LineBreakConfig getLineBreakConfig() {
return mLineBreakConfig;
}
/** @hide */
@IntDef(value = { UNUSABLE, NEED_RECOMPUTE, USABLE })
@Retention(RetentionPolicy.SOURCE)
@@ -262,8 +296,9 @@ public class PrecomputedText implements Spannable {
/** @hide */
public @CheckResultUsableResult int checkResultUsable(@NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy,
@Layout.HyphenationFrequency int frequency) {
@Layout.HyphenationFrequency int frequency, @Nullable LineBreakConfig lbConfig) {
if (mBreakStrategy == strategy && mHyphenationFrequency == frequency
&& isLineBreakEquals(mLineBreakConfig, lbConfig)
&& mPaint.equalsForTextMeasurement(paint)) {
return mTextDir == textDir ? USABLE : NEED_RECOMPUTE;
} else {
@@ -271,6 +306,29 @@ public class PrecomputedText implements Spannable {
}
}
/**
* Check the two LineBreakConfig instances are equal.
* This method assumes they are equal if one parameter is null and the other parameter has
* a LineBreakStyle value of LineBreakConfig.LINE_BREAK_STYLE_NONE.
*
* @param o1 the first LineBreakConfig instance.
* @param o2 the second LineBreakConfig instance.
* @return true if the two LineBreakConfig instances are equal.
*/
private boolean isLineBreakEquals(LineBreakConfig o1, LineBreakConfig o2) {
if (Objects.equals(o1, o2)) {
return true;
}
if (o1 == null && (o2 != null
&& o2.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) {
return true;
} else if (o2 == null && (o1 != null
&& o1.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) {
return true;
}
return false;
}
/**
* Check if the same text layout.
*
@@ -286,21 +344,25 @@ public class PrecomputedText implements Spannable {
}
Params param = (Params) o;
return checkResultUsable(param.mPaint, param.mTextDir, param.mBreakStrategy,
param.mHyphenationFrequency) == Params.USABLE;
param.mHyphenationFrequency, param.mLineBreakConfig) == Params.USABLE;
}
@Override
public int hashCode() {
// TODO: implement MinikinPaint::hashCode and use it to keep consistency with equals.
int lineBreakStyle = (mLineBreakConfig != null)
? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE;
return Objects.hash(mPaint.getTextSize(), mPaint.getTextScaleX(), mPaint.getTextSkewX(),
mPaint.getLetterSpacing(), mPaint.getWordSpacing(), mPaint.getFlags(),
mPaint.getTextLocales(), mPaint.getTypeface(),
mPaint.getFontVariationSettings(), mPaint.isElegantTextHeight(), mTextDir,
mBreakStrategy, mHyphenationFrequency);
mBreakStrategy, mHyphenationFrequency, lineBreakStyle);
}
@Override
public String toString() {
int lineBreakStyle = (mLineBreakConfig != null)
? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE;
return "{"
+ "textSize=" + mPaint.getTextSize()
+ ", textScaleX=" + mPaint.getTextScaleX()
@@ -313,6 +375,7 @@ public class PrecomputedText implements Spannable {
+ ", textDir=" + mTextDir
+ ", breakStrategy=" + mBreakStrategy
+ ", hyphenationFrequency=" + mHyphenationFrequency
+ ", lineBreakStyle=" + lineBreakStyle
+ "}";
}
};
@@ -369,7 +432,8 @@ public class PrecomputedText implements Spannable {
final PrecomputedText.Params hintParams = hintPct.getParams();
final @Params.CheckResultUsableResult int checkResult =
hintParams.checkResultUsable(params.mPaint, params.mTextDir,
params.mBreakStrategy, params.mHyphenationFrequency);
params.mBreakStrategy, params.mHyphenationFrequency,
params.mLineBreakConfig);
switch (checkResult) {
case Params.USABLE:
return hintPct;
@@ -418,9 +482,9 @@ public class PrecomputedText implements Spannable {
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(),
hyphenationMode, computeLayout, pct.getMeasuredParagraph(i),
null /* no recycle */)));
params.getTextPaint(), params.getLineBreakConfig(), pct, paraStart, paraEnd,
params.getTextDirection(), hyphenationMode, computeLayout,
pct.getMeasuredParagraph(i), null /* no recycle */)));
}
return result.toArray(new ParagraphInfo[result.size()]);
}
@@ -456,8 +520,9 @@ public class PrecomputedText implements Spannable {
}
result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(),
hyphenationMode, computeLayout, null /* no hint */, null /* no recycle */)));
params.getTextPaint(), params.getLineBreakConfig(), text, paraStart, paraEnd,
params.getTextDirection(), hyphenationMode, computeLayout, null /* no hint */,
null /* no recycle */)));
}
return result.toArray(new ParagraphInfo[result.size()]);
}
@@ -544,11 +609,11 @@ public class PrecomputedText implements Spannable {
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) {
@Layout.HyphenationFrequency int frequency, @NonNull LineBreakConfig lbConfig) {
if (mStart != start || mEnd != end) {
return Params.UNUSABLE;
} else {
return mParams.checkResultUsable(paint, textDir, strategy, frequency);
return mParams.checkResultUsable(paint, textDir, strategy, frequency, lbConfig);
}
}

View File

@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Paint;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.LineBreaker;
import android.os.Build;
import android.text.style.LeadingMarginSpan;
@@ -402,6 +403,21 @@ public class StaticLayout extends Layout {
return this;
}
/**
* Set the line break configuration. The line break will be passed to native used for
* calculating the text wrapping. The default value of the line break style is
* {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}
*
* @param lineBreakConfig the line break configuration for text wrapping.
* @return this builder, useful for chaining.
* @see android.widget.TextView#setLineBreakConfig
*/
@NonNull
public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
mLineBreakConfig = lineBreakConfig;
return this;
}
/**
* Build the {@link StaticLayout} after options have been set.
*
@@ -438,6 +454,7 @@ public class StaticLayout extends Layout {
@Nullable private int[] mRightIndents;
private int mJustificationMode;
private boolean mAddLastLineLineSpacing;
private LineBreakConfig mLineBreakConfig;
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -670,7 +687,7 @@ public class StaticLayout extends Layout {
PrecomputedText precomputed = (PrecomputedText) source;
final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
precomputed.checkResultUsable(bufStart, bufEnd, textDir, paint,
b.mBreakStrategy, b.mHyphenationFrequency);
b.mBreakStrategy, b.mHyphenationFrequency, b.mLineBreakConfig);
switch (checkResult) {
case PrecomputedText.Params.UNUSABLE:
break;
@@ -680,6 +697,7 @@ public class StaticLayout extends Layout {
.setBreakStrategy(b.mBreakStrategy)
.setHyphenationFrequency(b.mHyphenationFrequency)
.setTextDirection(textDir)
.setLineBreakConfig(b.mLineBreakConfig)
.build();
precomputed = PrecomputedText.create(precomputed, newParams);
paragraphInfo = precomputed.getParagraphInfo();
@@ -692,8 +710,8 @@ public class StaticLayout extends Layout {
}
if (paragraphInfo == null) {
final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
b.mBreakStrategy, b.mHyphenationFrequency);
final PrecomputedText.Params param = new PrecomputedText.Params(paint,
b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
bufEnd, false /* computeLayout */);
}

View File

@@ -79,6 +79,7 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.graphics.text.LineBreakConfig;
import android.icu.text.DecimalFormatSymbols;
import android.os.AsyncTask;
import android.os.Build;
@@ -348,6 +349,7 @@ import java.util.function.Supplier;
* @attr ref android.R.styleable#TextView_fontVariationSettings
* @attr ref android.R.styleable#TextView_breakStrategy
* @attr ref android.R.styleable#TextView_hyphenationFrequency
* @attr ref android.R.styleable#TextView_lineBreakStyle
* @attr ref android.R.styleable#TextView_autoSizeTextType
* @attr ref android.R.styleable#TextView_autoSizeMinTextSize
* @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
@@ -775,6 +777,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private Layout mLayout;
private boolean mLocalesChanged = false;
private int mTextSizeUnit = -1;
private LineBreakConfig mLineBreakConfig = new LineBreakConfig();
// This is used to reflect the current user preference for changing font weight and making text
// more bold.
@@ -1442,6 +1445,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
break;
case com.android.internal.R.styleable.TextView_lineBreakStyle:
mLineBreakConfig.setLineBreakStyle(
a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE));
break;
case com.android.internal.R.styleable.TextView_autoSizeTextType:
mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
break;
@@ -4787,6 +4795,42 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mHyphenationFrequency;
}
/**
* Sets line break configuration indicates which strategy needs to be used when calculating the
* text wrapping. There are thee strategies for the line break style(lb):
* {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
* {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
* {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}.
* The default value of the line break style is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE},
* which means no line break style is specified.
* See <a href="https://drafts.csswg.org/css-text/#line-break-property">
* the line-break property</a>
*
* @param lineBreakConfig the line break config for text wrapping.
*/
public void setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
if (mLineBreakConfig.equals(lineBreakConfig)) {
return;
}
mLineBreakConfig.set(lineBreakConfig);
if (mLayout != null) {
nullLayouts();
requestLayout();
invalidate();
}
}
/**
* Get the current line break configuration for text wrapping.
*
* @return the current line break configuration to be used for text wrapping.
*/
public @NonNull LineBreakConfig getLineBreakConfig() {
LineBreakConfig lbConfig = new LineBreakConfig();
lbConfig.set(mLineBreakConfig);
return lbConfig;
}
/**
* Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
*
@@ -4794,7 +4838,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see PrecomputedText
*/
public @NonNull PrecomputedText.Params getTextMetricsParams() {
return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
return new PrecomputedText.Params(new TextPaint(mTextPaint), mLineBreakConfig,
getTextDirectionHeuristic(),
mBreakStrategy, mHyphenationFrequency);
}
@@ -4810,6 +4855,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mTextDir = params.getTextDirection();
mBreakStrategy = params.getBreakStrategy();
mHyphenationFrequency = params.getHyphenationFrequency();
mLineBreakConfig.set(params.getLineBreakConfig());
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -6348,7 +6394,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
mHyphenationFrequency);
mHyphenationFrequency, mLineBreakConfig);
switch (checkResult) {
case PrecomputedText.Params.UNUSABLE:
throw new IllegalArgumentException(
@@ -9244,7 +9290,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setBreakStrategy(mBreakStrategy)
.setHyphenationFrequency(mHyphenationFrequency)
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(mLineBreakConfig);
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -9358,7 +9405,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setBreakStrategy(mBreakStrategy)
.setHyphenationFrequency(mHyphenationFrequency)
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(mLineBreakConfig);
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -9725,7 +9773,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setHyphenationFrequency(getHyphenationFrequency())
.setJustificationMode(getJustificationMode())
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setTextDirection(getTextDirectionHeuristic());
.setTextDirection(getTextDirectionHeuristic())
.setLineBreakConfig(mLineBreakConfig);
final StaticLayout layout = layoutBuilder.build();

View File

@@ -5417,6 +5417,17 @@
ignores some hyphen character related typographic features, e.g. kerning. -->
<enum name="fullFast" value="4" />
</attr>
<!-- Indicates the line break strategies can be used when calculating the text wrapping. -->
<attr name="lineBreakStyle">
<!-- No line break style specific. -->
<enum name="none" value="0" />
<!-- Use the least restrictive rule for line-breaking. -->
<enum name="loose" value="1" />
<!-- Indicate breaking text with the most comment set of line-breaking rules. -->
<enum name="normal" value="2" />
<!-- ndicates breaking text with the most strictest line-breaking rules. -->
<enum name="strict" value="3" />
</attr>
<!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
works only for TextView. -->
<attr name="autoSizeTextType" format="enum">

View File

@@ -3211,6 +3211,7 @@
</staging-public-group-final>
<public type="attr" name="shouldUseDefaultUnfoldTransition" id="0x0101064c" />
<public type="attr" name="lineBreakStyle" id="0x0101064d" />
<staging-public-group-final type="id" first-id="0x01fe0000">
<public name="accessibilityActionDragStart" />

View File

@@ -136,8 +136,8 @@ public class MeasuredParagraphTest {
MeasuredParagraph mt = null;
mt = MeasuredParagraph.buildForStaticLayout(
PAINT, "XXX", 0, 3, LTR, MeasuredText.Builder.HYPHENATION_MODE_NONE, false,
null /* no hint */, null);
PAINT, null /* line break config */, "XXX", 0, 3, LTR,
MeasuredText.Builder.HYPHENATION_MODE_NONE, false, null /* no hint */, null);
assertNotNull(mt);
assertNotNull(mt.getChars());
assertEquals("XXX", charsToString(mt.getChars()));
@@ -152,8 +152,8 @@ public class MeasuredParagraphTest {
// Recycle it
MeasuredParagraph mt2 = MeasuredParagraph.buildForStaticLayout(
PAINT, "_VVV_", 1, 4, RTL, MeasuredText.Builder.HYPHENATION_MODE_NONE, false,
null /* no hint */, mt);
PAINT, null /* line break config */, "_VVV_", 1, 4, RTL,
MeasuredText.Builder.HYPHENATION_MODE_NONE, false, null /* no hint */, mt);
assertEquals(mt2, mt);
assertNotNull(mt2.getChars());
assertEquals("VVV", charsToString(mt.getChars()));

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2022 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.graphics.text;
import android.annotation.IntDef;
import android.annotation.Nullable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* Indicates the strategies can be used when calculating the text wrapping.
*
* See <a href="https://drafts.csswg.org/css-text/#line-break-property">the line-break property</a>
*/
public final class LineBreakConfig {
/**
* No line break style specified.
*/
public static final int LINE_BREAK_STYLE_NONE = 0;
/**
* Use the least restrictive rule for line-breaking. This is usually used for short lines.
*/
public static final int LINE_BREAK_STYLE_LOOSE = 1;
/**
* Indicate breaking text with the most comment set of line-breaking rules.
*/
public static final int LINE_BREAK_STYLE_NORMAL = 2;
/**
* Indicates breaking text with the most strictest line-breaking rules.
*/
public static final int LINE_BREAK_STYLE_STRICT = 3;
/** @hide */
@IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = {
LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL,
LINE_BREAK_STYLE_STRICT
})
@Retention(RetentionPolicy.SOURCE)
public @interface LineBreakStyle {}
private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE;
public LineBreakConfig() {
}
/**
* Set the line break configuration.
*
* @param config the new line break configuration.
*/
public void set(@Nullable LineBreakConfig config) {
if (config != null) {
mLineBreakStyle = config.getLineBreakStyle();
} else {
mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
}
}
/**
* Get the line break style.
*
* @return The current line break style to be used for the text wrapping.
*/
public @LineBreakStyle int getLineBreakStyle() {
return mLineBreakStyle;
}
/**
* Set the line break style.
*
* @param lineBreakStyle the new line break style.
*/
public void setLineBreakStyle(@LineBreakStyle int lineBreakStyle) {
mLineBreakStyle = lineBreakStyle;
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (this == o) return true;
if (!(o instanceof LineBreakConfig)) return false;
LineBreakConfig that = (LineBreakConfig) o;
return mLineBreakStyle == that.mLineBreakStyle;
}
@Override
public int hashCode() {
return Objects.hash(mLineBreakStyle);
}
}

View File

@@ -239,11 +239,33 @@ public class MeasuredText {
*/
public @NonNull Builder appendStyleRun(@NonNull Paint paint, @IntRange(from = 0) int length,
boolean isRtl) {
return appendStyleRun(paint, null, length, isRtl);
}
/**
* Apply styles to the given length.
*
* Keeps an internal offset which increases at every append. The initial value for this
* offset is zero. After the style is applied the internal offset is moved to {@code offset
* + length}, and next call will start from this new position.
*
* @param paint a paint
* @param lineBreakConfig a line break configuration.
* @param length a length to be applied with a given paint, can not exceed the length of the
* text
* @param isRtl true if the text is in RTL context, otherwise false.
*/
public @NonNull Builder appendStyleRun(@NonNull Paint paint,
@Nullable LineBreakConfig lineBreakConfig, @IntRange(from = 0) int length,
boolean isRtl) {
Preconditions.checkNotNull(paint);
Preconditions.checkArgument(length > 0, "length can not be negative");
final int end = mCurrentOffset + length;
Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
nAddStyleRun(mNativePtr, paint.getNativeInstance(), mCurrentOffset, end, isRtl);
int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() :
LineBreakConfig.LINE_BREAK_STYLE_NONE;
nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, mCurrentOffset, end,
isRtl);
mCurrentOffset = end;
return this;
}
@@ -423,12 +445,14 @@ public class MeasuredText {
*
* @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
* @param paintPtr The native paint pointer to be applied.
* @param lineBreakStyle The line break style of the text.
* @param start The start offset in the copied buffer.
* @param end The end offset in the copied buffer.
* @param isRtl True if the text is RTL.
*/
private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
/* Non Zero */ long paintPtr,
int lineBreakStyle,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
boolean isRtl);

View File

@@ -65,11 +65,11 @@ static jlong nInitBuilder(CRITICAL_JNI_PARAMS) {
// Regular JNI
static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
jlong paintPtr, jint start, jint end, jboolean isRtl) {
jlong paintPtr, jint lbStyle, jint start, jint end, jboolean isRtl) {
Paint* paint = toPaint(paintPtr);
const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), isRtl);
toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), lbStyle, isRtl);
}
// Regular JNI
@@ -144,7 +144,7 @@ static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
static const JNINativeMethod gMTBuilderMethods[] = {
// MeasuredParagraphBuilder native functions.
{"nInitBuilder", "()J", (void*)nInitBuilder},
{"nAddStyleRun", "(JJIIZ)V", (void*)nAddStyleRun},
{"nAddStyleRun", "(JJIIIZ)V", (void*)nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
{"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
{"nFreeBuilder", "(J)V", (void*)nFreeBuilder},