Extract native methods into isolated classes
StaticLayout/MeasuredParagraph uses native methods which is a pain point
of porting TextView to JetPack.
To make minimize the dependency to the native methods, extract native
methods and put them into a thin wrapper class.
The performance impact is limited:
android.text.StaticLayoutPerfTest:
PrecomputedText Balanced Hyphenation : 602 -> 644: ( +42, +7.0%)
PrecomputedText Balanced NoHyphenation: 457 -> 476: ( +19, +4.2%)
PrecomputedText Greedy Hyphenation : 397 -> 412: ( +15, +3.8%)
PrecomputedText Greedy NoHyphenation : 397 -> 411: ( +14, +3.5%)
RandomText Balanced Hyphenation : 17,594 -> 17,715: (+121, +0.7%)
RandomText Balanced NoHyphenation : 7,146 -> 7,236: ( +90, +1.3%)
RandomText Greedy Hyphenation : 7,125 -> 7,196: ( +71, +1.0%)
RandomText Greedy NoHyphenation : 7,099 -> 7,187: ( +88, +1.2%)
draw
PrecomputedText NoStyle : 614 -> 628: ( +14, +2.3%)
PrecomputedText Style : 778 -> 826: ( +48, +6.2%)
RandomText NoStyle : 537 -> 540: ( +3, +0.6%)
RandomText Style : 786 -> 759: ( -27, -3.4%)
Bug: N/A
Test: atest CtsWidgetTestCases:EditTextTest
CtsWidgetTestCases:TextViewFadingEdgeTest
FrameworksCoreTests:TextViewFallbackLineSpacingTest
FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest
CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest
CtsTextTestCases FrameworksCoreTests:android.text
CtsWidgetTestCases:TextViewPrecomputedTextTest
Change-Id: I976df4db63be241af395dd30dd94182f76bdae53
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index c2c3182..9bf8cd2 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -30,10 +30,6 @@
import android.text.style.ReplacementSpan;
import android.util.Pools.SynchronizedPool;
-import dalvik.annotation.optimization.CriticalNative;
-
-import libcore.util.NativeAllocationRegistry;
-
import java.util.Arrays;
/**
@@ -62,9 +58,6 @@
public class MeasuredParagraph {
private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
- private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);
-
private MeasuredParagraph() {} // Use build static functions instead.
private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1);
@@ -128,24 +121,7 @@
private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
// The native MeasuredParagraph.
- // 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;
- }
- }
+ private @Nullable NativeMeasuredParagraph mNativeMeasuredParagraph;
// Following two objects are for avoiding object allocation.
private @NonNull TextPaint mCachedPaint = new TextPaint();
@@ -173,7 +149,7 @@
mWidths.clear();
mFontMetrics.clear();
mSpanEndCache.clear();
- unbindNativeObject();
+ mNativeMeasuredParagraph = null;
}
/**
@@ -267,10 +243,10 @@
* Returns the native ptr of the MeasuredParagraph.
*
* This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
- * Returns 0 in other cases.
+ * Returns null in other cases.
*/
- public /* Maybe Zero */ long getNativePtr() {
- return mNativePtr;
+ public NativeMeasuredParagraph getNativeMeasuredParagraph() {
+ return mNativeMeasuredParagraph;
}
/**
@@ -283,7 +259,7 @@
* @param end the exclusive end offset of the target region in the text
*/
public float getWidth(int start, int end) {
- if (mNativePtr == 0) {
+ if (mNativeMeasuredParagraph == null) {
// We have result in Java.
final float[] widths = mWidths.getRawArray();
float r = 0.0f;
@@ -293,7 +269,7 @@
return r;
} else {
// We have result in native.
- return nGetWidth(mNativePtr, start, end);
+ return mNativeMeasuredParagraph.getWidth(start, end);
}
}
@@ -305,7 +281,16 @@
*/
public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@NonNull Rect bounds) {
- nGetBounds(mNativePtr, mCopiedBuffer, start, end, bounds);
+ mNativeMeasuredParagraph.getBounds(mCopiedBuffer, start, end, bounds);
+ }
+
+ /**
+ * Returns a width of the character at the offset.
+ *
+ * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
+ */
+ public float getCharWidthAt(@IntRange(from = 0) int offset) {
+ return mNativeMeasuredParagraph.getCharWidthAt(offset);
}
/**
@@ -364,7 +349,7 @@
if (mt.mSpanned == null) {
// No style change by MetricsAffectingSpan. Just measure all text.
mt.applyMetricsAffectingSpan(
- paint, null /* spans */, start, end, 0 /* native static layout ptr */);
+ paint, null /* spans */, start, end, null /* native builder ptr */);
} else {
// There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
int spanEnd;
@@ -374,7 +359,7 @@
MetricAffectingSpan.class);
spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
mt.applyMetricsAffectingSpan(
- paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
+ paint, spans, spanStart, spanEnd, null /* native builder ptr */);
}
}
return mt;
@@ -406,25 +391,16 @@
@Nullable MeasuredParagraph recycle) {
final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
mt.resetAndAnalyzeBidi(text, start, end, textDir);
+ final NativeMeasuredParagraph.Builder builder = new NativeMeasuredParagraph.Builder();
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(
- nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
- computeHyphenation, computeLayout));
- } finally {
- nFreeBuilder(nativeBuilderPtr);
- }
- return mt;
- }
-
- long nativeBuilderPtr = nInitBuilder();
- try {
+ mt.mNativeMeasuredParagraph = builder.build(mt.mCopiedBuffer, computeHyphenation,
+ computeLayout);
+ } else {
if (mt.mSpanned == null) {
// No style change by MetricsAffectingSpan. Just measure all text.
- mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
+ mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, builder);
mt.mSpanEndCache.append(end);
} else {
// There may be a MetricsAffectingSpan. Split into span transitions and apply
@@ -437,15 +413,12 @@
MetricAffectingSpan.class);
spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
MetricAffectingSpan.class);
- mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
- nativeBuilderPtr);
+ mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, builder);
mt.mSpanEndCache.append(spanEnd);
}
}
- mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
- computeHyphenation, computeLayout));
- } finally {
- nFreeBuilder(nativeBuilderPtr);
+ mt.mNativeMeasuredParagraph = builder.build(mt.mCopiedBuffer, computeHyphenation,
+ computeLayout);
}
return mt;
@@ -517,13 +490,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 nativeBuilderPtr) {
+ @Nullable NativeMeasuredParagraph.Builder builder) {
// 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 (nativeBuilderPtr == 0) {
+ if (builder == null) {
// Assigns all width to the first character. This is the same behavior as minikin.
mWidths.set(start, width);
if (end > start + 1) {
@@ -531,24 +504,22 @@
}
mWholeWidth += width;
} else {
- nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
- width);
+ builder.addReplacementRun(mCachedPaint, 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 nativeBuilderPtr) {
+ @Nullable NativeMeasuredParagraph.Builder builder) {
if (mLtrWithoutBidi) {
// If the whole text is LTR direction, just apply whole region.
- if (nativeBuilderPtr == 0) {
+ if (builder == null) {
mWholeWidth += mCachedPaint.getTextRunAdvances(
mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
mWidths.getRawArray(), start);
} else {
- nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
- false /* isRtl */);
+ builder.addStyleRun(mCachedPaint, start, end, false /* isRtl */);
}
} else {
// If there is multiple bidi levels, split into individual bidi level and apply style.
@@ -558,14 +529,13 @@
for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point
final boolean isRtl = (level & 0x1) != 0;
- if (nativeBuilderPtr == 0) {
+ if (builder == null) {
final int levelLength = levelEnd - levelStart;
mWholeWidth += mCachedPaint.getTextRunAdvances(
mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
isRtl, mWidths.getRawArray(), levelStart);
} else {
- nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
- levelEnd, isRtl);
+ builder.addStyleRun(mCachedPaint, levelStart, levelEnd, isRtl);
}
if (levelEnd == end) {
break;
@@ -582,12 +552,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 nativeBuilderPtr) {
+ @Nullable NativeMeasuredParagraph.Builder builder) {
mCachedPaint.set(paint);
// XXX paint should not have a baseline shift, but...
mCachedPaint.baselineShift = 0;
- final boolean needFontMetrics = nativeBuilderPtr != 0;
+ final boolean needFontMetrics = builder != null;
if (needFontMetrics && mCachedFm == null) {
mCachedFm = new Paint.FontMetricsInt();
@@ -610,15 +580,14 @@
final int startInCopiedBuffer = start - mTextStart;
final int endInCopiedBuffer = end - mTextStart;
- if (nativeBuilderPtr != 0) {
+ if (builder != null) {
mCachedPaint.getFontMetricsInt(mCachedFm);
}
if (replacement != null) {
- applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
- nativeBuilderPtr);
+ applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, builder);
} else {
- applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
+ applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, builder);
}
if (needFontMetrics) {
@@ -689,59 +658,6 @@
* This only works if the MeasuredParagraph is computed with buildForStaticLayout.
*/
public @IntRange(from = 0) int getMemoryUsage() {
- return nGetMemoryUsage(mNativePtr);
+ return mNativeMeasuredParagraph.getMemoryUsage();
}
-
- private static native /* Non Zero */ long nInitBuilder();
-
- /**
- * Apply style to make native measured text.
- *
- * @param nativeBuilderPtr The native MeasuredParagraph 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 MeasuredParagraph 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 nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
- @NonNull char[] text,
- boolean computeHyphenation,
- boolean computeLayout);
-
- private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
-
- @CriticalNative
- private static native float nGetWidth(/* Non Zero */ long nativePtr,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end);
-
- @CriticalNative
- private static native /* Non Zero */ long nGetReleaseFunc();
-
- @CriticalNative
- private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr);
-
- private static native void nGetBounds(long nativePtr, char[] buf, int start, int end,
- Rect rect);
}
diff --git a/core/java/android/text/NativeLineBreaker.java b/core/java/android/text/NativeLineBreaker.java
new file mode 100644
index 0000000..a31b336
--- /dev/null
+++ b/core/java/android/text/NativeLineBreaker.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package android.text;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * A native implementation of the line breaker.
+ * TODO: Consider to make this class public.
+ * @hide
+ */
+public class NativeLineBreaker {
+
+ /**
+ * A result object of a line breaking
+ */
+ public static class LineBreaks {
+ public int breakCount;
+ private static final int INITIAL_SIZE = 16;
+ public int[] breaks = new int[INITIAL_SIZE];
+ public float[] widths = new float[INITIAL_SIZE];
+ public float[] ascents = new float[INITIAL_SIZE];
+ public float[] descents = new float[INITIAL_SIZE];
+ public int[] flags = new int[INITIAL_SIZE];
+ // breaks, widths, and flags should all have the same length
+ }
+
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ NativeLineBreaker.class.getClassLoader(), nGetReleaseFunc(), 64);
+
+ private final long mNativePtr;
+
+ /**
+ * A constructor of NativeLineBreaker
+ */
+ public NativeLineBreaker(@Layout.BreakStrategy int breakStrategy,
+ @Layout.HyphenationFrequency int hyphenationFrequency,
+ boolean justify, @Nullable int[] indents) {
+ mNativePtr = nInit(breakStrategy, hyphenationFrequency, justify, indents);
+ sRegistry.registerNativeAllocation(this, mNativePtr);
+ }
+
+ /**
+ * Break text into lines
+ *
+ * @param chars an array of characters
+ * @param measuredPara a result of the text measurement
+ * @param length a length of the target text from the begining
+ * @param firstWidth a width of the first width of the line in this paragraph
+ * @param firstWidthLineCount a number of lines that has the length of the firstWidth
+ * @param restWidth a width of the rest of the lines.
+ * @param variableTabStops an array of tab stop widths
+ * @param defaultTabStop a width of the tab stop
+ * @param indentsOffset an offset of the indents to be used.
+ * @param out output buffer
+ * @return a number of the lines
+ */
+ @NonNull public int computeLineBreaks(
+ @NonNull char[] chars,
+ @NonNull NativeMeasuredParagraph measuredPara,
+ @IntRange(from = 0) int length,
+ @FloatRange(from = 0.0f) float firstWidth,
+ @IntRange(from = 0) int firstWidthLineCount,
+ @FloatRange(from = 0.0f) float restWidth,
+ @Nullable int[] variableTabStops,
+ int defaultTabStop,
+ @IntRange(from = 0) int indentsOffset,
+ @NonNull LineBreaks out) {
+ return nComputeLineBreaks(
+ mNativePtr,
+
+ // Inputs
+ chars,
+ measuredPara.getNativePtr(),
+ length,
+ firstWidth,
+ firstWidthLineCount,
+ restWidth,
+ variableTabStops,
+ defaultTabStop,
+ indentsOffset,
+
+ // Outputs
+ out,
+ out.breaks.length,
+ out.breaks,
+ out.widths,
+ out.ascents,
+ out.descents,
+ out.flags);
+
+ }
+
+ @FastNative
+ private static native long nInit(
+ @Layout.BreakStrategy int breakStrategy,
+ @Layout.HyphenationFrequency int hyphenationFrequency,
+ boolean isJustified,
+ @Nullable int[] indents);
+
+ @CriticalNative
+ private static native long nGetReleaseFunc();
+
+ // 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
+ // The individual character widths will be returned in charWidths. The length of
+ // charWidths must be at least the length of the text.
+ private static native int nComputeLineBreaks(
+ /* non zero */ long nativePtr,
+
+ // Inputs
+ @NonNull char[] text,
+ /* Non Zero */ long measuredTextPtr,
+ @IntRange(from = 0) int length,
+ @FloatRange(from = 0.0f) float firstWidth,
+ @IntRange(from = 0) int firstWidthLineCount,
+ @FloatRange(from = 0.0f) float restWidth,
+ @Nullable int[] variableTabStops,
+ int defaultTabStop,
+ @IntRange(from = 0) int indentsOffset,
+
+ // Outputs
+ @NonNull LineBreaks recycle,
+ @IntRange(from = 0) int recycleLength,
+ @NonNull int[] recycleBreaks,
+ @NonNull float[] recycleWidths,
+ @NonNull float[] recycleAscents,
+ @NonNull float[] recycleDescents,
+ @NonNull int[] recycleFlags);
+}
diff --git a/core/java/android/text/NativeMeasuredParagraph.java b/core/java/android/text/NativeMeasuredParagraph.java
new file mode 100644
index 0000000..d03674f
--- /dev/null
+++ b/core/java/android/text/NativeMeasuredParagraph.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+package android.text;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * A native implementation of measured paragraph.
+ * TODO: Consider to make this class public.
+ * @hide
+ */
+public class NativeMeasuredParagraph {
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ NativeMeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);
+
+ private long mNativePtr;
+
+ // Use builder instead.
+ private NativeMeasuredParagraph(long ptr) {
+ mNativePtr = ptr;
+ }
+
+ /**
+ * Returns a width of the given region
+ */
+ public float getWidth(int start, int end) {
+ return nGetWidth(mNativePtr, start, end);
+ }
+
+ /**
+ * Returns a memory usage of the native object.
+ */
+ public int getMemoryUsage() {
+ return nGetMemoryUsage(mNativePtr);
+ }
+
+ /**
+ * Fills the boundary box of the given region
+ */
+ public void getBounds(char[] buf, int start, int end, Rect rect) {
+ nGetBounds(mNativePtr, buf, start, end, rect);
+ }
+
+ /**
+ * Returns the width of the character at the given offset
+ */
+ public float getCharWidthAt(int offset) {
+ return nGetCharWidthAt(mNativePtr, offset);
+ }
+
+ /**
+ * Returns a native pointer of the underlying native object.
+ */
+ public long getNativePtr() {
+ return mNativePtr;
+ }
+
+ @CriticalNative
+ private static native float nGetWidth(/* Non Zero */ long nativePtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end);
+
+ @CriticalNative
+ private static native /* Non Zero */ long nGetReleaseFunc();
+
+ @CriticalNative
+ private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr);
+
+ private static native void nGetBounds(long nativePtr, char[] buf, int start, int end,
+ Rect rect);
+
+ @CriticalNative
+ private static native float nGetCharWidthAt(long nativePtr, int offset);
+
+ /**
+ * A builder for the NativeMeasuredParagraph
+ */
+ public static class Builder {
+ private final long mNativePtr;
+
+ public Builder() {
+ mNativePtr = nInitBuilder();
+ }
+
+ /**
+ * Apply styles to given range
+ */
+ public void addStyleRun(@NonNull Paint paint, int start, int end, boolean isRtl) {
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
+ }
+
+ /**
+ * Tells native that the given range is replaced with the object of given width.
+ */
+ public void addReplacementRun(@NonNull Paint paint, int start, int end, float width) {
+ nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
+ }
+
+ /**
+ * Build the NativeMeasuredParagraph
+ */
+ public NativeMeasuredParagraph build(char[] text, boolean computeHyphenation,
+ boolean computeLayout) {
+ try {
+ long ptr = nBuildNativeMeasuredParagraph(mNativePtr, text, computeHyphenation,
+ computeLayout);
+ NativeMeasuredParagraph res = new NativeMeasuredParagraph(ptr);
+ sRegistry.registerNativeAllocation(res, ptr);
+ return res;
+ } finally {
+ nFreeBuilder(mNativePtr);
+ }
+ }
+
+ private static native /* Non Zero */ long nInitBuilder();
+
+ /**
+ * Apply style to make native measured text.
+ *
+ * @param nativeBuilderPtr The native MeasuredParagraph 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 MeasuredParagraph 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 nBuildNativeMeasuredParagraph(
+ /* Non Zero */ long nativeBuilderPtr,
+ @NonNull char[] text,
+ boolean computeHyphenation,
+ boolean computeLayout);
+
+ private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
+ }
+}
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 027ead3..b7ea012 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -518,6 +518,21 @@
}
/**
+ * Returns a width of a character at offset
+ *
+ * @param offset an offset of the text.
+ * @return a width of the character.
+ * @hide
+ */
+ public float getCharWidthAt(@IntRange(from = 0) int offset) {
+ Preconditions.checkArgument(0 <= offset && offset < mText.length(), "invalid offset");
+ final int paraIndex = findParaIndex(offset);
+ final int paraStart = getParagraphStart(paraIndex);
+ final int paraEnd = getParagraphEnd(paraIndex);
+ return getMeasuredParagraph(paraIndex).getCharWidthAt(offset - paraStart);
+ }
+
+ /**
* Returns the size of native PrecomputedText memory usage.
*
* Note that this is not guaranteed to be accurate. Must be used only for testing purposes.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 4b78aa2..6dad238 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Paint;
-import android.text.AutoGrowArray.FloatArray;
import android.text.style.LeadingMarginSpan;
import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
import android.text.style.LineHeightSpan;
@@ -32,9 +31,6 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
-import dalvik.annotation.optimization.CriticalNative;
-import dalvik.annotation.optimization.FastNative;
-
import java.util.Arrays;
/**
@@ -57,7 +53,7 @@
*
* - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
* native.
- * - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+ * - Run NativeLineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
*
* After all paragraphs, call finish() to release expensive buffers.
*/
@@ -586,8 +582,7 @@
float ellipsizedWidth = b.mEllipsizedWidth;
TextUtils.TruncateAt ellipsize = b.mEllipsize;
final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
- LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
- FloatArray widths = new FloatArray();
+ NativeLineBreaker.LineBreaks lineBreaks = new NativeLineBreaker.LineBreaks();
mLineCount = 0;
mEllipsized = false;
@@ -615,7 +610,7 @@
indents = null;
}
- final long nativePtr = nInit(
+ final NativeLineBreaker lineBreaker = new NativeLineBreaker(
b.mBreakStrategy, b.mHyphenationFrequency,
// TODO: Support more justification mode, e.g. letter spacing, stretching.
b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
@@ -639,243 +634,219 @@
bufEnd, false /* computeLayout */);
}
- try {
- for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
- final int paraStart = paraIndex == 0
- ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
- final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
+ for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
+ final int paraStart = paraIndex == 0
+ ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
+ final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
- int firstWidthLineCount = 1;
- int firstWidth = outerWidth;
- int restWidth = outerWidth;
+ int firstWidthLineCount = 1;
+ int firstWidth = outerWidth;
+ int restWidth = outerWidth;
- LineHeightSpan[] chooseHt = null;
+ LineHeightSpan[] chooseHt = null;
+ if (spanned != null) {
+ LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+ LeadingMarginSpan.class);
+ for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
+ firstWidth -= sp[i].getLeadingMargin(true);
+ restWidth -= sp[i].getLeadingMargin(false);
- if (spanned != null) {
- LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
- LeadingMarginSpan.class);
- for (int i = 0; i < sp.length; i++) {
- LeadingMarginSpan lms = sp[i];
- firstWidth -= sp[i].getLeadingMargin(true);
- restWidth -= sp[i].getLeadingMargin(false);
+ // LeadingMarginSpan2 is odd. The count affects all
+ // leading margin spans, not just this particular one
+ if (lms instanceof LeadingMarginSpan2) {
+ LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+ firstWidthLineCount = Math.max(firstWidthLineCount,
+ lms2.getLeadingMarginLineCount());
+ }
+ }
- // LeadingMarginSpan2 is odd. The count affects all
- // leading margin spans, not just this particular one
- if (lms instanceof LeadingMarginSpan2) {
- LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
- firstWidthLineCount = Math.max(firstWidthLineCount,
- lms2.getLeadingMarginLineCount());
- }
+ chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+ if (chooseHt.length == 0) {
+ chooseHt = null; // So that out() would not assume it has any contents
+ } else {
+ if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+ chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
}
- chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+ for (int i = 0; i < chooseHt.length; i++) {
+ int o = spanned.getSpanStart(chooseHt[i]);
- if (chooseHt.length == 0) {
- chooseHt = null; // So that out() would not assume it has any contents
+ if (o < paraStart) {
+ // starts in this layout, before the
+ // current paragraph
+
+ chooseHtv[i] = getLineTop(getLineForOffset(o));
+ } else {
+ // starts in this paragraph
+
+ chooseHtv[i] = v;
+ }
+ }
+ }
+ }
+ // tab stop locations
+ int[] variableTabStops = null;
+ if (spanned != null) {
+ TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+ paraEnd, TabStopSpan.class);
+ if (spans.length > 0) {
+ int[] stops = new int[spans.length];
+ for (int i = 0; i < spans.length; i++) {
+ stops[i] = spans[i].getTabStop();
+ }
+ Arrays.sort(stops, 0, stops.length);
+ variableTabStops = stops;
+ }
+ }
+
+ final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
+ final char[] chs = measuredPara.getChars();
+ final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+ final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
+ int breakCount = lineBreaker.computeLineBreaks(
+ measuredPara.getChars(),
+ measuredPara.getNativeMeasuredParagraph(),
+ paraEnd - paraStart,
+ firstWidth,
+ firstWidthLineCount,
+ restWidth,
+ variableTabStops,
+ TAB_INCREMENT,
+ mLineCount,
+ lineBreaks);
+
+ final int[] breaks = lineBreaks.breaks;
+ final float[] lineWidths = lineBreaks.widths;
+ final float[] ascents = lineBreaks.ascents;
+ final float[] descents = lineBreaks.descents;
+ final int[] flags = lineBreaks.flags;
+
+ final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+ final boolean ellipsisMayBeApplied = ellipsize != null
+ && (ellipsize == TextUtils.TruncateAt.END
+ || (mMaximumVisibleLineCount == 1
+ && ellipsize != TextUtils.TruncateAt.MARQUEE));
+ if (0 < remainingLineCount && remainingLineCount < breakCount
+ && ellipsisMayBeApplied) {
+ // Calculate width
+ float width = 0;
+ int flag = 0; // XXX May need to also have starting hyphen edit
+ for (int i = remainingLineCount - 1; i < breakCount; i++) {
+ if (i == breakCount - 1) {
+ width += lineWidths[i];
} else {
- if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
- chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
- }
-
- for (int i = 0; i < chooseHt.length; i++) {
- int o = spanned.getSpanStart(chooseHt[i]);
-
- if (o < paraStart) {
- // starts in this layout, before the
- // current paragraph
-
- chooseHtv[i] = getLineTop(getLineForOffset(o));
- } else {
- // starts in this paragraph
-
- chooseHtv[i] = v;
- }
+ for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+ width += measuredPara.getCharWidthAt(j - paraStart);
}
}
+ flag |= flags[i] & TAB_MASK;
+ }
+ // Treat the last line and overflowed lines as a single line.
+ breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+ lineWidths[remainingLineCount - 1] = width;
+ flags[remainingLineCount - 1] = flag;
+
+ breakCount = remainingLineCount;
+ }
+
+ // here is the offset of the starting character of the line we are currently
+ // measuring
+ int here = paraStart;
+
+ int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+ int fmCacheIndex = 0;
+ int spanEndCacheIndex = 0;
+ int breakIndex = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ // retrieve end of span
+ spanEnd = spanEndCache[spanEndCacheIndex++];
+
+ // retrieve cached metrics, order matches above
+ fm.top = fmCache[fmCacheIndex * 4 + 0];
+ fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+ fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+ fm.descent = fmCache[fmCacheIndex * 4 + 3];
+ fmCacheIndex++;
+
+ if (fm.top < fmTop) {
+ fmTop = fm.top;
+ }
+ if (fm.ascent < fmAscent) {
+ fmAscent = fm.ascent;
+ }
+ if (fm.descent > fmDescent) {
+ fmDescent = fm.descent;
+ }
+ if (fm.bottom > fmBottom) {
+ fmBottom = fm.bottom;
}
- // tab stop locations
- int[] variableTabStops = null;
- if (spanned != null) {
- TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
- paraEnd, TabStopSpan.class);
- if (spans.length > 0) {
- int[] stops = new int[spans.length];
- for (int i = 0; i < spans.length; i++) {
- stops[i] = spans[i].getTabStop();
- }
- Arrays.sort(stops, 0, stops.length);
- variableTabStops = stops;
- }
+ // skip breaks ending before current span range
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+ breakIndex++;
}
- final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
- final char[] chs = measuredPara.getChars();
- final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
- final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
- // TODO: Stop keeping duplicated width copy in native and Java.
- widths.resize(chs.length);
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+ int endPos = paraStart + breaks[breakIndex];
- // measurement has to be done before performing line breaking
- // but we don't want to recompute fontmetrics or span ranges the
- // second time, so we cache those and then use those stored values
+ boolean moreChars = (endPos < bufEnd);
- int breakCount = nComputeLineBreaks(
- nativePtr,
+ final int ascent = fallbackLineSpacing
+ ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+ : fmAscent;
+ final int descent = fallbackLineSpacing
+ ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+ : fmDescent;
- // Inputs
- chs,
- measuredPara.getNativePtr(),
- paraEnd - paraStart,
- firstWidth,
- firstWidthLineCount,
- restWidth,
- variableTabStops,
- TAB_INCREMENT,
- mLineCount,
+ v = out(source, here, endPos,
+ ascent, descent, fmTop, fmBottom,
+ v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+ flags[breakIndex], needMultiply, measuredPara, bufEnd,
+ includepad, trackpad, addLastLineSpacing, chs,
+ paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
+ paint, moreChars);
- // Outputs
- lineBreaks,
- lineBreaks.breaks.length,
- lineBreaks.breaks,
- lineBreaks.widths,
- lineBreaks.ascents,
- lineBreaks.descents,
- lineBreaks.flags,
- widths.getRawArray());
-
- final int[] breaks = lineBreaks.breaks;
- final float[] lineWidths = lineBreaks.widths;
- final float[] ascents = lineBreaks.ascents;
- final float[] descents = lineBreaks.descents;
- final int[] flags = lineBreaks.flags;
-
- final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
- final boolean ellipsisMayBeApplied = ellipsize != null
- && (ellipsize == TextUtils.TruncateAt.END
- || (mMaximumVisibleLineCount == 1
- && ellipsize != TextUtils.TruncateAt.MARQUEE));
- if (0 < remainingLineCount && remainingLineCount < breakCount
- && ellipsisMayBeApplied) {
- // Calculate width and flag.
- float width = 0;
- int flag = 0; // XXX May need to also have starting hyphen edit
- for (int i = remainingLineCount - 1; i < breakCount; i++) {
- if (i == breakCount - 1) {
- width += lineWidths[i];
- } else {
- for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
- width += widths.get(j);
- }
- }
- flag |= flags[i] & TAB_MASK;
- }
- // Treat the last line and overflowed lines as a single line.
- breaks[remainingLineCount - 1] = breaks[breakCount - 1];
- lineWidths[remainingLineCount - 1] = width;
- flags[remainingLineCount - 1] = flag;
-
- breakCount = remainingLineCount;
- }
-
- // here is the offset of the starting character of the line we are currently
- // measuring
- int here = paraStart;
-
- int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
- int fmCacheIndex = 0;
- int spanEndCacheIndex = 0;
- int breakIndex = 0;
- for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
- // retrieve end of span
- spanEnd = spanEndCache[spanEndCacheIndex++];
-
- // retrieve cached metrics, order matches above
- fm.top = fmCache[fmCacheIndex * 4 + 0];
- fm.bottom = fmCache[fmCacheIndex * 4 + 1];
- fm.ascent = fmCache[fmCacheIndex * 4 + 2];
- fm.descent = fmCache[fmCacheIndex * 4 + 3];
- fmCacheIndex++;
-
- if (fm.top < fmTop) {
+ if (endPos < spanEnd) {
+ // preserve metrics for current span
fmTop = fm.top;
- }
- if (fm.ascent < fmAscent) {
- fmAscent = fm.ascent;
- }
- if (fm.descent > fmDescent) {
- fmDescent = fm.descent;
- }
- if (fm.bottom > fmBottom) {
fmBottom = fm.bottom;
+ fmAscent = fm.ascent;
+ fmDescent = fm.descent;
+ } else {
+ fmTop = fmBottom = fmAscent = fmDescent = 0;
}
- // skip breaks ending before current span range
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
- breakIndex++;
+ here = endPos;
+ breakIndex++;
+
+ if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+ return;
}
-
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
- int endPos = paraStart + breaks[breakIndex];
-
- boolean moreChars = (endPos < bufEnd);
-
- final int ascent = fallbackLineSpacing
- ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
- : fmAscent;
- final int descent = fallbackLineSpacing
- ? Math.max(fmDescent, Math.round(descents[breakIndex]))
- : fmDescent;
- v = out(source, here, endPos,
- ascent, descent, fmTop, fmBottom,
- v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
- flags[breakIndex], needMultiply, measuredPara, bufEnd,
- includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
- paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
- paint, moreChars);
-
- if (endPos < spanEnd) {
- // preserve metrics for current span
- fmTop = fm.top;
- fmBottom = fm.bottom;
- fmAscent = fm.ascent;
- fmDescent = fm.descent;
- } else {
- fmTop = fmBottom = fmAscent = fmDescent = 0;
- }
-
- here = endPos;
- breakIndex++;
-
- if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
- return;
- }
- }
- }
-
- if (paraEnd == bufEnd) {
- break;
}
}
- if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
- && mLineCount < mMaximumVisibleLineCount) {
- final MeasuredParagraph measuredPara =
- MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
- paint.getFontMetricsInt(fm);
- v = out(source,
- bufEnd, bufEnd, fm.ascent, fm.descent,
- fm.top, fm.bottom,
- v,
- spacingmult, spacingadd, null,
- null, fm, 0,
- needMultiply, measuredPara, bufEnd,
- includepad, trackpad, addLastLineSpacing, null,
- null, bufStart, ellipsize,
- ellipsizedWidth, 0, paint, false);
+ if (paraEnd == bufEnd) {
+ break;
}
- } finally {
- nFinish(nativePtr);
+ }
+
+ if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
+ && mLineCount < mMaximumVisibleLineCount) {
+ final MeasuredParagraph measuredPara =
+ MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
+ paint.getFontMetricsInt(fm);
+ v = out(source,
+ bufEnd, bufEnd, fm.ascent, fm.descent,
+ fm.top, fm.bottom,
+ v,
+ spacingmult, spacingadd, null,
+ null, fm, 0,
+ needMultiply, measuredPara, bufEnd,
+ includepad, trackpad, addLastLineSpacing, null,
+ bufStart, ellipsize,
+ ellipsizedWidth, 0, paint, false);
}
}
@@ -884,7 +855,7 @@
final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
final int bufEnd, final boolean includePad, final boolean trackPad,
- final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
+ final boolean addLastLineLineSpacing, final char[] chs,
final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
final float textWidth, final TextPaint paint, final boolean moreChars) {
final int j = mLineCount;
@@ -942,7 +913,7 @@
(!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
ellipsize == TextUtils.TruncateAt.END);
if (doEllipsis) {
- calculateEllipsis(start, end, widths, widthStart,
+ calculateEllipsis(start, end, measured, widthStart,
ellipsisWidth, ellipsize, j,
textWidth, paint, forceEllipsis);
}
@@ -1026,7 +997,7 @@
}
private void calculateEllipsis(int lineStart, int lineEnd,
- float[] widths, int widthStart,
+ MeasuredParagraph measured, int widthStart,
float avail, TextUtils.TruncateAt where,
int line, float textWidth, TextPaint paint,
boolean forceEllipsis) {
@@ -1050,9 +1021,10 @@
int i;
for (i = len; i > 0; i--) {
- float w = widths[i - 1 + lineStart - widthStart];
+ float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
if (w + sum + ellipsisWidth > avail) {
- while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
+ while (i < len
+ && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
i++;
}
break;
@@ -1074,7 +1046,7 @@
int i;
for (i = 0; i < len; i++) {
- float w = widths[i + lineStart - widthStart];
+ float w = measured.getCharWidthAt(i + lineStart - widthStart);
if (w + sum + ellipsisWidth > avail) {
break;
@@ -1097,10 +1069,12 @@
float ravail = (avail - ellipsisWidth) / 2;
for (right = len; right > 0; right--) {
- float w = widths[right - 1 + lineStart - widthStart];
+ float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
if (w + rsum > ravail) {
- while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
+ while (right < len
+ && measured.getCharWidthAt(right + lineStart - widthStart)
+ == 0.0f) {
right++;
}
break;
@@ -1110,7 +1084,7 @@
float lavail = avail - ellipsisWidth - rsum;
for (left = 0; left < right; left++) {
- float w = widths[left + lineStart - widthStart];
+ float w = measured.getCharWidthAt(left + lineStart - widthStart);
if (w + lsum > lavail) {
break;
@@ -1306,47 +1280,6 @@
? mMaxLineHeight : super.getHeight();
}
- @FastNative
- private static native long nInit(
- @BreakStrategy int breakStrategy,
- @HyphenationFrequency int hyphenationFrequency,
- boolean isJustified,
- @Nullable int[] indents);
-
- @CriticalNative
- private static native void nFinish(long nativePtr);
-
- // 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
- // The individual character widths will be returned in charWidths. The length of charWidths must
- // be at least the length of the text.
- private static native int nComputeLineBreaks(
- /* non zero */ long nativePtr,
-
- // Inputs
- @NonNull char[] text,
- /* Non Zero */ long measuredTextPtr,
- @IntRange(from = 0) int length,
- @FloatRange(from = 0.0f) float firstWidth,
- @IntRange(from = 0) int firstWidthLineCount,
- @FloatRange(from = 0.0f) float restWidth,
- @Nullable int[] variableTabStops,
- int defaultTabStop,
- @IntRange(from = 0) int indentsOffset,
-
- // Outputs
- @NonNull LineBreaks recycle,
- @IntRange(from = 0) int recycleLength,
- @NonNull int[] recycleBreaks,
- @NonNull float[] recycleWidths,
- @NonNull float[] recycleAscents,
- @NonNull float[] recycleDescents,
- @NonNull int[] recycleFlags,
- @NonNull float[] charWidths);
-
private int mLineCount;
private int mTopPadding, mBottomPadding;
private int mColumns;
@@ -1396,8 +1329,7 @@
private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
- // This is used to return three arrays from a single JNI call when
- // performing line breaking
+ // Unused, here because of gray list private API accesses.
/*package*/ static class LineBreaks {
private static final int INITIAL_SIZE = 16;
public int[] breaks = new int[INITIAL_SIZE];
diff --git a/core/java/android/view/RecordingCanvas.java b/core/java/android/view/RecordingCanvas.java
index 18cc10f..74fa9e8 100644
--- a/core/java/android/view/RecordingCanvas.java
+++ b/core/java/android/view/RecordingCanvas.java
@@ -515,7 +515,7 @@
contextStart - paraStart,
contextEnd - contextStart,
x, y, isRtl, paint.getNativeInstance(),
- mp.getNativePtr());
+ mp.getNativeMeasuredParagraph().getNativePtr());
return;
}
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b675698..3d80f3d 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -84,8 +84,8 @@
"android_view_VelocityTracker.cpp",
"android_text_AndroidCharacter.cpp",
"android_text_Hyphenator.cpp",
+ "android_text_LineBreaker.cpp",
"android_text_MeasuredParagraph.cpp",
- "android_text_StaticLayout.cpp",
"android_os_Debug.cpp",
"android_os_GraphicsEnvironment.cpp",
"android_os_HidlSupport.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index bd2a8de..0c625a7 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -183,7 +183,7 @@
extern int register_android_text_AndroidCharacter(JNIEnv *env);
extern int register_android_text_Hyphenator(JNIEnv *env);
extern int register_android_text_MeasuredParagraph(JNIEnv* env);
-extern int register_android_text_StaticLayout(JNIEnv *env);
+extern int register_android_text_LineBreaker(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
@@ -1334,7 +1334,7 @@
REG_JNI(register_android_text_AndroidCharacter),
REG_JNI(register_android_text_Hyphenator),
REG_JNI(register_android_text_MeasuredParagraph),
- REG_JNI(register_android_text_StaticLayout),
+ REG_JNI(register_android_text_LineBreaker),
REG_JNI(register_android_view_InputDevice),
REG_JNI(register_android_view_KeyCharacterMap),
REG_JNI(register_android_os_Process),
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_LineBreaker.cpp
similarity index 92%
rename from core/jni/android_text_StaticLayout.cpp
rename to core/jni/android_text_LineBreaker.cpp
index fec5b69..dac108e 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_LineBreaker.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "StaticLayout"
+#define LOG_TAG "LineBreaker"
#include "unicode/locid.h"
#include "unicode/brkiter.h"
@@ -76,11 +76,15 @@
jintArrayToFloatVector(env, indents)));
}
-// CriticalNative
static void nFinish(jlong nativePtr) {
delete toNative(nativePtr);
}
+// CriticalNative
+static jlong nGetReleaseFunc() {
+ return reinterpret_cast<jlong>(nFinish);
+}
+
static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
jfloatArray recycleWidths, jfloatArray recycleAscents,
jfloatArray recycleDescents, jintArray recycleFlags,
@@ -144,9 +148,6 @@
recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
recycleFlags, recycleLength, result);
- env->SetFloatArrayRegion(charWidths, 0, measuredText->widths.size(),
- measuredText->widths.data());
-
return static_cast<jint>(result.breakPoints.size());
}
@@ -160,7 +161,7 @@
")J", (void*) nInit},
// Critical Natives
- {"nFinish", "(J)V", (void*) nFinish},
+ {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},
// Regular JNI
{"nComputeLineBreaks", "("
@@ -178,21 +179,20 @@
"I" // indentsOffset
// Outputs
- "Landroid/text/StaticLayout$LineBreaks;" // recycle
+ "Landroid/text/NativeLineBreaker$LineBreaks;" // recycle
"I" // recycleLength
"[I" // recycleBreaks
"[F" // recycleWidths
"[F" // recycleAscents
"[F" // recycleDescents
"[I" // recycleFlags
- "[F" // charWidths
")I", (void*) nComputeLineBreaks}
};
-int register_android_text_StaticLayout(JNIEnv* env)
+int register_android_text_LineBreaker(JNIEnv* env)
{
gLineBreaks_class = MakeGlobalRefOrDie(env,
- FindClassOrDie(env, "android/text/StaticLayout$LineBreaks"));
+ FindClassOrDie(env, "android/text/NativeLineBreaker$LineBreaks"));
gLineBreaks_fieldID.breaks = GetFieldIDOrDie(env, gLineBreaks_class, "breaks", "[I");
gLineBreaks_fieldID.widths = GetFieldIDOrDie(env, gLineBreaks_class, "widths", "[F");
@@ -200,7 +200,8 @@
gLineBreaks_fieldID.descents = GetFieldIDOrDie(env, gLineBreaks_class, "descents", "[F");
gLineBreaks_fieldID.flags = GetFieldIDOrDie(env, gLineBreaks_class, "flags", "[I");
- return RegisterMethodsOrDie(env, "android/text/StaticLayout", gMethods, NELEM(gMethods));
+ return RegisterMethodsOrDie(env, "android/text/NativeLineBreaker",
+ gMethods, NELEM(gMethods));
}
}
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index 9eb6f8d..18f509c 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -109,6 +109,10 @@
return r;
}
+static jfloat nGetCharWidthAt(jlong ptr, jint offset) {
+ return toMeasuredParagraph(ptr)->widths[offset];
+}
+
// Regular JNI
static void nGetBounds(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jint start, jint end,
jobject bounds) {
@@ -138,23 +142,29 @@
return static_cast<jint>(toMeasuredParagraph(ptr)->getMemoryUsage());
}
-static const JNINativeMethod gMethods[] = {
+static const JNINativeMethod gMTBuilderMethods[] = {
// MeasuredParagraphBuilder native functions.
{"nInitBuilder", "()J", (void*) nInitBuilder},
{"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
{"nBuildNativeMeasuredParagraph", "(J[CZZ)J", (void*) nBuildNativeMeasuredParagraph},
{"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
+};
+static const JNINativeMethod gMTMethods[] = {
// MeasuredParagraph native functions.
{"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives
{"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*) nGetBounds}, // Regular JNI
{"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives
{"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage}, // Critical Native
+ {"nGetCharWidthAt", "(JI)F", (void*) nGetCharWidthAt}, // Critical Native
};
int register_android_text_MeasuredParagraph(JNIEnv* env) {
- return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods));
+ return RegisterMethodsOrDie(env, "android/text/NativeMeasuredParagraph",
+ gMTMethods, NELEM(gMTMethods))
+ + RegisterMethodsOrDie(env, "android/text/NativeMeasuredParagraph$Builder",
+ gMTBuilderMethods, NELEM(gMTBuilderMethods));
}
}
diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index 6f1d47d..f3d6013 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import android.content.Context;
import android.graphics.Typeface;
@@ -72,7 +73,7 @@
assertEquals(0, mt.getWidths().size());
assertEquals(0, mt.getSpanEndCache().size());
assertEquals(0, mt.getFontMetrics().size());
- assertEquals(0, mt.getNativePtr());
+ assertNull(mt.getNativeMeasuredParagraph());
// Recycle it
MeasuredParagraph mt2 = MeasuredParagraph.buildForBidi("_VVV_", 1, 4, RTL, mt);
@@ -84,7 +85,7 @@
assertEquals(0, mt2.getWidths().size());
assertEquals(0, mt2.getSpanEndCache().size());
assertEquals(0, mt2.getFontMetrics().size());
- assertEquals(0, mt2.getNativePtr());
+ assertNull(mt.getNativeMeasuredParagraph());
mt2.recycle();
}
@@ -106,7 +107,7 @@
assertEquals(10, mt.getWidths().get(2), 0);
assertEquals(0, mt.getSpanEndCache().size());
assertEquals(0, mt.getFontMetrics().size());
- assertEquals(0, mt.getNativePtr());
+ assertNull(mt.getNativeMeasuredParagraph());
// Recycle it
MeasuredParagraph mt2 =
@@ -123,7 +124,7 @@
assertEquals(5, mt2.getWidths().get(2), 0);
assertEquals(0, mt2.getSpanEndCache().size());
assertEquals(0, mt2.getFontMetrics().size());
- assertEquals(0, mt2.getNativePtr());
+ assertNull(mt.getNativeMeasuredParagraph());
mt2.recycle();
}
@@ -143,7 +144,7 @@
assertEquals(1, mt.getSpanEndCache().size());
assertEquals(3, mt.getSpanEndCache().get(0));
assertNotEquals(0, mt.getFontMetrics().size());
- assertNotEquals(0, mt.getNativePtr());
+ assertNotNull(mt.getNativeMeasuredParagraph());
// Recycle it
MeasuredParagraph mt2 =
@@ -158,7 +159,7 @@
assertEquals(1, mt2.getSpanEndCache().size());
assertEquals(4, mt2.getSpanEndCache().get(0));
assertNotEquals(0, mt2.getFontMetrics().size());
- assertNotEquals(0, mt2.getNativePtr());
+ assertNotNull(mt.getNativeMeasuredParagraph());
mt2.recycle();
}