summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt8
-rw-r--r--core/java/android/text/BoringLayout.java43
-rw-r--r--core/java/android/text/ClientFlags.java7
-rw-r--r--core/java/android/text/DynamicLayout.java46
-rw-r--r--core/java/android/text/Layout.java68
-rw-r--r--core/java/android/text/StaticLayout.java89
-rw-r--r--core/java/android/text/TextFlags.java2
-rw-r--r--core/java/android/widget/TextView.java86
-rw-r--r--graphics/java/android/graphics/Paint.java52
9 files changed, 371 insertions, 30 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 639a99134d3d..9c408bb79449 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16259,6 +16259,8 @@ package android.graphics {
public static class Paint.FontMetricsInt {
ctor public Paint.FontMetricsInt();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetricsInt);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetrics);
field public int ascent;
field public int bottom;
field public int descent;
@@ -46718,6 +46720,7 @@ package android.text {
method @NonNull public android.text.DynamicLayout.Builder setJustificationMode(int);
method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method @NonNull public android.text.DynamicLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.DynamicLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean);
method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
@@ -46906,6 +46909,7 @@ package android.text {
method public int getLineVisibleEnd(int);
method public float getLineWidth(int);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @IntRange(from=1) public final int getMaxLines();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
method public int getOffsetForHorizontal(int, float);
method public int getOffsetToLeftOf(int);
method public int getOffsetToRightOf(int);
@@ -46972,6 +46976,7 @@ package android.text {
method @NonNull public android.text.Layout.Builder setLineSpacingAmount(float);
method @NonNull public android.text.Layout.Builder setLineSpacingMultiplier(@FloatRange(from=0) float);
method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.Layout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]);
method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
@@ -47243,6 +47248,7 @@ package android.text {
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 @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.StaticLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method public android.text.StaticLayout.Builder setText(CharSequence);
method @NonNull public android.text.StaticLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean);
@@ -59928,6 +59934,7 @@ package android.widget {
method public int getMinHeight();
method public int getMinLines();
method public int getMinWidth();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
method public final android.text.method.MovementMethod getMovementMethod();
method public int getOffsetForPosition(float, float);
method public android.text.TextPaint getPaint();
@@ -60064,6 +60071,7 @@ package android.widget {
method public void setMinHeight(int);
method public void setMinLines(int);
method public void setMinWidth(int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method public final void setMovementMethod(android.text.method.MovementMethod);
method public void setOnEditorActionListener(android.widget.TextView.OnEditorActionListener);
method public void setPaintFlags(int);
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 65a1da6b81b8..4c8188801eff 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -190,7 +190,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
@Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
boolean useFallbackLineSpacing) {
return replaceOrMake(source, paint, outerWidth, align, 1.0f, 0.0f, metrics, includePad,
- ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */);
+ ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */,
+ null /* minimumFontMetrics */);
}
/** @hide */
@@ -199,7 +200,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
@NonNull Alignment align, float spacingMultiplier, float spacingAmount,
@NonNull BoringLayout.Metrics metrics, boolean includePad,
@Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
- boolean useFallbackLineSpacing, boolean useBoundsForWidth) {
+ boolean useFallbackLineSpacing, boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
boolean trust;
if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
@@ -270,7 +272,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
spacingAdd, includePad, false /* fallbackLineSpacing */,
outerwidth /* ellipsizedWidth */, null /* ellipsize */, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
- null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+ null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false,
+ null);
mEllipsizedWidth = outerwidth;
mEllipsizedStart = 0;
@@ -343,7 +346,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
ellipsizedWidth, ellipsize, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
null /* rightIndents */, JUSTIFICATION_MODE_NONE,
- LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */);
+ LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */, null);
}
/** @hide */
@@ -359,12 +362,13 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
int ellipsizedWidth,
TextUtils.TruncateAt ellipsize,
Metrics metrics,
- boolean useBoundsForWidth) {
+ boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
this(text, paint, width, align, TextDirectionHeuristics.LTR,
spacingMult, spacingAdd, includePad, fallbackLineSpacing, ellipsizedWidth,
ellipsize, 1 /* maxLines */, Layout.BREAK_STRATEGY_SIMPLE,
Layout.HYPHENATION_FREQUENCY_NONE, null, null, Layout.JUSTIFICATION_MODE_NONE,
- LineBreakConfig.NONE, metrics, useBoundsForWidth);
+ LineBreakConfig.NONE, metrics, useBoundsForWidth, minimumFontMetrics);
}
/* package */ BoringLayout(
@@ -387,12 +391,13 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
int justificationMode,
LineBreakConfig lineBreakConfig,
Metrics metrics,
- boolean useBoundsForWidth) {
+ boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
super(text, paint, width, align, textDir, spacingMult, spacingAdd, includePad,
fallbackLineSpacing, ellipsizedWidth, ellipsize, maxLines, breakStrategy,
hyphenationFrequency, leftIndents, rightIndents, justificationMode,
- lineBreakConfig, useBoundsForWidth);
+ lineBreakConfig, useBoundsForWidth, minimumFontMetrics);
boolean trust;
@@ -548,6 +553,15 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Metrics metrics) {
+ return isBoring(text, paint, textDir, useFallbackLineSpacing, null, metrics);
+ }
+
+ /**
+ * @hide
+ */
+ public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
+ @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
+ @Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
final int textLength = text.length();
if (hasAnyInterestingChars(text, textLength)) {
return null; // There are some interesting characters. Not boring.
@@ -570,6 +584,19 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
fm.reset();
}
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (minimumFontMetrics == null) {
+ paint.getFontMetricsIntForLocale(fm);
+ } else {
+ fm.set(minimumFontMetrics);
+ // Because the font metrics is provided by public APIs, adjust the top/bottom with
+ // ascent/descent: top must be smaller than ascent, bottom must be larger than
+ // descent.
+ fm.top = Math.min(fm.top, fm.ascent);
+ fm.bottom = Math.max(fm.bottom, fm.descent);
+ }
+ }
+
TextLine line = TextLine.obtain();
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index e17a955121b0..0421d5aaa69b 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -47,4 +47,11 @@ public class ClientFlags {
public static boolean useBoundsForWidth() {
return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH);
}
+
+ /**
+ * @see Flags#fixLineHeightForLocale()
+ */
+ public static boolean fixLineHeightForLocale() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
+ }
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index a0cd0748f509..7b9cb6afd6a0 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
@@ -315,6 +316,43 @@ public class DynamicLayout extends Layout {
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the
+ * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+ * used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
+ /**
* Build the {@link DynamicLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this method.
@@ -347,6 +385,7 @@ public class DynamicLayout extends Layout {
private int mEllipsizedWidth;
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
+ private @Nullable Paint.FontMetrics mMinimumFontMetrics;
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -422,7 +461,7 @@ public class DynamicLayout extends Layout {
false /* fallbackLineSpacing */, ellipsizedWidth, ellipsize,
Integer.MAX_VALUE /* maxLines */, breakStrategy, hyphenationFrequency,
null /* leftIndents */, null /* rightIndents */, justificationMode,
- lineBreakConfig, false /* useBoundsForWidth */);
+ lineBreakConfig, false /* useBoundsForWidth */, null /* minimumFontMetrics */);
final Builder b = Builder.obtain(base, paint, width)
.setAlignment(align)
@@ -448,7 +487,7 @@ public class DynamicLayout extends Layout {
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
Integer.MAX_VALUE /* maxLines */, b.mBreakStrategy, b.mHyphenationFrequency,
null /* leftIndents */, null /* rightIndents */, b.mJustificationMode,
- b.mLineBreakConfig, b.mUseBoundsForWidth);
+ b.mLineBreakConfig, b.mUseBoundsForWidth, b.mMinimumFontMetrics);
mDisplay = b.mDisplay;
mIncludePad = b.mIncludePad;
@@ -476,6 +515,7 @@ public class DynamicLayout extends Layout {
mBase = b.mBase;
mFallbackLineSpacing = b.mFallbackLineSpacing;
mUseBoundsForWidth = b.mUseBoundsForWidth;
+ mMinimumFontMetrics = b.mMinimumFontMetrics;
if (b.mEllipsize != null) {
mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
mEllipsizedWidth = b.mEllipsizedWidth;
@@ -672,6 +712,7 @@ public class DynamicLayout extends Layout {
.setAddLastLineLineSpacing(!islast)
.setIncludePad(false)
.setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics)
.setCalculateBounds(true);
reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
@@ -1324,6 +1365,7 @@ public class DynamicLayout extends Layout {
private Rect mTempRect = new Rect();
private boolean mUseBoundsForWidth;
+ @Nullable Paint.FontMetrics mMinimumFontMetrics;
@UnsupportedAppUsage
private static StaticLayout sStaticLayout = null;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 4f4dea780171..47c29d968558 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.annotation.FlaggedApi;
@@ -287,7 +288,7 @@ public abstract class Layout {
this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
spacingMult, spacingAdd, false, false, 0, null, Integer.MAX_VALUE,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null, null,
- JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+ JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false, null);
}
/**
@@ -336,7 +337,8 @@ public abstract class Layout {
int[] rightIndents,
int justificationMode,
LineBreakConfig lineBreakConfig,
- boolean useBoundsForWidth
+ boolean useBoundsForWidth,
+ Paint.FontMetrics minimumFontMetrics
) {
if (width < 0)
@@ -371,6 +373,7 @@ public abstract class Layout {
mJustificationMode = justificationMode;
mLineBreakConfig = lineBreakConfig;
mUseBoundsForWidth = useBoundsForWidth;
+ mMinimumFontMetrics = minimumFontMetrics;
}
/**
@@ -3332,6 +3335,7 @@ public abstract class Layout {
private int mJustificationMode;
private LineBreakConfig mLineBreakConfig;
private boolean mUseBoundsForWidth;
+ private @Nullable Paint.FontMetrics mMinimumFontMetrics;
/** @hide */
@IntDef(prefix = { "DIR_" }, value = {
@@ -3787,12 +3791,48 @@ public abstract class Layout {
return this;
}
+ /**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left it as default, the font
+ * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
private BoringLayout.Metrics isBoring() {
if (mStart != 0 || mEnd != mText.length()) { // BoringLayout only support entire text.
return null;
}
BoringLayout.Metrics metrics = BoringLayout.isBoring(mText, mPaint, mTextDir,
- mFallbackLineSpacing, null);
+ mFallbackLineSpacing, mMinimumFontMetrics, null);
if (metrics == null) {
return null;
}
@@ -3833,7 +3873,8 @@ public abstract class Layout {
mText, mPaint, mWidth, mAlignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad, mFallbackLineSpacing, mEllipsizedWidth, mEllipsize, mMaxLines,
mBreakStrategy, mHyphenationFrequency, mLeftIndents, mRightIndents,
- mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth);
+ mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
}
@@ -3858,6 +3899,7 @@ public abstract class Layout {
private int mJustificationMode = JUSTIFICATION_MODE_NONE;
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
+ private Paint.FontMetrics mMinimumFontMetrics;
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -4164,4 +4206,22 @@ public abstract class Layout {
public boolean getUseBoundsForWidth() {
return mUseBoundsForWidth;
}
+
+ /**
+ * Get the minimum font metrics used for line spacing.
+ *
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ *
+ * @return a minimum font metrics. {@code null} for using the value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ */
+ @Nullable
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Paint.FontMetrics getMinimumFontMetrics() {
+ return mMinimumFontMetrics;
+ }
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 01279cea073f..77e616b358cb 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.annotation.FlaggedApi;
@@ -460,6 +461,43 @@ public class StaticLayout extends Layout {
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the
+ * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+ * used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
+ /**
* Build the {@link StaticLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this
@@ -520,6 +558,7 @@ public class StaticLayout extends Layout {
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
private boolean mCalculateBounds;
+ @Nullable private Paint.FontMetrics mMinimumFontMetrics;
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -550,7 +589,8 @@ public class StaticLayout extends Layout {
null, // rightIndents
JUSTIFICATION_MODE_NONE,
null, // lineBreakConfig,
- false // useBoundsForWidth
+ false, // useBoundsForWidth
+ null // minimumFontMetrics
);
mColumns = COLUMNS_ELLIPSIZE;
@@ -627,7 +667,8 @@ public class StaticLayout extends Layout {
b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
- b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth);
+ b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth,
+ b.mMinimumFontMetrics);
mColumns = columnSize;
if (b.mEllipsize != null) {
@@ -711,6 +752,35 @@ public class StaticLayout extends Layout {
indents = null;
}
+ int defaultTop;
+ int defaultAscent;
+ int defaultDescent;
+ int defaultBottom;
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (b.mMinimumFontMetrics != null) {
+ defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
+ defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
+ defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
+ defaultBottom = (int) Math.ceil(b.mMinimumFontMetrics.bottom);
+ } else {
+ paint.getFontMetricsIntForLocale(fm);
+ defaultTop = fm.top;
+ defaultAscent = fm.ascent;
+ defaultDescent = fm.descent;
+ defaultBottom = fm.bottom;
+ }
+
+ // Because the font metrics is provided by public APIs, adjust the top/bottom with
+ // ascent/descent: top must be smaller than ascent, bottom must be larger than descent.
+ defaultTop = Math.min(defaultTop, defaultAscent);
+ defaultBottom = Math.max(defaultBottom, defaultDescent);
+ } else {
+ defaultTop = 0;
+ defaultAscent = 0;
+ defaultDescent = 0;
+ defaultBottom = 0;
+ }
+
final LineBreaker lineBreaker = new LineBreaker.Builder()
.setBreakStrategy(b.mBreakStrategy)
.setHyphenationFrequency(getBaseHyphenationFrequency(b.mHyphenationFrequency))
@@ -889,7 +959,10 @@ public class StaticLayout extends Layout {
// measuring
int here = paraStart;
- int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+ int fmTop = defaultTop;
+ int fmBottom = defaultBottom;
+ int fmAscent = defaultAscent;
+ int fmDescent = defaultDescent;
int fmCacheIndex = 0;
int spanEndCacheIndex = 0;
int breakIndex = 0;
@@ -982,7 +1055,15 @@ public class StaticLayout extends Layout {
&& mLineCount < mMaximumVisibleLineCount) {
final MeasuredParagraph measuredPara =
MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
- paint.getFontMetricsInt(fm);
+ if (ClientFlags.fixLineHeightForLocale()) {
+ fm.top = defaultTop;
+ fm.ascent = defaultAscent;
+ fm.descent = defaultDescent;
+ fm.bottom = defaultBottom;
+ } else {
+ paint.getFontMetricsInt(fm);
+ }
+
v = out(source,
bufEnd, bufEnd, fm.ascent, fm.descent,
fm.top, fm.bottom,
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index b8b30c230e5e..24663862400d 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -58,6 +58,7 @@ public final class TextFlags {
Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN,
Flags.FLAG_PHRASE_STRICT_FALLBACK,
Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
+ Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
};
/**
@@ -69,6 +70,7 @@ public final class TextFlags {
Flags.noBreakNoHyphenationSpan(),
Flags.phraseStrictFallback(),
Flags.useBoundsForWidth(),
+ Flags.fixLineHeightForLocale(),
};
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a0628c4b9580..6da6a64dc042 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -28,6 +28,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.R;
@@ -865,6 +866,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private final boolean mUseTextPaddingForUiTranslation;
private boolean mUseBoundsForWidth;
+ @Nullable private Paint.FontMetrics mMinimumFontMetrics;
@ViewDebug.ExportedProperty(category = "text")
@UnsupportedAppUsage
@@ -4901,6 +4903,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the font
+ * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin script.
+ * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese,
+ * the line spacing for Japanese is reserved if the TextView contains English text. If the
+ * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font
+ * metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value
+ * obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see #getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ }
+
+ /**
+ * Get the minimum font metrics used for line spacing.
+ *
+ * @see #setMinimumFontMetrics(Paint.FontMetrics)
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ *
+ * @return a minimum font metrics. {@code null} for using the value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ */
+ @Nullable
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Paint.FontMetrics getMinimumFontMetrics() {
+ return mMinimumFontMetrics;
+ }
+
+ /**
* @return whether fallback line spacing is enabled, {@code true} by default
*
* @see #setFallbackLineSpacing(boolean)
@@ -10683,7 +10737,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (hintBoring == UNKNOWN_BORING) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mHintBoring);
+ isFallbackLineSpacingForBoringLayout(),
+ mMinimumFontMetrics, mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -10732,7 +10787,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -10796,12 +10852,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mLineBreakStyle, mLineBreakWordStyle))
.setUseBoundsForWidth(mUseBoundsForWidth)
.setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
- .setEllipsizedWidth(ellipsisWidth);
+ .setEllipsizedWidth(ellipsisWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
result = builder.build();
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -10815,7 +10872,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, null, wantWidth,
isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth);
+ mUseBoundsForWidth, mMinimumFontMetrics);
} else {
result = new BoringLayout(
mTransformed,
@@ -10829,7 +10886,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
wantWidth,
null,
boring,
- mUseBoundsForWidth);
+ mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
if (useSaved) {
@@ -10841,7 +10899,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth, isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth);
+ mUseBoundsForWidth, mMinimumFontMetrics);
} else {
result = new BoringLayout(
mTransformed,
@@ -10855,7 +10913,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ellipsisWidth,
effectiveEllipsize,
boring,
- mUseBoundsForWidth);
+ mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
}
}
@@ -10874,7 +10933,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -11002,7 +11062,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (des < 0) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -11042,7 +11102,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (hintDes < 0) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mHintBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics,
+ mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -11254,7 +11315,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setTextDirection(getTextDirectionHeuristic())
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
final StaticLayout layout = layoutBuilder.build();
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 9fde0fd6e6ab..4eaa01309ab1 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2113,6 +2113,31 @@ public class Paint {
* The recommended additional space to add between lines of text.
*/
public float leading;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof FontMetrics)) return false;
+ FontMetrics that = (FontMetrics) o;
+ return that.top == top && that.ascent == ascent && that.descent == descent
+ && that.bottom == bottom && that.leading == leading;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(top, ascent, descent, bottom, leading);
+ }
+
+ @Override
+ public String toString() {
+ return "FontMetrics{"
+ + "top=" + top
+ + ", ascent=" + ascent
+ + ", descent=" + descent
+ + ", bottom=" + bottom
+ + ", leading=" + leading
+ + '}';
+ }
}
/**
@@ -2309,6 +2334,33 @@ public class Paint {
*/
public int leading;
+ /**
+ * Set values from {@link FontMetricsInt}.
+ * @param fontMetricsInt a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetricsInt fontMetricsInt) {
+ top = fontMetricsInt.top;
+ ascent = fontMetricsInt.ascent;
+ descent = fontMetricsInt.descent;
+ bottom = fontMetricsInt.bottom;
+ leading = fontMetricsInt.leading;
+ }
+
+ /**
+ * Set values from {@link FontMetrics} with rounding accordingly.
+ * @param fontMetrics a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetrics fontMetrics) {
+ // See GraphicsJNI::set_metrics_int method for consistency.
+ top = (int) Math.floor(fontMetrics.top);
+ ascent = Math.round(fontMetrics.ascent);
+ descent = Math.round(fontMetrics.descent);
+ bottom = (int) Math.ceil(fontMetrics.bottom);
+ leading = Math.round(fontMetrics.leading);
+ }
+
@Override public String toString() {
return "FontMetricsInt: top=" + top + " ascent=" + ascent +
" descent=" + descent + " bottom=" + bottom +