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

Commit 6e39dafc authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Add bounding box based text layout APIs.

This CL adds bounding box based text layout features.
By setting setUseBoundsForWidth, the line break width and drawing
offset is adjusted based on bounding box instead of advances.

Bug: 63938206
Test: atest CtsTextTestCases

Change-Id: I993c455eee1b4100656db9aef38675e3cda3309d
parent e5ef19d4
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -17679,6 +17679,7 @@ package android.graphics.text {
    method @NonNull public android.graphics.text.LineBreaker.Builder setHyphenationFrequency(int);
    method @NonNull public android.graphics.text.LineBreaker.Builder setIndents(@Nullable int[]);
    method @NonNull public android.graphics.text.LineBreaker.Builder setJustificationMode(int);
    method @NonNull public android.graphics.text.LineBreaker.Builder setUseBoundsForWidth(boolean);
  }
  public static class LineBreaker.ParagraphConstraints {
@@ -47302,6 +47303,7 @@ package android.text {
  public static class BoringLayout.Metrics extends android.graphics.Paint.FontMetricsInt {
    ctor public BoringLayout.Metrics();
    method @NonNull public android.graphics.RectF getDrawingBoundingBox();
    field public int width;
  }
@@ -47343,6 +47345,7 @@ package android.text {
    method @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 @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
    method @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean);
    method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
  }
@@ -47485,6 +47488,7 @@ package android.text {
  public abstract class Layout {
    ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float);
    method @NonNull public android.graphics.RectF computeDrawingBoundingBox();
    method public void draw(android.graphics.Canvas);
    method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int);
    method public void draw(@NonNull android.graphics.Canvas, @Nullable java.util.List<android.graphics.Path>, @Nullable java.util.List<android.graphics.Paint>, @Nullable android.graphics.Path, @Nullable android.graphics.Paint, int);
@@ -47546,6 +47550,7 @@ package android.text {
    method @NonNull public final CharSequence getText();
    method @NonNull public final android.text.TextDirectionHeuristic getTextDirectionHeuristic();
    method public abstract int getTopPadding();
    method public boolean getUseBoundsForWidth();
    method @IntRange(from=0) public final int getWidth();
    method public final void increaseWidthTo(int);
    method public boolean isFallbackLineSpacingEnabled();
@@ -47595,6 +47600,7 @@ package android.text {
    method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int);
    method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]);
    method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic);
    method @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
  }
  public static class Layout.Directions {
@@ -47863,6 +47869,7 @@ package android.text {
    method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int);
    method public android.text.StaticLayout.Builder setText(CharSequence);
    method @NonNull public android.text.StaticLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
    method @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean);
    method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean);
  }
@@ -60548,6 +60555,7 @@ package android.widget {
    method public final android.text.method.TransformationMethod getTransformationMethod();
    method public android.graphics.Typeface getTypeface();
    method public android.text.style.URLSpan[] getUrls();
    method public boolean getUseBoundsForWidth();
    method public boolean hasSelection();
    method public boolean isAllCaps();
    method public boolean isCursorVisible();
@@ -60690,6 +60698,7 @@ package android.widget {
    method public final void setTransformationMethod(android.text.method.TransformationMethod);
    method public void setTypeface(@Nullable android.graphics.Typeface, int);
    method public void setTypeface(@Nullable android.graphics.Typeface);
    method public void setUseBoundsForWidth(boolean);
    method public void setWidth(int);
    field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
    field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
+84 −11
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.text.LineBreakConfig;
import android.text.style.ParagraphStyle;

@@ -191,6 +192,17 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
            @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad,
            @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 */);
    }

    /** @hide */
    public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source,
            @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth,
            @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 trust;

        if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
@@ -202,7 +214,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
            trust = true;
        } else {
            replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this),
                    paint, outerWidth, align, 1f, 0f);
                    paint, outerWidth, align, spacingMultiplier, spacingAmount);

            mEllipsizedWidth = ellipsizedWidth;
            trust = false;
@@ -263,8 +275,7 @@ 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);
                null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);

        mEllipsizedWidth = outerwidth;
        mEllipsizedStart = 0;
