Merge "Implement textDirection heuristic selection."

This commit is contained in:
Fabrice Di Meglio
2011-07-15 09:49:48 -07:00
committed by Android (Google) Code Review
15 changed files with 1048 additions and 430 deletions

View File

@@ -226,7 +226,17 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
*/
public static Metrics isBoring(CharSequence text,
TextPaint paint) {
return isBoring(text, paint, null);
return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null);
}
/**
* Returns null if not boring; the width, ascent, and descent if boring.
* @hide
*/
public static Metrics isBoring(CharSequence text,
TextPaint paint,
TextDirectionHeuristic textDir) {
return isBoring(text, paint, textDir, null);
}
/**
@@ -235,6 +245,17 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
* if boring.
*/
public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics);
}
/**
* Returns null if not boring; the width, ascent, and descent in the
* provided Metrics object (or a new one if the provided one was null)
* if boring.
* @hide
*/
public static Metrics isBoring(CharSequence text, TextPaint paint,
TextDirectionHeuristic textDir, Metrics metrics) {
char[] temp = TextUtils.obtain(500);
int length = text.length();
boolean boring = true;
@@ -258,6 +279,11 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
break outer;
}
}
if (textDir.isRtl(temp, 0, n)) {
boring = false;
break outer;
}
}
TextUtils.recycle(temp);

View File

@@ -75,12 +75,31 @@ extends Layout
float spacingmult, float spacingadd,
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
super((ellipsize == null)
? display
: (display instanceof Spanned)
? new SpannedEllipsizer(display)
this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth);
}
/**
* Make a layout for the transformed text (password transformation
* being the primary example of a transformation)
* that will be updated as the base text is changed.
* If ellipsize is non-null, the Layout will ellipsize the text
* down to ellipsizedWidth.
* *
* *@hide
*/
public DynamicLayout(CharSequence base, CharSequence display,
TextPaint paint,
int width, Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
super((ellipsize == null)
? display
: (display instanceof Spanned)
? new SpannedEllipsizer(display)
: new Ellipsizer(display),
paint, width, align, spacingmult, spacingadd);
paint, width, align, textDir, spacingmult, spacingadd);
mBase = base;
mDisplay = display;
@@ -259,7 +278,7 @@ extends Layout
reflowed = new StaticLayout(true);
reflowed.generate(text, where, where + after,
getPaint(), getWidth(), getAlignment(),
getPaint(), getWidth(), getAlignment(), getTextDirectionHeuristic(),
getSpacingMultiplier(), getSpacingAdd(),
false, true, mEllipsizedWidth, mEllipsizeAt);
int n = reflowed.getLineCount();

View File

