Merge "Compute hyphenated word pieces in MeasuredText"
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 93a0fc3..0ac167f 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -190,8 +190,11 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -206,8 +209,11 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -222,8 +228,11 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -238,8 +247,11 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -254,8 +266,11 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final MeasuredText text = MeasuredText.build(
- generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR);
+ final MeasuredText text = new MeasuredText.Builder(
+ generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+ .build();
state.resumeTiming();
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
diff --git a/api/current.txt b/api/current.txt
index 847671e..5e8d144 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -42320,10 +42320,10 @@
}
public class MeasuredText implements android.text.Spanned {
- method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
- method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
method public char charAt(int);
+ method public int getBreakStrategy();
method public int getEnd();
+ method public int getHyphenationFrequency();
method public android.text.TextPaint getPaint();
method public int getParagraphCount();
method public int getParagraphEnd(int);
@@ -42340,6 +42340,15 @@
method public java.lang.CharSequence subSequence(int, int);
}
+ public static final class MeasuredText.Builder {
+ ctor public MeasuredText.Builder(java.lang.CharSequence, android.text.TextPaint);
+ method public android.text.MeasuredText build();
+ method public android.text.MeasuredText.Builder setBreakStrategy(int);
+ method public android.text.MeasuredText.Builder setHyphenationFrequency(int);
+ method public android.text.MeasuredText.Builder setRange(int, int);
+ method public android.text.MeasuredText.Builder setTextDirection(android.text.TextDirectionHeuristic);
+ }
+
public abstract interface NoCopySpan {
}
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index c93e036..c7d4a4a 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -356,6 +356,7 @@
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@NonNull TextDirectionHeuristic textDir,
+ boolean computeHyphenation,
@Nullable MeasuredParagraph recycle) {
final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
mt.resetAndAnalyzeBidi(text, start, end, textDir);
@@ -365,7 +366,8 @@
long nativeBuilderPtr = nInitBuilder();
try {
mt.bindNativeObject(
- nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+ nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
+ computeHyphenation));
} finally {
nFreeBuilder(nativeBuilderPtr);
}
@@ -394,7 +396,8 @@
mt.mSpanEndCache.append(spanEnd);
}
}
- mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+ mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
+ computeHyphenation));
} finally {
nFreeBuilder(nativeBuilderPtr);
}
@@ -668,7 +671,8 @@
@FloatRange(from = 0) float width);
private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
- @NonNull char[] text);
+ @NonNull char[] text,
+ boolean computeHyphenation);
private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 2c30360..b96b489 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -52,70 +52,147 @@
// The sorted paragraph end offsets.
private final @NonNull int[] mParagraphBreakPoints;
- /**
- * Build MeasuredText from the text.
- *
- * @param text The text to be measured.
- * @param paint The paint to be used for drawing.
- * @param textDir The text direction.
- * @return The measured text.
- */
- public static @NonNull MeasuredText build(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @NonNull TextDirectionHeuristic textDir) {
- return MeasuredText.build(text, paint, textDir, 0, text.length());
- }
+ // The break strategy for this measured text.
+ private final @Layout.BreakStrategy int mBreakStrategy;
+
+ // The hyphenation frequency for this measured text.
+ private final @Layout.HyphenationFrequency int mHyphenationFrequency;
/**
- * Build MeasuredText from the specific range of the text..
- *
- * @param text The text to be measured.
- * @param paint The paint to be used for drawing.
- * @param textDir The text direction.
- * @param start The inclusive start offset of the text.
- * @param end The exclusive start offset of the text.
- * @return The measured text.
+ * A Builder for MeasuredText
*/
- public static @NonNull MeasuredText build(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @NonNull TextDirectionHeuristic textDir,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end) {
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(paint);
- Preconditions.checkNotNull(textDir);
- Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
- Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
+ public static final class Builder {
+ // Mandatory parameters.
+ private final @NonNull CharSequence mText;
+ private final @NonNull TextPaint mPaint;
- final IntArray paragraphEnds = new IntArray();
- final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
+ // Members to be updated by setters.
+ private @IntRange(from = 0) int mStart;
+ private @IntRange(from = 0) int mEnd;
+ private TextDirectionHeuristic mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
+ private @Layout.BreakStrategy int mBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
+ private @Layout.HyphenationFrequency int mHyphenationFrequency =
+ Layout.HYPHENATION_FREQUENCY_NORMAL;
- int paraEnd = 0;
- for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
- if (paraEnd < 0) {
- // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
- paraEnd = end;
- } else {
- paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
- }
- paragraphEnds.add(paraEnd);
- measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
- paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
+ /**
+ * Builder constructor
+ *
+ * @param text The text to be measured.
+ * @param paint The paint to be used for drawing.
+ */
+ public Builder(@NonNull CharSequence text, @NonNull TextPaint paint) {
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(paint);
+
+ mText = text;
+ mPaint = paint;
+ mStart = 0;
+ mEnd = text.length();
}
- return new MeasuredText(text, start, end, paint, textDir,
- measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]),
- paragraphEnds.toArray());
- }
+ /**
+ * Set the range of measuring target.
+ *
+ * @param start The measuring target start offset in the text.
+ * @param end The measuring target end offset in the text.
+ */
+ public @NonNull Builder setRange(@IntRange(from = 0) int start,
+ @IntRange(from = 0) int end) {
+ Preconditions.checkArgumentInRange(start, 0, mText.length(), "start");
+ Preconditions.checkArgumentInRange(end, 0, mText.length(), "end");
+ Preconditions.checkArgument(start <= end, "The range is reversed.");
- // Use MeasuredText.build instead.
+ mStart = start;
+ mEnd = end;
+ return this;
+ }
+
+ /**
+ * Set the text direction heuristic
+ *
+ * The default value is {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
+ *
+ * @param textDir The text direction heuristic for resolving bidi behavior.
+ * @return this builder, useful for chaining.
+ */
+ public @NonNull Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
+ Preconditions.checkNotNull(textDir);
+ mTextDir = textDir;
+ return this;
+ }
+
+ /**
+ * Set the break strategy
+ *
+ * The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}.
+ *
+ * @param breakStrategy The break strategy.
+ * @return this builder, useful for chaining.
+ */
+ public @NonNull Builder setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
+ mBreakStrategy = breakStrategy;
+ return this;
+ }
+
+ /**
+ * Set the hyphenation frequency
+ *
+ * The default value is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
+ *
+ * @param hyphenationFrequency The hyphenation frequency.
+ * @return this builder, useful for chaining.
+ */
+ public @NonNull Builder setHyphenationFrequency(
+ @Layout.HyphenationFrequency int hyphenationFrequency) {
+ mHyphenationFrequency = hyphenationFrequency;
+ return this;
+ }
+
+ /**
+ * Build the measured text
+ *
+ * @return the measured text.
+ */
+ public @NonNull MeasuredText build() {
+ final boolean needHyphenation = mBreakStrategy != Layout.BREAK_STRATEGY_SIMPLE
+ && mHyphenationFrequency != Layout.HYPHENATION_FREQUENCY_NONE;
+
+ final IntArray paragraphEnds = new IntArray();
+ final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
+
+ int paraEnd = 0;
+ for (int paraStart = mStart; paraStart < mEnd; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(mText, LINE_FEED, paraStart, mEnd);
+ if (paraEnd < 0) {
+ // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
+ // end.
+ paraEnd = mEnd;
+ } else {
+ paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
+ }
+
+ paragraphEnds.add(paraEnd);
+ measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
+ mPaint, mText, paraStart, paraEnd, mTextDir, needHyphenation,
+ null /* no recycle */));
+ }
+
+ return new MeasuredText(mText, mStart, mEnd, mPaint, mTextDir, mBreakStrategy,
+ mHyphenationFrequency, measuredTexts.toArray(
+ new MeasuredParagraph[measuredTexts.size()]),
+ paragraphEnds.toArray());
+ }
+ };
+
+ // Use MeasuredText.Builder instead.
private MeasuredText(@NonNull CharSequence text,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir,
+ @Layout.BreakStrategy int breakStrategy,
+ @Layout.HyphenationFrequency int frequency,
@NonNull MeasuredParagraph[] measuredTexts,
@NonNull int[] paragraphBreakPoints) {
mText = text;
@@ -125,6 +202,8 @@
mMeasuredParagraphs = measuredTexts;
mParagraphBreakPoints = paragraphBreakPoints;
mTextDir = textDir;
+ mBreakStrategy = breakStrategy;
+ mHyphenationFrequency = frequency;
}
/**
@@ -190,6 +269,20 @@
return mMeasuredParagraphs[paraIndex];
}
+ /**
+ * Returns the break strategy for this text.
+ */
+ public @Layout.BreakStrategy int getBreakStrategy() {
+ return mBreakStrategy;
+ }
+
+ /**
+ * Returns the hyphenation frequency for this text.
+ */
+ public @Layout.HyphenationFrequency int getHyphenationFrequency() {
+ return mHyphenationFrequency;
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////////
// Spanned overrides
//
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 36bec86..70d6486 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -653,26 +653,40 @@
MeasuredText measured = null;
final Spanned spanned;
+ final boolean canUseMeasuredText;
if (source instanceof MeasuredText) {
measured = (MeasuredText) source;
- final CharSequence original = measured.getText();
- spanned = (original instanceof Spanned) ? (Spanned) original : null;
-
if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
// The buffer position has changed. Re-measure here.
- measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd);
+ canUseMeasuredText = false;
+ } else if (b.mBreakStrategy != measured.getBreakStrategy()
+ || b.mHyphenationFrequency != measured.getHyphenationFrequency()) {
+ // The computed hyphenation pieces may not be able to used. Re-measure it.
+ canUseMeasuredText = false;
} else {
// We can use measured information.
-
- // Overwrite with the one when emeasured.
- // TODO: Give an option for developer not to overwrite and measure again here?
- textDir = measured.getTextDir();
- paint = measured.getPaint();
+ canUseMeasuredText = true;
}
} else {
- measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd);
+ canUseMeasuredText = false;
+ }
+
+ if (!canUseMeasuredText) {
+ measured = new MeasuredText.Builder(source, paint)
+ .setRange(bufStart, bufEnd)
+ .setTextDirection(textDir)
+ .setBreakStrategy(b.mBreakStrategy)
+ .setHyphenationFrequency(b.mHyphenationFrequency)
+ .build();
spanned = (source instanceof Spanned) ? (Spanned) source : null;
+ } else {
+ final CharSequence original = measured.getText();
+ spanned = (original instanceof Spanned) ? (Spanned) original : null;
+ // Overwrite with the one when measured.
+ // TODO: Give an option for developer not to overwrite and measure again here?
+ textDir = measured.getTextDir();
+ paint = measured.getPaint();
}
try {
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index bdae0b2..58c05b4 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -85,12 +85,12 @@
// Regular JNI
static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr,
- jcharArray javaText) {
+ jcharArray javaText, jboolean computeHyphenation) {
ScopedCharArrayRO text(env, javaText);
const minikin::U16StringPiece textBuffer(text.get(), text.size());
// Pass the ownership to Java.
- return toJLong(toBuilder(builderPtr)->build(textBuffer).release());
+ return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation).release());
}
// Regular JNI
@@ -108,7 +108,7 @@
{"nInitBuilder", "()J", (void*) nInitBuilder},
{"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
- {"nBuildNativeMeasuredParagraph", "(J[C)J", (void*) nBuildNativeMeasuredParagraph},
+ {"nBuildNativeMeasuredParagraph", "(J[CZ)J", (void*) nBuildNativeMeasuredParagraph},
{"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
// MeasuredParagraph native functions.
diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index 5d33397..f6300ee 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -132,7 +132,7 @@
public void buildForStaticLayout() {
MeasuredParagraph mt = null;
- mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
+ mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, false, null);
assertNotNull(mt);
assertNotNull(mt.getChars());
assertEquals("XXX", charsToString(mt.getChars()));
@@ -147,7 +147,7 @@
// Recycle it
MeasuredParagraph mt2 =
- MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
+ MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, false, mt);
assertEquals(mt2, mt);
assertNotNull(mt2.getChars());
assertEquals("VVV", charsToString(mt.getChars()));