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

Commit ab9b479d authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Prepare NativeLineBreaker.LineBreaks to be public API

This is a ground work of making NativeLineBreaker/MeasuredParagraph
public.

This CL rename NativeLineBreaker.LineBreaks to NativeLineBreaker.Result.
This CL also moving array re-allocation logic from native code to Java in
StaticLayout. Ideally, we can remove array but this CL keeps array since
that introduces additional complexity.

Here is a raw perf test result:
android.text.StaticLayoutPerfTest:
  create
    PrecomputedText Balanced Hyphenation  :    667 ->    810: (+143, +21.4%)
    PrecomputedText Balanced NoHyphenation:    510 ->    594: ( +84, +16.5%)
    PrecomputedText Greedy Hyphenation    :    446 ->    530: ( +84, +18.8%)
    PrecomputedText Greedy NoHyphenation  :    448 ->    533: ( +85, +19.0%)
    RandomText Balanced Hyphenation       : 17,982 -> 18,176: (+194, +1.1%)
    RandomText Balanced NoHyphenation     :  7,348 ->  7,461: (+113, +1.5%)
    RandomText Greedy Hyphenation         :  7,293 ->  7,330: ( +37, +0.5%)
    RandomText Greedy NoHyphenation       :  7,289 ->  7,352: ( +63, +0.9%)
  draw
    PrecomputedText NoStyle               :    645 ->    645: (  +0, +0.0%)
    PrecomputedText Style                 :    941 ->    947: (  +6, +0.6%)
    RandomText NoStyle                    :    541 ->    543: (  +2, +0.4%)
    RandomText Style                      :    751 ->    753: (  +2, +0.3%)
android.widget.TextViewPrecomputedTextPerfTest:
  newLayout
    PrecomputedText                       :    839 ->    983: (+144, +17.2%)
    PrecomputedText Selectable            :    883 ->  1,053: (+170, +19.3%)
    RandomText                            : 17,594 -> 17,761: (+167, +0.9%)
    RandomText Selectable                 : 18,331 -> 18,695: (+364, +2.0%)
  onDraw
    PrecomputedText                       :  1,233 ->  1,385: (+152, +12.3%)
    PrecomputedText Selectable            :  1,164 ->  1,207: ( +43, +3.7%)
    RandomText                            : 18,836 -> 19,042: (+206, +1.1%)
    RandomText Selectable                 : 19,025 -> 19,092: ( +67, +0.4%)
  onMeasure
    PrecomputedText                       :    853 ->    991: (+138, +16.2%)
    PrecomputedText Selectable            :  1,077 ->  1,234: (+157, +14.6%)
    RandomText                            : 17,613 -> 17,913: (+300, +1.7%)
    RandomText Selectable                 : 18,696 -> 18,922: (+226, +1.2%)
  setText
    PrecomputedText                       :    133 ->    136: (  +3, +2.3%)
    PrecomputedText Selectable            :    190 ->    192: (  +2, +1.1%)
    RandomText                            :     10 ->     11: (  +1, +10.0%)
    RandomText Selectable                 :     49 ->     50: (  +1, +2.0%)

Bug: 112327179
Test: atest FrameworksCoreTests CtsGraphicsTestCases CtsWidgetTestCases
    CtsTextTestCases

Change-Id: I25f41dacf73e00a859f43331bc53f8d123116192
parent 912a1e9e
Loading
Loading
Loading
Loading
+108 −37
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;