@@ -16,8 +16,6 @@
package android.text;
import com.android.internal.util.ArrayUtils;
import android.emoji.EmojiFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -32,6 +30,8 @@ import android.text.style.ParagraphStyle;
import android.text.style.ReplacementSpan;
import android.text.style.TabStopSpan;
import com.android.internal.util.ArrayUtils;
import java.util.Arrays;
/**
@@ -113,6 +113,29 @@ public abstract class Layout {
protected Layout(CharSequence text, TextPaint paint,
int width, Alignment align,
float spacingMult, float spacingAdd) {
this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
spacingMult, spacingAdd);
}
/**
* Subclasses of Layout use this constructor to set the display text,
* width, and other standard properties.
* @param text the text to render
* @param paint the default paint for the layout. Styles can override
* various attributes of the paint.
* @param width the wrapping width for the text.
* @param align whether to left, right, or center the text. Styles can
* override the alignment.
* @param spacingMult factor by which to scale the font size to get the
* default line spacing
* @param spacingAdd amount to add to the default line spacing
*
* @hide
*/
protected Layout(CharSequence text, TextPaint paint,
int width, Alignment align, TextDirectionHeuristic textDir,
float spacingMult, float spacingAdd) {
if (width < 0)
throw new IllegalArgumentException("Layout: " + width + " < 0");
@@ -133,6 +156,7 @@ public abstract class Layout {
mSpacingMult = spacingMult;
mSpacingAdd = spacingAdd;
mSpannedText = text instanceof Spanned;
mTextDir = textDir;
}
/**
@@ -530,6 +554,14 @@ public abstract class Layout {
return mSpacingAdd;
}
/**
* Return the heuristic used to determine paragraph text direction.
* @hide
*/
public final TextDirectionHeuristic getTextDirectionHeuristic() {
return mTextDir;
}
/**
* Return the number of lines of text in this layout.
*/
@@ -1419,7 +1451,7 @@ public abstract class Layout {
MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
try {
mt.setPara(text, start, end, DIR_REQUEST_LTR);
mt.setPara(text, start, end, TextDirectionHeuristics.LTR);
Directions directions;
int dir;
if (mt.mEasy) {
@@ -1769,6 +1801,7 @@ public abstract class Layout {
private float mSpacingAdd;
private static final Rect sTempRect = new Rect();
private boolean mSpannedText;
private TextDirectionHeuristic mTextDir;
public static final int DIR_LEFT_TO_RIGHT = 1;
public static final int DIR_RIGHT_TO_LEFT = -1;

View File

@@ -85,7 +85,7 @@ class MeasuredText {
* Analyzes text for bidirectional runs. Allocates working buffers.
*/
/* package */
void setPara(CharSequence text, int start, int end, int bidiRequest) {
void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
mText = text;
mTextStart = start;
@@ -115,13 +115,29 @@ class MeasuredText {
}
}
if (TextUtils.doesNotNeedBidi(mChars, 0, len)) {
if ((textDir == TextDirectionHeuristics.LTR ||
textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
TextUtils.doesNotNeedBidi(mChars, 0, len)) {
mDir = Layout.DIR_LEFT_TO_RIGHT;
mEasy = true;
} else {
if (mLevels == null || mLevels.length < len) {
mLevels = new byte[ArrayUtils.idealByteArraySize(len)];
}
int bidiRequest;
if (textDir == TextDirectionHeuristics.LTR) {
bidiRequest = Layout.DIR_REQUEST_LTR;
} else if (textDir == TextDirectionHeuristics.RTL) {
bidiRequest = Layout.DIR_REQUEST_RTL;
} else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
} else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
} else {
boolean isRtl = textDir.isRtl(mChars, 0, len);
bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
}
mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
mEasy = false;
}

View File

@@ -16,8 +16,6 @@
package android.text;
import com.android.internal.util.ArrayUtils;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.text.style.LeadingMarginSpan;
@@ -26,6 +24,8 @@ import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.TabStopSpan;
import com.android.internal.util.ArrayUtils;
/**
* StaticLayout is a Layout for text that will not be edited after it
* is laid out. Use {@link DynamicLayout} for text that may change.
@@ -46,6 +46,17 @@ public class StaticLayout extends Layout {
spacingmult, spacingadd, includepad);
}
/**
* @hide
*/
public StaticLayout(CharSequence source, TextPaint paint,
int width, Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
boolean includepad) {
this(source, 0, source.length(), paint, width, align, textDir,
spacingmult, spacingadd, includepad);
}
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align,
@@ -55,9 +66,35 @@ public class StaticLayout extends Layout {
spacingmult, spacingadd, includepad, null, 0);
}
/**
* @hide
*/
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
boolean includepad) {
this(source, bufstart, bufend, paint, outerwidth, align, textDir,
spacingmult, spacingadd, includepad, null, 0);
}
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align,
float spacingmult, float spacingadd,
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
this(source, bufstart, bufend, paint, outerwidth, align,
TextDirectionHeuristics.FIRSTSTRONG_LTR,
spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth);
}
/**
* @hide
*/
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align,
Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
@@ -66,7 +103,7 @@ public class StaticLayout extends Layout {
: (source instanceof Spanned)
? new SpannedEllipsizer(source)
: new Ellipsizer(source),
paint, outerwidth, align, spacingmult, spacingadd);
paint, outerwidth, align, textDir, spacingmult, spacingadd);
/*
* This is annoying, but we can't refer to the layout until
@@ -96,7 +133,7 @@ public class StaticLayout extends Layout {
mMeasured = MeasuredText.obtain();
generate(source, bufstart, bufend, paint, outerwidth, align,
generate(source, bufstart, bufend, paint, outerwidth, align, textDir,
spacingmult, spacingadd, includepad, includepad,
ellipsizedWidth, ellipsize);
@@ -116,7 +153,7 @@ public class StaticLayout extends Layout {
/* package */ void generate(CharSequence source, int bufStart, int bufEnd,
TextPaint paint, int outerWidth,
Alignment align,
Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
boolean includepad, boolean trackpad,
float ellipsizedWidth, TextUtils.TruncateAt ellipsize) {
@@ -157,7 +194,7 @@ public class StaticLayout extends Layout {
LeadingMarginSpan lms = sp[i];
firstWidth -= sp[i].getLeadingMargin(true);
restWidth -= sp[i].getLeadingMargin(false);
// LeadingMarginSpan2 is odd. The count affects all
// leading margin spans, not just this particular one,
// and start from the top of the span, not the top of the
@@ -195,7 +232,7 @@ public class StaticLayout extends Layout {
}
}
measured.setPara(source, paraStart, paraEnd, DIR_REQUEST_DEFAULT_LTR);
measured.setPara(source, paraStart, paraEnd, textDir);
char[] chs = measured.mChars;
float[] widths = measured.mWidths;
byte[] chdirs = measured.mLevels;

View File

@@ -0,0 +1,13 @@
// Copyright 2011 Google Inc. All Rights Reserved.
package android.text;
/**
* Interface for objects that guess at the paragraph direction by examining text.
*
* @hide
*/
public interface TextDirectionHeuristic {
/** @hide */ boolean isRtl(CharSequence text, int start, int end);
/** @hide */ boolean isRtl(char[] text, int start, int count);
}

View File

@@ -0,0 +1,310 @@
// Copyright 2011 Google Inc. All Rights Reserved.
package android.text;
/**
* Some objects that implement TextDirectionHeuristic.
* @hide
*/
public class TextDirectionHeuristics {
/** Always decides that the direction is left to right. */
public static final TextDirectionHeuristic LTR =
new TextDirectionHeuristicInternal(null /* no algorithm */, false);
/** Always decides that the direction is right to left. */
public static final TextDirectionHeuristic RTL =
new TextDirectionHeuristicInternal(null /* no algorithm */, true);
/**
* Determines the direction based on the first strong directional character,
* including bidi format chars, falling back to left to right if it
* finds none. This is the default behavior of the Unicode Bidirectional
* Algorithm.
*/
public static final TextDirectionHeuristic FIRSTSTRONG_LTR =
new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false);
/**
* Determines the direction based on the first strong directional character,
* including bidi format chars, falling back to right to left if it
* finds none. This is similar to the default behavior of the Unicode
* Bidirectional Algorithm, just with different fallback behavior.
*/
public static final TextDirectionHeuristic FIRSTSTRONG_RTL =
new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true);
/**
* If the text contains any strong right to left non-format character, determines
* that the direction is right to left, falling back to left to right if it
* finds none.
*/
public static final TextDirectionHeuristic ANYRTL_LTR =
new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false);
/**
* If the text contains any strong left to right non-format character, determines
* that the direction is left to right, falling back to right to left if it
* finds none.
*/
public static final TextDirectionHeuristic ANYLTR_RTL =
new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_LTR, true);
/**
* Examines only the strong directional non-format characters, and if either
* left to right or right to left characters are 60% or more of this total,
* determines that the direction follows the majority of characters. Falls
* back to left to right if neither direction meets this threshold.
*/
public static final TextDirectionHeuristic CHARCOUNT_LTR =
new TextDirectionHeuristicInternal(CharCount.INSTANCE_DEFAULT, false);
/**
* Examines only the strong directional non-format characters, and if either
* left to right or right to left characters are 60% or more of this total,
* determines that the direction follows the majority of characters. Falls
* back to right to left if neither direction meets this threshold.
*/
public static final TextDirectionHeuristic CHARCOUNT_RTL =
new TextDirectionHeuristicInternal(CharCount.INSTANCE_DEFAULT, true);
private static enum TriState {
TRUE, FALSE, UNKNOWN;
}
/**
* Computes the text direction based on an algorithm. Subclasses implement
* {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the
* direction from the text alone.
* @hide
*/
public static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic {
private final TextDirectionAlgorithm mAlgorithm;
public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) {
mAlgorithm = algorithm;
}
/**
* Return true if the default text direction is rtl.
*/
abstract protected boolean defaultIsRtl();
@Override
public boolean isRtl(CharSequence text, int start, int end) {
if (text == null || start < 0 || end < start || text.length() < end) {
throw new IllegalArgumentException();
}
if (mAlgorithm == null) {
return defaultIsRtl();
}
text = text.subSequence(start, end);
char[] chars = text.toString().toCharArray();
return doCheck(chars, 0, chars.length);
}
@Override
public boolean isRtl(char[] chars, int start, int count) {
if (chars == null || start < 0 || count < 0 || chars.length - count < start) {
throw new IllegalArgumentException();
}
if (mAlgorithm == null) {
return defaultIsRtl();
}
return doCheck(chars, start, count);
}
private boolean doCheck(char[] chars, int start, int count) {
switch(mAlgorithm.checkRtl(chars, start, count)) {
case TRUE:
return true;
case FALSE:
return false;
default:
return defaultIsRtl();
}
}
}
private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl {
private final boolean mDefaultIsRtl;
private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm,
boolean defaultIsRtl) {
super(algorithm);
mDefaultIsRtl = defaultIsRtl;
}
@Override
protected boolean defaultIsRtl() {
return mDefaultIsRtl;
}
}
private static TriState isRtlText(int directionality) {
switch (directionality) {
case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
return TriState.FALSE;
case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
return TriState.TRUE;
default:
return TriState.UNKNOWN;
}
}
private static TriState isRtlTextOrFormat(int directionality) {
switch (directionality) {
case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
return TriState.FALSE;
case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
return TriState.TRUE;
default:
return TriState.UNKNOWN;
}
}
/**
* Interface for an algorithm to guess the direction of a paragraph of text.
*
* @hide
*/
public static interface TextDirectionAlgorithm {
/**
* Returns whether the range of text is RTL according to the algorithm.
*
* @hide
*/
TriState checkRtl(char[] text, int start, int count);
}
/**
* Algorithm that uses the first strong directional character to determine
* the paragraph direction. This is the standard Unicode Bidirectional
* algorithm.
*
* @hide
*/
public static class FirstStrong implements TextDirectionAlgorithm {
@Override
public TriState checkRtl(char[] text, int start, int count) {
TriState result = TriState.UNKNOWN;
for (int i = start, e = start + count; i < e && result == TriState.UNKNOWN; ++i) {
result = isRtlTextOrFormat(Character.getDirectionality(text[i]));
}
return result;
}
private FirstStrong() {
}
public static final FirstStrong INSTANCE = new FirstStrong();
}
/**
* Algorithm that uses the presence of any strong directional non-format
* character (e.g. excludes LRE, LRO, RLE, RLO) to determine the
* direction of text.
*
* @hide
*/
public static class AnyStrong implements TextDirectionAlgorithm {
private final boolean mLookForRtl;
@Override
public TriState checkRtl(char[] text, int start, int count) {
boolean haveUnlookedFor = false;
for (int i = start, e = start + count; i < e; ++i) {
switch (isRtlText(Character.getDirectionality(text[i]))) {
case TRUE:
if (mLookForRtl) {
return TriState.TRUE;
}
haveUnlookedFor = true;
break;
case FALSE:
if (!mLookForRtl) {
return TriState.FALSE;
}
haveUnlookedFor = true;
break;
default:
break;
}
}
if (haveUnlookedFor) {
return mLookForRtl ? TriState.FALSE : TriState.TRUE;
}
return TriState.UNKNOWN;
}
private AnyStrong(boolean lookForRtl) {
this.mLookForRtl = lookForRtl;
}
public static final AnyStrong INSTANCE_RTL = new AnyStrong(true);
public static final AnyStrong INSTANCE_LTR = new AnyStrong(false);
}
/**
* Algorithm that uses the relative proportion of strong directional
* characters (excluding LRE, LRO, RLE, RLO) to determine the direction
* of the paragraph, if the proportion exceeds a given threshold.
*
* @hide
*/
public static class CharCount implements TextDirectionAlgorithm {
private final float mThreshold;
@Override
public TriState checkRtl(char[] text, int start, int count) {
int countLtr = 0;
int countRtl = 0;
for(int i = start, e = start + count; i < e; ++i) {
switch (isRtlText(Character.getDirectionality(text[i]))) {
case TRUE:
++countLtr;
break;
case FALSE:
++countRtl;
break;
default:
break;
}
}
int limit = (int)((countLtr + countRtl) * mThreshold);
if (limit > 0) {
if (countLtr > limit) {
return TriState.FALSE;
}
if (countRtl > limit) {
return TriState.TRUE;
}
}
return TriState.UNKNOWN;
}
private CharCount(float threshold) {
mThreshold = threshold;
}
public static CharCount withThreshold(float threshold) {
if (threshold < 0 || threshold > 1) {
throw new IllegalArgumentException();
}
if (threshold == DEFAULT_THRESHOLD) {
return INSTANCE_DEFAULT;
}
return new CharCount(threshold);
}
public static final float DEFAULT_THRESHOLD = 0.6f;
public static final CharCount INSTANCE_DEFAULT = new CharCount(DEFAULT_THRESHOLD);
}
}

