diff options
| -rw-r--r-- | core/java/android/text/NativeLineBreaker.java | 145 | ||||
| -rw-r--r-- | core/java/android/text/StaticLayout.java | 57 | ||||
| -rw-r--r-- | core/jni/android_text_LineBreaker.cpp | 132 |
3 files changed, 197 insertions, 137 deletions
diff --git a/core/java/android/text/NativeLineBreaker.java b/core/java/android/text/NativeLineBreaker.java index 2bcfa5fe0857..94e10e89acbd 100644 --- a/core/java/android/text/NativeLineBreaker.java +++ b/core/java/android/text/NativeLineBreaker.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.Px; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -258,16 +259,91 @@ 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]; - // TODO: Introduce Hyphenator for explaining the meaning of flags. - public int[] flags = new int[INITIAL_SIZE]; - // breaks, widths, and flags should all have the same length + public static class Result { + // Following two contstant must be synced with minikin's line breaker. + private static final int TAB_MASK = 0x20000000; + private static final int HYPHEN_MASK = 0xFF; + + private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + Result.class.getClassLoader(), nGetReleaseResultFunc(), 32); + private final long mPtr; + + private Result(long ptr) { + mPtr = ptr; + sRegistry.registerNativeAllocation(this, mPtr); + } + + /** + * Returns a number of line count. + * + * @return number of lines + */ + public @IntRange(from = 0) int getLineCount() { + return nGetLineCount(mPtr); + } + + /** + * Returns a break offset of the line. + * + * @param lineIndex an index of the line. + * @return the break offset. + */ + public @IntRange(from = 0) int getLineBreakOffset(@IntRange(from = 0) int lineIndex) { + return nGetLineBreakOffset(mPtr, lineIndex); + } + + /** + * Returns a width of the line in pixels. + * + * @param lineIndex an index of the line. + * @return a width of the line in pixexls + */ + public @Px float getLineWidth(@IntRange(from = 0) int lineIndex) { + return nGetLineWidth(mPtr, lineIndex); + } + + /** + * Returns an entier font ascent of the line in pixels. + * + * @param lineIndex an index of the line. + * @return an entier font ascent of the line in pixels. + */ + public @Px float getLineAscent(@IntRange(from = 0) int lineIndex) { + return nGetLineAscent(mPtr, lineIndex); + } + + /** + * Returns an entier font descent of the line in pixels. + * + * @param lineIndex an index of the line. + * @return an entier font descent of the line in pixels. + */ + public @Px float getLineDescent(@IntRange(from = 0) int lineIndex) { + return nGetLineDescent(mPtr, lineIndex); + } + + /** + * Returns true if the line has a TAB character. + * + * @param lineIndex an index of the line. + * @return true if the line has a TAB character + */ + public boolean hasLineTab(int lineIndex) { + return (nGetLineFlag(mPtr, lineIndex) & TAB_MASK) != 0; + } + + /** + * Returns a packed packed hyphen edit for the line. + * + * @param lineIndex an index of the line. + * @return a packed hyphen edit for the line. + * @see android.text.Hyphenator#unpackStartHyphenEdit(int) + * @see android.text.Hyphenator#unpackEndHyphenEdit(int) + * @see android.text.Hyphenator#packHyphenEdit(int,int) + */ + public int getLineHyphenEdit(int lineIndex) { + return (nGetLineFlag(mPtr, lineIndex) & HYPHEN_MASK); + } } private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( @@ -294,14 +370,12 @@ public class NativeLineBreaker { * @param measuredPara a result of the text measurement * @param constraints for a single paragraph * @param lineNumber a line number of this paragraph - * @param out object to set line break information for the given paragraph */ - public void computeLineBreaks( + public Result computeLineBreaks( @NonNull NativeMeasuredParagraph measuredPara, @NonNull ParagraphConstraints constraints, - @IntRange(from = 0) int lineNumber, - @NonNull LineBreaks out) { - out.breakCount = nComputeLineBreaks( + @IntRange(from = 0) int lineNumber) { + return new Result(nComputeLineBreaks( mNativePtr, // Inputs @@ -313,17 +387,7 @@ public class NativeLineBreaker { constraints.mWidth, constraints.mVariableTabStops, constraints.mDefaultTabStop, - lineNumber, - - // Outputs - out, - out.breaks.length, - out.breaks, - out.widths, - out.ascents, - out.descents, - out.flags); - + lineNumber)); } @FastNative @@ -341,7 +405,7 @@ public class NativeLineBreaker { // 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( + private static native long nComputeLineBreaks( /* non zero */ long nativePtr, // Inputs @@ -353,14 +417,21 @@ public class NativeLineBreaker { @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); + @IntRange(from = 0) int indentsOffset); + + // Result accessors + @CriticalNative + private static native int nGetLineCount(long ptr); + @CriticalNative + private static native int nGetLineBreakOffset(long ptr, int idx); + @CriticalNative + private static native float nGetLineWidth(long ptr, int idx); + @CriticalNative + private static native float nGetLineAscent(long ptr, int idx); + @CriticalNative + private static native float nGetLineDescent(long ptr, int idx); + @CriticalNative + private static native int nGetLineFlag(long ptr, int idx); + @CriticalNative + private static native long nGetReleaseResultFunc(); } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index e1ffef01feae..d2f085369448 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -599,7 +599,14 @@ public class StaticLayout extends Layout { float ellipsizedWidth = b.mEllipsizedWidth; TextUtils.TruncateAt ellipsize = b.mEllipsize; final boolean addLastLineSpacing = b.mAddLastLineLineSpacing; - NativeLineBreaker.LineBreaks lineBreaks = new NativeLineBreaker.LineBreaks(); + + int lineBreakCapacity = 0; + int[] breaks = null; + float[] lineWidths = null; + float[] ascents = null; + float[] descents = null; + boolean[] hasTabs = null; + int[] hyphenEdits = null; mLineCount = 0; mEllipsized = false; @@ -732,14 +739,27 @@ public class StaticLayout extends Layout { constraints.setIndent(firstWidth, firstWidthLineCount); constraints.setTabStops(variableTabStops, TAB_INCREMENT); - lineBreaker.computeLineBreaks(measuredPara.getNativeMeasuredParagraph(), - constraints, mLineCount, lineBreaks); - int breakCount = lineBreaks.breakCount; - 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; + NativeLineBreaker.Result res = lineBreaker.computeLineBreaks( + measuredPara.getNativeMeasuredParagraph(), constraints, mLineCount); + int breakCount = res.getLineCount(); + if (lineBreakCapacity < breakCount) { + lineBreakCapacity = breakCount; + breaks = new int[lineBreakCapacity]; + lineWidths = new float[lineBreakCapacity]; + ascents = new float[lineBreakCapacity]; + descents = new float[lineBreakCapacity]; + hasTabs = new boolean[lineBreakCapacity]; + hyphenEdits = new int[lineBreakCapacity]; + } + + for (int i = 0; i < breakCount; ++i) { + breaks[i] = res.getLineBreakOffset(i); + lineWidths[i] = res.getLineWidth(i); + ascents[i] = res.getLineAscent(i); + descents[i] = res.getLineDescent(i); + hasTabs[i] = res.hasLineTab(i); + hyphenEdits[i] = res.getLineHyphenEdit(i); + } final int remainingLineCount = mMaximumVisibleLineCount - mLineCount; final boolean ellipsisMayBeApplied = ellipsize != null @@ -750,7 +770,7 @@ public class StaticLayout extends Layout { && ellipsisMayBeApplied) { // Calculate width float width = 0; - int flag = 0; // XXX May need to also have starting hyphen edit + boolean hasTab = false; // XXX May need to also have starting hyphen edit for (int i = remainingLineCount - 1; i < breakCount; i++) { if (i == breakCount - 1) { width += lineWidths[i]; @@ -759,12 +779,12 @@ public class StaticLayout extends Layout { width += measuredPara.getCharWidthAt(j - paraStart); } } - flag |= flags[i] & TAB_MASK; + hasTab |= hasTabs[i]; } // 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; + hasTabs[remainingLineCount - 1] = hasTab; breakCount = remainingLineCount; } @@ -821,8 +841,8 @@ public class StaticLayout extends Layout { v = out(source, here, endPos, ascent, descent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, - flags[breakIndex], needMultiply, measuredPara, bufEnd, - includepad, trackpad, addLastLineSpacing, chs, + hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply, + measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs, paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, moreChars); @@ -860,7 +880,7 @@ public class StaticLayout extends Layout { fm.top, fm.bottom, v, spacingmult, spacingadd, null, - null, fm, 0, + null, fm, false, 0, needMultiply, measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, null, bufStart, ellipsize, @@ -871,7 +891,8 @@ public class StaticLayout extends Layout { private int out(final CharSequence text, final int start, final int end, int above, int below, int top, int bottom, int v, final float spacingmult, final float spacingadd, final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm, - final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured, + final boolean hasTab, final int hyphenEdit, final boolean needMultiply, + @NonNull final MeasuredParagraph measured, final int bufEnd, final boolean includePad, final boolean trackPad, final boolean addLastLineLineSpacing, final char[] chs, final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, @@ -1005,8 +1026,8 @@ public class StaticLayout extends Layout { // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining // one bit for start field - lines[off + TAB] |= flags & TAB_MASK; - lines[off + HYPHEN] = flags; + lines[off + TAB] |= hasTab ? TAB_MASK : 0; + lines[off + HYPHEN] = hyphenEdit; lines[off + DIR] |= dir << DIR_SHIFT; mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart); diff --git a/core/jni/android_text_LineBreaker.cpp b/core/jni/android_text_LineBreaker.cpp index dac108ef5497..543910727ffd 100644 --- a/core/jni/android_text_LineBreaker.cpp +++ b/core/jni/android_text_LineBreaker.cpp @@ -41,17 +41,6 @@ namespace android { -struct JLineBreaksID { - jfieldID breaks; - jfieldID widths; - jfieldID ascents; - jfieldID descents; - jfieldID flags; -}; - -static jclass gLineBreaks_class; -static JLineBreaksID gLineBreaks_fieldID; - static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray javaArray) { if (javaArray == nullptr) { return std::vector<float>(); @@ -85,34 +74,7 @@ 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, - jint recycleLength, const minikin::LineBreakResult& result) { - const size_t nBreaks = result.breakPoints.size(); - if ((size_t)recycleLength < nBreaks) { - // have to reallocate buffers - recycleBreaks = env->NewIntArray(nBreaks); - recycleWidths = env->NewFloatArray(nBreaks); - recycleAscents = env->NewFloatArray(nBreaks); - recycleDescents = env->NewFloatArray(nBreaks); - recycleFlags = env->NewIntArray(nBreaks); - - env->SetObjectField(recycle, gLineBreaks_fieldID.breaks, recycleBreaks); - env->SetObjectField(recycle, gLineBreaks_fieldID.widths, recycleWidths); - env->SetObjectField(recycle, gLineBreaks_fieldID.ascents, recycleAscents); - env->SetObjectField(recycle, gLineBreaks_fieldID.descents, recycleDescents); - env->SetObjectField(recycle, gLineBreaks_fieldID.flags, recycleFlags); - } - // copy data - env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, result.breakPoints.data()); - env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, result.widths.data()); - env->SetFloatArrayRegion(recycleAscents, 0, nBreaks, result.ascents.data()); - env->SetFloatArrayRegion(recycleDescents, 0, nBreaks, result.descents.data()); - env->SetIntArrayRegion(recycleFlags, 0, nBreaks, result.flags.data()); -} - -static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, +static jlong nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, // Inputs jcharArray javaText, jlong measuredTextPtr, @@ -122,18 +84,7 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, 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) { - + jint indentsOffset) { minikin::android::StaticLayoutNative* builder = toNative(nativePtr); ScopedCharArrayRO text(env, javaText); @@ -141,14 +92,44 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, minikin::U16StringPiece u16Text(text.get(), length); minikin::MeasuredText* measuredText = reinterpret_cast<minikin::MeasuredText*>(measuredTextPtr); - minikin::LineBreakResult result = builder->computeBreaks( - u16Text, *measuredText, firstWidth, firstWidthLineCount, restWidth, indentsOffset, - tabStops.get(), tabStops.size(), defaultTabStop); - recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents, - recycleFlags, recycleLength, result); + std::unique_ptr<minikin::LineBreakResult> result = + std::make_unique<minikin::LineBreakResult>(builder->computeBreaks( + u16Text, *measuredText, firstWidth, firstWidthLineCount, restWidth, indentsOffset, + tabStops.get(), tabStops.size(), defaultTabStop)); + return reinterpret_cast<jlong>(result.release()); +} + +static jint nGetLineCount(jlong ptr) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->breakPoints.size(); +} + +static jint nGetLineBreakOffset(jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->breakPoints[i]; +} + +static jfloat nGetLineWidth(jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->widths[i]; +} - return static_cast<jint>(result.breakPoints.size()); +static jfloat nGetLineAscent(jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->ascents[i]; +} + +static jfloat nGetLineDescent(jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->descents[i]; +} + +static jint nGetLineFlag(jlong ptr, jint i) { + return reinterpret_cast<minikin::LineBreakResult*>(ptr)->flags[i]; +} + +static void nReleaseResult(jlong ptr) { + delete reinterpret_cast<minikin::LineBreakResult*>(ptr); +} + +static jlong nGetReleaseResultFunc() { + return reinterpret_cast<jlong>(nReleaseResult); } static const JNINativeMethod gMethods[] = { @@ -166,8 +147,6 @@ static const JNINativeMethod gMethods[] = { // Regular JNI {"nComputeLineBreaks", "(" "J" // nativePtr - - // Inputs "[C" // text "J" // MeasuredParagraph ptr. "I" // length @@ -177,31 +156,20 @@ static const JNINativeMethod gMethods[] = { "[I" // variableTabStops "I" // defaultTabStop "I" // indentsOffset - - // Outputs - "Landroid/text/NativeLineBreaker$LineBreaks;" // recycle - "I" // recycleLength - "[I" // recycleBreaks - "[F" // recycleWidths - "[F" // recycleAscents - "[F" // recycleDescents - "[I" // recycleFlags - ")I", (void*) nComputeLineBreaks} + ")J", (void*) nComputeLineBreaks}, + + // Result accessors, CriticalNatives + {"nGetLineCount", "(J)I", (void*)nGetLineCount}, + {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset}, + {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth}, + {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent}, + {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent}, + {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag}, + {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc}, }; -int register_android_text_LineBreaker(JNIEnv* env) -{ - gLineBreaks_class = MakeGlobalRefOrDie(env, - FindClassOrDie(env, "android/text/NativeLineBreaker$LineBreaks")); - - gLineBreaks_fieldID.breaks = GetFieldIDOrDie(env, gLineBreaks_class, "breaks", "[I"); - gLineBreaks_fieldID.widths = GetFieldIDOrDie(env, gLineBreaks_class, "widths", "[F"); - gLineBreaks_fieldID.ascents = GetFieldIDOrDie(env, gLineBreaks_class, "ascents", "[F"); - gLineBreaks_fieldID.descents = GetFieldIDOrDie(env, gLineBreaks_class, "descents", "[F"); - gLineBreaks_fieldID.flags = GetFieldIDOrDie(env, gLineBreaks_class, "flags", "[I"); - - return RegisterMethodsOrDie(env, "android/text/NativeLineBreaker", - gMethods, NELEM(gMethods)); +int register_android_text_LineBreaker(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/text/NativeLineBreaker", gMethods, NELEM(gMethods)); } } |