diff options
author | Raph Levien <raph@google.com> | 2015-03-04 10:41:30 -0800 |
---|---|---|
committer | Raph Levien <raph@google.com> | 2015-03-13 10:11:57 -0700 |
commit | 70616ecd22fafccf2fab7565ccfbb3b5f91c5580 (patch) | |
tree | a564de1bc1b4dc9d764b2d7ba2f5958f6c634a41 | |
parent | 044644c71722c8094a69d7bc8e68f73032bf5c7c (diff) | |
download | frameworks_base-70616ecd22fafccf2fab7565ccfbb3b5f91c5580.zip frameworks_base-70616ecd22fafccf2fab7565ccfbb3b5f91c5580.tar.gz frameworks_base-70616ecd22fafccf2fab7565ccfbb3b5f91c5580.tar.bz2 |
Start moving text measurement into native code
We want to move text measurement into native code, mostly so that
alternate measurement for hyphens can be performant. This patch begins
that migration, in the main StaticLayout case, but still surfaces the
widths array to Java (for ellipsis calculation), and also includes a
hack (used mostly for testing) for computing widths in Java and sending
them down to native code when TextPaint is subclassed.
Change-Id: I476c9e8b3aa8e4e3552eb18f66c4bcd5683f3a72
-rw-r--r-- | core/java/android/text/Layout.java | 2 | ||||
-rw-r--r-- | core/java/android/text/MeasuredText.java | 46 | ||||
-rw-r--r-- | core/java/android/text/StaticLayout.java | 76 | ||||
-rw-r--r-- | core/java/android/text/TextUtils.java | 4 | ||||
-rw-r--r-- | core/jni/android_text_StaticLayout.cpp | 97 |
5 files changed, 184 insertions, 41 deletions
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index b84c3aa..fcf1828 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -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) { diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index 832002c..55df206 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -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; - totalAdvance += - paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q); + if (widths != null) { + totalAdvance += + 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); - float[] w = mWidths; - w[mPos] = wid; - for (int i = mPos + 1, e = mPos + len; i < e; i++) - w[i] = 0; + 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; } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 0d35f9c..ee39e27 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -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; diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 1bb35f6..676986d 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -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; diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index e5ae147..8800f0b 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -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} }; |