View File

@@ -16,9 +16,6 @@
package android.text;
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
@@ -45,6 +42,9 @@ import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Printer;
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.regex.Pattern;
@@ -1001,13 +1001,37 @@ public class TextUtils {
* will be padded with zero-width spaces to preserve the original
* length and offsets instead of truncating.
* If <code>callback</code> is non-null, it will be called to
* report the start and end of the ellipsized range.
* report the start and end of the ellipsized range. TextDirection
* is determined by the first strong directional character.
*/
public static CharSequence ellipsize(CharSequence text,
TextPaint paint,
float avail, TruncateAt where,
boolean preserveLength,
EllipsizeCallback callback) {
return ellipsize(text, paint, avail, where, preserveLength, callback,
TextDirectionHeuristics.FIRSTSTRONG_LTR);
}
/**
* Returns the original text if it fits in the specified width
* given the properties of the specified Paint,
* or, if it does not fit, a copy with ellipsis character added
* at the specified edge or center.
* If <code>preserveLength</code> is specified, the returned copy
* will be padded with zero-width spaces to preserve the original
* length and offsets instead of truncating.
* If <code>callback</code> is non-null, it will be called to
* report the start and end of the ellipsized range.
*
* @hide
*/
public static CharSequence ellipsize(CharSequence text,
TextPaint paint,
float avail, TruncateAt where,
boolean preserveLength,
EllipsizeCallback callback,
TextDirectionHeuristic textDir) {
if (sEllipsis == null) {
Resources r = Resources.getSystem();
sEllipsis = r.getString(R.string.ellipsis);
@@ -1017,8 +1041,7 @@ public class TextUtils {
MeasuredText mt = MeasuredText.obtain();
try {
float width = setPara(mt, paint, text, 0, text.length(),
Layout.DIR_REQUEST_DEFAULT_LTR);
float width = setPara(mt, paint, text, 0, text.length(), textDir);
if (width <= avail) {
if (callback != null) {
@@ -1108,11 +1131,20 @@ public class TextUtils {
TextPaint p, float avail,
String oneMore,
String more) {
return commaEllipsize(text, p, avail, oneMore, more,
TextDirectionHeuristics.FIRSTSTRONG_LTR);
}
/**
* @hide
*/
public static CharSequence commaEllipsize(CharSequence text, TextPaint p,
float avail, String oneMore, String more, TextDirectionHeuristic textDir) {
MeasuredText mt = MeasuredText.obtain();
try {
int len = text.length();
float width = setPara(mt, p, text, 0, len, Layout.DIR_REQUEST_DEFAULT_LTR);
float width = setPara(mt, p, text, 0, len, textDir);
if (width <= avail) {
return text;
}
@@ -1135,9 +1167,6 @@ public class TextUtils {
int count = 0;
float[] widths = mt.mWidths;
int request = mt.mDir == 1 ? Layout.DIR_REQUEST_LTR :
Layout.DIR_REQUEST_RTL;
MeasuredText tempMt = MeasuredText.obtain();
for (int i = 0; i < len; i++) {
w += widths[i];
@@ -1155,7 +1184,7 @@ public class TextUtils {
}
// XXX this is probably ok, but need to look at it more
tempMt.setPara(format, 0, format.length(), request);
tempMt.setPara(format, 0, format.length(), textDir);
float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
if (w + moreWid <= avail) {
@@ -1175,9 +1204,9 @@ public class TextUtils {
}
private static float setPara(MeasuredText mt, TextPaint paint,
CharSequence text, int start, int end, int bidiRequest) {
CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
mt.setPara(text, start, end, bidiRequest);
mt.setPara(text, start, end, textDir);
float width;
Spanned sp = text instanceof Spanned ? (Spanned) text : null;

View File

@@ -16,13 +16,6 @@
package android.view;
import android.util.FloatProperty;
import android.util.LocaleUtil;
import android.util.Property;
import com.android.internal.R;
import com.android.internal.util.Predicate;
import com.android.internal.view.menu.MenuBuilder;
import android.content.ClipData;
import android.content.Context;
import android.content.res.Configuration;
@@ -53,11 +46,14 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.LocaleUtil;
import android.util.Log;
import android.util.Pool;
import android.util.Poolable;
import android.util.PoolableManager;
import android.util.Pools;
import android.util.Property;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.ContextMenu.ContextMenuInfo;
@@ -72,6 +68,10 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.ScrollBarDrawable;
import com.android.internal.R;
import com.android.internal.util.Predicate;
import com.android.internal.view.menu.MenuBuilder;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -2492,12 +2492,6 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
*/
private boolean mSendingHoverAccessibilityEvents;
/**
* Undefined text direction (used by resolution algorithm).
* @hide
*/
public static final int TEXT_DIRECTION_UNDEFINED = -1;
/**
* Text direction is inherited thru {@link ViewGroup}
* @hide
@@ -2507,7 +2501,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
/**
* Text direction is using "first strong algorithm". The first strong directional character
* determines the paragraph direction. If there is no strong directional character, the
* paragraph direction is the views resolved ayout direction.
* paragraph direction is the view's resolved ayout direction.
*
* @hide
*/
@@ -2516,7 +2510,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
/**
* Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains
* any strong RTL character, otherwise it is LTR if it contains any strong LTR characters.
* If there are neither, the paragraph direction is the views resolved layout direction.
* If there are neither, the paragraph direction is the view's resolved layout direction.
*
* @hide
*/
@@ -2560,7 +2554,6 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "text", mapping = {
@ViewDebug.IntToString(from = TEXT_DIRECTION_UNDEFINED, to = "UNDEFINED"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
@@ -2568,21 +2561,25 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
@ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL")
})
protected int mTextDirection = DEFAULT_TEXT_DIRECTION;
private int mTextDirection = DEFAULT_TEXT_DIRECTION;
/**
* The resolved text direction. If resolution has not yet been done or has been reset, it will
* be equal to {@link #TEXT_DIRECTION_UNDEFINED}. Otherwise it will be either {@link #TEXT_DIRECTION_LTR}
* or {@link #TEXT_DIRECTION_RTL}.
* The resolved text direction. This needs resolution if the value is
* TEXT_DIRECTION_INHERIT. The resolution matches mTextDirection if that is
* not TEXT_DIRECTION_INHERIT, otherwise resolution proceeds up the parent
* chain of the view.
*
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "text", mapping = {
@ViewDebug.IntToString(from = TEXT_DIRECTION_UNDEFINED, to = "UNDEFINED"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_CHAR_COUNT, to = "CHAR_COUNT"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL")
})
protected int mResolvedTextDirection = TEXT_DIRECTION_UNDEFINED;
private int mResolvedTextDirection = TEXT_DIRECTION_INHERIT;
/**
* Consistency verifier for debugging purposes.
@@ -13066,43 +13063,41 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
*
* @return the resolved text direction. Return one of:
*
* {@link #TEXT_DIRECTION_FIRST_STRONG}
* {@link #TEXT_DIRECTION_ANY_RTL},
* {@link #TEXT_DIRECTION_CHAR_COUNT},
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
*
* @hide
*/
public int getResolvedTextDirection() {
if (!isResolvedTextDirection()) {
if (mResolvedTextDirection == TEXT_DIRECTION_INHERIT) {
resolveTextDirection();
}
return mResolvedTextDirection;
}
/**
* Resolve the text direction. Classes that extend View and want to do a specific text direction
* resolution will need to implement this method and set the mResolvedTextDirection to
* either TEXT_DIRECTION_LTR if direction is LTR or TEXT_DIRECTION_RTL if
* direction is RTL.
* Resolve the text direction.
*/
protected void resolveTextDirection() {
if (mTextDirection != TEXT_DIRECTION_INHERIT) {
mResolvedTextDirection = mTextDirection;
return;
}
if (mParent != null && mParent instanceof ViewGroup) {
mResolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection();
return;
}
mResolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG;
}
/**
* Return if the text direction has been resolved or not.
*
* @return true, if resolved and false if not resolved
*
* @hide
*/
public boolean isResolvedTextDirection() {
return (mResolvedTextDirection != TEXT_DIRECTION_UNDEFINED);
}
/**
* Reset resolved text direction. Will be resolved during a call to getResolvedLayoutDirection().
* Reset resolved text direction. Will be resolved during a call to getResolvedTextDirection().
*/
protected void resetResolvedTextDirection() {
mResolvedTextDirection = TEXT_DIRECTION_UNDEFINED;
mResolvedTextDirection = TEXT_DIRECTION_INHERIT;
}
//

View File

@@ -41,6 +41,7 @@ import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
import android.view.animation.Transformation;
import com.android.internal.R;
import com.android.internal.util.Predicate;
@@ -5012,37 +5013,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
/**
* This method will be called during text direction resolution (text direction resolution
* inheritance)
*/
@Override
protected void resolveTextDirection() {
int resolvedTextDirection;
switch(mTextDirection) {
default:
case TEXT_DIRECTION_INHERIT:
// Try to the text direction from the parent layout
if (mParent != null && mParent instanceof ViewGroup) {
resolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection();
} else {
// We reached the top of the View hierarchy, so set the text direction
// heuristic to "first strong"
resolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG;
}
break;
// Pass down the hierarchy the following text direction values
case TEXT_DIRECTION_FIRST_STRONG:
case TEXT_DIRECTION_ANY_RTL:
case TEXT_DIRECTION_CHAR_COUNT:
case TEXT_DIRECTION_LTR:
case TEXT_DIRECTION_RTL:
resolvedTextDirection = mTextDirection;
break;
}
mResolvedTextDirection = resolvedTextDirection;
}
@Override
protected void resetResolvedTextDirection() {
super.resetResolvedTextDirection();

View File

@@ -59,6 +59,13 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
import android.text.TextDirectionHeuristic;
import android.text.TextDirectionHeuristics;
import android.text.TextDirectionHeuristics.AnyStrong;
import android.text.TextDirectionHeuristics.CharCount;
import android.text.TextDirectionHeuristics.FirstStrong;
import android.text.TextDirectionHeuristics.TextDirectionAlgorithm;
import android.text.TextDirectionHeuristics.TextDirectionHeuristicImpl;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -5992,14 +5999,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Layout.Alignment alignment = getLayoutAlignment();
boolean shouldEllipsize = mEllipsize != null && mInput == null;
if (mTextDir == null) {
resolveTextDirection();
}
if (mText instanceof Spannable) {
mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
alignment, mSpacingMult,
alignment, mTextDir, mSpacingMult,
mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
ellipsisWidth);
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -6036,23 +6046,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else if (shouldEllipsize) {
mLayout = new StaticLayout(mTransformed,
0, mTransformed.length(),
mTextPaint, w, alignment, mSpacingMult,
mTextPaint, w, alignment, mTextDir, mSpacingMult,
mSpacingAdd, mIncludePad, mEllipsize,
ellipsisWidth);
} else {
mLayout = new StaticLayout(mTransformed, mTextPaint,
w, alignment, mSpacingMult, mSpacingAdd,
w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad);
}
} else if (shouldEllipsize) {
mLayout = new StaticLayout(mTransformed,
0, mTransformed.length(),
mTextPaint, w, alignment, mSpacingMult,
mTextPaint, w, alignment, mTextDir, mSpacingMult,
mSpacingAdd, mIncludePad, mEllipsize,
ellipsisWidth);
} else {
mLayout = new StaticLayout(mTransformed, mTextPaint,
w, alignment, mSpacingMult, mSpacingAdd,
w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad);
}
}
@@ -6064,7 +6074,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (shouldEllipsize) hintWidth = w;
if (hintBoring == UNKNOWN_BORING) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
@@ -6102,23 +6112,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else if (shouldEllipsize) {
mHintLayout = new StaticLayout(mHint,
0, mHint.length(),
mTextPaint, hintWidth, alignment, mSpacingMult,
mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
mSpacingAdd, mIncludePad, mEllipsize,
ellipsisWidth);
} else {
mHintLayout = new StaticLayout(mHint, mTextPaint,
hintWidth, alignment, mSpacingMult, mSpacingAdd,
hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad);
}
} else if (shouldEllipsize) {
mHintLayout = new StaticLayout(mHint,
0, mHint.length(),
mTextPaint, hintWidth, alignment, mSpacingMult,
mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
mSpacingAdd, mIncludePad, mEllipsize,
ellipsisWidth);
} else {
mHintLayout = new StaticLayout(mHint, mTextPaint,
hintWidth, alignment, mSpacingMult, mSpacingAdd,
hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad);
}
}
@@ -6219,6 +6229,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
BoringLayout.Metrics boring = UNKNOWN_BORING;
BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
if (mTextDir == null) {
resolveTextDirection();
}
int des = -1;
boolean fromexisting = false;
@@ -6231,7 +6245,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (des < 0) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -10439,11 +10453,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mInBatchEditControllers;
}
private class TextViewDirectionHeuristic extends TextDirectionHeuristicImpl {
private TextViewDirectionHeuristic(TextDirectionAlgorithm algorithm) {
super(algorithm);
}
@Override
protected boolean defaultIsRtl() {
return getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
}
/**
* Resolve the text direction.
*
* Text direction of paragraphs in a TextView is determined using a heuristic. If the correct
* text direction cannot be determined by the heuristic, the views resolved layout direction
* text direction cannot be determined by the heuristic, the view's resolved layout direction
* determines the direction.
*
* This heuristic and result is applied individually to each paragraph in a TextView, based on
@@ -10452,157 +10476,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@Override
protected void resolveTextDirection() {
int resolvedTextDirection = TEXT_DIRECTION_UNDEFINED;
switch(mTextDirection) {
super.resolveTextDirection();
int textDir = getResolvedTextDirection();
switch (textDir) {
default:
case TEXT_DIRECTION_INHERIT:
// Try to the text direction from the parent layout. If not possible, then we will
// use the default layout direction to decide later
if (mParent != null && mParent instanceof ViewGroup) {
resolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection();
}
break;
case TEXT_DIRECTION_FIRST_STRONG:
resolvedTextDirection = getTextDirectionFromFirstStrong(mText);
mTextDir = new TextViewDirectionHeuristic(FirstStrong.INSTANCE);
break;
case TEXT_DIRECTION_ANY_RTL:
resolvedTextDirection = getTextDirectionFromAnyRtl(mText);
mTextDir = new TextViewDirectionHeuristic(AnyStrong.INSTANCE_RTL);
break;
case TEXT_DIRECTION_CHAR_COUNT:
resolvedTextDirection = getTextDirectionFromCharCount(mText);
mTextDir = new TextViewDirectionHeuristic(CharCount.INSTANCE_DEFAULT);
break;
case TEXT_DIRECTION_LTR:
resolvedTextDirection = TEXT_DIRECTION_LTR;
mTextDir = TextDirectionHeuristics.LTR;
break;
case TEXT_DIRECTION_RTL:
resolvedTextDirection = TEXT_DIRECTION_RTL;
mTextDir = TextDirectionHeuristics.RTL;
break;
}
// if we have been so far unable to get the text direction from the heuristics, then we are
// falling back using the layout direction
if (resolvedTextDirection == TEXT_DIRECTION_UNDEFINED) {
switch(getResolvedLayoutDirection()) {
default:
case LAYOUT_DIRECTION_LTR:
resolvedTextDirection = TEXT_DIRECTION_LTR;
break;
case LAYOUT_DIRECTION_RTL:
resolvedTextDirection = TEXT_DIRECTION_RTL;
break;
}
}
mResolvedTextDirection = resolvedTextDirection;
}
/**
* Get text direction following the "first strong" heuristic.
*
* @param cs the CharSequence used to get the text direction.
*
* @return {@link #TEXT_DIRECTION_RTL} if direction it RTL, {@link #TEXT_DIRECTION_LTR} if
* direction it LTR or {@link #TEXT_DIRECTION_UNDEFINED} if direction cannot be found.
*/
private static int getTextDirectionFromFirstStrong(final CharSequence cs) {
final int length = cs.length();
if (length == 0) {
return TEXT_DIRECTION_UNDEFINED;
}
for(int i = 0; i < length; i++) {
final char c = cs.charAt(i);
final byte dir = Character.getDirectionality(c);
if (isStrongLtrChar(dir)) {
return TEXT_DIRECTION_LTR;
} else if (isStrongRtlChar(dir)) {
return TEXT_DIRECTION_RTL;
}
}
return TEXT_DIRECTION_UNDEFINED;
}
/**
* Get text direction following the "any RTL" heuristic.
*
* @param cs the CharSequence used to get the text direction.
*
* @return {@link #TEXT_DIRECTION_RTL} if direction it RTL, {@link #TEXT_DIRECTION_LTR} if
* direction it LTR or {@link #TEXT_DIRECTION_UNDEFINED} if direction cannot be found.
*/
private static int getTextDirectionFromAnyRtl(final CharSequence cs) {
final int length = cs.length();
if (length == 0) {
return TEXT_DIRECTION_UNDEFINED;
}
boolean foundStrongLtr = false;
boolean foundStrongRtl = false;
for(int i = 0; i < length; i++) {
final char c = cs.charAt(i);
final byte dir = Character.getDirectionality(c);
if (isStrongLtrChar(dir)) {
foundStrongLtr = true;
} else if (isStrongRtlChar(dir)) {
foundStrongRtl = true;
break;
}
}
if (foundStrongRtl) {
return TEXT_DIRECTION_RTL;
}
if (foundStrongLtr) {
return TEXT_DIRECTION_LTR;
}
return TEXT_DIRECTION_UNDEFINED;
}
/**
* Get text direction following the "char count" heuristic.
*
* @param cs the CharSequence used to get the text direction.
*
* @return {@link #TEXT_DIRECTION_RTL} if direction it RTL, {@link #TEXT_DIRECTION_LTR} if
* direction it LTR or {@link #TEXT_DIRECTION_UNDEFINED} if direction cannot be found.
*/
private int getTextDirectionFromCharCount(CharSequence cs) {
final int length = cs.length();
if (length == 0) {
return TEXT_DIRECTION_UNDEFINED;
}
int countLtr = 0;
int countRtl = 0;
for(int i = 0; i < length; i++) {
final char c = cs.charAt(i);
final byte dir = Character.getDirectionality(c);
if (isStrongLtrChar(dir)) {
countLtr++;
} else if (isStrongRtlChar(dir)) {
countRtl++;
}
}
final float percentLtr = ((float) countLtr) / (countLtr + countRtl);
if (percentLtr > DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD) {
return TEXT_DIRECTION_LTR;
}
final float percentRtl = ((float) countRtl) / (countLtr + countRtl);
if (percentRtl > DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD) {
return TEXT_DIRECTION_RTL;
}
return TEXT_DIRECTION_UNDEFINED;
}
/**
* Return true if the char direction is corresponding to a "strong RTL char" following the
* Unicode Bidirectional Algorithm (UBA).
*/
private static boolean isStrongRtlChar(final byte dir) {
return (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC);
}
/**
* Return true if the char direction is corresponding to a "strong LTR char" following the
* Unicode Bidirectional Algorithm (UBA).
*/
private static boolean isStrongLtrChar(final byte dir) {
return (dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT);
}
/**
@@ -10768,6 +10662,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private BoringLayout mSavedLayout, mSavedHintLayout;
private TextDirectionHeuristic mTextDir = null;
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
private InputFilter[] mFilters = NO_FILTERS;
private static final Spanned EMPTY_SPANNED = new SpannedString("");