From 95c7a13f2ac4f31ed3aaec9b47b9a29a3dbca978 Mon Sep 17 00:00:00 2001 From: Roozbeh Pournader Date: Tue, 12 May 2015 12:01:06 -0700 Subject: [PATCH] Add hyphenationFrequency attribute to TextView and StaticLayout. This patch adds plumbing to TextView and StaticLayout to control the frequency of automatic hyphenation used in laying out paragraphs. Bug: 21038249 Change-Id: Ib45de190eb0a1ed738e69fd61f2b39561b11aec7 --- api/current.txt | 7 ++++ api/system-current.txt | 7 ++++ core/java/android/text/DynamicLayout.java | 10 ++++-- core/java/android/text/Layout.java | 29 +++++++++++++++ core/java/android/text/StaticLayout.java | 22 ++++++++++-- core/java/android/widget/TextView.java | 43 +++++++++++++++++++++-- core/jni/android_text_StaticLayout.cpp | 8 +++-- core/res/res/values/attrs.xml | 11 ++++++ core/res/res/values/public.xml | 1 + core/res/res/values/styles.xml | 2 ++ 10 files changed, 128 insertions(+), 12 deletions(-) diff --git a/api/current.txt b/api/current.txt index c307199cc17c5..9ff983f16b8c6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -653,6 +653,7 @@ package android { field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353 field public static final int horizontalSpacing = 16843028; // 0x1010114 field public static final int host = 16842792; // 0x1010028 + field public static final int hyphenationFrequency = 16844024; // 0x10104f8 field public static final int icon = 16842754; // 0x1010002 field public static final int iconPreview = 16843337; // 0x1010249 field public static final int iconTint = 16843999; // 0x10104df @@ -32166,6 +32167,9 @@ package android.text { field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0 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_NONE = 0; // 0x0 + field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1 } public static final class Layout.Alignment extends java.lang.Enum { @@ -32364,6 +32368,7 @@ package android.text { method public android.text.StaticLayout.Builder setBreakStrategy(int); method public android.text.StaticLayout.Builder setEllipsize(android.text.TextUtils.TruncateAt); method public android.text.StaticLayout.Builder setEllipsizedWidth(int); + method public android.text.StaticLayout.Builder setHyphenationFrequency(int); method public android.text.StaticLayout.Builder setIncludePad(boolean); method public android.text.StaticLayout.Builder setIndents(int[], int[]); method public android.text.StaticLayout.Builder setLineSpacing(float, float); @@ -41446,6 +41451,7 @@ package android.widget { method public int getHighlightColor(); method public java.lang.CharSequence getHint(); method public final android.content.res.ColorStateList getHintTextColors(); + method public int getHyphenationFrequency(); method public int getImeActionId(); method public java.lang.CharSequence getImeActionLabel(); method public int getImeOptions(); @@ -41552,6 +41558,7 @@ package android.widget { method public final void setHintTextColor(int); method public final void setHintTextColor(android.content.res.ColorStateList); method public void setHorizontallyScrolling(boolean); + method public void setHyphenationFrequency(int); method public void setImeActionLabel(java.lang.CharSequence, int); method public void setImeOptions(int); method public void setIncludeFontPadding(boolean); diff --git a/api/system-current.txt b/api/system-current.txt index 57b4c6cb7a308..f3fc88d809123 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -725,6 +725,7 @@ package android { field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353 field public static final int horizontalSpacing = 16843028; // 0x1010114 field public static final int host = 16842792; // 0x1010028 + field public static final int hyphenationFrequency = 16844024; // 0x10104f8 field public static final int icon = 16842754; // 0x1010002 field public static final int iconPreview = 16843337; // 0x1010249 field public static final int iconTint = 16843999; // 0x10104df @@ -34388,6 +34389,9 @@ package android.text { field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0 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_NONE = 0; // 0x0 + field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1 } public static final class Layout.Alignment extends java.lang.Enum { @@ -34586,6 +34590,7 @@ package android.text { method public android.text.StaticLayout.Builder setBreakStrategy(int); method public android.text.StaticLayout.Builder setEllipsize(android.text.TextUtils.TruncateAt); method public android.text.StaticLayout.Builder setEllipsizedWidth(int); + method public android.text.StaticLayout.Builder setHyphenationFrequency(int); method public android.text.StaticLayout.Builder setIncludePad(boolean); method public android.text.StaticLayout.Builder setIndents(int[], int[]); method public android.text.StaticLayout.Builder setLineSpacing(float, float); @@ -43979,6 +43984,7 @@ package android.widget { method public int getHighlightColor(); method public java.lang.CharSequence getHint(); method public final android.content.res.ColorStateList getHintTextColors(); + method public int getHyphenationFrequency(); method public int getImeActionId(); method public java.lang.CharSequence getImeActionLabel(); method public int getImeOptions(); @@ -44085,6 +44091,7 @@ package android.widget { method public final void setHintTextColor(int); method public final void setHintTextColor(android.content.res.ColorStateList); method public void setHorizontallyScrolling(boolean); + method public void setHyphenationFrequency(int); method public void setImeActionLabel(java.lang.CharSequence, int); method public void setImeOptions(int); method public void setIncludeFontPadding(boolean); diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index fc65f63f27b27..e99a960fb4738 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -79,7 +79,8 @@ public class DynamicLayout extends Layout boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR, - spacingmult, spacingadd, includepad, StaticLayout.BREAK_STRATEGY_SIMPLE, + spacingmult, spacingadd, includepad, + StaticLayout.BREAK_STRATEGY_SIMPLE, StaticLayout.HYPHENATION_FREQUENCY_NONE, ellipsize, ellipsizedWidth); } @@ -96,7 +97,7 @@ public class DynamicLayout extends Layout TextPaint paint, int width, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, - boolean includepad, int breakStrategy, + boolean includepad, int breakStrategy, int hyphenationFrequency, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { super((ellipsize == null) ? display @@ -122,6 +123,7 @@ public class DynamicLayout extends Layout mIncludePad = includepad; mBreakStrategy = breakStrategy; + mHyphenationFrequency = hyphenationFrequency; /* * This is annoying, but we can't refer to the layout until @@ -293,7 +295,8 @@ public class DynamicLayout extends Layout .setLineSpacing(getSpacingAdd(), getSpacingMultiplier()) .setEllipsizedWidth(mEllipsizedWidth) .setEllipsize(mEllipsizeAt) - .setBreakStrategy(mBreakStrategy); + .setBreakStrategy(mBreakStrategy) + .setHyphenationFrequency(mHyphenationFrequency); reflowed.generate(b, false, true); int n = reflowed.getLineCount(); @@ -719,6 +722,7 @@ public class DynamicLayout extends Layout private int mEllipsizedWidth; private TextUtils.TruncateAt mEllipsizeAt; private int mBreakStrategy; + private int mHyphenationFrequency; private PackedIntVector mInts; private PackedObjectVector mObjects; diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 60de02a77d100..f176240dbb73d 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -71,6 +71,35 @@ public abstract class Layout { */ public static final int BREAK_STRATEGY_BALANCED = 2; + /** @hide */ + @IntDef({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; + private static final ParagraphStyle[] NO_PARA_SPANS = ArrayUtils.emptyArray(ParagraphStyle.class); diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 59c7c6d34eb83..d6d046be30a87 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -92,6 +92,7 @@ public class StaticLayout extends Layout { b.mEllipsize = null; b.mMaxLines = Integer.MAX_VALUE; b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; + b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; b.mMeasuredText = MeasuredText.obtain(); return b; @@ -275,6 +276,19 @@ public class StaticLayout extends Layout { return this; } + /** + * Set hyphenation frequency, to control the amount of automatic hyphenation used. The + * default is {@link Layout#HYPHENATION_FREQUENCY_NONE}. + * + * @param hyphenationFrequency hyphenation frequency for the paragraph + * @return this builder, useful for chaining + * @see android.widget.TextView#setHyphenationFrequency + */ + public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) { + mHyphenationFrequency = hyphenationFrequency; + return this; + } + /** * Set indents. Arguments are arrays holding an indent amount, one per line, measured in * pixels. For lines past the last element in the array, the last element repeats. @@ -302,7 +316,8 @@ public class StaticLayout extends Layout { * the native code is as follows. * * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab - * stops, break strategy (and possibly other parameters in the future). + * stops, break strategy, and hyphenation frequency (and possibly other parameters in the + * future). * * Then, for each run within the paragraph: * - setLocale (this must be done at least for the first run, optional afterwards) @@ -377,6 +392,7 @@ public class StaticLayout extends Layout { TextUtils.TruncateAt mEllipsize; int mMaxLines; int mBreakStrategy; + int mHyphenationFrequency; Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); @@ -644,7 +660,7 @@ public class StaticLayout extends Layout { nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, firstWidth, firstWidthLineCount, restWidth, - variableTabStops, TAB_INCREMENT, b.mBreakStrategy); + variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency); // measurement has to be done before performing line breaking // but we don't want to recompute fontmetrics or span ranges the @@ -1153,7 +1169,7 @@ public class StaticLayout extends Layout { // Set up paragraph text and settings; done as one big method to minimize jni crossings private static native void nSetupParagraph(long nativePtr, char[] text, int length, float firstWidth, int firstWidthLineCount, float restWidth, - int[] variableTabStops, int defaultTabStop, int breakStrategy); + int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency); private static native float nAddStyleRun(long nativePtr, long nativePaint, long nativeTypeface, int start, int end, boolean isRtl); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 68c49cd063029..a98939289df2d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -238,6 +238,7 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; * @attr ref android.R.styleable#TextView_letterSpacing * @attr ref android.R.styleable#TextView_fontFeatureSettings * @attr ref android.R.styleable#TextView_breakStrategy + * @attr ref android.R.styleable#TextView_hyphenationFrequency * @attr ref android.R.styleable#TextView_leftIndents * @attr ref android.R.styleable#TextView_rightIndents */ @@ -555,6 +556,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private float mSpacingAdd = 0.0f; private int mBreakStrategy; + private int mHyphenationFrequency; private int[] mLeftIndents; private int[] mRightIndents; @@ -696,6 +698,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener float letterSpacing = 0; String fontFeatureSettings = null; mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; + mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; final Resources.Theme theme = context.getTheme(); @@ -1154,6 +1157,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); break; + case com.android.internal.R.styleable.TextView_hyphenationFrequency: + mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); + break; + case com.android.internal.R.styleable.TextView_leftIndents: TypedArray margins = res.obtainTypedArray(a.getResourceId(attr, View.NO_ID)); mLeftIndents = parseDimensionArray(margins); @@ -3049,6 +3056,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mBreakStrategy; } + /** + * Sets the hyphenation frequency. The default value for both TextView and EditText, which is set + * from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. + * + * @attr ref android.R.styleable#TextView_hyphenationFrequency + * @see #getHyphenationFrequency() + */ + public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { + mHyphenationFrequency = hyphenationFrequency; + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + + /** + * @return the currently set hyphenation frequency. + * + * @attr ref android.R.styleable#TextView_hyphenationFrequency + * @see #setHyphenationFrequency(int) + */ + @Layout.HyphenationFrequency + public int getHyphenationFrequency() { + return mHyphenationFrequency; + } + /** * Set indents. Arguments are arrays holding an indent amount, one per line, measured in * pixels. For lines past the last element in the array, the last element repeats. @@ -6637,7 +6671,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDir(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setBreakStrategy(mBreakStrategy); + .setBreakStrategy(mBreakStrategy) + .setHyphenationFrequency(mHyphenationFrequency); if (mLeftIndents != null || mRightIndents != null) { builder.setIndents(mLeftIndents, mRightIndents); } @@ -6678,7 +6713,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Layout result = null; if (mText instanceof Spannable) { result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, - alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mBreakStrategy, + alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, + mBreakStrategy, mHyphenationFrequency, getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth); } else { if (boring == UNKNOWN_BORING) { @@ -6726,7 +6762,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDir(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setBreakStrategy(mBreakStrategy); + .setBreakStrategy(mBreakStrategy) + .setHyphenationFrequency(mHyphenationFrequency); if (mLeftIndents != null || mRightIndents != null) { builder.setIndents(mLeftIndents, mRightIndents); } diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index 5e73ef250435c..90e4bb64133e9 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -48,10 +48,11 @@ struct JLineBreaksID { static jclass gLineBreaks_class; static JLineBreaksID gLineBreaks_fieldID; -// set text and set a number of parameters for creating a layout (width, tabstops, strategy) +// set text and set a number of parameters for creating a layout (width, tabstops, strategy, +// hyphenFrequency) static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length, jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth, - jintArray variableTabStops, jint defaultTabStop, jint strategy) { + jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency) { LineBreaker* b = reinterpret_cast(nativePtr); b->resize(length); env->GetCharArrayRegion(text, 0, length, b->buffer()); @@ -64,6 +65,7 @@ static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray tex b->setTabStops(stops.get(), stops.size(), defaultTabStop); } b->setStrategy(static_cast(strategy)); + b->setHyphenationFrequency(static_cast(hyphenFrequency)); } static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks, @@ -177,7 +179,7 @@ static JNINativeMethod gMethods[] = { {"nFinishBuilder", "(J)V", (void*) nFinishBuilder}, {"nLoadHyphenator", "(Ljava/lang/String;)J", (void*) nLoadHyphenator}, {"nSetLocale", "(JLjava/lang/String;J)V", (void*) nSetLocale}, - {"nSetupParagraph", "(J[CIFIF[III)V", (void*) nSetupParagraph}, + {"nSetupParagraph", "(J[CIFIF[IIII)V", (void*) nSetupParagraph}, {"nSetIndents", "(J[I)V", (void*) nSetIndents}, {"nAddStyleRun", "(JJJIIZ)F", (void*) nAddStyleRun}, {"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun}, diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 0b96d22eb4952..726401c760a0e 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4369,6 +4369,17 @@ + + + + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index e403a166034c5..b60a333164c36 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2688,4 +2688,5 @@ + diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 3c3d2861447ca..4c02d79c70807 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -498,6 +498,7 @@ please see styles_device_defaults.xml. ?attr/textEditSuggestionItemLayout ?attr/textCursorDrawable high_quality + normal