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

Commit 676fa348 authored by Raph Levien's avatar Raph Levien Committed by Android (Google) Code Review
Browse files

Merge "Start moving text measurement into native code"

parents 484bc6e5 70616ecd
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1564,7 +1564,7 @@ public abstract class Layout {
        MeasuredText mt = MeasuredText.obtain();
        TextLine tl = TextLine.obtain();
        try {
            mt.setPara(text, start, end, TextDirectionHeuristics.LTR);
            mt.setPara(text, start, end, TextDirectionHeuristics.LTR, null);
            Directions directions;
            int dir;
            if (mt.mEasy) {
+38 −8
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ class MeasuredText {

    private int mPos;
    private TextPaint mWorkPaint;
    private StaticLayout.Builder mBuilder;

    private MeasuredText() {
        mWorkPaint = new TextPaint();
@@ -81,6 +82,7 @@ class MeasuredText {

    void finish() {
        mText = null;
        mBuilder = null;
        if (mLen > 1000) {
            mWidths = null;
            mChars = null;
@@ -95,7 +97,9 @@ class MeasuredText {
    /**
     * Analyzes text for bidirectional runs.  Allocates working buffers.
     */
    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
            StaticLayout.Builder builder) {
        mBuilder = builder;
        mText = text;
        mTextStart = start;

@@ -164,9 +168,24 @@ class MeasuredText {
        int p = mPos;
        mPos = p + len;

        // try to do widths measurement in native code, but use Java if paint has been subclassed
        // FIXME: may want to eliminate special case for subclass
        float[] widths = null;
        if (mBuilder == null || paint.getClass() != TextPaint.class) {
            widths = mWidths;
        }
        if (mEasy) {
            boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
            return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
            float width = 0;
            if (widths != null) {
                width = paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, widths, p);
                if (mBuilder != null) {
                    mBuilder.addMeasuredRun(p, p + len, widths);
                }
            } else {
                width = mBuilder.addStyleRun(paint, p, p + len, isRtl);
            }
            return width;
        }

        float totalAdvance = 0;
@@ -174,8 +193,15 @@ class MeasuredText {
        for (int q = p, i = p + 1, e = p + len;; ++i) {
            if (i == e || mLevels[i] != level) {
                boolean isRtl = (level & 0x1) != 0;
                if (widths != null) {
                    totalAdvance +=
                        paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
                            paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, widths, q);
                    if (mBuilder != null) {
                        mBuilder.addMeasuredRun(q, i, widths);
                    }
                } else {
                    totalAdvance += mBuilder.addStyleRun(paint, q, i, isRtl);
                }
                if (i == e) {
                    break;
                }
@@ -211,10 +237,14 @@ class MeasuredText {
            // Use original text.  Shouldn't matter.
            wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
                    mTextStart + mPos + len, fm);
            if (mBuilder == null) {
                float[] w = mWidths;
                w[mPos] = wid;
                for (int i = mPos + 1, e = mPos + len; i < e; i++)
                    w[i] = 0;
            } else {
                mBuilder.addReplacementRun(mPos, mPos + len, wid);
            }
            mPos += len;
        }

+58 −18
Original line number Diff line number Diff line
@@ -166,18 +166,48 @@ public class StaticLayout extends Layout {
            return this;
        }

        /* @hide */
        public void setLocale(Locale locale) {
        /**
         * Measurement and break iteration is done in native code. The protocol for using
         * the native code is as follows.
         *
         * For each paragraph, do a nSetText of the paragraph text. Then, for each run within the
         * paragraph:
         *  - setLocale (this must be done at least for the first run, optional afterwards)
         *  - one of the following, depending on the type of run:
         *    + addStyleRun (a text run, to be measured in native code)
         *    + addMeasuredRun (a run already measured in Java, passed into native code)
         *    + addReplacementRun (a replacement run, width is given)
         *
         * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
         * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
         *
         * After all paragraphs, call finish() to release expensive buffers.
         */

        private void setLocale(Locale locale) {
            if (!locale.equals(mLocale)) {
                nBuilderSetLocale(mNativePtr, locale.toLanguageTag());
                nSetLocale(mNativePtr, locale.toLanguageTag());
                mLocale = locale;
            }
        }

        /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
            return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
                    start, end, isRtl);
        }

        /* package */ void addMeasuredRun(int start, int end, float[] widths) {
            nAddMeasuredRun(mNativePtr, start, end, widths);
        }

        /* package */ void addReplacementRun(int start, int end, float width) {
            nAddReplacementRun(mNativePtr, start, end, width);
        }

        public StaticLayout build() {
            // TODO: can optimize based on whether ellipsis is needed
            StaticLayout result = new StaticLayout(mText);
            result.initFromBuilder(this);
            result.generate(this, this.mIncludePad, this.mIncludePad);
            recycle(this);
            return result;
        }
@@ -321,7 +351,7 @@ public class StaticLayout extends Layout {
        mLines = new int[mLineDirections.length];
        mMaximumVisibleLineCount = maxLines;

        initFromBuilder(b);
        generate(b, b.mIncludePad, b.mIncludePad);

        Builder.recycle(b);
    }
@@ -334,10 +364,6 @@ public class StaticLayout extends Layout {
        mLines = new int[mLineDirections.length];
    }

    private void initFromBuilder(Builder b) {
        generate(b, b.mIncludePad, b.mIncludePad);
    }

    /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
        CharSequence source = b.mText;
        int bufStart = b.mStart;
@@ -427,12 +453,13 @@ public class StaticLayout extends Layout {
                }
            }

            measured.setPara(source, paraStart, paraEnd, textDir);
            measured.setPara(source, paraStart, paraEnd, textDir, b);
            char[] chs = measured.mChars;
            float[] widths = measured.mWidths;
            byte[] chdirs = measured.mLevels;
            int dir = measured.mDir;
            boolean easy = measured.mEasy;
            nSetText(b.mNativePtr, chs, paraEnd - paraStart);

            // measurement has to be done before performing line breaking
            // but we don't want to recompute fontmetrics or span ranges the
@@ -493,7 +520,8 @@ public class StaticLayout extends Layout {
                }
            }

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

@@ -576,7 +604,7 @@ public class StaticLayout extends Layout {
                mLineCount < mMaximumVisibleLineCount) {
            // Log.e("text", "output last " + bufEnd);

            measured.setPara(source, bufEnd, bufEnd, textDir);
            measured.setPara(source, bufEnd, bufEnd, textDir, b);

            paint.getFontMetricsInt(fm);

@@ -933,21 +961,33 @@ public class StaticLayout extends Layout {
        return mEllipsizedWidth;
    }

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

    private static native void nSetText(long nativePtr, char[] text, int length);

    private static native float nAddStyleRun(long nativePtr, long nativePaint,
            long nativeTypeface, int start, int end, boolean isRtl);

    private static native void nAddMeasuredRun(long nativePtr,
            int start, int end, float[] widths);

    private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);

    private static native void nGetWidths(long nativePtr, float[] widths);

    // populates LineBreaks and returns the number of breaks found
    //
    // 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(long nativePtr, char[] text, float[] widths,
    private static native int nComputeLineBreaks(long nativePtr,
            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;
+2 −2
Original line number Diff line number Diff line
@@ -1259,7 +1259,7 @@ public class TextUtils {
                    }

                    // XXX this is probably ok, but need to look at it more
                    tempMt.setPara(format, 0, format.length(), textDir);
                    tempMt.setPara(format, 0, format.length(), textDir, null);
                    float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);

                    if (w + moreWid <= avail) {
@@ -1281,7 +1281,7 @@ public class TextUtils {
    private static float setPara(MeasuredText mt, TextPaint paint,
            CharSequence text, int start, int end, TextDirectionHeuristic textDir) {

        mt.setPara(text, start, end, textDir);
        mt.setPara(text, start, end, textDir, null);

        float width;
        Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+85 −12
Original line number Diff line number Diff line
@@ -29,6 +29,11 @@
#include <list>
#include <algorithm>

#include "SkPaint.h"
#include "SkTypeface.h"
#include "MinikinSkia.h"
#include "MinikinUtils.h"
#include "Paint.h"

namespace android {

@@ -57,12 +62,21 @@ class Builder {

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

        size_t size() const {
            return mTextBuf.size();
        }

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

        float* widths() {
            return mWidthBuf.data();
        }

        // set text to current contents of buffer
        void setText() {
            UErrorCode status = U_ZERO_ERROR;
@@ -74,6 +88,8 @@ class Builder {
            if (mTextBuf.size() > MAX_TEXT_BUF_RETAIN) {
                mTextBuf.clear();
                mTextBuf.shrink_to_fit();
                mWidthBuf.clear();
                mWidthBuf.shrink_to_fit();
            }
        }

@@ -81,11 +97,17 @@ class Builder {
            return mBreakIterator;
        }

        float measureStyleRun(Paint* paint, TypefaceImpl* typeface, size_t start, size_t end,
                bool isRtl);

        void addReplacement(size_t start, size_t end, float width);

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

static const int CHAR_SPACE = 0x20;
@@ -543,22 +565,24 @@ void computePrimitives(const jchar* textArr, const jfloat* widthsArr, jint lengt
    primitives->push_back(p);
}

// sets the text on the builder
static void nSetText(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, int length) {
    Builder* b = reinterpret_cast<Builder*>(nativePtr);
    b->resize(length);
    env->GetCharArrayRegion(text, 0, length, b->buffer());
    b->setText();
}

static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
                               jcharArray inputText, jfloatArray widths, jint length,
                               jint length,
                               jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
                               jintArray variableTabStops, jint defaultTabStop, jboolean optimize,
                               jobject recycle, jintArray recycleBreaks,
                               jfloatArray recycleWidths, jbooleanArray recycleFlags,
                               jint recycleLength) {
    std::vector<int> breaks;

    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);
    std::vector<int> breaks;

    icu::BreakIterator* breakIterator = b->breakIterator();
    int loc = breakIterator->first();
@@ -569,7 +593,7 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
    // TODO: all these allocations can be moved into the builder
    std::vector<Primitive> primitives;
    TabStops tabStops(env, variableTabStops, defaultTabStop);
    computePrimitives(b->buffer(), widthsScopedArr.get(), length, breaks, tabStops, &primitives);
    computePrimitives(b->buffer(), b->widths(), length, breaks, tabStops, &primitives);

    LineWidth lineWidth(firstWidth, firstWidthLineLimit, restWidth);
    std::vector<int> computedBreaks;
@@ -602,7 +626,7 @@ static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) {
    b->finish();
}

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

@@ -611,12 +635,61 @@ static void nBuilderSetLocale(JNIEnv* env, jclass, jlong nativePtr, jstring java
    }
}

float Builder::measureStyleRun(Paint* paint, TypefaceImpl* typeface, size_t start, size_t end,
        bool isRtl) {
    Layout layout;
    int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
    // TODO: should we provide more context?
    MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, mTextBuf.data() + start, 0,
            end - start, end - start);
    layout.getAdvances(mWidthBuf.data() + start);
    return layout.getAdvance();
}

void Builder::addReplacement(size_t start, size_t end, float width) {
    mWidthBuf[start] = width;
    std::fill(&mWidthBuf[start + 1], &mWidthBuf[end], 0.0f);
}

// Basically similar to Paint.getTextRunAdvances but with C++ interface
static jfloat nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr,
        jlong nativePaint, jlong nativeTypeface, jint start, jint end, jboolean isRtl) {
    Builder* b = reinterpret_cast<Builder*>(nativePtr);
    Paint* paint = reinterpret_cast<Paint*>(nativePaint);
    TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(nativeTypeface);
    return b->measureStyleRun(paint, typeface, start, end, isRtl);
}

// Accept width measurements for the run, passed in from Java
static void nAddMeasuredRun(JNIEnv* env, jclass, jlong nativePtr,
        jint start, jint end, jfloatArray widths) {
    Builder* b = reinterpret_cast<Builder*>(nativePtr);
    env->GetFloatArrayRegion(widths, start, end - start, b->widths() + start);
}

static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr,
        jint start, jint end, jfloat width) {
    Builder* b = reinterpret_cast<Builder*>(nativePtr);
    b->addReplacement(start, end, width);
}

static void nGetWidths(JNIEnv* env, jclass, jlong nativePtr, jfloatArray widths) {
    Builder* b = reinterpret_cast<Builder*>(nativePtr);
    env->SetFloatArrayRegion(widths, 0, b->size(), b->widths());
}

static JNINativeMethod gMethods[] = {
    // TODO performance: many of these are candidates for fast jni, awaiting guidance
    {"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",
    {"nSetLocale", "(JLjava/lang/String;)V", (void*) nSetLocale},
    {"nSetText", "(J[CI)V", (void*) nSetText},
    {"nAddStyleRun", "(JJJIIZ)F", (void*) nAddStyleRun},
    {"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun},
    {"nAddReplacementRun", "(JIIF)V", (void*) nAddReplacementRun},
    {"nGetWidths", "(J[F)V", (void*) nGetWidths},
    {"nComputeLineBreaks", "(JIFIF[IIZLandroid/text/StaticLayout$LineBreaks;[I[F[ZI)I",
        (void*) nComputeLineBreaks}
};