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

Commit 4c1f12ef authored by Raph Levien's avatar Raph Levien
Browse files

Add JNI StaticLayout.Builder

This patch adds a native C++ Builder object for StaticLayout to
complement the Java one introduced in a previous patch.

The Builder object contains state used in constructing a layout, as well
as temporary buffers, to avoid having to allocate such. In particular,
it holds a break iterator, so avoids the cost of constructing that in
the common case of a single locale.

Change-Id: I1125103b7ccf00b8674c1586c3ea8d5d915fdd5b
parent d3ab692d
Loading
Loading
Loading
Loading
+36 −4
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;

import java.util.Arrays;
import java.util.Locale;

/**
 * StaticLayout is a Layout for text that will not be edited after it
@@ -51,6 +52,10 @@ public class StaticLayout extends Layout {
     * @hide
     */
    public final static class Builder {
        private Builder() {
            mNativePtr = nNewBuilder();
        }

        static Builder obtain() {
            Builder b = null;
            synchronized (sLock) {
@@ -96,6 +101,7 @@ public class StaticLayout extends Layout {

        // release any expensive state
        /* package */ void finish() {
            nFinishBuilder(mNativePtr);
            mMeasuredText.finish();
        }

@@ -160,6 +166,14 @@ public class StaticLayout extends Layout {
            return this;
        }

        /* @hide */
        public void setLocale(Locale locale) {
            if (!locale.equals(mLocale)) {
                nBuilderSetLocale(mNativePtr, locale.toLanguageTag());
                mLocale = locale;
            }
        }

        public StaticLayout build() {
            // TODO: can optimize based on whether ellipsis is needed
            StaticLayout result = new StaticLayout(mText);
@@ -168,6 +182,17 @@ public class StaticLayout extends Layout {
            return result;
        }

        @Override
        protected void finalize() throws Throwable {
            try {
                nFreeBuilder(mNativePtr);
            } finally {
                super.finalize();
            }
        }

        /* package */ long mNativePtr;

        CharSequence mText;
        int mStart;
        int mEnd;
@@ -186,6 +211,8 @@ public class StaticLayout extends Layout {
        // This will go away and be subsumed by native builder code
        MeasuredText mMeasuredText;

        Locale mLocale;

        private static final Object sLock = new Object();
        private static final Builder[] sCached = new Builder[3];
    }
@@ -322,13 +349,13 @@ public class StaticLayout extends Layout {
        float spacingadd = b.mSpacingAdd;
        float ellipsizedWidth = b.mEllipsizedWidth;
        TextUtils.TruncateAt ellipsize = b.mEllipsize;
        LineBreaks lineBreaks = new LineBreaks();
        LineBreaks lineBreaks = new LineBreaks();  // TODO: move to builder to avoid allocation costs
        // store span end locations
        int[] spanEndCache = new int[4];
        // store fontMetrics per span range
        // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
        int[] fmCache = new int[4 * 4];
        final String localeLanguageTag = paint.getTextLocale().toLanguageTag();
        b.setLocale(paint.getTextLocale());  // TODO: also respect LocaleSpan within the text

        mLineCount = 0;

@@ -466,7 +493,7 @@ public class StaticLayout extends Layout {
                }
            }

            int breakCount = nComputeLineBreaks(localeLanguageTag, chs, widths, paraEnd - paraStart, firstWidth,
            int breakCount = nComputeLineBreaks(b.mNativePtr, chs, widths, paraEnd - paraStart, firstWidth,
                    firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks,
                    lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);

@@ -911,11 +938,16 @@ public class StaticLayout extends Layout {
    // the arrays inside the LineBreaks objects are passed in as well
    // to reduce the number of JNI calls in the common case where the
    // arrays do not have to be resized
    private static native int nComputeLineBreaks(String locale, char[] text, float[] widths,
    private static native int nComputeLineBreaks(long nativePtr, char[] text, float[] widths,
            int length, float firstWidth, int firstWidthLineCount, float restWidth,
            int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
            int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);

    private static native long nNewBuilder();
    private static native void nFreeBuilder(long nativePtr);
    private static native void nFinishBuilder(long nativePtr);
    private static native void nBuilderSetLocale(long nativePtr, String locale);

    private int mLineCount;
    private int mTopPadding, mBottomPadding;
    private int mColumns;
+91 −51
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
#include <list>
#include <algorithm>


namespace android {

struct JLineBreaksID {
@@ -40,6 +41,53 @@ struct JLineBreaksID {
static jclass gLineBreaks_class;
static JLineBreaksID gLineBreaks_fieldID;

class Builder {
    public:
        ~Builder() {
            utext_close(&mUText);
            delete mBreakIterator;
        }

        void setLocale(const Locale& locale) {
            delete mBreakIterator;
            UErrorCode status = U_ZERO_ERROR;
            mBreakIterator = BreakIterator::createLineInstance(locale, status);
            // TODO: check status
        }

        void resize(size_t size) {
            mTextBuf.resize(size);
        }

        uint16_t* buffer() {
            return mTextBuf.data();
        }

        // set text to current contents of buffer
        void setText() {
            UErrorCode status = U_ZERO_ERROR;
            utext_openUChars(&mUText, mTextBuf.data(), mTextBuf.size(), &status);
            mBreakIterator->setText(&mUText, status);
        }

        void finish() {
            if (mTextBuf.size() > MAX_TEXT_BUF_RETAIN) {
                mTextBuf.clear();
                mTextBuf.shrink_to_fit();
            }
        }

        BreakIterator* breakIterator() const {
            return mBreakIterator;
        }

    private:
        const size_t MAX_TEXT_BUF_RETAIN = 32678;
        BreakIterator* mBreakIterator = nullptr;
        UText mUText = UTEXT_INITIALIZER;
        std::vector<uint16_t>mTextBuf;
};

static const int CHAR_SPACE = 0x20;
static const int CHAR_TAB = 0x09;
static const int CHAR_NEWLINE = 0x0a;
@@ -50,7 +98,7 @@ class TabStops {
        // specified stops must be a sorted array (allowed to be null)
        TabStops(JNIEnv* env, jintArray stops, jint defaultTabWidth) :
            mStops(env), mTabWidth(defaultTabWidth) {
                if (stops != NULL) {
                if (stops != nullptr) {
                    mStops.reset(stops);
                    mNumStops = mStops.size();
                } else {
@@ -430,37 +478,6 @@ class GreedyLineBreaker : public LineBreaker {
        }
};

class ScopedBreakIterator {
    public:
        ScopedBreakIterator(JNIEnv* env, BreakIterator* breakIterator, const jchar* inputText,
                            jint length) : mBreakIterator(breakIterator), mChars(inputText) {
            UErrorCode status = U_ZERO_ERROR;
            mUText = utext_openUChars(NULL, mChars, length, &status);
            if (mUText == NULL) {
                return;
            }

            mBreakIterator->setText(mUText, status);
        }

        inline BreakIterator* operator->() {
            return mBreakIterator;
        }

        ~ScopedBreakIterator() {
            utext_close(mUText);
            delete mBreakIterator;
        }
    private:
        BreakIterator* mBreakIterator;
        const jchar* mChars;
        UText* mUText;

        // disable copying and assignment
        ScopedBreakIterator(const ScopedBreakIterator&);
        void operator=(const ScopedBreakIterator&);
};

static jint recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
                        jfloatArray recycleWidths, jbooleanArray recycleFlags,
                        jint recycleLength, const std::vector<jint>& breaks,
@@ -526,7 +543,7 @@ void computePrimitives(const jchar* textArr, const jfloat* widthsArr, jint lengt
    primitives->push_back(p);
}

static jint nComputeLineBreaks(JNIEnv* env, jclass, jstring javaLocaleName,
static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
                               jcharArray inputText, jfloatArray widths, jint length,
                               jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
                               jintArray variableTabStops, jint defaultTabStop, jboolean optimize,
@@ -535,29 +552,24 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jstring javaLocaleName,
                               jint recycleLength) {
    std::vector<int> breaks;

    ScopedCharArrayRO textScopedArr(env, inputText);
    Builder* b = reinterpret_cast<Builder*>(nativePtr);
    b->resize(length);
    env->GetCharArrayRegion(inputText, 0, length, b->buffer());
    b->setText();

    // TODO: this array access is pretty inefficient, but we'll replace it anyway
    ScopedFloatArrayRO widthsScopedArr(env, widths);

    ScopedIcuLocale icuLocale(env, javaLocaleName);
    if (icuLocale.valid()) {
        UErrorCode status = U_ZERO_ERROR;
        BreakIterator* it = BreakIterator::createLineInstance(icuLocale.locale(), status);
        if (!U_SUCCESS(status) || it == NULL) {
            if (it) {
                delete it;
            }
        } else {
            ScopedBreakIterator breakIterator(env, it, textScopedArr.get(), length);
    BreakIterator* breakIterator = b->breakIterator();
    int loc = breakIterator->first();
    while ((loc = breakIterator->next()) != BreakIterator::DONE) {
        breaks.push_back(loc);
    }
        }
    }

    // TODO: all these allocations can be moved into the builder
    std::vector<Primitive> primitives;
    TabStops tabStops(env, variableTabStops, defaultTabStop);
    computePrimitives(textScopedArr.get(), widthsScopedArr.get(), length, breaks, tabStops, &primitives);
    computePrimitives(b->buffer(), widthsScopedArr.get(), length, breaks, tabStops, &primitives);

    LineWidth lineWidth(firstWidth, firstWidthLineLimit, restWidth);
    std::vector<int> computedBreaks;
@@ -571,13 +583,41 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jstring javaLocaleName,
        GreedyLineBreaker breaker(primitives, lineWidth);
        breaker.computeBreaks(&computedBreaks, &computedWidths, &computedFlags);
    }
    b->finish();

    return recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleFlags, recycleLength,
            computedBreaks, computedWidths, computedFlags);
}

static jlong nNewBuilder(JNIEnv*, jclass) {
    return reinterpret_cast<jlong>(new Builder);
}

static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) {
    delete reinterpret_cast<Builder*>(nativePtr);
}

static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) {
    Builder* b = reinterpret_cast<Builder*>(nativePtr);
    b->finish();
}

static void nBuilderSetLocale(JNIEnv* env, jclass, jlong nativePtr, jstring javaLocaleName) {
    ScopedIcuLocale icuLocale(env, javaLocaleName);
    Builder* b = reinterpret_cast<Builder*>(nativePtr);

    if (icuLocale.valid()) {
        b->setLocale(icuLocale.locale());
    }
}

static JNINativeMethod gMethods[] = {
    {"nComputeLineBreaks", "(Ljava/lang/String;[C[FIFIF[IIZLandroid/text/StaticLayout$LineBreaks;[I[F[ZI)I", (void*) nComputeLineBreaks}
    {"nNewBuilder", "()J", (void*) nNewBuilder},
    {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
    {"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
    {"nBuilderSetLocale", "(JLjava/lang/String;)V", (void*) nBuilderSetLocale},
    {"nComputeLineBreaks", "(J[C[FIFIF[IIZLandroid/text/StaticLayout$LineBreaks;[I[F[ZI)I",
        (void*) nComputeLineBreaks}
};

int register_android_text_StaticLayout(JNIEnv* env)