diff options
author | Raph Levien <raph@google.com> | 2015-03-13 17:26:30 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-03-13 17:26:32 +0000 |
commit | 676fa348d04e3f2e6f315a913eb45c79ec6bb03c (patch) | |
tree | e08649ba3fe27e57fb102c1cc73240fa3057f5f5 | |
parent | 484bc6e5a79af1b7b0a2eb682802006522b274fd (diff) | |
parent | 70616ecd22fafccf2fab7565ccfbb3b5f91c5580 (diff) | |
download | frameworks_base-676fa348d04e3f2e6f315a913eb45c79ec6bb03c.zip frameworks_base-676fa348d04e3f2e6f315a913eb45c79ec6bb03c.tar.gz frameworks_base-676fa348d04e3f2e6f315a913eb45c79ec6bb03c.tar.bz2 |
Merge "Start moving text measurement into native code"
-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} }; |