diff options
| author | Doug Felt <dougfelt@google.com> | 2011-07-07 11:57:48 -0700 |
|---|---|---|
| committer | Doug Felt <dougfelt@google.com> | 2011-07-14 11:24:33 -0700 |
| commit | cb379120456d8065d742021fc5c66748fc8a11a8 (patch) | |
| tree | 980cb4378ded06d096f8606073a2f32245f6df87 /core/java/android/text/TextDirectionHeuristics.java | |
| parent | ad3f935ce9f3308edc62d56a0059e0761c720077 (diff) | |
Implement textDirection heuristic selection.
Change-Id: I2fcf18de573f2d66494fa5ed61e4273c3c6078c7
Diffstat (limited to 'core/java/android/text/TextDirectionHeuristics.java')
| -rw-r--r-- | core/java/android/text/TextDirectionHeuristics.java | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java new file mode 100644 index 000000000000..5f9ffc5bf2f0 --- /dev/null +++ b/core/java/android/text/TextDirectionHeuristics.java @@ -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); + } +} |