@@ -341,7 +352,28 @@ 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);
                LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */);
    }

    /** @hide */
    public BoringLayout(
            CharSequence text,
            TextPaint paint,
            int width,
            Alignment align,
            float spacingMult,
            float spacingAdd,
            boolean includePad,
            boolean fallbackLineSpacing,
            int ellipsizedWidth,
            TextUtils.TruncateAt ellipsize,
            Metrics metrics,
            boolean useBoundsForWidth) {
        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);
    }

    /* package */ BoringLayout(
@@ -363,12 +395,13 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
            int[] rightIndents,
            int justificationMode,
            LineBreakConfig lineBreakConfig,
            Metrics metrics) {
            Metrics metrics,
            boolean useBoundsForWidth) {

        super(text, paint, width, align, textDir, spacingMult, spacingAdd, includePad,
                fallbackLineSpacing, ellipsizedWidth, ellipsize, maxLines, breakStrategy,
                hyphenationFrequency, leftIndents, rightIndents, justificationMode,
                lineBreakConfig);
                lineBreakConfig, useBoundsForWidth);


        boolean trust;
@@ -425,7 +458,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
            line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
                    Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
                    mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing);
            mMax = (int) Math.ceil(line.metrics(null));
            mMax = (int) Math.ceil(line.metrics(null, null, false));
            TextLine.recycle(line);
        }

@@ -433,6 +466,9 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
            mTopPadding = metrics.top - metrics.ascent;
            mBottomPadding = metrics.bottom - metrics.descent;
        }

        mDrawingBounds.set(metrics.mDrawingBounds);
        mDrawingBounds.offset(0, mBottom - mDesc);
    }

    /**
@@ -555,7 +591,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
                0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
                0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
                useFallbackLineSpacing);
        fm.width = (int) Math.ceil(line.metrics(fm));
        fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false));
        TextLine.recycle(line);

        return fm;
@@ -604,13 +640,21 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback

    @Override
    public float getLineMax(int line) {
        if (getUseBoundsForWidth()) {
            return super.getLineMax(line);
        } else {
            return mMax;
        }
    }

    @Override
    public float getLineWidth(int line) {
        if (getUseBoundsForWidth()) {
            return super.getLineWidth(line);
        } else {
            return (line == 0 ? mMax : 0);
        }
    }

    @Override
    public final Directions getLineDirections(int line) {
@@ -647,12 +691,29 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
        return mUseFallbackLineSpacing;
    }

    @Override
    public @NonNull RectF computeDrawingBoundingBox() {
        return mDrawingBounds;
    }

    // Override draw so it will be faster.
    @Override
    public void draw(Canvas c, Path highlight, Paint highlightpaint,
                     int cursorOffset) {
        if (mDirect != null && highlight == null) {
            if (getUseBoundsForWidth()) {
                c.save();
                RectF drawingRect = computeDrawingBoundingBox();
                if (drawingRect.left < 0) {
                    c.translate(-drawingRect.left, 0);
                }
            }

            c.drawText(mDirect, 0, mBottom - mDesc, mPaint);

            if (getUseBoundsForWidth()) {
                c.restore();
            }
        } else {
            super.draw(c, highlight, highlightpaint, cursorOffset);
        }
@@ -674,12 +735,23 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
    private int mTopPadding, mBottomPadding;
    private float mMax;
    private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount;
    private final RectF mDrawingBounds = new RectF();

    public static class Metrics extends Paint.FontMetricsInt {
        public int width;
        private final RectF mDrawingBounds = new RectF();

        /**
         * Returns drawing bounding box.
         *
         * @return a drawing bounding box.
         */
        @NonNull public RectF getDrawingBoundingBox() {
            return mDrawingBounds;
        }

        @Override public String toString() {
            return super.toString() + " width=" + width;
            return super.toString() + " width=" + width + ", drawingBounds = " + mDrawingBounds;
        }

        private void reset() {
@@ -689,6 +761,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
            descent = 0;
            width = 0;
            leading = 0;
            mDrawingBounds.setEmpty();
        }
    }
}
+32 −3
Original line number Diff line number Diff line
@@ -285,6 +285,29 @@ public class DynamicLayout extends Layout {
            return this;
        }

        /**
         * Set true for using width of bounding box as a source of automatic line breaking and
         * drawing.
         *
         * If this value is false, the Layout determines the drawing offset and automatic line
         * breaking based on total advances. By setting true, use all joined glyph's bounding boxes
         * as a source of text width.
         *
         * If the font has glyphs that have negative bearing X or its xMax is greater than advance,
         * the glyph clipping can happen because the drawing area may be bigger. By setting this to
         * true, the Layout will reserve more spaces for drawing.
         *
         * @param useBoundsForWidth True for using bounding box, false for advances.
         * @return this builder instance
         * @see Layout#getUseBoundsForWidth()
         * @see Layout.Builder#setUseBoundsForWidth(boolean)
         */
        @NonNull
        public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
            mUseBoundsForWidth = useBoundsForWidth;
            return this;
        }

        /**
         * Build the {@link DynamicLayout} after options have been set.
         *
@@ -317,6 +340,7 @@ public class DynamicLayout extends Layout {
        private TextUtils.TruncateAt mEllipsize;
        private int mEllipsizedWidth;
        private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
        private boolean mUseBoundsForWidth;

        private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();

@@ -392,7 +416,7 @@ public class DynamicLayout extends Layout {
                false /* fallbackLineSpacing */, ellipsizedWidth, ellipsize,
                Integer.MAX_VALUE /* maxLines */, breakStrategy, hyphenationFrequency,
                null /* leftIndents */, null /* rightIndents */, justificationMode,
                lineBreakConfig);
                lineBreakConfig, false /* useBoundsForWidth */);

        final Builder b = Builder.obtain(base, paint, width)
                .setAlignment(align)
