Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5019e276 authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Adjust the line height to the locale based font metrics

The new API setMinimumFontMetrics API is useful for reserving a
minimum amount of line spacing.

Bug: 303326708
Test: CtsGraphicsTestCases CtsTextTestCases
Change-Id: Ic25162b3a19d2b002b690560fde9512ea72bb492
parent d560c96b
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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);
+35 −8
Original line number Diff line number Diff line
@@ -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,
+7 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+44 −2
Original line number Diff line number Diff line
@@ -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;

@@ -314,6 +315,43 @@ public class DynamicLayout extends 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 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.
         *
@@ -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;
+64 −4
Original line number Diff line number Diff line
@@ -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;
    }
}
Loading