summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaph Levien <raph@google.com>2015-03-04 10:41:30 -0800
committerRaph Levien <raph@google.com>2015-03-13 10:11:57 -0700
commit70616ecd22fafccf2fab7565ccfbb3b5f91c5580 (patch)
treea564de1bc1b4dc9d764b2d7ba2f5958f6c634a41
parent044644c71722c8094a69d7bc8e68f73032bf5c7c (diff)
downloadframeworks_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.java2
-rw-r--r--core/java/android/text/MeasuredText.java46
-rw-r--r--core/java/android/text/StaticLayout.java76
-rw-r--r--core/java/android/text/TextUtils.java4
-rw-r--r--core/jni/android_text_StaticLayout.cpp97
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}
};