Merge "Start moving text measurement into native code"
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user