Reorganize JNI in StaticLayout
This CL reorganize the JNI strategy as follows:
- Remove nNewBuilder/nFreeBuilder.
- Make addStyleRun/addReplacementRun CriticalNative.
- Remove nSetupParagraph and pass necessary arguments to
nComputeLineBreaks instead.
Here is a performance scores: (w/o patch -> w/ patch)
StaticLayoutPerfTest (median):
createRandom: 3,755,090 -> 3,781,394 (+0.70%)
createRandom Balanced: 3,702,837 -> 3,730,435 (+0.74%)
TextViewOnMeasurePerfTest (median):
measure_AtMost: 39,172,360 -> 35,883,014 (-8.4%)
measure_Exactly: 38,005,066 -> 34,585,052 (-9.0%)
measure_Unspecified: 67,191,780 -> 63,100,545 (-6.1%)
Bug: 65024629
Test: bit CtsTextTestCases:*
Test: bit CtsWidgetTestCases:*
Change-Id: If9db1fdd2b03a1cf240f87322c1e852cf8085fff
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index ac5c2e9..4d2a962 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1910,7 +1910,7 @@
MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
try {
- mt.setPara(text, start, end, textDir, null);
+ mt.setPara(text, start, end, textDir);
Directions directions;
int dir;
if (mt.mEasy) {
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index ffc44a7..3d9fba7 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -39,7 +39,6 @@
private int mPos;
private TextPaint mWorkPaint;
- private StaticLayout.Builder mBuilder;
private MeasuredText() {
mWorkPaint = new TextPaint();
@@ -82,7 +81,6 @@
void finish() {
mText = null;
- mBuilder = null;
if (mLen > 1000) {
mWidths = null;
mChars = null;
@@ -93,9 +91,7 @@
/**
* Analyzes text for bidirectional runs. Allocates working buffers.
*/
- void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
- StaticLayout.Builder builder) {
- mBuilder = builder;
+ void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
mText = text;
mTextStart = start;
@@ -159,12 +155,12 @@
/**
* Apply the style.
*
- * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled
- * text width.
- * If StaticLayout.Builder is provided in setPara() method, this method just passes the style
- * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0.
+ * If nativeStaticLayoutPtr is 0, this method measures the styled text width.
+ * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native
+ * code by calling StaticLayout.addstyleRun() and returns 0.
*/
- float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm,
+ long nativeStaticLayoutPtr) {
if (fm != null) {
paint.getFontMetricsInt(fm);
}
@@ -174,10 +170,10 @@
if (mEasy) {
final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
- if (mBuilder == null) {
+ if (nativeStaticLayoutPtr == 0) {
return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
} else {
- mBuilder.addStyleRun(paint, p, p + len, isRtl);
+ StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl);
return 0.0f; // Builder.addStyleRun doesn't return the width.
}
}
@@ -187,12 +183,12 @@
for (int q = p, i = p + 1, e = p + len;; ++i) {
if (i == e || mLevels[i] != level) {
final boolean isRtl = (level & 0x1) != 0;
- if (mBuilder == null) {
+ if (nativeStaticLayoutPtr == 0) {
totalAdvance +=
paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
} else {
// Builder.addStyleRun doesn't return the width.
- mBuilder.addStyleRun(paint, q, i, isRtl);
+ StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl);
}
if (i == e) {
break;
@@ -201,11 +197,15 @@
level = mLevels[i];
}
}
- return totalAdvance; // If mBuilder is null, the result is zero.
+ return totalAdvance; // If nativeStaticLayoutPtr is 0, the result is zero.
+ }
+
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+ return addStyleRun(paint, len, fm, 0 /* native ptr */);
}
float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
- Paint.FontMetricsInt fm) {
+ Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) {
TextPaint workPaint = mWorkPaint;
workPaint.set(paint);
@@ -224,18 +224,18 @@
float wid;
if (replacement == null) {
- wid = addStyleRun(workPaint, len, fm);
+ wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr);
} else {
// Use original text. Shouldn't matter.
wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
mTextStart + mPos + len, fm);
- if (mBuilder == null) {
+ if (nativeStaticLayoutPtr == 0) {
float[] w = mWidths;
w[mPos] = wid;
for (int i = mPos + 1, e = mPos + len; i < e; i++)
w[i] = 0;
} else {
- mBuilder.addReplacementRun(paint, mPos, mPos + len, wid);
+ StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid);
}
mPos += len;
}
@@ -253,6 +253,11 @@
return wid;
}
+ float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+ Paint.FontMetricsInt fm) {
+ return addStyleRun(paint, spans, len, fm, 0 /* native ptr */);
+ }
+
int breakText(int limit, boolean forwards, float width) {
float[] w = mWidths;
if (forwards) {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 5c60188..c0fc44f 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -32,6 +32,9 @@
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,9 +60,7 @@
* default values.
*/
public final static class Builder {
- private Builder() {
- mNativePtr = nNewBuilder();
- }
+ private Builder() {}
/**
* Obtain a builder for constructing StaticLayout objects.
@@ -116,13 +117,11 @@
b.mRightIndents = null;
b.mLeftPaddings = null;
b.mRightPaddings = null;
- nFinishBuilder(b.mNativePtr);
sPool.release(b);
}
// release any expensive state
/* package */ void finish() {
- nFinishBuilder(mNativePtr);
mText = null;
mPaint = null;
mLeftIndents = null;
@@ -405,32 +404,6 @@
}
/**
- * Measurement and break iteration is done in native code. The protocol for using
- * the native code is as follows.
- *
- * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
- * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
- * future).
- *
- * Then, for each run within the paragraph:
- * - one of the following, 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 */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
- nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
- }
-
- /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
- nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
- }
-
- /**
* Build the {@link StaticLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this
@@ -446,17 +419,6 @@
return result;
}
- @Override
- protected void finalize() throws Throwable {
- try {
- nFreeBuilder(mNativePtr);
- } finally {
- super.finalize();
- }
- }
-
- /* package */ long mNativePtr;
-
private CharSequence mText;
private int mStart;
private int mEnd;
@@ -694,270 +656,294 @@
indents = null;
}
- int paraEnd;
- for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
- if (paraEnd < 0)
- paraEnd = bufEnd;
- else
- paraEnd++;
+ final long nativePtr = nInit(
+ b.mBreakStrategy, b.mHyphenationFrequency,
+ // TODO: Support more justification mode, e.g. letter spacing, stretching.
+ b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
+ indents, mLeftPaddings, mRightPaddings);
- int firstWidthLineCount = 1;
- int firstWidth = outerWidth;
- int restWidth = outerWidth;
+ try {
+ int paraEnd;
+ for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
+ if (paraEnd < 0) {
+ paraEnd = bufEnd;
+ } else {
+ paraEnd++;
+ }
- LineHeightSpan[] chooseHt = null;
+ int firstWidthLineCount = 1;
+ int firstWidth = outerWidth;
+ int restWidth = outerWidth;
- 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);
+ LineHeightSpan[] chooseHt = null;
- // 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());
+ 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());
+ }
+ }
+
+ 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);
+ }
+
+ 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;
+ }
+ }
}
}
- chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+ measured.setPara(source, paraStart, paraEnd, textDir);
+ char[] chs = measured.mChars;
+ float[] widths = measured.mWidths;
+ byte[] chdirs = measured.mLevels;
+ int dir = measured.mDir;
+ boolean easy = measured.mEasy;
- 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);
+ // 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;
+ }
+ }
+
+ // 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
+ int fmCacheCount = 0;
+ int spanEndCacheCount = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ if (fmCacheCount * 4 >= fmCache.length) {
+ int[] grow = new int[fmCacheCount * 4 * 2];
+ System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+ fmCache = grow;
}
- for (int i = 0; i < chooseHt.length; i++) {
- int o = spanned.getSpanStart(chooseHt[i]);
+ if (spanEndCacheCount >= spanEndCache.length) {
+ int[] grow = new int[spanEndCacheCount * 2];
+ System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+ spanEndCache = grow;
+ }
- if (o < paraStart) {
- // starts in this layout, before the
- // current paragraph
+ if (spanned == null) {
+ spanEnd = paraEnd;
+ int spanLen = spanEnd - spanStart;
+ measured.addStyleRun(paint, spanLen, fm, nativePtr);
+ } else {
+ spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+ MetricAffectingSpan.class);
+ int spanLen = spanEnd - spanStart;
+ MetricAffectingSpan[] spans =
+ spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, spanned,
+ MetricAffectingSpan.class);
+ measured.addStyleRun(paint, spans, spanLen, fm, nativePtr);
+ }
- chooseHtv[i] = getLineTop(getLineForOffset(o));
+ // the order of storage here (top, bottom, ascent, descent) has to match the
+ // code below where these values are retrieved
+ fmCache[fmCacheCount * 4 + 0] = fm.top;
+ fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+ fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+ fmCache[fmCacheCount * 4 + 3] = fm.descent;
+ fmCacheCount++;
+
+ spanEndCache[spanEndCacheCount] = spanEnd;
+ spanEndCacheCount++;
+ }
+
+ int breakCount = nComputeLineBreaks(
+ nativePtr,
+
+ // Inputs
+ chs,
+ paraEnd - paraStart,
+ firstWidth,
+ firstWidthLineCount,
+ restWidth,
+ variableTabStops,
+ TAB_INCREMENT,
+ mLineCount,
+
+ // Outputs
+ lineBreaks,
+ lineBreaks.breaks.length,
+ lineBreaks.breaks,
+ lineBreaks.widths,
+ lineBreaks.ascents,
+ lineBreaks.descents,
+ lineBreaks.flags,
+ widths);
+
+ 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 {
- // starts in this paragraph
-
- chooseHtv[i] = v;
+ for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+ width += widths[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;
- measured.setPara(source, paraStart, paraEnd, textDir, b);
- char[] chs = measured.mChars;
- float[] widths = measured.mWidths;
- byte[] chdirs = measured.mLevels;
- int dir = measured.mDir;
- boolean easy = measured.mEasy;
-
- // 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;
- }
- }
-
- nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
- firstWidth, firstWidthLineCount, restWidth,
- variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
- // TODO: Support more justification mode, e.g. letter spacing, stretching.
- b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
- // TODO: indents and paddings don't need to get passed to native code for every
- // paragraph. Pass them to native code just once.
- indents, mLeftPaddings, mRightPaddings, mLineCount);
-
- // 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
- int fmCacheCount = 0;
- int spanEndCacheCount = 0;
- for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
- if (fmCacheCount * 4 >= fmCache.length) {
- int[] grow = new int[fmCacheCount * 4 * 2];
- System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
- fmCache = grow;
+ breakCount = remainingLineCount;
}
- if (spanEndCacheCount >= spanEndCache.length) {
- int[] grow = new int[spanEndCacheCount * 2];
- System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
- spanEndCache = grow;
- }
+ // here is the offset of the starting character of the line we are currently
+ // measuring
+ int here = paraStart;
- if (spanned == null) {
- spanEnd = paraEnd;
- int spanLen = spanEnd - spanStart;
- measured.addStyleRun(paint, spanLen, fm);
- } else {
- spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
- MetricAffectingSpan.class);
- int spanLen = spanEnd - spanStart;
- MetricAffectingSpan[] spans =
- spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
- spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
- measured.addStyleRun(paint, spans, spanLen, fm);
- }
+ 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++];
- // the order of storage here (top, bottom, ascent, descent) has to match the code below
- // where these values are retrieved
- fmCache[fmCacheCount * 4 + 0] = fm.top;
- fmCache[fmCacheCount * 4 + 1] = fm.bottom;
- fmCache[fmCacheCount * 4 + 2] = fm.ascent;
- fmCache[fmCacheCount * 4 + 3] = fm.descent;
- fmCacheCount++;
+ // 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++;
- spanEndCache[spanEndCacheCount] = spanEnd;
- spanEndCacheCount++;
- }
-
- int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
- lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
- lineBreaks.breaks.length, widths);
-
- 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[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) {
- fmTop = fm.top;
- }
- if (fm.ascent < fmAscent) {
- fmAscent = fm.ascent;
- }
- if (fm.descent > fmDescent) {
- fmDescent = fm.descent;
- }
- if (fm.bottom > fmBottom) {
- fmBottom = fm.bottom;
- }
-
- // skip breaks ending before current span range
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
- breakIndex++;
- }
-
- 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, chdirs, dir, easy, bufEnd, includepad, trackpad,
- addLastLineSpacing, chs, widths, paraStart, ellipsize,
- ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
-
- if (endPos < spanEnd) {
- // preserve metrics for current span
+ if (fm.top < fmTop) {
fmTop = fm.top;
- fmBottom = fm.bottom;
+ }
+ if (fm.ascent < fmAscent) {
fmAscent = fm.ascent;
+ }
+ if (fm.descent > fmDescent) {
fmDescent = fm.descent;
- } else {
- fmTop = fmBottom = fmAscent = fmDescent = 0;
+ }
+ if (fm.bottom > fmBottom) {
+ fmBottom = fm.bottom;
}
- here = endPos;
- breakIndex++;
-
- if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
- return;
+ // skip breaks ending before current span range
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+ breakIndex++;
}
+
+ 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, chdirs, dir, easy, bufEnd,
+ includepad, trackpad, addLastLineSpacing, chs, widths, 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 (paraEnd == bufEnd)
- break;
- }
+ if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
+ && mLineCount < mMaximumVisibleLineCount) {
+ measured.setPara(source, bufEnd, bufEnd, textDir);
- if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
- mLineCount < mMaximumVisibleLineCount) {
- measured.setPara(source, bufEnd, bufEnd, textDir, b);
+ paint.getFontMetricsInt(fm);
- 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.mLevels, measured.mDir, measured.mEasy, bufEnd,
- includepad, trackpad, addLastLineSpacing, null,
- null, bufStart, ellipsize,
- ellipsizedWidth, 0, paint, false);
+ v = out(source,
+ bufEnd, bufEnd, fm.ascent, fm.descent,
+ fm.top, fm.bottom,
+ v,
+ spacingmult, spacingadd, null,
+ null, fm, 0,
+ needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
+ includepad, trackpad, addLastLineSpacing, null,
+ null, bufStart, ellipsize,
+ ellipsizedWidth, 0, paint, false);
+ }
+ } finally {
+ nFinish(nativePtr);
}
}
@@ -1487,26 +1473,51 @@
mMaxLineHeight : super.getHeight();
}
- private static native long nNewBuilder();
- private static native void nFreeBuilder(long nativePtr);
- private static native void nFinishBuilder(long nativePtr);
+ /**
+ * 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.
+ */
- // Set up paragraph text and settings; done as one big method to minimize jni crossings
- private static native void nSetupParagraph(
- /* non zero */ long nativePtr, @NonNull char[] text, @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, @BreakStrategy int breakStrategy,
- @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
- @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
- @IntRange(from = 0) int indentsOffset);
+ /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end,
+ boolean isRtl) {
+ nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl);
+ }
- // TODO: Make this method CriticalNative once native code defers doing layouts.
+ /* 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,
+ @HyphenationFrequency int hyphenationFrequency,
+ boolean isJustified,
+ @Nullable int[] indents,
+ @Nullable int[] leftPaddings,
+ @Nullable int[] rightPaddings);
+
+ @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);
- // TODO: Make this method CriticalNative once native code defers doing layouts.
+ @CriticalNative
private static native void nAddReplacementRun(
/* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@@ -1519,10 +1530,28 @@
// 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(long nativePtr, LineBreaks recycle,
- int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
- float[] recycleDescents, int[] recycleFlags, int recycleLength,
- float[] charWidths);
+ private static native int nComputeLineBreaks(
+ /* non zero */ long nativePtr,
+
+ // Inputs
+ @NonNull char[] text,
+ @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;
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 68afeec..cbdaa69 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1519,7 +1519,7 @@
}
// XXX this is probably ok, but need to look at it more
- tempMt.setPara(format, 0, format.length(), textDir, null);
+ tempMt.setPara(format, 0, format.length(), textDir);
float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
if (w + moreWid <= avail) {
@@ -1541,7 +1541,7 @@
private static float setPara(MeasuredText mt, TextPaint paint,
CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
- mt.setPara(text, start, end, textDir, null);
+ mt.setPara(text, start, end, textDir);
float width;
Spanned sp = text instanceof Spanned ? (Spanned) text : null;
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 04e9dfd..c1419ba 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -55,11 +55,11 @@
class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate {
public:
JNILineBreakerLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
- std::vector<float>&& indents, std::vector<float>&& leftPaddings,
- std::vector<float>&& rightPaddings, int32_t indentsAndPaddingsOffset)
+ const std::vector<float>& indents, const std::vector<float>& leftPaddings,
+ const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset)
: mFirstWidth(firstWidth), mFirstLineCount(firstLineCount), mRestWidth(restWidth),
- mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
- mRightPaddings(std::move(rightPaddings)), mOffset(indentsAndPaddingsOffset) {}
+ mIndents(indents), mLeftPaddings(leftPaddings),
+ mRightPaddings(rightPaddings), mOffset(indentsAndPaddingsOffset) {}
float getLineWidth(size_t lineNo) override {
const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount)
@@ -91,9 +91,9 @@
const float mFirstWidth;
const int32_t mFirstLineCount;
const float mRestWidth;
- const std::vector<float> mIndents;
- const std::vector<float> mLeftPaddings;
- const std::vector<float> mRightPaddings;
+ const std::vector<float>& mIndents;
+ const std::vector<float>& mLeftPaddings;
+ const std::vector<float>& mRightPaddings;
const int32_t mOffset;
};
@@ -106,32 +106,132 @@
}
}
+class Run {
+ public:
+ Run(int32_t start, int32_t end) : mStart(start), mEnd(end) {}
+ virtual ~Run() {}
+
+ virtual void addTo(minikin::LineBreaker* lineBreaker) = 0;
+
+ protected:
+ const int32_t mStart;
+ const int32_t mEnd;
+
+ private:
+ // Forbid copy and assign.
+ Run(const Run&) = delete;
+ void operator=(const Run&) = delete;
+};
+
+class StyleRun : public Run {
+ public:
+ StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
+ std::shared_ptr<minikin::FontCollection>&& collection,
+ minikin::FontStyle&& style, bool isRtl)
+ : Run(start, end), mPaint(std::move(paint)), mCollection(std::move(collection)),
+ mStyle(std::move(style)), mIsRtl(isRtl) {}
+
+ void addTo(minikin::LineBreaker* lineBreaker) override {
+ lineBreaker->addStyleRun(&mPaint, mCollection, mStyle, mStart, mEnd, mIsRtl);
+ }
+
+ private:
+ minikin::MinikinPaint mPaint;
+ std::shared_ptr<minikin::FontCollection> mCollection;
+ minikin::FontStyle mStyle;
+ const bool mIsRtl;
+};
+
+class Replacement : public Run {
+ public:
+ Replacement(int32_t start, int32_t end, float width, uint32_t localeListId)
+ : Run(start, end), mWidth(width), mLocaleListId(localeListId) {}
+
+ void addTo(minikin::LineBreaker* lineBreaker) override {
+ lineBreaker->addReplacement(mStart, mEnd, mWidth, mLocaleListId);
+ }
+
+ private:
+ const float mWidth;
+ const uint32_t mLocaleListId;
+};
+
+class StaticLayoutNative {
+ public:
+ StaticLayoutNative(
+ minikin::BreakStrategy strategy, minikin::HyphenationFrequency frequency,
+ bool isJustified, std::vector<float>&& indents, std::vector<float>&& leftPaddings,
+ std::vector<float>&& rightPaddings)
+ : mStrategy(strategy), mFrequency(frequency), mIsJustified(isJustified),
+ mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
+ mRightPaddings(std::move(rightPaddings)) {}
+
+ void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
+ std::shared_ptr<minikin::FontCollection> collection,
+ minikin::FontStyle&& style, bool isRtl) {
+ mRuns.emplace_back(std::make_unique<StyleRun>(
+ start, end, std::move(paint), std::move(collection), std::move(style), isRtl));
+ }
+
+ void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
+ mRuns.emplace_back(std::make_unique<Replacement>(start, end, width, localeListId));
+ }
+
+ // Only valid while this instance is alive.
+ inline std::unique_ptr<minikin::LineBreaker::LineWidthDelegate> buildLineWidthDelegate(
+ float firstWidth, int32_t firstLineCount, float restWidth,
+ int32_t indentsAndPaddingsOffset) {
+ return std::make_unique<JNILineBreakerLineWidth>(
+ firstWidth, firstLineCount, restWidth, mIndents, mLeftPaddings, mRightPaddings,
+ indentsAndPaddingsOffset);
+ }
+
+ void addRuns(minikin::LineBreaker* lineBreaker) {
+ for (const auto& run : mRuns) {
+ run->addTo(lineBreaker);
+ }
+ }
+
+ void clearRuns() {
+ mRuns.clear();
+ }
+
+ inline minikin::BreakStrategy getStrategy() const { return mStrategy; }
+ inline minikin::HyphenationFrequency getFrequency() const { return mFrequency; }
+ inline bool isJustified() const { return mIsJustified; }
+
+ private:
+ const minikin::BreakStrategy mStrategy;
+ const minikin::HyphenationFrequency mFrequency;
+ const bool mIsJustified;
+ const std::vector<float> mIndents;
+ const std::vector<float> mLeftPaddings;
+ const std::vector<float> mRightPaddings;
+
+ std::vector<std::unique_ptr<Run>> mRuns;
+};
+
+static inline StaticLayoutNative* toNative(jlong ptr) {
+ return reinterpret_cast<StaticLayoutNative*>(ptr);
+}
+
// set text and set a number of parameters for creating a layout (width, tabstops, strategy,
// hyphenFrequency)
-static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
- jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
- jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency,
- jboolean isJustified, jintArray indents, jintArray leftPaddings, jintArray rightPaddings,
- jint indentsAndPaddingsOffset) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
- b->resize(length);
- env->GetCharArrayRegion(text, 0, length, b->buffer());
- b->setText();
- if (variableTabStops == nullptr) {
- b->setTabStops(nullptr, 0, defaultTabStop);
- } else {
- ScopedIntArrayRO stops(env, variableTabStops);
- b->setTabStops(stops.get(), stops.size(), defaultTabStop);
- }
- b->setStrategy(static_cast<minikin::BreakStrategy>(strategy));
- b->setHyphenationFrequency(static_cast<minikin::HyphenationFrequency>(hyphenFrequency));
- b->setJustified(isJustified);
+static jlong nInit(JNIEnv* env, jclass /* unused */,
+ jint breakStrategy, jint hyphenationFrequency, jboolean isJustified,
+ jintArray indents, jintArray leftPaddings, jintArray rightPaddings) {
+ return reinterpret_cast<jlong>(new StaticLayoutNative(
+ static_cast<minikin::BreakStrategy>(breakStrategy),
+ static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
+ isJustified,
+ jintArrayToFloatVector(env, indents),
+ jintArrayToFloatVector(env, leftPaddings),
+ jintArrayToFloatVector(env, rightPaddings)));
+}
- // TODO: copy indents and paddings only once when LineBreaker is started to be used.
- b->setLineWidthDelegate(std::make_unique<JNILineBreakerLineWidth>(
- firstWidth, firstWidthLineLimit, restWidth, jintArrayToFloatVector(env, indents),
- jintArrayToFloatVector(env, leftPaddings), jintArrayToFloatVector(env, rightPaddings),
- indentsAndPaddingsOffset));
+// CriticalNative
+static void nFinish(jlong nativePtr) {
+ delete toNative(nativePtr);
}
static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
@@ -163,42 +263,65 @@
}
static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
- jobject recycle, jintArray recycleBreaks,
- jfloatArray recycleWidths, jfloatArray recycleAscents,
- jfloatArray recycleDescents, jintArray recycleFlags,
- jint recycleLength, jfloatArray charWidths) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+ // Inputs
+ jcharArray text,
+ jint length,
+ jfloat firstWidth,
+ jint firstWidthLineCount,
+ jfloat restWidth,
+ jintArray variableTabStops,
+ jint defaultTabStop,
+ jint indentsOffset,
- size_t nBreaks = b->computeBreaks();
+ // Outputs
+ jobject recycle,
+ jint recycleLength,
+ jintArray recycleBreaks,
+ jfloatArray recycleWidths,
+ jfloatArray recycleAscents,
+ jfloatArray recycleDescents,
+ jintArray recycleFlags,
+ jfloatArray charWidths) {
+
+ StaticLayoutNative* builder = toNative(nativePtr);
+
+ // TODO: Reorganize minikin APIs.
+ minikin::LineBreaker b;
+ b.resize(length);
+ env->GetCharArrayRegion(text, 0, length, b.buffer());
+ b.setText();
+ if (variableTabStops == nullptr) {
+ b.setTabStops(nullptr, 0, defaultTabStop);
+ } else {
+ ScopedIntArrayRO stops(env, variableTabStops);
+ b.setTabStops(stops.get(), stops.size(), defaultTabStop);
+ }
+ b.setStrategy(builder->getStrategy());
+ b.setHyphenationFrequency(builder->getFrequency());
+ b.setJustified(builder->isJustified());
+ b.setLineWidthDelegate(builder->buildLineWidthDelegate(
+ firstWidth, firstWidthLineCount, restWidth, indentsOffset));
+
+ builder->addRuns(&b);
+
+ size_t nBreaks = b.computeBreaks();
recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
- recycleFlags, recycleLength, nBreaks, b->getBreaks(), b->getWidths(), b->getAscents(),
- b->getDescents(), b->getFlags());
+ recycleFlags, recycleLength, nBreaks, b.getBreaks(), b.getWidths(), b.getAscents(),
+ b.getDescents(), b.getFlags());
- env->SetFloatArrayRegion(charWidths, 0, b->size(), b->charWidths());
+ env->SetFloatArrayRegion(charWidths, 0, b.size(), b.charWidths());
- b->finish();
+ b.finish();
+ builder->clearRuns();
return static_cast<jint>(nBreaks);
}
-static jlong nNewBuilder(JNIEnv*, jclass) {
- return reinterpret_cast<jlong>(new minikin::LineBreaker);
-}
-
-static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) {
- delete reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-}
-
-static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
- b->finish();
-}
-
// Basically similar to Paint.getTextRunAdvances but with C++ interface
-static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start,
- jint end, jboolean isRtl) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+// CriticalNative
+static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint end, jboolean isRtl) {
+ StaticLayoutNative* builder = toNative(nativePtr);
Paint* paint = reinterpret_cast<Paint*>(nativePaint);
const Typeface* typeface = paint->getAndroidTypeface();
minikin::MinikinPaint minikinPaint;
@@ -206,26 +329,59 @@
minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint,
typeface);
- b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start, end, isRtl);
+ builder->addStyleRun(
+ start, end, std::move(minikinPaint), resolvedTypeface->fFontCollection, std::move(style),
+ isRtl);
}
-static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint,
- jint start, jint end, jfloat width) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+// CriticalNative
+static void nAddReplacementRun(jlong nativePtr, jlong nativePaint, jint start, jint end,
+ jfloat width) {
+ StaticLayoutNative* builder = toNative(nativePtr);
Paint* paint = reinterpret_cast<Paint*>(nativePaint);
- b->addReplacement(start, end, width, paint->getMinikinLangListId());
+ builder->addReplacementRun(start, end, width, paint->getMinikinLangListId());
}
static const JNINativeMethod gMethods[] = {
- // TODO performance: many of these are candidates for fast jni, awaiting guidance
- {"nNewBuilder", "()J", (void*) nNewBuilder},
- {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
- {"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
- {"nSetupParagraph", "(J[CIFIF[IIIIZ[I[I[II)V", (void*) nSetupParagraph},
+ // Fast Natives
+ {"nInit", "("
+ "I" // breakStrategy
+ "I" // hyphenationFrequency
+ "Z" // isJustified
+ "[I" // indents
+ "[I" // left paddings
+ "[I" // right paddings
+ ")J", (void*) nInit},
+
+ // Critical Natives
+ {"nFinish", "(J)V", (void*) nFinish},
{"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
- {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II[F)I",
- (void*) nComputeLineBreaks}
+
+ // Regular JNI
+ {"nComputeLineBreaks", "("
+ "J" // nativePtr
+
+ // Inputs
+ "[C" // text
+ "I" // length
+ "F" // firstWidth
+ "I" // firstWidthLineCount
+ "F" // restWidth
+ "[I" // variableTabStops
+ "I" // defaultTabStop
+ "I" // indentsOffset
+
+ // Outputs
+ "Landroid/text/StaticLayout$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)