Associate native MeasuredText with Java one.
To measure text beforehand, remove static layout dependency from
MeasuredText. Now MeasuredText can compute native measured text
by itself and StaticLayout use it for line breaking.
This CL introduce one additional JNI method call per paragraph during
line breaking but looks negligible cost.
Here is a raw performance test result on walleye-userdebug.
StaticLayoutPerfTest (median, N=100):
createRandom: 7,879,440 -> 7,964,789 (+1.08%)
createRandom Balanced: 7,835,192 -> 7,848,151 (+0.17%)
TextViewOnMeasurePerfTest (median, N=100):
measure_AtMost: 92,599,175 -> 93,027,121 (+0.47%)
measure_Exactly: 89,949,922 -> 90,439,886 (+0.54%)
measure_Unspecified: 148,645,916 -> 150,047,694 (+0.94%)
Bug: 67504091
Test: bit CtsTextTestCases:*
Test: bit CtsWidgetTestCases:*
Test: bit CtsGraphicsTestCases:*
Test: bit FrameworksCoreTests:android.text.StaticLayoutTest
Change-Id: Ie932903845645e50cfa0cb428babb31a44babc47
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 71aaf8f..14d6f9e 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -29,6 +29,10 @@
import android.text.style.ReplacementSpan;
import android.util.Pools.SynchronizedPool;
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
import java.util.Arrays;
/**
@@ -57,6 +61,9 @@
public class MeasuredText {
private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
+
private MeasuredText() {} // Use build static functions instead.
private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1);
@@ -119,6 +126,26 @@
// See getFontMetrics comments.
private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
+ // The native MeasuredText.
+ // See getNativePtr comments.
+ // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
+ private /* Maybe Zero */ long mNativePtr = 0;
+ private @Nullable Runnable mNativeObjectCleaner;
+
+ // Associate the native object to this Java object.
+ private void bindNativeObject(/* Non Zero*/ long nativePtr) {
+ mNativePtr = nativePtr;
+ mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
+ }
+
+ // Decouple the native object from this Java object and release the native object.
+ private void unbindNativeObject() {
+ if (mNativePtr != 0) {
+ mNativeObjectCleaner.run();
+ mNativePtr = 0;
+ }
+ }
+
// Following two objects are for avoiding object allocation.
private @NonNull TextPaint mCachedPaint = new TextPaint();
private @Nullable Paint.FontMetricsInt mCachedFm;
@@ -145,6 +172,7 @@
mWidths.clear();
mFontMetrics.clear();
mSpanEndCache.clear();
+ unbindNativeObject();
}
/**
@@ -226,6 +254,16 @@
}
/**
+ * Returns the native ptr of the MeasuredText.
+ *
+ * This is available only if the MeasureText is computed with computeForStaticLayout.
+ * Returns 0 in other cases.
+ */
+ public /* Maybe Zero */ long getNativePtr() {
+ return mNativePtr;
+ }
+
+ /**
* Generates new MeasuredText for Bidi computation.
*
* If recycle is null, this returns new instance. If recycle is not null, this fills computed
@@ -308,7 +346,6 @@
* @param start the inclusive start offset of the target region in the text
* @param end the exclusive end offset of the target region in the text
* @param textDir the text direction
- * @param nativeStaticLayoutPtr the pointer to the native static layout object
* @param recycle pass existing MeasuredText if you want to recycle it.
*
* @return measured text
@@ -319,31 +356,46 @@
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@NonNull TextDirectionHeuristic textDir,
- /* Non-Zero */ long nativeStaticLayoutPtr,
@Nullable MeasuredText recycle) {
final MeasuredText mt = recycle == null ? obtain() : recycle;
mt.resetAndAnalyzeBidi(text, start, end, textDir);
if (mt.mTextLength == 0) {
+ // Need to build empty native measured text for StaticLayout.
+ // TODO: Stop creating empty measured text for empty lines.
+ long nativeBuilderPtr = nInitBuilder();
+ try {
+ mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
+ } finally {
+ nFreeBuilder(nativeBuilderPtr);
+ }
return mt;
}
- if (mt.mSpanned == null) {
- // No style change by MetricsAffectingSpan. Just measure all text.
- mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end,
- nativeStaticLayoutPtr);
- mt.mSpanEndCache.append(end);
- } else {
- // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
- int spanEnd;
- for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
- spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
- MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
- MetricAffectingSpan.class);
- spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
- mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
- nativeStaticLayoutPtr);
- mt.mSpanEndCache.append(spanEnd);
+ long nativeBuilderPtr = nInitBuilder();
+ try {
+ if (mt.mSpanned == null) {
+ // No style change by MetricsAffectingSpan. Just measure all text.
+ mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
+ mt.mSpanEndCache.append(end);
+ } else {
+ // There may be a MetricsAffectingSpan. Split into span transitions and apply
+ // styles.
+ int spanEnd;
+ for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
+ spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
+ MetricAffectingSpan.class);
+ MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
+ MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
+ MetricAffectingSpan.class);
+ mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
+ nativeBuilderPtr);
+ mt.mSpanEndCache.append(spanEnd);
+ }
}
+ mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
+ } finally {
+ nFreeBuilder(nativeBuilderPtr);
}
return mt;
@@ -415,13 +467,13 @@
private void applyReplacementRun(@NonNull ReplacementSpan replacement,
@IntRange(from = 0) int start, // inclusive, in copied buffer
@IntRange(from = 0) int end, // exclusive, in copied buffer
- /* Maybe Zero */ long nativeStaticLayoutPtr) {
+ /* Maybe Zero */ long nativeBuilderPtr) {
// Use original text. Shouldn't matter.
// TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
// backward compatibility? or Should we initialize them for getFontMetricsInt?
final float width = replacement.getSize(
mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
- if (nativeStaticLayoutPtr == 0) {
+ if (nativeBuilderPtr == 0) {
// Assigns all width to the first character. This is the same behavior as minikin.
mWidths.set(start, width);
if (end > start + 1) {
@@ -429,25 +481,26 @@
}
mWholeWidth += width;
} else {
- StaticLayout.addReplacementRun(nativeStaticLayoutPtr, mCachedPaint, start, end, width);
+ nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
+ width);
}
}
private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer
@IntRange(from = 0) int end, // exclusive, in copied buffer
- /* Maybe Zero */ long nativeStaticLayoutPtr) {
- if (nativeStaticLayoutPtr != 0) {
+ /* Maybe Zero */ long nativeBuilderPtr) {
+ if (nativeBuilderPtr != 0) {
mCachedPaint.getFontMetricsInt(mCachedFm);
}
if (mLtrWithoutBidi) {
// If the whole text is LTR direction, just apply whole region.
- if (nativeStaticLayoutPtr == 0) {
+ if (nativeBuilderPtr == 0) {
mWholeWidth += mCachedPaint.getTextRunAdvances(
mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
mWidths.getRawArray(), start);
} else {
- StaticLayout.addStyleRun(nativeStaticLayoutPtr, mCachedPaint, start, end,
+ nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
false /* isRtl */);
}
} else {
@@ -458,14 +511,14 @@
for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point
final boolean isRtl = (level & 0x1) != 0;
- if (nativeStaticLayoutPtr == 0) {
+ if (nativeBuilderPtr == 0) {
final int levelLength = levelEnd - levelStart;
mWholeWidth += mCachedPaint.getTextRunAdvances(
mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
isRtl, mWidths.getRawArray(), levelStart);
} else {
- StaticLayout.addStyleRun(
- nativeStaticLayoutPtr, mCachedPaint, levelStart, levelEnd, isRtl);
+ nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
+ levelEnd, isRtl);
}
if (levelEnd == end) {
break;
@@ -482,12 +535,12 @@
@Nullable MetricAffectingSpan[] spans,
@IntRange(from = 0) int start, // inclusive, in original text buffer
@IntRange(from = 0) int end, // exclusive, in original text buffer
- /* Maybe Zero */ long nativeStaticLayoutPtr) {
+ /* Maybe Zero */ long nativeBuilderPtr) {
mCachedPaint.set(paint);
// XXX paint should not have a baseline shift, but...
mCachedPaint.baselineShift = 0;
- final boolean needFontMetrics = nativeStaticLayoutPtr != 0;
+ final boolean needFontMetrics = nativeBuilderPtr != 0;
if (needFontMetrics && mCachedFm == null) {
mCachedFm = new Paint.FontMetricsInt();
@@ -512,9 +565,9 @@
if (replacement != null) {
applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
- nativeStaticLayoutPtr);
+ nativeBuilderPtr);
} else {
- applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeStaticLayoutPtr);
+ applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
}
if (needFontMetrics) {
@@ -580,4 +633,44 @@
}
return width;
}
+
+ private static native /* Non Zero */ long nInitBuilder();
+
+ /**
+ * Apply style to make native measured text.
+ *
+ * @param nativeBuilderPtr The native MeasuredText builder pointer.
+ * @param paintPtr The native paint pointer to be applied.
+ * @param start The start offset in the copied buffer.
+ * @param end The end offset in the copied buffer.
+ * @param isRtl True if the text is RTL.
+ */
+ private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ boolean isRtl);
+
+ /**
+ * Apply ReplacementRun to make native measured text.
+ *
+ * @param nativeBuilderPtr The native MeasuredText builder pointer.
+ * @param paintPtr The native paint pointer to be applied.
+ * @param start The start offset in the copied buffer.
+ * @param end The end offset in the copied buffer.
+ * @param width The width of the replacement.
+ */
+ private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @FloatRange(from = 0) float width);
+
+ private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr,
+ @NonNull char[] text);
+
+ private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
+
+ @CriticalNative
+ private static native /* Non Zero */ long nGetReleaseFunc();
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 81c82c9..400b075 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -48,6 +48,18 @@
* Canvas.drawText()} directly.</p>
*/
public class StaticLayout extends Layout {
+ /*
+ * The break iteration is done in native code. The protocol for using the native code is as
+ * follows.
+ *
+ * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
+ * following:
+ *
+ * - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native.
+ * - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+ *
+ * After all paragraphs, call finish() to release expensive buffers.
+ */
static final String TAG = "StaticLayout";
@@ -724,10 +736,11 @@
}
measured = MeasuredText.buildForStaticLayout(
- paint, source, paraStart, paraEnd, textDir, nativePtr, measured);
+ paint, source, paraStart, paraEnd, textDir, measured);
final char[] chs = measured.getChars();
final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
final int[] fmCache = measured.getFontMetrics().getRawArray();
+ // TODO: Stop keeping duplicated width copy in native and Java.
widths.resize(chs.length);
// measurement has to be done before performing line breaking
@@ -739,6 +752,7 @@
// Inputs
chs,
+ measured.getNativePtr(),
paraEnd - paraStart,
firstWidth,
firstWidthLineCount,
@@ -873,18 +887,14 @@
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
&& mLineCount < mMaximumVisibleLineCount) {
- measured = MeasuredText.buildForStaticLayout(
- paint, source, bufEnd, bufEnd, textDir, nativePtr, measured);
-
paint.getFontMetricsInt(fm);
-
v = out(source,
bufEnd, bufEnd, fm.ascent, fm.descent,
fm.top, fm.bottom,
v,
spacingmult, spacingadd, null,
null, fm, 0,
- needMultiply, measured, bufEnd,
+ needMultiply, null, bufEnd,
includepad, trackpad, addLastLineSpacing, null,
null, bufStart, ellipsize,
ellipsizedWidth, 0, paint, false);
@@ -902,7 +912,7 @@
private int out(final CharSequence text, final int start, final int end, int above, int below,
int top, int bottom, int v, final float spacingmult, final float spacingadd,
final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
- final int flags, final boolean needMultiply, final MeasuredText measured,
+ final int flags, final boolean needMultiply, @Nullable final MeasuredText measured,
final int bufEnd, final boolean includePad, final boolean trackPad,
final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
@@ -911,7 +921,7 @@
final int off = j * mColumns;
final int want = off + mColumns + TOP;
int[] lines = mLines;
- final int dir = measured.getParagraphDir();
+ final int dir = (start == end) ? Layout.DIR_LEFT_TO_RIGHT : measured.getParagraphDir();
if (want >= lines.length) {
final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
@@ -938,7 +948,11 @@
lines[off + TAB] |= flags & TAB_MASK;
lines[off + HYPHEN] = flags;
lines[off + DIR] |= dir << DIR_SHIFT;
- mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
+ if (start == end) {
+ mLineDirections[j] = Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ } else {
+ mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
+ }
final boolean firstLine = (j == 0);
final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
@@ -1415,33 +1429,6 @@
mMaxLineHeight : super.getHeight();
}
- /**
- * Measurement and break iteration is done in native code. The protocol for using
- * the native code is as follows.
- *
- * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
- * following:
- *
- * - Call one of the following methods for each run within the paragraph depending on the type
- * of run:
- * + addStyleRun (a text run, to be measured in native code)
- * + addReplacementRun (a replacement run, width is given)
- *
- * - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
- *
- * After all paragraphs, call finish() to release expensive buffers.
- */
-
- /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end,
- boolean isRtl) {
- nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl);
- }
-
- /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end,
- float width) {
- nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width);
- }
-
@FastNative
private static native long nInit(
@BreakStrategy int breakStrategy,
@@ -1454,17 +1441,6 @@
@CriticalNative
private static native void nFinish(long nativePtr);
- @CriticalNative
- private static native void nAddStyleRun(
- /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
- @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
-
- @CriticalNative
- private static native void nAddReplacementRun(
- /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
- @IntRange(from = 0) int start, @IntRange(from = 0) int end,
- @FloatRange(from = 0.0f) float width);
-
// populates LineBreaks and returns the number of breaks found
//
// the arrays inside the LineBreaks objects are passed in as well
@@ -1477,6 +1453,7 @@
// Inputs
@NonNull char[] text,
+ /* Non Zero */ long measuredTextPtr,
@IntRange(from = 0) int length,
@FloatRange(from = 0.0f) float firstWidth,
@IntRange(from = 0) int firstWidthLineCount,
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 961bcb9..8df0fb8 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -84,6 +84,7 @@
"android_view_VelocityTracker.cpp",
"android_text_AndroidCharacter.cpp",
"android_text_Hyphenator.cpp",
+ "android_text_MeasuredText.cpp",
"android_text_StaticLayout.cpp",
"android_os_Debug.cpp",
"android_os_GraphicsEnvironment.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f41aacd..9e907bd 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -177,6 +177,7 @@
extern int register_android_net_TrafficStats(JNIEnv* env);
extern int register_android_text_AndroidCharacter(JNIEnv *env);
extern int register_android_text_Hyphenator(JNIEnv *env);
+extern int register_android_text_MeasuredText(JNIEnv* env);
extern int register_android_text_StaticLayout(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
@@ -1333,6 +1334,7 @@
REG_JNI(register_android_content_XmlBlock),
REG_JNI(register_android_text_AndroidCharacter),
REG_JNI(register_android_text_Hyphenator),
+ REG_JNI(register_android_text_MeasuredText),
REG_JNI(register_android_text_StaticLayout),
REG_JNI(register_android_view_InputDevice),
REG_JNI(register_android_view_KeyCharacterMap),
diff --git a/core/jni/android_text_MeasuredText.cpp b/core/jni/android_text_MeasuredText.cpp
new file mode 100644
index 0000000..af9d131
--- /dev/null
+++ b/core/jni/android_text_MeasuredText.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MeasuredText"
+
+#include "ScopedIcuLocale.h"
+#include "unicode/locid.h"
+#include "unicode/brkiter.h"
+#include "utils/misc.h"
+#include "utils/Log.h"
+#include <nativehelper/ScopedStringChars.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/JNIHelp.h>
+#include "core_jni_helpers.h"
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <algorithm>
+
+#include "SkPaint.h"
+#include "SkTypeface.h"
+#include <hwui/MinikinSkia.h>
+#include <hwui/MinikinUtils.h>
+#include <hwui/Paint.h>
+#include <minikin/FontCollection.h>
+#include <minikin/AndroidLineBreakerHelper.h>
+#include <minikin/MinikinFont.h>
+
+namespace android {
+
+static inline minikin::MeasuredTextBuilder* toBuilder(jlong ptr) {
+ return reinterpret_cast<minikin::MeasuredTextBuilder*>(ptr);
+}
+
+static inline Paint* toPaint(jlong ptr) {
+ return reinterpret_cast<Paint*>(ptr);
+}
+
+static inline minikin::MeasuredText* toMeasuredText(jlong ptr) {
+ return reinterpret_cast<minikin::MeasuredText*>(ptr);
+}
+
+template<typename Ptr> static inline jlong toJLong(Ptr ptr) {
+ return reinterpret_cast<jlong>(ptr);
+}
+
+static void releaseMeasuredText(jlong measuredTextPtr) {
+ delete toMeasuredText(measuredTextPtr);
+}
+
+// Regular JNI
+static jlong nInitBuilder() {
+ return toJLong(new minikin::MeasuredTextBuilder());
+}
+
+// Regular JNI
+static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
+ jlong paintPtr, jint start, jint end, jboolean isRtl) {
+ Paint* paint = toPaint(paintPtr);
+ const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
+ minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
+ toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint),
+ typeface->fFontCollection, isRtl);
+}
+
+// Regular JNI
+static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
+ jlong paintPtr, jint start, jint end, jfloat width) {
+ toBuilder(builderPtr)->addReplacementRun(start, end, width,
+ toPaint(paintPtr)->getMinikinLocaleListId());
+}
+
+// Regular JNI
+static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr,
+ jcharArray javaText) {
+ ScopedCharArrayRO text(env, javaText);
+ const minikin::U16StringPiece textBuffer(text.get(), text.size());
+
+ // Pass the ownership to Java.
+ return toJLong(toBuilder(builderPtr)->build(textBuffer).release());
+}
+
+// Regular JNI
+static void nFreeBuilder(JNIEnv* env, jclass /* unused */, jlong builderPtr) {
+ delete toBuilder(builderPtr);
+}
+
+// CriticalNative
+static jlong nGetReleaseFunc() {
+ return toJLong(&releaseMeasuredText);
+}
+
+static const JNINativeMethod gMethods[] = {
+ // MeasuredTextBuilder native functions.
+ {"nInitBuilder", "()J", (void*) nInitBuilder},
+ {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
+ {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
+ {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText},
+ {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
+
+ // MeasuredText native functions.
+ {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives
+};
+
+int register_android_text_MeasuredText(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods));
+}
+
+}
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 601bd98..b5c23df 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -115,6 +115,7 @@
static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
// Inputs
jcharArray javaText,
+ jlong measuredTextPtr,
jint length,
jfloat firstWidth,
jint firstWidthLineCount,
@@ -139,39 +140,20 @@
ScopedNullableIntArrayRO tabStops(env, variableTabStops);
minikin::U16StringPiece u16Text(text.get(), length);
- minikin::MeasuredText measuredText = builder->measureText(u16Text);
+ minikin::MeasuredText* measuredText = reinterpret_cast<minikin::MeasuredText*>(measuredTextPtr);
minikin::LineBreakResult result = builder->computeBreaks(
- u16Text, measuredText, firstWidth, firstWidthLineCount, restWidth, indentsOffset,
+ u16Text, *measuredText, firstWidth, firstWidthLineCount, restWidth, indentsOffset,
tabStops.get(), tabStops.size(), defaultTabStop);
recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
recycleFlags, recycleLength, result);
- env->SetFloatArrayRegion(charWidths, 0, measuredText.widths.size(), measuredText.widths.data());
-
- builder->clearRuns();
+ env->SetFloatArrayRegion(charWidths, 0, measuredText->widths.size(),
+ measuredText->widths.data());
return static_cast<jint>(result.breakPoints.size());
}
-// Basically similar to Paint.getTextRunAdvances but with C++ interface
-// CriticalNative
-static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint end, jboolean isRtl) {
- minikin::android::StaticLayoutNative* builder = toNative(nativePtr);
- Paint* paint = reinterpret_cast<Paint*>(nativePaint);
- const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
- minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
- builder->addStyleRun(start, end, std::move(minikinPaint), typeface->fFontCollection, isRtl);
-}
-
-// CriticalNative
-static void nAddReplacementRun(jlong nativePtr, jlong nativePaint, jint start, jint end,
- jfloat width) {
- minikin::android::StaticLayoutNative* builder = toNative(nativePtr);
- Paint* paint = reinterpret_cast<Paint*>(nativePaint);
- builder->addReplacementRun(start, end, width, paint->getMinikinLocaleListId());
-}
-
static const JNINativeMethod gMethods[] = {
// Fast Natives
{"nInit", "("
@@ -185,8 +167,6 @@
// Critical Natives
{"nFinish", "(J)V", (void*) nFinish},
- {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
- {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
// Regular JNI
{"nComputeLineBreaks", "("
@@ -194,6 +174,7 @@
// Inputs
"[C" // text
+ "J" // MeasuredText ptr.
"I" // length
"F" // firstWidth
"I" // firstWidthLineCount
diff --git a/core/tests/coretests/src/android/text/MeasuredTextTest.java b/core/tests/coretests/src/android/text/MeasuredTextTest.java
index a690c5d..ddef0c6 100644
--- a/core/tests/coretests/src/android/text/MeasuredTextTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredTextTest.java
@@ -17,6 +17,7 @@
package android.text;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
@@ -71,6 +72,7 @@
assertEquals(0, mt.getWidths().size());
assertEquals(0, mt.getSpanEndCache().size());
assertEquals(0, mt.getFontMetrics().size());
+ assertEquals(0, mt.getNativePtr());
// Recycle it
MeasuredText mt2 = MeasuredText.buildForBidi("_VVV_", 1, 4, RTL, mt);
@@ -82,6 +84,7 @@
assertEquals(0, mt2.getWidths().size());
assertEquals(0, mt2.getSpanEndCache().size());
assertEquals(0, mt2.getFontMetrics().size());
+ assertEquals(0, mt2.getNativePtr());
mt2.recycle();
}
@@ -103,6 +106,7 @@
assertEquals(10, mt.getWidths().get(2), 0);
assertEquals(0, mt.getSpanEndCache().size());
assertEquals(0, mt.getFontMetrics().size());
+ assertEquals(0, mt.getNativePtr());
// Recycle it
MeasuredText mt2 = MeasuredText.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt);
@@ -118,6 +122,41 @@
assertEquals(5, mt2.getWidths().get(2), 0);
assertEquals(0, mt2.getSpanEndCache().size());
assertEquals(0, mt2.getFontMetrics().size());
+ assertEquals(0, mt2.getNativePtr());
+
+ mt2.recycle();
+ }
+
+ @Test
+ public void buildForStaticLayout() {
+ MeasuredText mt = null;
+
+ mt = MeasuredText.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
+ assertNotNull(mt);
+ assertNotNull(mt.getChars());
+ assertEquals("XXX", charsToString(mt.getChars()));
+ assertEquals(Layout.DIR_LEFT_TO_RIGHT, mt.getParagraphDir());
+ assertNotNull(mt.getDirections(0, 3));
+ assertEquals(0, mt.getWholeWidth(), 0);
+ assertEquals(0, mt.getWidths().size());
+ assertEquals(1, mt.getSpanEndCache().size());
+ assertEquals(3, mt.getSpanEndCache().get(0));
+ assertNotEquals(0, mt.getFontMetrics().size());
+ assertNotEquals(0, mt.getNativePtr());
+
+ // Recycle it
+ MeasuredText mt2 = MeasuredText.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
+ assertEquals(mt2, mt);
+ assertNotNull(mt2.getChars());
+ assertEquals("VVV", charsToString(mt.getChars()));
+ assertEquals(Layout.DIR_RIGHT_TO_LEFT, mt2.getParagraphDir());
+ assertNotNull(mt2.getDirections(0, 3));
+ assertEquals(0, mt2.getWholeWidth(), 0);
+ assertEquals(0, mt2.getWidths().size());
+ assertEquals(1, mt2.getSpanEndCache().size());
+ assertEquals(4, mt2.getSpanEndCache().get(0));
+ assertNotEquals(0, mt2.getFontMetrics().size());
+ assertNotEquals(0, mt2.getNativePtr());
mt2.recycle();
}