blob: 5f9ffc5bf2f014bb7a66a14ed40cfee3c1169f0d [file] [log] [blame]
// 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);
}
}