Merge "Implement textDirection heuristic selection."
This commit is contained in:
committed by
Android (Google) Code Review
commit
50e95eba5c
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
13
core/java/android/text/TextDirectionHeuristic.java
Normal file
13
core/java/android/text/TextDirectionHeuristic.java
Normal 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);
|
||||
}
|
||||
310
core/java/android/text/TextDirectionHeuristics.java
Normal file
310
core/java/android/text/TextDirectionHeuristics.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 view’s 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 view’s 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;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 view’s 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("");
|
||||
|
||||
Reference in New Issue
Block a user