@@ -418,7 +442,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.mLineBreakConfig, b.mUseBoundsForWidth);

        mDisplay = b.mDisplay;
        mIncludePad = b.mIncludePad;
@@ -445,6 +469,7 @@ public class DynamicLayout extends Layout {
    private void generate(@NonNull Builder b) {
        mBase = b.mBase;
        mFallbackLineSpacing = b.mFallbackLineSpacing;
        mUseBoundsForWidth = b.mUseBoundsForWidth;
        if (b.mEllipsize != null) {
            mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
            mEllipsizedWidth = b.mEllipsizedWidth;
@@ -639,7 +664,9 @@ public class DynamicLayout extends Layout {
                .setJustificationMode(mJustificationMode)
                .setLineBreakConfig(mLineBreakConfig)
                .setAddLastLineLineSpacing(!islast)
                .setIncludePad(false);
                .setIncludePad(false)
                .setUseBoundsForWidth(mUseBoundsForWidth)
                .setCalculateBounds(true);

        reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
        int n = reflowed.getLineCount();
@@ -1290,6 +1317,8 @@ public class DynamicLayout extends Layout {

    private Rect mTempRect = new Rect();

    private boolean mUseBoundsForWidth;

    @UnsupportedAppUsage
    private static StaticLayout sStaticLayout = null;
    private static StaticLayout.Builder sBuilder = null;
+144 −11

File changed.

Preview size limit exceeded, changes collapsed.

+6 −3
Original line number Diff line number Diff line
@@ -460,10 +460,11 @@ public class MeasuredParagraph {
            @NonNull TextDirectionHeuristic textDir,
            int hyphenationMode,
            boolean computeLayout,
            boolean computeBounds,
            @Nullable MeasuredParagraph hint,
            @Nullable MeasuredParagraph recycle) {
        return buildForStaticLayoutInternal(paint, lineBreakConfig, text, start, end, textDir,
                hyphenationMode, computeLayout, hint, recycle, null);
                hyphenationMode, computeLayout, computeBounds, hint, recycle, null);
    }

    /**
@@ -498,7 +499,7 @@ public class MeasuredParagraph {
            boolean computeLayout,
            @Nullable StyleRunCallback testCallback) {
        return buildForStaticLayoutInternal(paint, lineBreakConfig, text, start, end, textDir,
                hyphenationMode, computeLayout, null, null, testCallback);
                hyphenationMode, computeLayout, false, null, null, testCallback);
    }

    private static @NonNull MeasuredParagraph buildForStaticLayoutInternal(
@@ -510,6 +511,7 @@ public class MeasuredParagraph {
            @NonNull TextDirectionHeuristic textDir,
            int hyphenationMode,
            boolean computeLayout,
            boolean computeBounds,
            @Nullable MeasuredParagraph hint,
            @Nullable MeasuredParagraph recycle,
            @Nullable StyleRunCallback testCallback) {
@@ -519,7 +521,8 @@ public class MeasuredParagraph {
        if (hint == null) {
            builder = new MeasuredText.Builder(mt.mCopiedBuffer)
                    .setComputeHyphenation(hyphenationMode)
                    .setComputeLayout(computeLayout);
                    .setComputeLayout(computeLayout)
                    .setComputeBounds(computeBounds);
        } else {
            builder = new MeasuredText.Builder(hint.mMeasuredText);
        }
Loading