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

Commit 41bb4fa7 authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Introduce NativeLineBreaker.Builder and ParagraphConstraint

To make NativeLineBreaker public API, introduce
NativeLineBreaker.Builder and ParagraphConstraint.

Here is a performance differences:

android.text.StaticLayoutPerfTest:
    PrecomputedText Balanced Hyphenation  :    635 ->    684: ( +49, +7.7%)
    PrecomputedText Balanced NoHyphenation:    477 ->    523: ( +46, +9.6%)
    PrecomputedText Greedy Hyphenation    :    413 ->    463: ( +50, +12.1%)
    PrecomputedText Greedy NoHyphenation  :    413 ->    463: ( +50, +12.1%)
    RandomText Balanced Hyphenation       : 18,030 -> 18,157: (+127, +0.7%)
    RandomText Balanced NoHyphenation     :  7,390 ->  7,388: (  -2, -0.0%)
    RandomText Greedy Hyphenation         :  7,319 ->  7,331: ( +12, +0.2%)
    RandomText Greedy NoHyphenation       :  7,316 ->  7,375: ( +59, +0.8%)
  draw
    PrecomputedText NoStyle               :    631 ->    627: (  -4, -0.6%)
    PrecomputedText Style                 :    842 ->    847: (  +5, +0.6%)
    RandomText NoStyle                    :    531 ->    547: ( +16, +3.0%)
    RandomText Style                      :    754 ->    758: (  +4, +0.5%)

Bug: 112327179
Test: atest CtsWidgetTestCases:EditTextTest
    CtsWidgetTestCases:TextViewFadingEdgeTest
    FrameworksCoreTests:TextViewFallbackLineSpacingTest
    FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest
    CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest
    CtsTextTestCases FrameworksCoreTests:android.text
    CtsWidgetTestCases:TextViewPrecomputedTextTest
    CtsGraphicsTestCases:android.graphics.fonts

