Loading core/java/android/text/MeasuredText.java +125 −32 Original line number Diff line number Diff line Loading @@ -29,6 +29,10 @@ import android.text.style.MetricAffectingSpan; import android.text.style.ReplacementSpan; import android.util.Pools.SynchronizedPool; import dalvik.annotation.optimization.CriticalNative; import libcore.util.NativeAllocationRegistry; import java.util.Arrays; /** Loading Loading @@ -57,6 +61,9 @@ import java.util.Arrays; public class MeasuredText { private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024); private MeasuredText() {} // Use build static functions instead. private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1); Loading Loading @@ -119,6 +126,26 @@ public class MeasuredText { // See getFontMetrics comments. private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); // The native MeasuredText. // See getNativePtr comments. // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. private /* Maybe Zero */ long mNativePtr = 0; private @Nullable Runnable mNativeObjectCleaner; // Associate the native object to this Java object. private void bindNativeObject(/* Non Zero*/ long nativePtr) { mNativePtr = nativePtr; mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); } // Decouple the native object from this Java object and release the native object. private void unbindNativeObject() { if (mNativePtr != 0) { mNativeObjectCleaner.run(); mNativePtr = 0; } } // Following two objects are for avoiding object allocation. private @NonNull TextPaint mCachedPaint = new TextPaint(); private @Nullable Paint.FontMetricsInt mCachedFm; Loading @@ -145,6 +172,7 @@ public class MeasuredText { mWidths.clear(); mFontMetrics.clear(); mSpanEndCache.clear(); unbindNativeObject(); } /** Loading Loading @@ -225,6 +253,16 @@ public class MeasuredText { return mFontMetrics; } /** * Returns the native ptr of the MeasuredText. * * This is available only if the MeasureText is computed with computeForStaticLayout. * Returns 0 in other cases. */ public /* Maybe Zero */ long getNativePtr() { return mNativePtr; } /** * Generates new MeasuredText for Bidi computation. * Loading Loading @@ -308,7 +346,6 @@ public class MeasuredText { * @param start the inclusive start offset of the target region in the text * @param end the exclusive end offset of the target region in the text * @param textDir the text direction * @param nativeStaticLayoutPtr the pointer to the native static layout object * @param recycle pass existing MeasuredText if you want to recycle it. * * @return measured text Loading @@ -319,32 +356,47 @@ public class MeasuredText { @IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, /* Non-Zero */ long nativeStaticLayoutPtr, @Nullable MeasuredText recycle) { final MeasuredText mt = recycle == null ? obtain() : recycle; mt.resetAndAnalyzeBidi(text, start, end, textDir); if (mt.mTextLength == 0) { // Need to build empty native measured text for StaticLayout. // TODO: Stop creating empty measured text for empty lines. long nativeBuilderPtr = nInitBuilder(); try { mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); } finally { nFreeBuilder(nativeBuilderPtr); } return mt; } long nativeBuilderPtr = nInitBuilder(); try { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeStaticLayoutPtr); mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); mt.mSpanEndCache.append(end); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. // There may be a MetricsAffectingSpan. Split into span transitions and apply // styles. int spanEnd; for (int spanStart = start; spanStart < end; spanStart = spanEnd) { spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, nativeStaticLayoutPtr); nativeBuilderPtr); mt.mSpanEndCache.append(spanEnd); } } mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); } finally { nFreeBuilder(nativeBuilderPtr); } return mt; } Loading Loading @@ -415,13 +467,13 @@ public class MeasuredText { private void applyReplacementRun(@NonNull ReplacementSpan replacement, @IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer /* Maybe Zero */ long nativeStaticLayoutPtr) { /* Maybe Zero */ long nativeBuilderPtr) { // Use original text. Shouldn't matter. // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for // backward compatibility? or Should we initialize them for getFontMetricsInt? final float width = replacement.getSize( mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); if (nativeStaticLayoutPtr == 0) { if (nativeBuilderPtr == 0) { // Assigns all width to the first character. This is the same behavior as minikin. mWidths.set(start, width); if (end > start + 1) { Loading @@ -429,25 +481,26 @@ public class MeasuredText { } mWholeWidth += width; } else { StaticLayout.addReplacementRun(nativeStaticLayoutPtr, mCachedPaint, start, end, width); nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, width); } } private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer /* Maybe Zero */ long nativeStaticLayoutPtr) { if (nativeStaticLayoutPtr != 0) { /* Maybe Zero */ long nativeBuilderPtr) { if (nativeBuilderPtr != 0) { mCachedPaint.getFontMetricsInt(mCachedFm); } if (mLtrWithoutBidi) { // If the whole text is LTR direction, just apply whole region. if (nativeStaticLayoutPtr == 0) { if (nativeBuilderPtr == 0) { mWholeWidth += mCachedPaint.getTextRunAdvances( mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, mWidths.getRawArray(), start); } else { StaticLayout.addStyleRun(nativeStaticLayoutPtr, mCachedPaint, start, end, nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, false /* isRtl */); } } else { Loading @@ -458,14 +511,14 @@ public class MeasuredText { for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point final boolean isRtl = (level & 0x1) != 0; if (nativeStaticLayoutPtr == 0) { if (nativeBuilderPtr == 0) { final int levelLength = levelEnd - levelStart; mWholeWidth += mCachedPaint.getTextRunAdvances( mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, isRtl, mWidths.getRawArray(), levelStart); } else { StaticLayout.addStyleRun( nativeStaticLayoutPtr, mCachedPaint, levelStart, levelEnd, isRtl); nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, levelEnd, isRtl); } if (levelEnd == end) { break; Loading @@ -482,12 +535,12 @@ public class MeasuredText { @Nullable MetricAffectingSpan[] spans, @IntRange(from = 0) int start, // inclusive, in original text buffer @IntRange(from = 0) int end, // exclusive, in original text buffer /* Maybe Zero */ long nativeStaticLayoutPtr) { /* Maybe Zero */ long nativeBuilderPtr) { mCachedPaint.set(paint); // XXX paint should not have a baseline shift, but... mCachedPaint.baselineShift = 0; final boolean needFontMetrics = nativeStaticLayoutPtr != 0; final boolean needFontMetrics = nativeBuilderPtr != 0; if (needFontMetrics && mCachedFm == null) { mCachedFm = new Paint.FontMetricsInt(); Loading @@ -512,9 +565,9 @@ public class MeasuredText { if (replacement != null) { applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, nativeStaticLayoutPtr); nativeBuilderPtr); } else { applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeStaticLayoutPtr); applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); } if (needFontMetrics) { Loading Loading @@ -580,4 +633,44 @@ public class MeasuredText { } return width; } private static native /* Non Zero */ long nInitBuilder(); /** * Apply style to make native measured text. * * @param nativeBuilderPtr The native MeasuredText builder pointer. * @param paintPtr The native paint pointer to be applied. * @param start The start offset in the copied buffer. * @param end The end offset in the copied buffer. * @param isRtl True if the text is RTL. */ private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); /** * Apply ReplacementRun to make native measured text. * * @param nativeBuilderPtr The native MeasuredText builder pointer. * @param paintPtr The native paint pointer to be applied. * @param start The start offset in the copied buffer. * @param end The end offset in the copied buffer. * @param width The width of the replacement. */ private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @FloatRange(from = 0) float width); private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr, @NonNull char[] text); private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); @CriticalNative private static native /* Non Zero */ long nGetReleaseFunc(); } core/java/android/text/StaticLayout.java +24 −47 Original line number Diff line number Diff line Loading @@ -48,6 +48,18 @@ import java.util.Arrays; * Canvas.drawText()} directly.</p> */ public class StaticLayout extends Layout { /* * The break iteration is done in native code. The protocol for using the native code is as * follows. * * First, call nInit to setup native line breaker object. Then, for each paragraph, do the * following: * * - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native. * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. */ static final String TAG = "StaticLayout"; Loading Loading @@ -724,10 +736,11 @@ public class StaticLayout extends Layout { } measured = MeasuredText.buildForStaticLayout( paint, source, paraStart, paraEnd, textDir, nativePtr, measured); paint, source, paraStart, paraEnd, textDir, measured); final char[] chs = measured.getChars(); final int[] spanEndCache = measured.getSpanEndCache().getRawArray(); final int[] fmCache = measured.getFontMetrics().getRawArray(); // TODO: Stop keeping duplicated width copy in native and Java. widths.resize(chs.length); // measurement has to be done before performing line breaking Loading @@ -739,6 +752,7 @@ public class StaticLayout extends Layout { // Inputs chs, measured.getNativePtr(), paraEnd - paraStart, firstWidth, firstWidthLineCount, Loading Loading @@ -873,18 +887,14 @@ public class StaticLayout extends Layout { if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { measured = MeasuredText.buildForStaticLayout( paint, source, bufEnd, bufEnd, textDir, nativePtr, measured); paint.getFontMetricsInt(fm); v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, fm.top, fm.bottom, v, spacingmult, spacingadd, null, null, fm, 0, needMultiply, measured, bufEnd, needMultiply, null, bufEnd, includepad, trackpad, addLastLineSpacing, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false); Loading @@ -902,7 +912,7 @@ 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, final MeasuredText measured, final int flags, final boolean needMultiply, @Nullable final MeasuredText measured, final int bufEnd, final boolean includePad, final boolean trackPad, final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, Loading @@ -911,7 +921,7 @@ public class StaticLayout extends Layout { final int off = j * mColumns; final int want = off + mColumns + TOP; int[] lines = mLines; final int dir = measured.getParagraphDir(); final int dir = (start == end) ? Layout.DIR_LEFT_TO_RIGHT : measured.getParagraphDir(); if (want >= lines.length) { final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want)); Loading @@ -938,7 +948,11 @@ public class StaticLayout extends Layout { lines[off + TAB] |= flags & TAB_MASK; lines[off + HYPHEN] = flags; lines[off + DIR] |= dir << DIR_SHIFT; if (start == end) { mLineDirections[j] = Layout.DIRS_ALL_LEFT_TO_RIGHT; } else { mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart); } final boolean firstLine = (j == 0); final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount); Loading Loading @@ -1415,33 +1429,6 @@ public class StaticLayout extends Layout { mMaxLineHeight : super.getHeight(); } /** * Measurement and break iteration is done in native code. The protocol for using * the native code is as follows. * * First, call nInit to setup native line breaker object. Then, for each paragraph, do the * following: * * - Call one of the following methods for each run within the paragraph depending on the type * of run: * + addStyleRun (a text run, to be measured in native code) * + addReplacementRun (a replacement run, width is given) * * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. */ /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end, boolean isRtl) { nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl); } /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end, float width) { nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width); } @FastNative private static native long nInit( @BreakStrategy int breakStrategy, Loading @@ -1454,17 +1441,6 @@ public class StaticLayout extends Layout { @CriticalNative private static native void nFinish(long nativePtr); @CriticalNative private static native void nAddStyleRun( /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); @CriticalNative private static native void nAddReplacementRun( /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @FloatRange(from = 0.0f) float width); // populates LineBreaks and returns the number of breaks found // // the arrays inside the LineBreaks objects are passed in as well Loading @@ -1477,6 +1453,7 @@ public class StaticLayout extends Layout { // Inputs @NonNull char[] text, /* Non Zero */ long measuredTextPtr, @IntRange(from = 0) int length, @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount, Loading core/jni/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ cc_library_shared { "android_view_VelocityTracker.cpp", "android_text_AndroidCharacter.cpp", "android_text_Hyphenator.cpp", "android_text_MeasuredText.cpp", "android_text_StaticLayout.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", Loading core/jni/AndroidRuntime.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -177,6 +177,7 @@ extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_net_TrafficStats(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_Hyphenator(JNIEnv *env); extern int register_android_text_MeasuredText(JNIEnv* env); extern int register_android_text_StaticLayout(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); Loading Loading @@ -1333,6 +1334,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_XmlBlock), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), REG_JNI(register_android_text_MeasuredText), REG_JNI(register_android_text_StaticLayout), REG_JNI(register_android_view_InputDevice), REG_JNI(register_android_view_KeyCharacterMap), Loading core/jni/android_text_MeasuredText.cpp 0 → 100644 +122 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "MeasuredText" #include "ScopedIcuLocale.h" #include "unicode/locid.h" #include "unicode/brkiter.h" #include "utils/misc.h" #include "utils/Log.h" #include <nativehelper/ScopedStringChars.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/JNIHelp.h> #include "core_jni_helpers.h" #include <cstdint> #include <vector> #include <list> #include <algorithm> #include "SkPaint.h" #include "SkTypeface.h" #include <hwui/MinikinSkia.h> #include <hwui/MinikinUtils.h> #include <hwui/Paint.h> #include <minikin/FontCollection.h> #include <minikin/AndroidLineBreakerHelper.h> #include <minikin/MinikinFont.h> namespace android { static inline minikin::MeasuredTextBuilder* toBuilder(jlong ptr) { return reinterpret_cast<minikin::MeasuredTextBuilder*>(ptr); } static inline Paint* toPaint(jlong ptr) { return reinterpret_cast<Paint*>(ptr); } static inline minikin::MeasuredText* toMeasuredText(jlong ptr) { return reinterpret_cast<minikin::MeasuredText*>(ptr); } template<typename Ptr> static inline jlong toJLong(Ptr ptr) { return reinterpret_cast<jlong>(ptr); } static void releaseMeasuredText(jlong measuredTextPtr) { delete toMeasuredText(measuredTextPtr); } // Regular JNI static jlong nInitBuilder() { return toJLong(new minikin::MeasuredTextBuilder()); } // Regular JNI static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, jlong paintPtr, jint start, jint end, jboolean isRtl) { Paint* paint = toPaint(paintPtr); const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface()); minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), typeface->fFontCollection, isRtl); } // Regular JNI static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, jlong paintPtr, jint start, jint end, jfloat width) { toBuilder(builderPtr)->addReplacementRun(start, end, width, toPaint(paintPtr)->getMinikinLocaleListId()); } // Regular JNI static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jcharArray javaText) { ScopedCharArrayRO text(env, javaText); const minikin::U16StringPiece textBuffer(text.get(), text.size()); // Pass the ownership to Java. return toJLong(toBuilder(builderPtr)->build(textBuffer).release()); } // Regular JNI static void nFreeBuilder(JNIEnv* env, jclass /* unused */, jlong builderPtr) { delete toBuilder(builderPtr); } // CriticalNative static jlong nGetReleaseFunc() { return toJLong(&releaseMeasuredText); } static const JNINativeMethod gMethods[] = { // MeasuredTextBuilder native functions. {"nInitBuilder", "()J", (void*) nInitBuilder}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, // MeasuredText native functions. {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives }; int register_android_text_MeasuredText(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods)); } } Loading
core/java/android/text/MeasuredText.java +125 −32 Original line number Diff line number Diff line Loading @@ -29,6 +29,10 @@ import android.text.style.MetricAffectingSpan; import android.text.style.ReplacementSpan; import android.util.Pools.SynchronizedPool; import dalvik.annotation.optimization.CriticalNative; import libcore.util.NativeAllocationRegistry; import java.util.Arrays; /** Loading Loading @@ -57,6 +61,9 @@ import java.util.Arrays; public class MeasuredText { private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024); private MeasuredText() {} // Use build static functions instead. private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1); Loading Loading @@ -119,6 +126,26 @@ public class MeasuredText { // See getFontMetrics comments. private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); // The native MeasuredText. // See getNativePtr comments. // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. private /* Maybe Zero */ long mNativePtr = 0; private @Nullable Runnable mNativeObjectCleaner; // Associate the native object to this Java object. private void bindNativeObject(/* Non Zero*/ long nativePtr) { mNativePtr = nativePtr; mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); } // Decouple the native object from this Java object and release the native object. private void unbindNativeObject() { if (mNativePtr != 0) { mNativeObjectCleaner.run(); mNativePtr = 0; } } // Following two objects are for avoiding object allocation. private @NonNull TextPaint mCachedPaint = new TextPaint(); private @Nullable Paint.FontMetricsInt mCachedFm; Loading @@ -145,6 +172,7 @@ public class MeasuredText { mWidths.clear(); mFontMetrics.clear(); mSpanEndCache.clear(); unbindNativeObject(); } /** Loading Loading @@ -225,6 +253,16 @@ public class MeasuredText { return mFontMetrics; } /** * Returns the native ptr of the MeasuredText. * * This is available only if the MeasureText is computed with computeForStaticLayout. * Returns 0 in other cases. */ public /* Maybe Zero */ long getNativePtr() { return mNativePtr; } /** * Generates new MeasuredText for Bidi computation. * Loading Loading @@ -308,7 +346,6 @@ public class MeasuredText { * @param start the inclusive start offset of the target region in the text * @param end the exclusive end offset of the target region in the text * @param textDir the text direction * @param nativeStaticLayoutPtr the pointer to the native static layout object * @param recycle pass existing MeasuredText if you want to recycle it. * * @return measured text Loading @@ -319,32 +356,47 @@ public class MeasuredText { @IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, /* Non-Zero */ long nativeStaticLayoutPtr, @Nullable MeasuredText recycle) { final MeasuredText mt = recycle == null ? obtain() : recycle; mt.resetAndAnalyzeBidi(text, start, end, textDir); if (mt.mTextLength == 0) { // Need to build empty native measured text for StaticLayout. // TODO: Stop creating empty measured text for empty lines. long nativeBuilderPtr = nInitBuilder(); try { mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); } finally { nFreeBuilder(nativeBuilderPtr); } return mt; } long nativeBuilderPtr = nInitBuilder(); try { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeStaticLayoutPtr); mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); mt.mSpanEndCache.append(end); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. // There may be a MetricsAffectingSpan. Split into span transitions and apply // styles. int spanEnd; for (int spanStart = start; spanStart < end; spanStart = spanEnd) { spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, nativeStaticLayoutPtr); nativeBuilderPtr); mt.mSpanEndCache.append(spanEnd); } } mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); } finally { nFreeBuilder(nativeBuilderPtr); } return mt; } Loading Loading @@ -415,13 +467,13 @@ public class MeasuredText { private void applyReplacementRun(@NonNull ReplacementSpan replacement, @IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer /* Maybe Zero */ long nativeStaticLayoutPtr) { /* Maybe Zero */ long nativeBuilderPtr) { // Use original text. Shouldn't matter. // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for // backward compatibility? or Should we initialize them for getFontMetricsInt? final float width = replacement.getSize( mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); if (nativeStaticLayoutPtr == 0) { if (nativeBuilderPtr == 0) { // Assigns all width to the first character. This is the same behavior as minikin. mWidths.set(start, width); if (end > start + 1) { Loading @@ -429,25 +481,26 @@ public class MeasuredText { } mWholeWidth += width; } else { StaticLayout.addReplacementRun(nativeStaticLayoutPtr, mCachedPaint, start, end, width); nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, width); } } private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer /* Maybe Zero */ long nativeStaticLayoutPtr) { if (nativeStaticLayoutPtr != 0) { /* Maybe Zero */ long nativeBuilderPtr) { if (nativeBuilderPtr != 0) { mCachedPaint.getFontMetricsInt(mCachedFm); } if (mLtrWithoutBidi) { // If the whole text is LTR direction, just apply whole region. if (nativeStaticLayoutPtr == 0) { if (nativeBuilderPtr == 0) { mWholeWidth += mCachedPaint.getTextRunAdvances( mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, mWidths.getRawArray(), start); } else { StaticLayout.addStyleRun(nativeStaticLayoutPtr, mCachedPaint, start, end, nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, false /* isRtl */); } } else { Loading @@ -458,14 +511,14 @@ public class MeasuredText { for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point final boolean isRtl = (level & 0x1) != 0; if (nativeStaticLayoutPtr == 0) { if (nativeBuilderPtr == 0) { final int levelLength = levelEnd - levelStart; mWholeWidth += mCachedPaint.getTextRunAdvances( mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, isRtl, mWidths.getRawArray(), levelStart); } else { StaticLayout.addStyleRun( nativeStaticLayoutPtr, mCachedPaint, levelStart, levelEnd, isRtl); nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, levelEnd, isRtl); } if (levelEnd == end) { break; Loading @@ -482,12 +535,12 @@ public class MeasuredText { @Nullable MetricAffectingSpan[] spans, @IntRange(from = 0) int start, // inclusive, in original text buffer @IntRange(from = 0) int end, // exclusive, in original text buffer /* Maybe Zero */ long nativeStaticLayoutPtr) { /* Maybe Zero */ long nativeBuilderPtr) { mCachedPaint.set(paint); // XXX paint should not have a baseline shift, but... mCachedPaint.baselineShift = 0; final boolean needFontMetrics = nativeStaticLayoutPtr != 0; final boolean needFontMetrics = nativeBuilderPtr != 0; if (needFontMetrics && mCachedFm == null) { mCachedFm = new Paint.FontMetricsInt(); Loading @@ -512,9 +565,9 @@ public class MeasuredText { if (replacement != null) { applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, nativeStaticLayoutPtr); nativeBuilderPtr); } else { applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeStaticLayoutPtr); applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); } if (needFontMetrics) { Loading Loading @@ -580,4 +633,44 @@ public class MeasuredText { } return width; } private static native /* Non Zero */ long nInitBuilder(); /** * Apply style to make native measured text. * * @param nativeBuilderPtr The native MeasuredText builder pointer. * @param paintPtr The native paint pointer to be applied. * @param start The start offset in the copied buffer. * @param end The end offset in the copied buffer. * @param isRtl True if the text is RTL. */ private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); /** * Apply ReplacementRun to make native measured text. * * @param nativeBuilderPtr The native MeasuredText builder pointer. * @param paintPtr The native paint pointer to be applied. * @param start The start offset in the copied buffer. * @param end The end offset in the copied buffer. * @param width The width of the replacement. */ private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @FloatRange(from = 0) float width); private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr, @NonNull char[] text); private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); @CriticalNative private static native /* Non Zero */ long nGetReleaseFunc(); }
core/java/android/text/StaticLayout.java +24 −47 Original line number Diff line number Diff line Loading @@ -48,6 +48,18 @@ import java.util.Arrays; * Canvas.drawText()} directly.</p> */ public class StaticLayout extends Layout { /* * The break iteration is done in native code. The protocol for using the native code is as * follows. * * First, call nInit to setup native line breaker object. Then, for each paragraph, do the * following: * * - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native. * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. */ static final String TAG = "StaticLayout"; Loading Loading @@ -724,10 +736,11 @@ public class StaticLayout extends Layout { } measured = MeasuredText.buildForStaticLayout( paint, source, paraStart, paraEnd, textDir, nativePtr, measured); paint, source, paraStart, paraEnd, textDir, measured); final char[] chs = measured.getChars(); final int[] spanEndCache = measured.getSpanEndCache().getRawArray(); final int[] fmCache = measured.getFontMetrics().getRawArray(); // TODO: Stop keeping duplicated width copy in native and Java. widths.resize(chs.length); // measurement has to be done before performing line breaking Loading @@ -739,6 +752,7 @@ public class StaticLayout extends Layout { // Inputs chs, measured.getNativePtr(), paraEnd - paraStart, firstWidth, firstWidthLineCount, Loading Loading @@ -873,18 +887,14 @@ public class StaticLayout extends Layout { if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { measured = MeasuredText.buildForStaticLayout( paint, source, bufEnd, bufEnd, textDir, nativePtr, measured); paint.getFontMetricsInt(fm); v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, fm.top, fm.bottom, v, spacingmult, spacingadd, null, null, fm, 0, needMultiply, measured, bufEnd, needMultiply, null, bufEnd, includepad, trackpad, addLastLineSpacing, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false); Loading @@ -902,7 +912,7 @@ 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, final MeasuredText measured, final int flags, final boolean needMultiply, @Nullable final MeasuredText measured, final int bufEnd, final boolean includePad, final boolean trackPad, final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, Loading @@ -911,7 +921,7 @@ public class StaticLayout extends Layout { final int off = j * mColumns; final int want = off + mColumns + TOP; int[] lines = mLines; final int dir = measured.getParagraphDir(); final int dir = (start == end) ? Layout.DIR_LEFT_TO_RIGHT : measured.getParagraphDir(); if (want >= lines.length) { final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want)); Loading @@ -938,7 +948,11 @@ public class StaticLayout extends Layout { lines[off + TAB] |= flags & TAB_MASK; lines[off + HYPHEN] = flags; lines[off + DIR] |= dir << DIR_SHIFT; if (start == end) { mLineDirections[j] = Layout.DIRS_ALL_LEFT_TO_RIGHT; } else { mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart); } final boolean firstLine = (j == 0); final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount); Loading Loading @@ -1415,33 +1429,6 @@ public class StaticLayout extends Layout { mMaxLineHeight : super.getHeight(); } /** * Measurement and break iteration is done in native code. The protocol for using * the native code is as follows. * * First, call nInit to setup native line breaker object. Then, for each paragraph, do the * following: * * - Call one of the following methods for each run within the paragraph depending on the type * of run: * + addStyleRun (a text run, to be measured in native code) * + addReplacementRun (a replacement run, width is given) * * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. */ /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end, boolean isRtl) { nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl); } /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end, float width) { nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width); } @FastNative private static native long nInit( @BreakStrategy int breakStrategy, Loading @@ -1454,17 +1441,6 @@ public class StaticLayout extends Layout { @CriticalNative private static native void nFinish(long nativePtr); @CriticalNative private static native void nAddStyleRun( /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); @CriticalNative private static native void nAddReplacementRun( /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @FloatRange(from = 0.0f) float width); // populates LineBreaks and returns the number of breaks found // // the arrays inside the LineBreaks objects are passed in as well Loading @@ -1477,6 +1453,7 @@ public class StaticLayout extends Layout { // Inputs @NonNull char[] text, /* Non Zero */ long measuredTextPtr, @IntRange(from = 0) int length, @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount, Loading
core/jni/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ cc_library_shared { "android_view_VelocityTracker.cpp", "android_text_AndroidCharacter.cpp", "android_text_Hyphenator.cpp", "android_text_MeasuredText.cpp", "android_text_StaticLayout.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", Loading
core/jni/AndroidRuntime.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -177,6 +177,7 @@ extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_net_TrafficStats(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_Hyphenator(JNIEnv *env); extern int register_android_text_MeasuredText(JNIEnv* env); extern int register_android_text_StaticLayout(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); Loading Loading @@ -1333,6 +1334,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_XmlBlock), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), REG_JNI(register_android_text_MeasuredText), REG_JNI(register_android_text_StaticLayout), REG_JNI(register_android_view_InputDevice), REG_JNI(register_android_view_KeyCharacterMap), Loading
core/jni/android_text_MeasuredText.cpp 0 → 100644 +122 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "MeasuredText" #include "ScopedIcuLocale.h" #include "unicode/locid.h" #include "unicode/brkiter.h" #include "utils/misc.h" #include "utils/Log.h" #include <nativehelper/ScopedStringChars.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/JNIHelp.h> #include "core_jni_helpers.h" #include <cstdint> #include <vector> #include <list> #include <algorithm> #include "SkPaint.h" #include "SkTypeface.h" #include <hwui/MinikinSkia.h> #include <hwui/MinikinUtils.h> #include <hwui/Paint.h> #include <minikin/FontCollection.h> #include <minikin/AndroidLineBreakerHelper.h> #include <minikin/MinikinFont.h> namespace android { static inline minikin::MeasuredTextBuilder* toBuilder(jlong ptr) { return reinterpret_cast<minikin::MeasuredTextBuilder*>(ptr); } static inline Paint* toPaint(jlong ptr) { return reinterpret_cast<Paint*>(ptr); } static inline minikin::MeasuredText* toMeasuredText(jlong ptr) { return reinterpret_cast<minikin::MeasuredText*>(ptr); } template<typename Ptr> static inline jlong toJLong(Ptr ptr) { return reinterpret_cast<jlong>(ptr); } static void releaseMeasuredText(jlong measuredTextPtr) { delete toMeasuredText(measuredTextPtr); } // Regular JNI static jlong nInitBuilder() { return toJLong(new minikin::MeasuredTextBuilder()); } // Regular JNI static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, jlong paintPtr, jint start, jint end, jboolean isRtl) { Paint* paint = toPaint(paintPtr); const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface()); minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), typeface->fFontCollection, isRtl); } // Regular JNI static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, jlong paintPtr, jint start, jint end, jfloat width) { toBuilder(builderPtr)->addReplacementRun(start, end, width, toPaint(paintPtr)->getMinikinLocaleListId()); } // Regular JNI static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jcharArray javaText) { ScopedCharArrayRO text(env, javaText); const minikin::U16StringPiece textBuffer(text.get(), text.size()); // Pass the ownership to Java. return toJLong(toBuilder(builderPtr)->build(textBuffer).release()); } // Regular JNI static void nFreeBuilder(JNIEnv* env, jclass /* unused */, jlong builderPtr) { delete toBuilder(builderPtr); } // CriticalNative static jlong nGetReleaseFunc() { return toJLong(&releaseMeasuredText); } static const JNINativeMethod gMethods[] = { // MeasuredTextBuilder native functions. {"nInitBuilder", "()J", (void*) nInitBuilder}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, // MeasuredText native functions. {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives }; int register_android_text_MeasuredText(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods)); } }