diff options
| -rw-r--r-- | apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java | 15 | ||||
| -rw-r--r-- | core/api/current.txt | 8 | ||||
| -rw-r--r-- | core/java/android/text/Layout.java | 27 | ||||
| -rw-r--r-- | core/java/android/text/MeasuredParagraph.java | 6 | ||||
| -rw-r--r-- | core/java/android/text/PrecomputedText.java | 27 | ||||
| -rw-r--r-- | core/java/android/text/StaticLayout.java | 16 | ||||
| -rw-r--r-- | core/res/res/values/attrs.xml | 10 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/text/MeasuredParagraphTest.java | 7 | ||||
| -rw-r--r-- | graphics/java/android/graphics/text/MeasuredText.java | 83 | ||||
| -rw-r--r-- | libs/hwui/jni/text/MeasuredText.cpp | 24 |
10 files changed, 196 insertions, 27 deletions
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 8a6c60f44702..1cd5d964e36f 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -138,6 +138,21 @@ public class StaticLayoutPerfTest { } @Test + public void testCreate_RandomText_NoStyled_Balanced_Hyphenation_Fast() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + state.resumeTiming(); + + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL_FAST) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .build(); + } + } + + @Test public void testCreate_RandomText_Styled_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { diff --git a/core/api/current.txt b/core/api/current.txt index 9514447582e9..7198285b9285 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -17264,8 +17264,12 @@ package android.graphics.text { method @NonNull public android.graphics.text.MeasuredText.Builder appendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float); method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, boolean); method @NonNull public android.graphics.text.MeasuredText build(); - method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean); + method @Deprecated @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean); + method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(int); method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean); + field public static final int HYPHENATION_MODE_FAST = 2; // 0x2 + field public static final int HYPHENATION_MODE_NONE = 0; // 0x0 + field public static final int HYPHENATION_MODE_NORMAL = 1; // 0x1 } public final class PositionedGlyphs { @@ -44451,8 +44455,10 @@ package android.text { field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1 field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2 + field public static final int HYPHENATION_FREQUENCY_FULL_FAST = 4; // 0x4 field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0 field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1 + field public static final int HYPHENATION_FREQUENCY_NORMAL_FAST = 3; // 0x3 field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1 field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0 } diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 505f400c60d2..da3e9b6d509c 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -82,7 +82,9 @@ public abstract class Layout { /** @hide */ @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = { HYPHENATION_FREQUENCY_NORMAL, + HYPHENATION_FREQUENCY_NORMAL_FAST, HYPHENATION_FREQUENCY_FULL, + HYPHENATION_FREQUENCY_FULL_FAST, HYPHENATION_FREQUENCY_NONE }) @Retention(RetentionPolicy.SOURCE) @@ -95,21 +97,40 @@ public abstract class Layout { * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used * as suggestions for potential line breaks. */ - public static final int HYPHENATION_FREQUENCY_NONE = LineBreaker.HYPHENATION_FREQUENCY_NONE; + public static final int HYPHENATION_FREQUENCY_NONE = 0; /** * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which * is a conservative default. Useful for informal cases, such as short sentences or chat * messages. */ - public static final int HYPHENATION_FREQUENCY_NORMAL = LineBreaker.HYPHENATION_FREQUENCY_NORMAL; + public static final int HYPHENATION_FREQUENCY_NORMAL = 1; /** * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical * in typography. Useful for running text and where it's important to put the maximum amount of * text in a screen with limited space. */ - public static final int HYPHENATION_FREQUENCY_FULL = LineBreaker.HYPHENATION_FREQUENCY_FULL; + public static final int HYPHENATION_FREQUENCY_FULL = 2; + + /** + * Value for hyphenation frequency indicating a light amount of automatic hyphenation with + * using faster algorithm. + * + * This option is useful for informal cases, such as short sentences or chat messages. To make + * text rendering faster with hyphenation, this algorithm ignores some hyphen character related + * typographic features, e.g. kerning. + */ + public static final int HYPHENATION_FREQUENCY_NORMAL_FAST = 3; + /** + * Value for hyphenation frequency indicating the full amount of automatic hyphenation with + * using faster algorithm. + * + * This option is useful for running text and where it's important to put the maximum amount of + * text in a screen with limited space. To make text rendering faster with hyphenation, this + * algorithm ignores some hyphen character related typographic features, e.g. kerning. + */ + public static final int HYPHENATION_FREQUENCY_FULL_FAST = 4; private static final ParagraphStyle[] NO_PARA_SPANS = ArrayUtils.emptyArray(ParagraphStyle.class); diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index 7e41878b3d4e..6a3c6182b96d 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -377,7 +377,7 @@ public class MeasuredParagraph { * @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 computeHyphenation true if need to compute hyphenation, otherwise false + * @param hyphenationMode a hyphenation mode * @param computeLayout true if need to compute full layout, otherwise false. * @param hint pass if you already have measured paragraph. * @param recycle pass existing MeasuredParagraph if you want to recycle it. @@ -390,7 +390,7 @@ public class MeasuredParagraph { @IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, - boolean computeHyphenation, + int hyphenationMode, boolean computeLayout, @Nullable MeasuredParagraph hint, @Nullable MeasuredParagraph recycle) { @@ -399,7 +399,7 @@ public class MeasuredParagraph { final MeasuredText.Builder builder; if (hint == null) { builder = new MeasuredText.Builder(mt.mCopiedBuffer) - .setComputeHyphenation(computeHyphenation) + .setComputeHyphenation(hyphenationMode) .setComputeLayout(computeLayout); } else { builder = new MeasuredText.Builder(hint.mMeasuredText); diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index 08741d6a7d88..152570ffd1c7 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -22,6 +22,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; +import android.graphics.text.MeasuredText; import android.text.style.MetricAffectingSpan; import com.android.internal.util.Preconditions; @@ -395,17 +396,30 @@ public class PrecomputedText implements Spannable { return new PrecomputedText(text, 0, text.length(), params, paraInfo); } + private static boolean isFastHyphenation(int frequency) { + return frequency == Layout.HYPHENATION_FREQUENCY_FULL_FAST + || frequency == Layout.HYPHENATION_FREQUENCY_NORMAL_FAST; + } + private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText( @NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) { final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE; + final int hyphenationMode; + if (needHyphenation) { + hyphenationMode = isFastHyphenation(params.getHyphenationFrequency()) + ? MeasuredText.Builder.HYPHENATION_MODE_FAST : + MeasuredText.Builder.HYPHENATION_MODE_NORMAL; + } else { + hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE; + } ArrayList<ParagraphInfo> result = new ArrayList<>(); for (int i = 0; i < pct.getParagraphCount(); ++i) { final int paraStart = pct.getParagraphStart(i); final int paraEnd = pct.getParagraphEnd(i); result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( params.getTextPaint(), pct, paraStart, paraEnd, params.getTextDirection(), - needHyphenation, computeLayout, pct.getMeasuredParagraph(i), + hyphenationMode, computeLayout, pct.getMeasuredParagraph(i), null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); @@ -421,6 +435,14 @@ public class PrecomputedText implements Spannable { Preconditions.checkNotNull(params); final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE; + final int hyphenationMode; + if (needHyphenation) { + hyphenationMode = isFastHyphenation(params.getHyphenationFrequency()) + ? MeasuredText.Builder.HYPHENATION_MODE_FAST : + MeasuredText.Builder.HYPHENATION_MODE_NORMAL; + } else { + hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE; + } int paraEnd = 0; for (int paraStart = start; paraStart < end; paraStart = paraEnd) { @@ -435,8 +457,7 @@ public class PrecomputedText implements Spannable { result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(), - needHyphenation, computeLayout, null /* no hint */, - null /* no recycle */))); + hyphenationMode, computeLayout, null /* no hint */, null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index f99d4300a848..6984e4dfccc4 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -591,6 +591,20 @@ public class StaticLayout extends Layout { generate(b, b.mIncludePad, b.mIncludePad); } + private static int getBaseHyphenationFrequency(int frequency) { + switch (frequency) { + case Layout.HYPHENATION_FREQUENCY_FULL: + case Layout.HYPHENATION_FREQUENCY_FULL_FAST: + return LineBreaker.HYPHENATION_FREQUENCY_FULL; + case Layout.HYPHENATION_FREQUENCY_NORMAL: + case Layout.HYPHENATION_FREQUENCY_NORMAL_FAST: + return LineBreaker.HYPHENATION_FREQUENCY_NORMAL; + case Layout.HYPHENATION_FREQUENCY_NONE: + default: + return LineBreaker.HYPHENATION_FREQUENCY_NONE; + } + } + /* package */ void generate(Builder b, boolean includepad, boolean trackpad) { final CharSequence source = b.mText; final int bufStart = b.mStart; @@ -641,7 +655,7 @@ public class StaticLayout extends Layout { final LineBreaker lineBreaker = new LineBreaker.Builder() .setBreakStrategy(b.mBreakStrategy) - .setHyphenationFrequency(b.mHyphenationFrequency) + .setHyphenationFrequency(getBaseHyphenationFrequency(b.mHyphenationFrequency)) // TODO: Support more justification mode, e.g. letter spacing, stretching. .setJustificationMode(b.mJustificationMode) .setIndents(indents) diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 819857f79813..dba7b514977a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5383,6 +5383,16 @@ <!-- Standard amount of hyphenation, useful for running text and for screens with limited space for text. --> <enum name="full" value="2" /> + + <!-- Same to hyphenationFrequency="normal" but using faster algorithm for measuring + hyphenation break points. To make text rendering faster with hyphenation, this algorithm + ignores some hyphen character related typographic features, e.g. kerning. --> + <enum name="normalFast" value="3" /> + + <!-- Same to hyphenationFrequency="full" but using faster algorithm for measuring + hyphenation break points. To make text rendering faster with hyphenation, this algorithm + ignores some hyphen character related typographic features, e.g. kerning. --> + <enum name="fullFast" value="4" /> </attr> <!-- Specify the type of auto-size. Note that this feature is not supported by EditText, works only for TextView. --> diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java index 57bb4349c4fd..d6a7682475f2 100644 --- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java +++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertNull; import android.content.Context; import android.graphics.Typeface; +import android.graphics.text.MeasuredText; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -135,7 +136,8 @@ public class MeasuredParagraphTest { MeasuredParagraph mt = null; mt = MeasuredParagraph.buildForStaticLayout( - PAINT, "XXX", 0, 3, LTR, false, false, null /* no hint */, null); + PAINT, "XXX", 0, 3, LTR, MeasuredText.Builder.HYPHENATION_MODE_NONE, false, + null /* no hint */, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -150,7 +152,8 @@ public class MeasuredParagraphTest { // Recycle it MeasuredParagraph mt2 = MeasuredParagraph.buildForStaticLayout( - PAINT, "_VVV_", 1, 4, RTL, false, false, null /* no hint */, mt); + PAINT, "_VVV_", 1, 4, RTL, MeasuredText.Builder.HYPHENATION_MODE_NONE, false, + null /* no hint */, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 31c3d09ed5b1..df5b3f582d8c 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -17,12 +17,14 @@ package android.graphics.text; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; import android.graphics.Paint; import android.graphics.Rect; +import android.util.Log; import com.android.internal.util.Preconditions; @@ -30,6 +32,9 @@ import dalvik.annotation.optimization.CriticalNative; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Result of text shaping of the single paragraph string. * @@ -49,6 +54,8 @@ import libcore.util.NativeAllocationRegistry; * </p> */ public class MeasuredText { + private static final String TAG = "MeasuredText"; + private long mNativePtr; private boolean mComputeHyphenation; private boolean mComputeLayout; @@ -179,6 +186,7 @@ public class MeasuredText { private final @NonNull char[] mText; private boolean mComputeHyphenation = false; private boolean mComputeLayout = true; + private boolean mFastHyphenation = false; private int mCurrentOffset = 0; private @Nullable MeasuredText mHintMt = null; @@ -275,10 +283,78 @@ public class MeasuredText { * Even if you pass false to this method, you can still enable automatic hyphenation of * LineBreaker but line break computation becomes slower. * + * @deprecated use setComputeHyphenation(int) instead. + * * @param computeHyphenation true if you want to use automatic hyphenations. */ public @NonNull Builder setComputeHyphenation(boolean computeHyphenation) { - mComputeHyphenation = computeHyphenation; + setComputeHyphenation( + computeHyphenation ? HYPHENATION_MODE_NORMAL : HYPHENATION_MODE_NONE); + return this; + } + + /** @hide */ + @IntDef(prefix = { "HYPHENATION_MODE_" }, value = { + HYPHENATION_MODE_NONE, + HYPHENATION_MODE_NORMAL, + HYPHENATION_MODE_FAST + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HyphenationMode {} + + /** + * A value for hyphenation calculation mode. + * + * This value indicates that no hyphenation points are calculated. + */ + public static final int HYPHENATION_MODE_NONE = 0; + + /** + * A value for hyphenation calculation mode. + * + * This value indicates that hyphenation points are calculated. + */ + public static final int HYPHENATION_MODE_NORMAL = 1; + + /** + * A value for hyphenation calculation mode. + * + * This value indicates that hyphenation points are calculated with faster algorithm. This + * algorithm measures text width with ignoring the context of hyphen character shaping, e.g. + * kerning. + */ + public static final int HYPHENATION_MODE_FAST = 2; + + /** + * By passing true to this method, the build method will calculate hyphenation break + * points faster with ignoring some typographic features, e.g. kerning. + * + * {@link #HYPHENATION_MODE_NONE} is by default. + * + * @see #setComputeHyphenation(boolean) + * + * @param mode a hyphenation mode. + */ + public @NonNull Builder setComputeHyphenation(@HyphenationMode int mode) { + switch (mode) { + case HYPHENATION_MODE_NONE: + mComputeHyphenation = false; + mFastHyphenation = false; + break; + case HYPHENATION_MODE_NORMAL: + mComputeHyphenation = true; + mFastHyphenation = false; + break; + case HYPHENATION_MODE_FAST: + mComputeHyphenation = true; + mFastHyphenation = true; + break; + default: + Log.e(TAG, "Unknown hyphenation mode: " + mode); + mComputeHyphenation = false; + mFastHyphenation = false; + break; + } return this; } @@ -319,7 +395,7 @@ public class MeasuredText { try { long hintPtr = (mHintMt == null) ? 0 : mHintMt.getNativePtr(); long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation, - mComputeLayout); + mComputeLayout, mFastHyphenation); final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation, mComputeLayout); sRegistry.registerNativeAllocation(res, ptr); @@ -378,7 +454,8 @@ public class MeasuredText { long hintMtPtr, @NonNull char[] text, boolean computeHyphenation, - boolean computeLayout); + boolean computeLayout, + boolean fastHyphenationMode); private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); } diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp index 7793746ee285..bd9bd7121f8a 100644 --- a/libs/hwui/jni/text/MeasuredText.cpp +++ b/libs/hwui/jni/text/MeasuredText.cpp @@ -80,15 +80,17 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong } // Regular JNI -static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, - jlong hintPtr, jcharArray javaText, jboolean computeHyphenation, - jboolean computeLayout) { +static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jlong hintPtr, + jcharArray javaText, jboolean computeHyphenation, + jboolean computeLayout, jboolean fastHyphenationMode) { ScopedCharArrayRO text(env, javaText); const minikin::U16StringPiece textBuffer(text.get(), text.size()); // Pass the ownership to Java. - return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation, computeLayout, - toMeasuredParagraph(hintPtr)).release()); + return toJLong(toBuilder(builderPtr) + ->build(textBuffer, computeHyphenation, computeLayout, + fastHyphenationMode, toMeasuredParagraph(hintPtr)) + .release()); } // Regular JNI @@ -140,12 +142,12 @@ static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { } static const JNINativeMethod gMTBuilderMethods[] = { - // MeasuredParagraphBuilder native functions. - {"nInitBuilder", "()J", (void*) nInitBuilder}, - {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, - {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, - {"nBuildMeasuredText", "(JJ[CZZ)J", (void*) nBuildMeasuredText}, - {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, + // MeasuredParagraphBuilder native functions. + {"nInitBuilder", "()J", (void*)nInitBuilder}, + {"nAddStyleRun", "(JJIIZ)V", (void*)nAddStyleRun}, + {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun}, + {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText}, + {"nFreeBuilder", "(J)V", (void*)nFreeBuilder}, }; static const JNINativeMethod gMTMethods[] = { |