Change-Id: I5b817bbc9f00e5806efa98f239ee20ff3d688c50
parent 8cc28a30
Loading
Loading
Loading
Loading
+18 −13
Original line number Diff line number Diff line
@@ -50,9 +50,9 @@ import java.util.Arrays;
public abstract class Layout {
    /** @hide */
    @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
            BREAK_STRATEGY_SIMPLE,
            BREAK_STRATEGY_HIGH_QUALITY,
            BREAK_STRATEGY_BALANCED
            NativeLineBreaker.BREAK_STRATEGY_SIMPLE,
            NativeLineBreaker.BREAK_STRATEGY_HIGH_QUALITY,
            NativeLineBreaker.BREAK_STRATEGY_BALANCED
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface BreakStrategy {}
@@ -63,19 +63,20 @@ public abstract class Layout {
     * before it (which yields a more consistent user experience when editing), but layout may not
     * be the highest quality.
     */
    public static final int BREAK_STRATEGY_SIMPLE = 0;
    public static final int BREAK_STRATEGY_SIMPLE = NativeLineBreaker.BREAK_STRATEGY_SIMPLE;

    /**
     * Value for break strategy indicating high quality line breaking, including automatic
     * hyphenation and doing whole-paragraph optimization of line breaks.
     */
    public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
    public static final int BREAK_STRATEGY_HIGH_QUALITY =
            NativeLineBreaker.BREAK_STRATEGY_HIGH_QUALITY;

    /**
     * Value for break strategy indicating balanced line breaking. The breaks are chosen to
     * make all lines as close to the same length as possible, including automatic hyphenation.
     */
    public static final int BREAK_STRATEGY_BALANCED = 2;
    public static final int BREAK_STRATEGY_BALANCED = NativeLineBreaker.BREAK_STRATEGY_BALANCED;

    /** @hide */
    @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
@@ -93,29 +94,32 @@ public abstract class Layout {
     * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used
     * as suggestions for potential line breaks.
     */
    public static final int HYPHENATION_FREQUENCY_NONE = 0;
    public static final int HYPHENATION_FREQUENCY_NONE =
            NativeLineBreaker.HYPHENATION_FREQUENCY_NONE;

    /**
     * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which
     * is a conservative default. Useful for informal cases, such as short sentences or chat
     * messages.
     */
    public static final int HYPHENATION_FREQUENCY_NORMAL = 1;
    public static final int HYPHENATION_FREQUENCY_NORMAL =
            NativeLineBreaker.HYPHENATION_FREQUENCY_NORMAL;

    /**
     * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical
     * in typography. Useful for running text and where it's important to put the maximum amount of
     * text in a screen with limited space.
     */
    public static final int HYPHENATION_FREQUENCY_FULL = 2;
    public static final int HYPHENATION_FREQUENCY_FULL =
            NativeLineBreaker.HYPHENATION_FREQUENCY_FULL;

    private static final ParagraphStyle[] NO_PARA_SPANS =
        ArrayUtils.emptyArray(ParagraphStyle.class);

    /** @hide */
    @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
            JUSTIFICATION_MODE_NONE,
            JUSTIFICATION_MODE_INTER_WORD
            NativeLineBreaker.JUSTIFICATION_MODE_NONE,
            NativeLineBreaker.JUSTIFICATION_MODE_INTER_WORD
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface JustificationMode {}
@@ -123,12 +127,13 @@ public abstract class Layout {
    /**
     * Value for justification mode indicating no justification.
     */
    public static final int JUSTIFICATION_MODE_NONE = 0;
    public static final int JUSTIFICATION_MODE_NONE = NativeLineBreaker.JUSTIFICATION_MODE_NONE;

    /**
     * Value for justification mode indicating the text is justified by stretching word spacing.
     */
    public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
    public static final int JUSTIFICATION_MODE_INTER_WORD =
            NativeLineBreaker.JUSTIFICATION_MODE_INTER_WORD;

    /*
     * Line spacing multiplier for default line spacing.
+249 −38
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.text;

import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,12 +27,233 @@ import dalvik.annotation.optimization.FastNative;

import libcore.util.NativeAllocationRegistry;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * A native implementation of the line breaker.
 * TODO: Consider to make this class public.
 * @hide
 */
public class NativeLineBreaker {
    @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
            BREAK_STRATEGY_SIMPLE,
            BREAK_STRATEGY_HIGH_QUALITY,
            BREAK_STRATEGY_BALANCED
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface BreakStrategy {}

    /**
     * Value for break strategy indicating simple line breaking. Automatic hyphens are not added
     * (though soft hyphens are respected), and modifying text generally doesn't affect the layout
     * before it (which yields a more consistent user experience when editing), but layout may not
     * be the highest quality.
     */
    public static final int BREAK_STRATEGY_SIMPLE = 0;

    /**
     * Value for break strategy indicating high quality line breaking, including automatic
     * hyphenation and doing whole-paragraph optimization of line breaks.
     */
    public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;

    /**
     * Value for break strategy indicating balanced line breaking. The breaks are chosen to
     * make all lines as close to the same length as possible, including automatic hyphenation.
     */
    public static final int BREAK_STRATEGY_BALANCED = 2;

    @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
            HYPHENATION_FREQUENCY_NORMAL,
            HYPHENATION_FREQUENCY_FULL,
            HYPHENATION_FREQUENCY_NONE
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface HyphenationFrequency {}

    /**
     * Value for hyphenation frequency indicating no automatic hyphenation. Useful
     * for backward compatibility, and for cases where the automatic hyphenation algorithm results
     * in incorrect hyphenation. Mid-word breaks may still happen when a word is wider than the
     * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used
     * as suggestions for potential line breaks.
     */
    public static final int HYPHENATION_FREQUENCY_NONE = 0;

    /**
     * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which
     * is a conservative default. Useful for informal cases, such as short sentences or chat
     * messages.
     */
    public static final int HYPHENATION_FREQUENCY_NORMAL = 1;

    /**
     * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical
     * in typography. Useful for running text and where it's important to put the maximum amount of
     * text in a screen with limited space.
     */
    public static final int HYPHENATION_FREQUENCY_FULL = 2;

    @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
            JUSTIFICATION_MODE_NONE,
            JUSTIFICATION_MODE_INTER_WORD
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface JustificationMode {}

    /**
     * Value for justification mode indicating no justification.
     */
    public static final int JUSTIFICATION_MODE_NONE = 0;

    /**
     * Value for justification mode indicating the text is justified by stretching word spacing.
     */
    public static final int JUSTIFICATION_MODE_INTER_WORD = 1;

    /**
     * A builder class of NativeLineBreaker.
     */
    public static class Builder {
        private @BreakStrategy int mBreakStrategy = BREAK_STRATEGY_SIMPLE;
        private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE;
        private @JustificationMode int mJustified = JUSTIFICATION_MODE_NONE;
        private @Nullable int[] mIndents = null;

        /**
         * Construct a builder class.
         */
        public Builder() {}

        /**
         * Set break strategy.
         */
        public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
            mBreakStrategy = breakStrategy;
            return this;
        }

        /**
         * Set hyphenation frequency.
         */
        public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
            mHyphenationFrequency = hyphenationFrequency;
            return this;
        }

        /**
         * Set whether the text is justified.
         */
        public Builder setJustified(@JustificationMode int justified) {
            mJustified = justified;
            return this;
        }

        /**
         * Set indents for entire text.
         *
         * Sets the total (left + right) indents in pixel per lines.
         */
        public Builder setIndents(@Nullable int[] indents) {
            mIndents = indents;
            return this;
        }

        /**
         * Returns the NativeLineBreaker with given parameters.
         */
        NativeLineBreaker build() {
            return new NativeLineBreaker(mBreakStrategy, mHyphenationFrequency, mJustified,
                    mIndents);
        }
    }

    /**
     * Line breaking constraints for single paragraph.
     */
    public static class ParagraphConstraints {
        private @FloatRange(from = 0.0f) float mWidth = 0;
        private @FloatRange(from = 0.0f) float mFirstWidth = 0;
        private @IntRange(from = 0) int mFirstWidthLineCount = 0;
        private @Nullable int[] mVariableTabStops = null;
        private @IntRange(from = 0) int mDefaultTabStop = 0;

        public ParagraphConstraints() {}

        /**
         * Set width for this paragraph.
         */
        public void setWidth(@FloatRange(from = 0.0f) float width) {
            mWidth = width;
        }

        /**
         * Set indent for this paragraph.
         *
         * @param firstWidth the line width of the starting of the paragraph
         * @param firstWidthLineCount the number of lines that applies the firstWidth
         */
        public void setIndent(@FloatRange(from = 0.0f) float firstWidth,
                @IntRange(from = 0) int firstWidthLineCount) {
            mFirstWidth = firstWidth;
            mFirstWidthLineCount = firstWidthLineCount;
        }

        /**
         * Set tab stops for this paragraph.
         *
         * @param tabStops the array of pixels of tap stopping position
         * @param defaultTabStop pixels of the default tab stopping position
         */
        public void setTabStops(@Nullable int[] tabStops, @IntRange(from = 0) int defaultTabStop) {
            mVariableTabStops = tabStops;
            mDefaultTabStop = defaultTabStop;
        }

        /**
         * Return the width for this paragraph in pixels.
         */
        public @FloatRange(from = 0.0f) float getWidth() {
            return mWidth;
        }

        /**
         * Return the first line's width for this paragraph in pixel.
         *
         * @see #setIndent(float, int)
         */
        public @FloatRange(from = 0.0f) float getFirstWidth() {
            return mFirstWidth;
        }

        /**
         * Return the number of lines to apply the first line's width.
         *
         * @see #setIndent(float, int)
         */
        public @IntRange(from = 0) int getFirstWidthLineCount() {
            return mFirstWidthLineCount;
        }

        /**
         * Returns the array of tab stops in pixels.
         *
         * @see #setTabStops(int[], int)
         */
        public @Nullable int[] getTabStops() {
            return mVariableTabStops;
        }

        /**
         * Returns the default tab stops in pixels.
         *
         * @see #setTabStop(int[], int)
         */
        public @IntRange(from = 0) int getDefaultTabStop() {
            return mDefaultTabStop;
        }
    }

    /**
     * A result object of a line breaking
@@ -43,6 +265,7 @@ public class NativeLineBreaker {
        public float[] widths = new float[INITIAL_SIZE];
        public float[] ascents = new float[INITIAL_SIZE];
        public float[] descents = new float[INITIAL_SIZE];
        // TODO: Introduce Hyphenator for explaining the meaning of flags.
        public int[] flags = new int[INITIAL_SIZE];
        // breaks, widths, and flags should all have the same length
    }
@@ -53,54 +276,44 @@ public class NativeLineBreaker {
    private final long mNativePtr;

    /**
     * A constructor of NativeLineBreaker
     * Use Builder instead.
     */
    public NativeLineBreaker(@Layout.BreakStrategy int breakStrategy,
            @Layout.HyphenationFrequency int hyphenationFrequency,
            boolean justify, @Nullable int[] indents) {
        mNativePtr = nInit(breakStrategy, hyphenationFrequency, justify, indents);
    private NativeLineBreaker(@BreakStrategy int breakStrategy,
            @HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify,
            @Nullable int[] indents) {
        mNativePtr = nInit(breakStrategy, hyphenationFrequency,
                justify == JUSTIFICATION_MODE_INTER_WORD, indents);
        sRegistry.registerNativeAllocation(this, mNativePtr);
    }

    /**
     * Break text into lines
     * Break paragraph into lines.
     *
     * The result is filled to out param.
     *
     * @param chars an array of characters
     * @param measuredPara a result of the text measurement
     * @param length a length of the target text from the begining
     * @param firstWidth a width of the first width of the line in this paragraph
     * @param firstWidthLineCount a number of lines that has the length of the firstWidth
     * @param restWidth a width of the rest of the lines.
     * @param variableTabStops an array of tab stop widths
     * @param defaultTabStop a width of the tab stop
     * @param indentsOffset an offset of the indents to be used.
     * @param out output buffer
     * @return a number of the lines
     */
    @NonNull public int computeLineBreaks(
            @NonNull char[] chars,
     * @param constraints for a single paragraph
     * @param lineNumber a line number of this paragraph
     * @param out object to set line break information for the given paragraph
     */
    public void computeLineBreaks(
            @NonNull NativeMeasuredParagraph measuredPara,
            @IntRange(from = 0) int length,
            @FloatRange(from = 0.0f) float firstWidth,
            @IntRange(from = 0) int firstWidthLineCount,
            @FloatRange(from = 0.0f) float restWidth,
            @Nullable int[] variableTabStops,
            int defaultTabStop,
            @IntRange(from = 0) int indentsOffset,
            @NonNull ParagraphConstraints constraints,
            @IntRange(from = 0) int lineNumber,
            @NonNull LineBreaks out) {
        return nComputeLineBreaks(
        out.breakCount = nComputeLineBreaks(
                mNativePtr,

                // Inputs
                chars,
                measuredPara.getChars(),
                measuredPara.getNativePtr(),
                length,
                firstWidth,
                firstWidthLineCount,
                restWidth,
                variableTabStops,
                defaultTabStop,
                indentsOffset,
                measuredPara.getChars().length,
                constraints.mFirstWidth,
                constraints.mFirstWidthLineCount,
                constraints.mWidth,
                constraints.mVariableTabStops,
                constraints.mDefaultTabStop,
                lineNumber,

                // Outputs
                out,
@@ -114,10 +327,8 @@ public class NativeLineBreaker {
    }

    @FastNative
    private static native long nInit(
            @Layout.BreakStrategy int breakStrategy,
            @Layout.HyphenationFrequency int hyphenationFrequency,
            boolean isJustified,
    private static native long nInit(@BreakStrategy int breakStrategy,
            @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
            @Nullable int[] indents);

    @CriticalNative
+11 −2
Original line number Diff line number Diff line
@@ -36,10 +36,19 @@ public class NativeMeasuredParagraph {
            NativeMeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);

    private long mNativePtr;
    private @NonNull char[] mChars;

    // Use builder instead.
    private NativeMeasuredParagraph(long ptr) {
    private NativeMeasuredParagraph(long ptr, @NonNull char[] chars) {
        mNativePtr = ptr;
        mChars = chars;
    }

    /**
     * Returns a characters of this paragraph.
     */
    public char[] getChars() {
        return mChars;
    }

    /**
@@ -126,7 +135,7 @@ public class NativeMeasuredParagraph {
            try {
                long ptr = nBuildNativeMeasuredParagraph(mNativePtr, text, computeHyphenation,
                        computeLayout);
                NativeMeasuredParagraph res = new NativeMeasuredParagraph(ptr);
                NativeMeasuredParagraph res = new NativeMeasuredParagraph(ptr, text);
                sRegistry.registerNativeAllocation(res, ptr);
                return res;
            } finally {
+16 −15
Original line number Diff line number Diff line
@@ -626,11 +626,16 @@ public class StaticLayout extends Layout {
            indents = null;
        }

        final NativeLineBreaker lineBreaker = new NativeLineBreaker(
                b.mBreakStrategy, b.mHyphenationFrequency,
        final NativeLineBreaker lineBreaker = new NativeLineBreaker.Builder()
                .setBreakStrategy(b.mBreakStrategy)
                .setHyphenationFrequency(b.mHyphenationFrequency)
                // TODO: Support more justification mode, e.g. letter spacing, stretching.
                b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
                indents);
                .setJustified(b.mJustificationMode)
                .setIndents(indents)
                .build();

        NativeLineBreaker.ParagraphConstraints constraints =
                new NativeLineBreaker.ParagraphConstraints();

        PrecomputedText.ParagraphInfo[] paragraphInfo = null;
        final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
@@ -721,18 +726,14 @@ public class StaticLayout extends Layout {
            final char[] chs = measuredPara.getChars();
            final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
            final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
            int breakCount = lineBreaker.computeLineBreaks(
                    measuredPara.getChars(),
                    measuredPara.getNativeMeasuredParagraph(),
                    paraEnd - paraStart,
                    firstWidth,
                    firstWidthLineCount,
                    restWidth,
                    variableTabStops,
                    TAB_INCREMENT,
                    mLineCount,
                    lineBreaks);

            constraints.setWidth(restWidth);
            constraints.setIndent(firstWidth, firstWidthLineCount);
            constraints.setTabStops(variableTabStops, TAB_INCREMENT);

            lineBreaker.computeLineBreaks(measuredPara.getNativeMeasuredParagraph(),
                    constraints, mLineCount, lineBreaks);
            int breakCount = lineBreaks.breakCount;
            final int[] breaks = lineBreaks.breaks;
            final float[] lineWidths = lineBreaks.widths;
            final float[] ascents = lineBreaks.ascents;