diff --git a/core/api/current.txt b/core/api/current.txt
index 1c574f22890a2..735de5421f165 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -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);
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 6a3c6182b96df..748d55123b9a4 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -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) {
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 152570ffd1c7a..ce63376a6d631 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -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);
}
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index b1bc7667da165..b10fc37bff2fd 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -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 */);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1a808b2e7c248..0fe06befa7893 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -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
+ * the line-break property
+ *
+ * @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();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a08bca3b3fe94..e232d858bb434 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5417,6 +5417,17 @@
ignores some hyphen character related typographic features, e.g. kerning. -->
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e801ac0ba71a6..e83f4a66883c3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3211,6 +3211,7 @@
+
diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index d6a7682475f22..045e746281a6e 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -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()));
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
new file mode 100644
index 0000000000000..4d818583fd236
--- /dev/null
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -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 the line-break property
+ */
+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);
+ }
+}
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index a34d0abcf753c..5f4afb7b98880 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -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);
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index bd9bd7121f8a0..09539ecc34b0e 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -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},