Add JNI StaticLayout.Builder
This patch adds a native C++ Builder object for StaticLayout to complement the Java one introduced in a previous patch. The Builder object contains state used in constructing a layout, as well as temporary buffers, to avoid having to allocate such. In particular, it holds a break iterator, so avoids the cost of constructing that in the common case of a single locale. Change-Id: I1125103b7ccf00b8674c1586c3ea8d5d915fdd5b
This commit is contained in:
@@ -28,6 +28,7 @@ import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.GrowingArrayUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* StaticLayout is a Layout for text that will not be edited after it
|
||||
@@ -51,6 +52,10 @@ public class StaticLayout extends Layout {
|
||||
* @hide
|
||||
*/
|
||||
public final static class Builder {
|
||||
private Builder() {
|
||||
mNativePtr = nNewBuilder();
|
||||
}
|
||||
|
||||
static Builder obtain() {
|
||||
Builder b = null;
|
||||
synchronized (sLock) {
|
||||
@@ -96,6 +101,7 @@ public class StaticLayout extends Layout {
|
||||
|
||||
// release any expensive state
|
||||
/* package */ void finish() {
|
||||
nFinishBuilder(mNativePtr);
|
||||
mMeasuredText.finish();
|
||||
}
|
||||
|
||||
@@ -160,6 +166,14 @@ public class StaticLayout extends Layout {
|
||||
return this;
|
||||
}
|
||||
|
||||
/* @hide */
|
||||
public void setLocale(Locale locale) {
|
||||
if (!locale.equals(mLocale)) {
|
||||
nBuilderSetLocale(mNativePtr, locale.toLanguageTag());
|
||||
mLocale = locale;
|
||||
}
|
||||
}
|
||||
|
||||
public StaticLayout build() {
|
||||
// TODO: can optimize based on whether ellipsis is needed
|
||||
StaticLayout result = new StaticLayout(mText);
|
||||
@@ -168,6 +182,17 @@ public class StaticLayout extends Layout {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
nFreeBuilder(mNativePtr);
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ long mNativePtr;
|
||||
|
||||
CharSequence mText;
|
||||
int mStart;
|
||||
int mEnd;
|
||||
@@ -186,6 +211,8 @@ public class StaticLayout extends Layout {
|
||||
// This will go away and be subsumed by native builder code
|
||||
MeasuredText mMeasuredText;
|
||||
|
||||
Locale mLocale;
|
||||
|
||||
private static final Object sLock = new Object();
|
||||
private static final Builder[] sCached = new Builder[3];
|
||||
}
|
||||
@@ -322,13 +349,13 @@ public class StaticLayout extends Layout {
|
||||
float spacingadd = b.mSpacingAdd;
|
||||
float ellipsizedWidth = b.mEllipsizedWidth;
|
||||
TextUtils.TruncateAt ellipsize = b.mEllipsize;
|
||||
LineBreaks lineBreaks = new LineBreaks();
|
||||
LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
|
||||
// store span end locations
|
||||
int[] spanEndCache = new int[4];
|
||||
// store fontMetrics per span range
|
||||
// must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
|
||||
int[] fmCache = new int[4 * 4];
|
||||
final String localeLanguageTag = paint.getTextLocale().toLanguageTag();
|
||||
b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
|
||||
|
||||
mLineCount = 0;
|
||||
|
||||
@@ -466,7 +493,7 @@ public class StaticLayout extends Layout {
|
||||
}
|
||||
}
|
||||
|
||||
int breakCount = nComputeLineBreaks(localeLanguageTag, chs, widths, paraEnd - paraStart, firstWidth,
|
||||
int breakCount = nComputeLineBreaks(b.mNativePtr, chs, widths, paraEnd - paraStart, firstWidth,
|
||||
firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks,
|
||||
lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
|
||||
|
||||
@@ -911,11 +938,16 @@ public class StaticLayout extends Layout {
|
||||
// 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(String locale, char[] text, float[] widths,
|
||||
private static native int nComputeLineBreaks(long nativePtr, char[] text, float[] widths,
|
||||
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;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <list>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
struct JLineBreaksID {
|
||||
@@ -40,6 +41,53 @@ struct JLineBreaksID {
|
||||
static jclass gLineBreaks_class;
|
||||
static JLineBreaksID gLineBreaks_fieldID;
|
||||
|
||||
class Builder {
|
||||
public:
|
||||
~Builder() {
|
||||
utext_close(&mUText);
|
||||
delete mBreakIterator;
|
||||
}
|
||||
|
||||
void setLocale(const Locale& locale) {
|
||||
delete mBreakIterator;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
mBreakIterator = BreakIterator::createLineInstance(locale, status);
|
||||
// TODO: check status
|
||||
}
|
||||
|
||||
void resize(size_t size) {
|
||||
mTextBuf.resize(size);
|
||||
}
|
||||
|
||||
uint16_t* buffer() {
|
||||
return mTextBuf.data();
|
||||
}
|
||||
|
||||
// set text to current contents of buffer
|
||||
void setText() {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
utext_openUChars(&mUText, mTextBuf.data(), mTextBuf.size(), &status);
|
||||
mBreakIterator->setText(&mUText, status);
|
||||
}
|
||||
|
||||
void finish() {
|
||||
if (mTextBuf.size() > MAX_TEXT_BUF_RETAIN) {
|
||||
mTextBuf.clear();
|
||||
mTextBuf.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
BreakIterator* breakIterator() const {
|
||||
return mBreakIterator;
|
||||
}
|
||||
|
||||
private:
|
||||
const size_t MAX_TEXT_BUF_RETAIN = 32678;
|
||||
BreakIterator* mBreakIterator = nullptr;
|
||||
UText mUText = UTEXT_INITIALIZER;
|
||||
std::vector<uint16_t>mTextBuf;
|
||||
};
|
||||
|
||||
static const int CHAR_SPACE = 0x20;
|
||||
static const int CHAR_TAB = 0x09;
|
||||
static const int CHAR_NEWLINE = 0x0a;
|
||||
@@ -50,7 +98,7 @@ class TabStops {
|
||||
// specified stops must be a sorted array (allowed to be null)
|
||||
TabStops(JNIEnv* env, jintArray stops, jint defaultTabWidth) :
|
||||
mStops(env), mTabWidth(defaultTabWidth) {
|
||||
if (stops != NULL) {
|
||||
if (stops != nullptr) {
|
||||
mStops.reset(stops);
|
||||
mNumStops = mStops.size();
|
||||
} else {
|
||||
@@ -430,37 +478,6 @@ class GreedyLineBreaker : public LineBreaker {
|
||||
}
|
||||
};
|
||||
|
||||
class ScopedBreakIterator {
|
||||
public:
|
||||
ScopedBreakIterator(JNIEnv* env, BreakIterator* breakIterator, const jchar* inputText,
|
||||
jint length) : mBreakIterator(breakIterator), mChars(inputText) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
mUText = utext_openUChars(NULL, mChars, length, &status);
|
||||
if (mUText == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBreakIterator->setText(mUText, status);
|
||||
}
|
||||
|
||||
inline BreakIterator* operator->() {
|
||||
return mBreakIterator;
|
||||
}
|
||||
|
||||
~ScopedBreakIterator() {
|
||||
utext_close(mUText);
|
||||
delete mBreakIterator;
|
||||
}
|
||||
private:
|
||||
BreakIterator* mBreakIterator;
|
||||
const jchar* mChars;
|
||||
UText* mUText;
|
||||
|
||||
// disable copying and assignment
|
||||
ScopedBreakIterator(const ScopedBreakIterator&);
|
||||
void operator=(const ScopedBreakIterator&);
|
||||
};
|
||||
|
||||
static jint recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
|
||||
jfloatArray recycleWidths, jbooleanArray recycleFlags,
|
||||
jint recycleLength, const std::vector<jint>& breaks,
|
||||
@@ -526,7 +543,7 @@ void computePrimitives(const jchar* textArr, const jfloat* widthsArr, jint lengt
|
||||
primitives->push_back(p);
|
||||
}
|
||||
|
||||
static jint nComputeLineBreaks(JNIEnv* env, jclass, jstring javaLocaleName,
|
||||
static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
|
||||
jcharArray inputText, jfloatArray widths, jint length,
|
||||
jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
|
||||
jintArray variableTabStops, jint defaultTabStop, jboolean optimize,
|
||||
@@ -535,29 +552,24 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jstring javaLocaleName,
|
||||
jint recycleLength) {
|
||||
std::vector<int> breaks;
|
||||
|
||||
ScopedCharArrayRO textScopedArr(env, inputText);
|
||||
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);
|
||||
|
||||
ScopedIcuLocale icuLocale(env, javaLocaleName);
|
||||
if (icuLocale.valid()) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
BreakIterator* it = BreakIterator::createLineInstance(icuLocale.locale(), status);
|
||||
if (!U_SUCCESS(status) || it == NULL) {
|
||||
if (it) {
|
||||
delete it;
|
||||
}
|
||||
} else {
|
||||
ScopedBreakIterator breakIterator(env, it, textScopedArr.get(), length);
|
||||
int loc = breakIterator->first();
|
||||
while ((loc = breakIterator->next()) != BreakIterator::DONE) {
|
||||
breaks.push_back(loc);
|
||||
}
|
||||
}
|
||||
BreakIterator* breakIterator = b->breakIterator();
|
||||
int loc = breakIterator->first();
|
||||
while ((loc = breakIterator->next()) != BreakIterator::DONE) {
|
||||
breaks.push_back(loc);
|
||||
}
|
||||
|
||||
// TODO: all these allocations can be moved into the builder
|
||||
std::vector<Primitive> primitives;
|
||||
TabStops tabStops(env, variableTabStops, defaultTabStop);
|
||||
computePrimitives(textScopedArr.get(), widthsScopedArr.get(), length, breaks, tabStops, &primitives);
|
||||
computePrimitives(b->buffer(), widthsScopedArr.get(), length, breaks, tabStops, &primitives);
|
||||
|
||||
LineWidth lineWidth(firstWidth, firstWidthLineLimit, restWidth);
|
||||
std::vector<int> computedBreaks;
|
||||
@@ -571,13 +583,41 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jstring javaLocaleName,
|
||||
GreedyLineBreaker breaker(primitives, lineWidth);
|
||||
breaker.computeBreaks(&computedBreaks, &computedWidths, &computedFlags);
|
||||
}
|
||||
b->finish();
|
||||
|
||||
return recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleFlags, recycleLength,
|
||||
computedBreaks, computedWidths, computedFlags);
|
||||
}
|
||||
|
||||
static jlong nNewBuilder(JNIEnv*, jclass) {
|
||||
return reinterpret_cast<jlong>(new Builder);
|
||||
}
|
||||
|
||||
static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) {
|
||||
delete reinterpret_cast<Builder*>(nativePtr);
|
||||
}
|
||||
|
||||
static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) {
|
||||
Builder* b = reinterpret_cast<Builder*>(nativePtr);
|
||||
b->finish();
|
||||
}
|
||||
|
||||
static void nBuilderSetLocale(JNIEnv* env, jclass, jlong nativePtr, jstring javaLocaleName) {
|
||||
ScopedIcuLocale icuLocale(env, javaLocaleName);
|
||||
Builder* b = reinterpret_cast<Builder*>(nativePtr);
|
||||
|
||||
if (icuLocale.valid()) {
|
||||
b->setLocale(icuLocale.locale());
|
||||
}
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
{"nComputeLineBreaks", "(Ljava/lang/String;[C[FIFIF[IIZLandroid/text/StaticLayout$LineBreaks;[I[F[ZI)I", (void*) nComputeLineBreaks}
|
||||
{"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",
|
||||
(void*) nComputeLineBreaks}
|
||||
};
|
||||
|
||||
int register_android_text_StaticLayout(JNIEnv* env)
|
||||
|
||||
Reference in New Issue
Block a user