diff options
| author | 2017-10-25 02:09:37 +0000 | |
|---|---|---|
| committer | 2017-10-25 02:09:37 +0000 | |
| commit | 09947c53e0b05ee35fde747c3f97d82f7619570e (patch) | |
| tree | fde6bb1a094fb439e2e15ebd1c691cf1c638fb36 | |
| parent | 0e0621a46bb23de1d2dea0902a133d0a1c0ba977 (diff) | |
| parent | b90e08f92ee2834f529937fc90265fee42b32254 (diff) | |
Merge "Reorganize JNI in StaticLayout"
| -rw-r--r-- | core/java/android/text/Layout.java | 2 | ||||
| -rw-r--r-- | core/java/android/text/MeasuredText.java | 43 | ||||
| -rw-r--r-- | core/java/android/text/StaticLayout.java | 613 | ||||
| -rw-r--r-- | core/java/android/text/TextUtils.java | 4 | ||||
| -rw-r--r-- | core/jni/android_text_StaticLayout.cpp | 294 |
5 files changed, 573 insertions, 383 deletions
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index ac5c2e926874..4d2a9629c83a 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1910,7 +1910,7 @@ public abstract class Layout { 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 ffc44a72c357..3d9fba71db74 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -39,7 +39,6 @@ class MeasuredText { private int mPos; private TextPaint mWorkPaint; - private StaticLayout.Builder mBuilder; private MeasuredText() { mWorkPaint = new TextPaint(); @@ -82,7 +81,6 @@ class MeasuredText { void finish() { mText = null; - mBuilder = null; if (mLen > 1000) { mWidths = null; mChars = null; @@ -93,9 +91,7 @@ class MeasuredText { /** * 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 @@ class MeasuredText { /** * 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 @@ class MeasuredText { 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 @@ class MeasuredText { 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 @@ class MeasuredText { 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 @@ class MeasuredText { 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 @@ class MeasuredText { 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 5c60188db1e4..c0fc44fd8ee1 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -32,6 +32,9 @@ import android.util.Pools.SynchronizedPool; 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 @@ public class StaticLayout extends Layout { * default values. */ public final static class Builder { - private Builder() { - mNativePtr = nNewBuilder(); - } + private Builder() {} /** * Obtain a builder for constructing StaticLayout objects. @@ -116,13 +117,11 @@ public class StaticLayout extends Layout { 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 @@ public class StaticLayout extends Layout { } /** - * 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 @@ public class StaticLayout extends Layout { 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 @@ public class StaticLayout extends Layout { 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++; - - int firstWidthLineCount = 1; - int firstWidth = outerWidth; - int restWidth = outerWidth; - - 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); - - // 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()); - } + 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); + + 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++; } - 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); + int firstWidthLineCount = 1; + int firstWidth = outerWidth; + int restWidth = outerWidth; + + 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); + + // 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()); + } } - for (int i = 0; i < chooseHt.length; i++) { - int o = spanned.getSpanStart(chooseHt[i]); + chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class); - if (o < paraStart) { - // starts in this layout, before the - // current paragraph + 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); + } - chooseHtv[i] = getLineTop(getLineForOffset(o)); - } else { - // starts in this paragraph + 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] = v; + chooseHtv[i] = getLineTop(getLineForOffset(o)); + } else { + // starts in this paragraph + + chooseHtv[i] = v; + } } } } - } - 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(); + 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; + + // 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; } - 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; - } - - if (spanEndCacheCount >= spanEndCache.length) { - int[] grow = new int[spanEndCacheCount * 2]; - System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); - spanEndCache = grow; - } - - 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); } - // 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++; + // 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; + } - spanEndCache[spanEndCacheCount] = spanEnd; - spanEndCacheCount++; - } + if (spanEndCacheCount >= spanEndCache.length) { + int[] grow = new int[spanEndCacheCount * 2]; + System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); + spanEndCache = grow; + } - 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]; + if (spanned == null) { + spanEnd = paraEnd; + int spanLen = spanEnd - spanStart; + measured.addStyleRun(paint, spanLen, fm, nativePtr); } else { - for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) { - width += widths[j]; - } + 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); } - 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; - } + // 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++; - // 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; + spanEndCache[spanEndCacheCount] = spanEnd; + spanEndCacheCount++; } - // skip breaks ending before current span range - while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { - breakIndex++; + 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 { + 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; } - 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 + // 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; - 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; + } + + // 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++; + here = endPos; + breakIndex++; - if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) { - return; + 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, b); - - 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); + if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) + && mLineCount < mMaximumVisibleLineCount) { + measured.setPara(source, bufEnd, bufEnd, textDir); + + 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); + } + } finally { + nFinish(nativePtr); } } @@ -1487,26 +1473,51 @@ public class StaticLayout extends Layout { mMaxLineHeight : super.getHeight(); } - private static native long nNewBuilder(); - private static native void nFreeBuilder(long nativePtr); - private static native void nFinishBuilder(long nativePtr); - - // 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); - - // TODO: Make this method CriticalNative once native code defers doing layouts. + /** + * Measurement and break iteration is done in native code. The protocol for using + * the native code is as follows. + * + * First, call nInit to setup native line breaker object. Then, for each paragraph, do the + * following: + * + * - Call one of the following methods for each run within the paragraph depending on the type + * of run: + * + addStyleRun (a text run, to be measured in native code) + * + addReplacementRun (a replacement run, width is given) + * + * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. + * + * After all paragraphs, call finish() to release expensive buffers. + */ + + /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end, + boolean isRtl) { + nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl); + } + + /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end, + float width) { + nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width); + } + + @FastNative + private static native long nInit( + @BreakStrategy int breakStrategy, + @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 @@ public class StaticLayout extends Layout { // 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 68afeecfc996..cbdaa69b5aa0 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -1519,7 +1519,7 @@ public class TextUtils { } // 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 @@ public class TextUtils { private static float setPara(MeasuredText mt, TextPaint paint, CharSequence text, int start, int end, TextDirectionHeuristic textDir) { - mt.setPara(text, start, end, textDir, 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 04e9dfd2706f..c1419ba6c7c6 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -55,11 +55,11 @@ static JLineBreaksID gLineBreaks_fieldID; 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 @@ class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate { 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 @@ static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray j } } +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); - - // 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)); +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))); +} + +// CriticalNative +static void nFinish(jlong nativePtr) { + delete toNative(nativePtr); } static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks, @@ -163,42 +263,65 @@ static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks, } 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, + + // 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)); - size_t nBreaks = b->computeBreaks(); + 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 @@ static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint 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) |