Introduce NativeLineBreaker.Builder and ParagraphConstraint
To make NativeLineBreaker public API, introduce
NativeLineBreaker.Builder and ParagraphConstraint.
Here is a performance differences:
android.text.StaticLayoutPerfTest:
PrecomputedText Balanced Hyphenation : 635 -> 684: ( +49, +7.7%)
PrecomputedText Balanced NoHyphenation: 477 -> 523: ( +46, +9.6%)
PrecomputedText Greedy Hyphenation : 413 -> 463: ( +50, +12.1%)
PrecomputedText Greedy NoHyphenation : 413 -> 463: ( +50, +12.1%)
RandomText Balanced Hyphenation : 18,030 -> 18,157: (+127, +0.7%)
RandomText Balanced NoHyphenation : 7,390 -> 7,388: ( -2, -0.0%)
RandomText Greedy Hyphenation : 7,319 -> 7,331: ( +12, +0.2%)
RandomText Greedy NoHyphenation : 7,316 -> 7,375: ( +59, +0.8%)
draw
PrecomputedText NoStyle : 631 -> 627: ( -4, -0.6%)
PrecomputedText Style : 842 -> 847: ( +5, +0.6%)
RandomText NoStyle : 531 -> 547: ( +16, +3.0%)
RandomText Style : 754 -> 758: ( +4, +0.5%)
Bug: 112327179
Test: atest CtsWidgetTestCases:EditTextTest
CtsWidgetTestCases:TextViewFadingEdgeTest
FrameworksCoreTests:TextViewFallbackLineSpacingTest
FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest
CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest
CtsTextTestCases FrameworksCoreTests:android.text
CtsWidgetTestCases:TextViewPrecomputedTextTest
CtsGraphicsTestCases:android.graphics.fonts
Change-Id: I5b817bbc9f00e5806efa98f239ee20ff3d688c50
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index ca952da..33c977b 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -50,9 +50,9 @@
public abstract class Layout {
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
- BREAK_STRATEGY_SIMPLE,
- BREAK_STRATEGY_HIGH_QUALITY,
- BREAK_STRATEGY_BALANCED
+ NativeLineBreaker.BREAK_STRATEGY_SIMPLE,
+ NativeLineBreaker.BREAK_STRATEGY_HIGH_QUALITY,
+ NativeLineBreaker.BREAK_STRATEGY_BALANCED
})
@Retention(RetentionPolicy.SOURCE)
public @interface BreakStrategy {}
@@ -63,19 +63,20 @@
* before it (which yields a more consistent user experience when editing), but layout may not
* be the highest quality.
*/
- public static final int BREAK_STRATEGY_SIMPLE = 0;
+ public static final int BREAK_STRATEGY_SIMPLE = NativeLineBreaker.BREAK_STRATEGY_SIMPLE;
/**
* Value for break strategy indicating high quality line breaking, including automatic
* hyphenation and doing whole-paragraph optimization of line breaks.
*/
- public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
+ public static final int BREAK_STRATEGY_HIGH_QUALITY =
+ NativeLineBreaker.BREAK_STRATEGY_HIGH_QUALITY;
/**
* Value for break strategy indicating balanced line breaking. The breaks are chosen to
* make all lines as close to the same length as possible, including automatic hyphenation.
*/
- public static final int BREAK_STRATEGY_BALANCED = 2;
+ public static final int BREAK_STRATEGY_BALANCED = NativeLineBreaker.BREAK_STRATEGY_BALANCED;
/** @hide */
@IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
@@ -93,29 +94,32 @@
* 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 = 0;
+ public static final int HYPHENATION_FREQUENCY_NONE =
+ NativeLineBreaker.HYPHENATION_FREQUENCY_NONE;
/**
* 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 = 1;
+ public static final int HYPHENATION_FREQUENCY_NORMAL =
+ NativeLineBreaker.HYPHENATION_FREQUENCY_NORMAL;
/**
* 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 = 2;
+ public static final int HYPHENATION_FREQUENCY_FULL =
+ NativeLineBreaker.HYPHENATION_FREQUENCY_FULL;
private static final ParagraphStyle[] NO_PARA_SPANS =
ArrayUtils.emptyArray(ParagraphStyle.class);
/** @hide */
@IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
- JUSTIFICATION_MODE_NONE,
- JUSTIFICATION_MODE_INTER_WORD
+ NativeLineBreaker.JUSTIFICATION_MODE_NONE,
+ NativeLineBreaker.JUSTIFICATION_MODE_INTER_WORD
})
@Retention(RetentionPolicy.SOURCE)
public @interface JustificationMode {}
@@ -123,12 +127,13 @@
/**
* Value for justification mode indicating no justification.
*/
- public static final int JUSTIFICATION_MODE_NONE = 0;
+ public static final int JUSTIFICATION_MODE_NONE = NativeLineBreaker.JUSTIFICATION_MODE_NONE;
/**
* Value for justification mode indicating the text is justified by stretching word spacing.
*/
- public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
+ public static final int JUSTIFICATION_MODE_INTER_WORD =
+ NativeLineBreaker.JUSTIFICATION_MODE_INTER_WORD;
/*
* Line spacing multiplier for default line spacing.
diff --git a/core/java/android/text/NativeLineBreaker.java b/core/java/android/text/NativeLineBreaker.java
index a31b336..2bcfa5f 100644
--- a/core/java/android/text/NativeLineBreaker.java
+++ b/core/java/android/text/NativeLineBreaker.java
@@ -17,6 +17,7 @@
package android.text;
import android.annotation.FloatRange;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,12 +27,233 @@
import libcore.util.NativeAllocationRegistry;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A native implementation of the line breaker.
* TODO: Consider to make this class public.
* @hide
*/
public class NativeLineBreaker {
+ @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
+ BREAK_STRATEGY_SIMPLE,
+ BREAK_STRATEGY_HIGH_QUALITY,
+ BREAK_STRATEGY_BALANCED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BreakStrategy {}
+
+ /**
+ * Value for break strategy indicating simple line breaking. Automatic hyphens are not added
+ * (though soft hyphens are respected), and modifying text generally doesn't affect the layout
+ * before it (which yields a more consistent user experience when editing), but layout may not
+ * be the highest quality.
+ */
+ public static final int BREAK_STRATEGY_SIMPLE = 0;
+
+ /**
+ * Value for break strategy indicating high quality line breaking, including automatic
+ * hyphenation and doing whole-paragraph optimization of line breaks.
+ */
+ public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
+
+ /**
+ * Value for break strategy indicating balanced line breaking. The breaks are chosen to
+ * make all lines as close to the same length as possible, including automatic hyphenation.
+ */
+ public static final int BREAK_STRATEGY_BALANCED = 2;
+
+ @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
+ HYPHENATION_FREQUENCY_NORMAL,
+ HYPHENATION_FREQUENCY_FULL,
+ HYPHENATION_FREQUENCY_NONE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HyphenationFrequency {}
+
+ /**
+ * Value for hyphenation frequency indicating no automatic hyphenation. Useful
+ * for backward compatibility, and for cases where the automatic hyphenation algorithm results
+ * in incorrect hyphenation. Mid-word breaks may still happen when a word is wider than the
+ * 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 = 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 = 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 = 2;
+
+ @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
+ JUSTIFICATION_MODE_NONE,
+ JUSTIFICATION_MODE_INTER_WORD
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface JustificationMode {}
+
+ /**
+ * Value for justification mode indicating no justification.
+ */
+ public static final int JUSTIFICATION_MODE_NONE = 0;
+
+ /**
+ * Value for justification mode indicating the text is justified by stretching word spacing.
+ */
+ public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
+
+ /**
+ * A builder class of NativeLineBreaker.
+ */
+ public static class Builder {
+ private @BreakStrategy int mBreakStrategy = BREAK_STRATEGY_SIMPLE;
+ private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE;
+ private @JustificationMode int mJustified = JUSTIFICATION_MODE_NONE;
+ private @Nullable int[] mIndents = null;
+
+ /**
+ * Construct a builder class.
+ */
+ public Builder() {}
+
+ /**
+ * Set break strategy.
+ */
+ public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
+ mBreakStrategy = breakStrategy;
+ return this;
+ }
+
+ /**
+ * Set hyphenation frequency.
+ */
+ public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
+ mHyphenationFrequency = hyphenationFrequency;
+ return this;
+ }
+
+ /**
+ * Set whether the text is justified.
+ */
+ public Builder setJustified(@JustificationMode int justified) {
+ mJustified = justified;
+ return this;
+ }
+
+ /**
+ * Set indents for entire text.
+ *
+ * Sets the total (left + right) indents in pixel per lines.
+ */
+ public Builder setIndents(@Nullable int[] indents) {
+ mIndents = indents;
+ return this;
+ }
+
+ /**
+ * Returns the NativeLineBreaker with given parameters.
+ */
+ NativeLineBreaker build() {
+ return new NativeLineBreaker(mBreakStrategy, mHyphenationFrequency, mJustified,
+ mIndents);
+ }
+ }
+
+ /**
+ * Line breaking constraints for single paragraph.
+ */
+ public static class ParagraphConstraints {
+ private @FloatRange(from = 0.0f) float mWidth = 0;
+ private @FloatRange(from = 0.0f) float mFirstWidth = 0;
+ private @IntRange(from = 0) int mFirstWidthLineCount = 0;
+ private @Nullable int[] mVariableTabStops = null;
+ private @IntRange(from = 0) int mDefaultTabStop = 0;
+
+ public ParagraphConstraints() {}
+
+ /**
+ * Set width for this paragraph.
+ */
+ public void setWidth(@FloatRange(from = 0.0f) float width) {
+ mWidth = width;
+ }
+
+ /**
+ * Set indent for this paragraph.
+ *
+ * @param firstWidth the line width of the starting of the paragraph
+ * @param firstWidthLineCount the number of lines that applies the firstWidth
+ */
+ public void setIndent(@FloatRange(from = 0.0f) float firstWidth,
+ @IntRange(from = 0) int firstWidthLineCount) {
+ mFirstWidth = firstWidth;
+ mFirstWidthLineCount = firstWidthLineCount;
+ }
+
+ /**
+ * Set tab stops for this paragraph.
+ *
+ * @param tabStops the array of pixels of tap stopping position
+ * @param defaultTabStop pixels of the default tab stopping position
+ */
+ public void setTabStops(@Nullable int[] tabStops, @IntRange(from = 0) int defaultTabStop) {
+ mVariableTabStops = tabStops;
+ mDefaultTabStop = defaultTabStop;
+ }
+
+ /**
+ * Return the width for this paragraph in pixels.
+ */
+ public @FloatRange(from = 0.0f) float getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Return the first line's width for this paragraph in pixel.
+ *
+ * @see #setIndent(float, int)
+ */
+ public @FloatRange(from = 0.0f) float getFirstWidth() {
+ return mFirstWidth;
+ }
+
+ /**
+ * Return the number of lines to apply the first line's width.
+ *
+ * @see #setIndent(float, int)
+ */
+ public @IntRange(from = 0) int getFirstWidthLineCount() {
+ return mFirstWidthLineCount;
+ }
+
+ /**
+ * Returns the array of tab stops in pixels.
+ *
+ * @see #setTabStops(int[], int)
+ */
+ public @Nullable int[] getTabStops() {
+ return mVariableTabStops;
+ }
+
+ /**
+ * Returns the default tab stops in pixels.
+ *
+ * @see #setTabStop(int[], int)
+ */
+ public @IntRange(from = 0) int getDefaultTabStop() {
+ return mDefaultTabStop;
+ }
+ }
/**
* A result object of a line breaking
@@ -43,6 +265,7 @@
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
}
@@ -53,54 +276,44 @@
private final long mNativePtr;
/**
- * A constructor of NativeLineBreaker
+ * Use Builder instead.
*/
- public NativeLineBreaker(@Layout.BreakStrategy int breakStrategy,
- @Layout.HyphenationFrequency int hyphenationFrequency,
- boolean justify, @Nullable int[] indents) {
- mNativePtr = nInit(breakStrategy, hyphenationFrequency, justify, indents);
+ private NativeLineBreaker(@BreakStrategy int breakStrategy,
+ @HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify,
+ @Nullable int[] indents) {
+ mNativePtr = nInit(breakStrategy, hyphenationFrequency,
+ justify == JUSTIFICATION_MODE_INTER_WORD, indents);
sRegistry.registerNativeAllocation(this, mNativePtr);
}
/**
- * Break text into lines
+ * Break paragraph into lines.
*
- * @param chars an array of characters
+ * The result is filled to out param.
+ *
* @param measuredPara a result of the text measurement
- * @param length a length of the target text from the begining
- * @param firstWidth a width of the first width of the line in this paragraph
- * @param firstWidthLineCount a number of lines that has the length of the firstWidth
- * @param restWidth a width of the rest of the lines.
- * @param variableTabStops an array of tab stop widths
- * @param defaultTabStop a width of the tab stop
- * @param indentsOffset an offset of the indents to be used.
- * @param out output buffer
- * @return a number of the lines
+ * @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
*/
- @NonNull public int computeLineBreaks(
- @NonNull char[] chars,
+ public void computeLineBreaks(
@NonNull NativeMeasuredParagraph measuredPara,
- @IntRange(from = 0) int length,
- @FloatRange(from = 0.0f) float firstWidth,
- @IntRange(from = 0) int firstWidthLineCount,
- @FloatRange(from = 0.0f) float restWidth,
- @Nullable int[] variableTabStops,
- int defaultTabStop,
- @IntRange(from = 0) int indentsOffset,
+ @NonNull ParagraphConstraints constraints,
+ @IntRange(from = 0) int lineNumber,
@NonNull LineBreaks out) {
- return nComputeLineBreaks(
+ out.breakCount = nComputeLineBreaks(
mNativePtr,
// Inputs
- chars,
+ measuredPara.getChars(),
measuredPara.getNativePtr(),
- length,
- firstWidth,
- firstWidthLineCount,
- restWidth,
- variableTabStops,
- defaultTabStop,
- indentsOffset,
+ measuredPara.getChars().length,
+ constraints.mFirstWidth,
+ constraints.mFirstWidthLineCount,
+ constraints.mWidth,
+ constraints.mVariableTabStops,
+ constraints.mDefaultTabStop,
+ lineNumber,
// Outputs
out,
@@ -114,10 +327,8 @@
}
@FastNative
- private static native long nInit(
- @Layout.BreakStrategy int breakStrategy,
- @Layout.HyphenationFrequency int hyphenationFrequency,
- boolean isJustified,
+ private static native long nInit(@BreakStrategy int breakStrategy,
+ @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
@Nullable int[] indents);
@CriticalNative
diff --git a/core/java/android/text/NativeMeasuredParagraph.java b/core/java/android/text/NativeMeasuredParagraph.java
index d03674f..bfdccca 100644
--- a/core/java/android/text/NativeMeasuredParagraph.java
+++ b/core/java/android/text/NativeMeasuredParagraph.java
@@ -36,10 +36,19 @@
NativeMeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);
private long mNativePtr;
+ private @NonNull char[] mChars;
// Use builder instead.
- private NativeMeasuredParagraph(long ptr) {
+ private NativeMeasuredParagraph(long ptr, @NonNull char[] chars) {
mNativePtr = ptr;
+ mChars = chars;
+ }
+
+ /**
+ * Returns a characters of this paragraph.
+ */
+ public char[] getChars() {
+ return mChars;
}
/**
@@ -126,7 +135,7 @@
try {
long ptr = nBuildNativeMeasuredParagraph(mNativePtr, text, computeHyphenation,
computeLayout);
- NativeMeasuredParagraph res = new NativeMeasuredParagraph(ptr);
+ NativeMeasuredParagraph res = new NativeMeasuredParagraph(ptr, text);
sRegistry.registerNativeAllocation(res, ptr);
return res;
} finally {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index dc01178..128f860 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -626,11 +626,16 @@
indents = null;
}
- final NativeLineBreaker lineBreaker = new NativeLineBreaker(
- b.mBreakStrategy, b.mHyphenationFrequency,
+ final NativeLineBreaker lineBreaker = new NativeLineBreaker.Builder()
+ .setBreakStrategy(b.mBreakStrategy)
+ .setHyphenationFrequency(b.mHyphenationFrequency)
// TODO: Support more justification mode, e.g. letter spacing, stretching.
- b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
- indents);
+ .setJustified(b.mJustificationMode)
+ .setIndents(indents)
+ .build();
+
+ NativeLineBreaker.ParagraphConstraints constraints =
+ new NativeLineBreaker.ParagraphConstraints();
PrecomputedText.ParagraphInfo[] paragraphInfo = null;
final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
@@ -721,18 +726,14 @@
final char[] chs = measuredPara.getChars();
final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
- int breakCount = lineBreaker.computeLineBreaks(
- measuredPara.getChars(),
- measuredPara.getNativeMeasuredParagraph(),
- paraEnd - paraStart,
- firstWidth,
- firstWidthLineCount,
- restWidth,
- variableTabStops,
- TAB_INCREMENT,
- mLineCount,
- lineBreaks);
+ constraints.setWidth(restWidth);
+ 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;