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

Commit a7554382 authored by Seigo Nonaka's avatar Seigo Nonaka Committed by Android (Google) Code Review
Browse files

Merge "Prepare NativeLineBreaker.LineBreaks to be public API"

parents 4a1b3e25 ab9b479d
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));
}

}