import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -258,16 +259,91 @@ public class NativeLineBreaker {
    /**
     * A result object of a line breaking
     */
    public static class LineBreaks {
        public int breakCount;
        private static final int INITIAL_SIZE = 16;
        public int[] breaks = new int[INITIAL_SIZE];
        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
    public static class Result {
        // Following two contstant must be synced with minikin's line breaker.
        private static final int TAB_MASK = 0x20000000;
        private static final int HYPHEN_MASK = 0xFF;

        private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
                Result.class.getClassLoader(), nGetReleaseResultFunc(), 32);
        private final long mPtr;

        private Result(long ptr) {
            mPtr = ptr;
            sRegistry.registerNativeAllocation(this, mPtr);
        }

        /**
         * Returns a number of line count.
         *
         * @return number of lines
         */
        public @IntRange(from = 0) int getLineCount() {
            return nGetLineCount(mPtr);
        }

        /**
         * Returns a break offset of the line.
         *
         * @param lineIndex an index of the line.
         * @return the break offset.
         */
        public @IntRange(from = 0) int getLineBreakOffset(@IntRange(from = 0) int lineIndex) {
            return nGetLineBreakOffset(mPtr, lineIndex);
        }

        /**
         * Returns a width of the line in pixels.
         *
         * @param lineIndex an index of the line.
         * @return a width of the line in pixexls
         */
        public @Px float getLineWidth(@IntRange(from = 0) int lineIndex) {
            return nGetLineWidth(mPtr, lineIndex);
        }

        /**
         * Returns an entier font ascent of the line in pixels.
         *
         * @param lineIndex an index of the line.
         * @return an entier font ascent of the line in pixels.
         */
        public @Px float getLineAscent(@IntRange(from = 0) int lineIndex) {
            return nGetLineAscent(mPtr, lineIndex);
        }

        /**
         * Returns an entier font descent of the line in pixels.
         *
         * @param lineIndex an index of the line.
         * @return an entier font descent of the line in pixels.
         */
        public @Px float getLineDescent(@IntRange(from = 0) int lineIndex) {
            return nGetLineDescent(mPtr, lineIndex);
        }

        /**
         * Returns true if the line has a TAB character.
         *
         * @param lineIndex an index of the line.
         * @return true if the line has a TAB character
         */
        public boolean hasLineTab(int lineIndex) {
            return (nGetLineFlag(mPtr, lineIndex) & TAB_MASK) != 0;
        }

        /**
         * Returns a packed packed hyphen edit for the line.
         *
         * @param lineIndex an index of the line.
         * @return a packed hyphen edit for the line.
         * @see android.text.Hyphenator#unpackStartHyphenEdit(int)
         * @see android.text.Hyphenator#unpackEndHyphenEdit(int)
         * @see android.text.Hyphenator#packHyphenEdit(int,int)
         */
        public int getLineHyphenEdit(int lineIndex) {
            return (nGetLineFlag(mPtr, lineIndex) & HYPHEN_MASK);
        }
    }

    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
@@ -294,14 +370,12 @@ public class NativeLineBreaker {
     * @param measuredPara a result of the text measurement
     * @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(
    public Result computeLineBreaks(
            @NonNull NativeMeasuredParagraph measuredPara,
            @NonNull ParagraphConstraints constraints,
            @IntRange(from = 0) int lineNumber,
            @NonNull LineBreaks out) {
        out.breakCount = nComputeLineBreaks(
            @IntRange(from = 0) int lineNumber) {
        return new Result(nComputeLineBreaks(
                mNativePtr,

                // Inputs
@@ -313,17 +387,7 @@ public class NativeLineBreaker {
                constraints.mWidth,
                constraints.mVariableTabStops,
                constraints.mDefaultTabStop,
                lineNumber,

                // Outputs
                out,
                out.breaks.length,
                out.breaks,
                out.widths,
                out.ascents,
                out.descents,
                out.flags);

                lineNumber));
    }

    @FastNative
@@ -341,7 +405,7 @@ public class NativeLineBreaker {
    // arrays do not have to be resized
    // The individual character widths will be returned in charWidths. The length of
    // charWidths must be at least the length of the text.
    private static native int nComputeLineBreaks(
    private static native long nComputeLineBreaks(
            /* non zero */ long nativePtr,

            // Inputs
@@ -353,14 +417,21 @@ public class NativeLineBreaker {
            @FloatRange(from = 0.0f) float restWidth,
            @Nullable int[] variableTabStops,
            int defaultTabStop,
            @IntRange(from = 0) int indentsOffset,

            // Outputs
            @NonNull LineBreaks recycle,
            @IntRange(from  = 0) int recycleLength,
            @NonNull int[] recycleBreaks,
            @NonNull float[] recycleWidths,
            @NonNull float[] recycleAscents,
            @NonNull float[] recycleDescents,
            @NonNull int[] recycleFlags);
            @IntRange(from = 0) int indentsOffset);

    // Result accessors
    @CriticalNative
    private static native int nGetLineCount(long ptr);
    @CriticalNative
    private static native int nGetLineBreakOffset(long ptr, int idx);
    @CriticalNative
    private static native float nGetLineWidth(long ptr, int idx);
    @CriticalNative
    private static native float nGetLineAscent(long ptr, int idx);
    @CriticalNative
    private static native float nGetLineDescent(long ptr, int idx);
    @CriticalNative
    private static native int nGetLineFlag(long ptr, int idx);
    @CriticalNative
    private static native long nGetReleaseResultFunc();
}
+39 −18
Original line number Diff line number Diff line
@@ -599,7 +599,14 @@ public class StaticLayout extends Layout {
        float ellipsizedWidth = b.mEllipsizedWidth;
        TextUtils.TruncateAt ellipsize = b.mEllipsize;
        final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
        NativeLineBreaker.LineBreaks lineBreaks = new NativeLineBreaker.LineBreaks();

        int lineBreakCapacity = 0;
        int[] breaks = null;
        float[] lineWidths = null;
        float[] ascents = null;
        float[] descents = null;
        boolean[] hasTabs = null;
        int[] hyphenEdits = null;

        mLineCount = 0;
        mEllipsized = false;
@@ -732,14 +739,27 @@ public class StaticLayout extends Layout {
            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;
            final float[] descents = lineBreaks.descents;
            final int[] flags = lineBreaks.flags;
            NativeLineBreaker.Result res = lineBreaker.computeLineBreaks(
                    measuredPara.getNativeMeasuredParagraph(), constraints, mLineCount);
            int breakCount = res.getLineCount();
            if (lineBreakCapacity < breakCount) {
                lineBreakCapacity = breakCount;
                breaks = new int[lineBreakCapacity];
                lineWidths = new float[lineBreakCapacity];
                ascents = new float[lineBreakCapacity];
                descents = new float[lineBreakCapacity];
                hasTabs = new boolean[lineBreakCapacity];
                hyphenEdits = new int[lineBreakCapacity];
            }

            for (int i = 0; i < breakCount; ++i) {
                breaks[i] = res.getLineBreakOffset(i);
                lineWidths[i] = res.getLineWidth(i);
                ascents[i] = res.getLineAscent(i);
                descents[i] = res.getLineDescent(i);
                hasTabs[i] = res.hasLineTab(i);
                hyphenEdits[i] = res.getLineHyphenEdit(i);
            }

            final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
            final boolean ellipsisMayBeApplied = ellipsize != null
@@ -750,7 +770,7 @@ public class StaticLayout extends Layout {
                    && ellipsisMayBeApplied) {
                // Calculate width
                float width = 0;
                int flag = 0;  // XXX May need to also have starting hyphen edit
                boolean hasTab = false;  // XXX May need to also have starting hyphen edit
                for (int i = remainingLineCount - 1; i < breakCount; i++) {
                    if (i == breakCount - 1) {
                        width += lineWidths[i];
@@ -759,12 +779,12 @@ public class StaticLayout extends Layout {
                            width += measuredPara.getCharWidthAt(j - paraStart);
                        }
                    }
                    flag |= flags[i] & TAB_MASK;
                    hasTab |= hasTabs[i];
                }
                // Treat the last line and overflowed lines as a single line.
                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
                lineWidths[remainingLineCount - 1] = width;
                flags[remainingLineCount - 1] = flag;
                hasTabs[remainingLineCount - 1] = hasTab;

                breakCount = remainingLineCount;
            }
@@ -821,8 +841,8 @@ public class StaticLayout extends Layout {
                    v = out(source, here, endPos,
                            ascent, descent, fmTop, fmBottom,
                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
                            flags[breakIndex], needMultiply, measuredPara, bufEnd,
                            includepad, trackpad, addLastLineSpacing, chs,
                            hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
                            measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
                            paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
                            paint, moreChars);

@@ -860,7 +880,7 @@ public class StaticLayout extends Layout {
                    fm.top, fm.bottom,
                    v,
                    spacingmult, spacingadd, null,
                    null, fm, 0,
                    null, fm, false, 0,
                    needMultiply, measuredPara, bufEnd,
                    includepad, trackpad, addLastLineSpacing, null,
                    bufStart, ellipsize,
@@ -871,7 +891,8 @@ public class StaticLayout extends Layout {
    private int out(final CharSequence text, final int start, final int end, int above, int below,
            int top, int bottom, int v, final float spacingmult, final float spacingadd,
            final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
            final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
            final boolean hasTab, final int hyphenEdit, final boolean needMultiply,
            @NonNull final MeasuredParagraph measured,
            final int bufEnd, final boolean includePad, final boolean trackPad,
            final boolean addLastLineLineSpacing, final char[] chs,
            final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
@@ -1005,8 +1026,8 @@ public class StaticLayout extends Layout {

        // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
        // one bit for start field
        lines[off + TAB] |= flags & TAB_MASK;
        lines[off + HYPHEN] = flags;
        lines[off + TAB] |= hasTab ? TAB_MASK : 0;
        lines[off + HYPHEN] = hyphenEdit;
        lines[off + DIR] |= dir << DIR_SHIFT;
        mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);

+50 −82
Original line number Diff line number Diff line
@@ -41,17 +41,6 @@

namespace android {

struct JLineBreaksID {
    jfieldID breaks;
    jfieldID widths;
    jfieldID ascents;
    jfieldID descents;
    jfieldID flags;
};

static jclass gLineBreaks_class;
static JLineBreaksID gLineBreaks_fieldID;

static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray javaArray) {
    if (javaArray == nullptr) {
         return std::vector<float>();
@@ -85,34 +74,7 @@ static jlong nGetReleaseFunc() {
    return reinterpret_cast<jlong>(nFinish);
}

static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
                        jfloatArray recycleWidths, jfloatArray recycleAscents,
                        jfloatArray recycleDescents, jintArray recycleFlags,
                        jint recycleLength, const minikin::LineBreakResult& result) {
    const size_t nBreaks = result.breakPoints.size();
    if ((size_t)recycleLength < nBreaks) {
        // have to reallocate buffers
        recycleBreaks = env->NewIntArray(nBreaks);
        recycleWidths = env->NewFloatArray(nBreaks);
        recycleAscents = env->NewFloatArray(nBreaks);
        recycleDescents = env->NewFloatArray(nBreaks);
        recycleFlags = env->NewIntArray(nBreaks);

        env->SetObjectField(recycle, gLineBreaks_fieldID.breaks, recycleBreaks);
        env->SetObjectField(recycle, gLineBreaks_fieldID.widths, recycleWidths);
        env->SetObjectField(recycle, gLineBreaks_fieldID.ascents, recycleAscents);
        env->SetObjectField(recycle, gLineBreaks_fieldID.descents, recycleDescents);
        env->SetObjectField(recycle, gLineBreaks_fieldID.flags, recycleFlags);
    }
    // copy data
    env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, result.breakPoints.data());
    env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, result.widths.data());
    env->SetFloatArrayRegion(recycleAscents, 0, nBreaks, result.ascents.data());
    env->SetFloatArrayRegion(recycleDescents, 0, nBreaks, result.descents.data());
    env->SetIntArrayRegion(recycleFlags, 0, nBreaks, result.flags.data());
}

static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
static jlong nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
        // Inputs
        jcharArray javaText,
        jlong measuredTextPtr,
@@ -122,18 +84,7 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
        jfloat restWidth,
        jintArray variableTabStops,
        jint defaultTabStop,
        jint indentsOffset,

        // Outputs
        jobject recycle,
        jint recycleLength,
        jintArray recycleBreaks,
        jfloatArray recycleWidths,
        jfloatArray recycleAscents,
        jfloatArray recycleDescents,
        jintArray recycleFlags,
        jfloatArray charWidths) {

        jint indentsOffset) {
    minikin::android::StaticLayoutNative* builder = toNative(nativePtr);

    ScopedCharArrayRO text(env, javaText);
@@ -141,14 +92,44 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,

    minikin::U16StringPiece u16Text(text.get(), length);
    minikin::MeasuredText* measuredText = reinterpret_cast<minikin::MeasuredText*>(measuredTextPtr);
    minikin::LineBreakResult result = builder->computeBreaks(

    std::unique_ptr<minikin::LineBreakResult> result =
          std::make_unique<minikin::LineBreakResult>(builder->computeBreaks(
                u16Text, *measuredText, firstWidth, firstWidthLineCount, restWidth, indentsOffset,
            tabStops.get(), tabStops.size(), defaultTabStop);
                tabStops.get(), tabStops.size(), defaultTabStop));
    return reinterpret_cast<jlong>(result.release());
}

static jint nGetLineCount(jlong ptr) {
    return reinterpret_cast<minikin::LineBreakResult*>(ptr)->breakPoints.size();
}

static jint nGetLineBreakOffset(jlong ptr, jint i) {
    return reinterpret_cast<minikin::LineBreakResult*>(ptr)->breakPoints[i];
}

static jfloat nGetLineWidth(jlong ptr, jint i) {
    return reinterpret_cast<minikin::LineBreakResult*>(ptr)->widths[i];
}

    recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
            recycleFlags, recycleLength, result);
static jfloat nGetLineAscent(jlong ptr, jint i) {
    return reinterpret_cast<minikin::LineBreakResult*>(ptr)->ascents[i];
}

static jfloat nGetLineDescent(jlong ptr, jint i) {
    return reinterpret_cast<minikin::LineBreakResult*>(ptr)->descents[i];
}

static jint nGetLineFlag(jlong ptr, jint i) {
    return reinterpret_cast<minikin::LineBreakResult*>(ptr)->flags[i];
}

static void nReleaseResult(jlong ptr) {
    delete reinterpret_cast<minikin::LineBreakResult*>(ptr);
}

    return static_cast<jint>(result.breakPoints.size());
static jlong nGetReleaseResultFunc() {
    return reinterpret_cast<jlong>(nReleaseResult);
}

static const JNINativeMethod gMethods[] = {
@@ -166,8 +147,6 @@ static const JNINativeMethod gMethods[] = {
    // Regular JNI
    {"nComputeLineBreaks", "("
        "J"  // nativePtr

        // Inputs
        "[C"  // text
        "J"  // MeasuredParagraph ptr.
        "I"  // length
@@ -177,31 +156,20 @@ static const JNINativeMethod gMethods[] = {
        "[I"  // variableTabStops
        "I"  // defaultTabStop
        "I"  // indentsOffset

        // Outputs
        "Landroid/text/NativeLineBreaker$LineBreaks;"  // recycle
        "I"  // recycleLength
        "[I"  // recycleBreaks
        "[F"  // recycleWidths
        "[F"  // recycleAscents
        "[F"  // recycleDescents
        "[I"  // recycleFlags
        ")I", (void*) nComputeLineBreaks}
        ")J", (void*) nComputeLineBreaks},

    // Result accessors, CriticalNatives
    {"nGetLineCount", "(J)I", (void*)nGetLineCount},
    {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset},
    {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth},
    {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent},
    {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent},
    {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag},
    {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc},
};

int register_android_text_LineBreaker(JNIEnv* env)
{
    gLineBreaks_class = MakeGlobalRefOrDie(env,
            FindClassOrDie(env, "android/text/NativeLineBreaker$LineBreaks"));

    gLineBreaks_fieldID.breaks = GetFieldIDOrDie(env, gLineBreaks_class, "breaks", "[I");
    gLineBreaks_fieldID.widths = GetFieldIDOrDie(env, gLineBreaks_class, "widths", "[F");
    gLineBreaks_fieldID.ascents = GetFieldIDOrDie(env, gLineBreaks_class, "ascents", "[F");
    gLineBreaks_fieldID.descents = GetFieldIDOrDie(env, gLineBreaks_class, "descents", "[F");
    gLineBreaks_fieldID.flags = GetFieldIDOrDie(env, gLineBreaks_class, "flags", "[I");

    return RegisterMethodsOrDie(env, "android/text/NativeLineBreaker",
                                gMethods, NELEM(gMethods));
int register_android_text_LineBreaker(JNIEnv* env) {
    return RegisterMethodsOrDie(env, "android/text/NativeLineBreaker", gMethods, NELEM(gMethods));
}

}