304 lines
12 KiB
Java
304 lines
12 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2011 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package android.text;
|
||
|
|
||
|
|
||
|
import android.view.View;
|
||
|
|
||
|
import java.nio.CharBuffer;
|
||
|
|
||
|
/**
|
||
|
* Some objects that implement {@link TextDirectionHeuristic}. Use these with
|
||
|
* the {@link BidiFormatter#unicodeWrap unicodeWrap()} methods in {@link BidiFormatter}.
|
||
|
* Also notice that these direction heuristics correspond to the same types of constants
|
||
|
* provided in the {@link android.view.View} class for {@link android.view.View#setTextDirection
|
||
|
* setTextDirection()}, such as {@link android.view.View#TEXT_DIRECTION_RTL}.
|
||
|
* <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
|
||
|
* you can use the support library's {@link androidx.core.text.TextDirectionHeuristicsCompat}
|
||
|
* class.
|
||
|
*
|
||
|
*/
|
||
|
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);
|
||
|
|
||
|
/**
|
||
|
* Force the paragraph direction to the Locale direction. Falls back to left to right.
|
||
|
*/
|
||
|
public static final TextDirectionHeuristic LOCALE = TextDirectionHeuristicLocale.INSTANCE;
|
||
|
|
||
|
/**
|
||
|
* State constants for taking care about true / false / unknown
|
||
|
*/
|
||
|
private static final int STATE_TRUE = 0;
|
||
|
private static final int STATE_FALSE = 1;
|
||
|
private static final int STATE_UNKNOWN = 2;
|
||
|
|
||
|
/* Returns STATE_TRUE for strong RTL characters, STATE_FALSE for strong LTR characters, and
|
||
|
* STATE_UNKNOWN for everything else.
|
||
|
*/
|
||
|
private static int isRtlCodePoint(int codePoint) {
|
||
|
switch (Character.getDirectionality(codePoint)) {
|
||
|
case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
|
||
|
return STATE_FALSE;
|
||
|
case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
|
||
|
case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
|
||
|
return STATE_TRUE;
|
||
|
case Character.DIRECTIONALITY_UNDEFINED:
|
||
|
// Unassigned characters still have bidi direction, defined at:
|
||
|
// http://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt
|
||
|
|
||
|
if ((0x0590 <= codePoint && codePoint <= 0x08FF) ||
|
||
|
(0xFB1D <= codePoint && codePoint <= 0xFDCF) ||
|
||
|
(0xFDF0 <= codePoint && codePoint <= 0xFDFF) ||
|
||
|
(0xFE70 <= codePoint && codePoint <= 0xFEFF) ||
|
||
|
(0x10800 <= codePoint && codePoint <= 0x10FFF) ||
|
||
|
(0x1E800 <= codePoint && codePoint <= 0x1EFFF)) {
|
||
|
// Unassigned RTL character
|
||
|
return STATE_TRUE;
|
||
|
} else if (
|
||
|
// Potentially-unassigned Default_Ignorable. Ranges are from unassigned
|
||
|
// characters that have Unicode property Other_Default_Ignorable_Code_Point
|
||
|
// plus some enlargening to cover bidi isolates and simplify checks.
|
||
|
(0x2065 <= codePoint && codePoint <= 0x2069) ||
|
||
|
(0xFFF0 <= codePoint && codePoint <= 0xFFF8) ||
|
||
|
(0xE0000 <= codePoint && codePoint <= 0xE0FFF) ||
|
||
|
// Non-character
|
||
|
(0xFDD0 <= codePoint && codePoint <= 0xFDEF) ||
|
||
|
((codePoint & 0xFFFE) == 0xFFFE) ||
|
||
|
// Currency symbol
|
||
|
(0x20A0 <= codePoint && codePoint <= 0x20CF) ||
|
||
|
// Unpaired surrogate
|
||
|
(0xD800 <= codePoint && codePoint <= 0xDFFF)) {
|
||
|
return STATE_UNKNOWN;
|
||
|
} else {
|
||
|
// Unassigned LTR character
|
||
|
return STATE_FALSE;
|
||
|
}
|
||
|
default:
|
||
|
return STATE_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.
|
||
|
*/
|
||
|
private 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(char[] array, int start, int count) {
|
||
|
return isRtl(CharBuffer.wrap(array), start, count);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean isRtl(CharSequence cs, int start, int count) {
|
||
|
if (cs == null || start < 0 || count < 0 || cs.length() - count < start) {
|
||
|
throw new IllegalArgumentException();
|
||
|
}
|
||
|
if (mAlgorithm == null) {
|
||
|
return defaultIsRtl();
|
||
|
}
|
||
|
return doCheck(cs, start, count);
|
||
|
}
|
||
|
|
||
|
private boolean doCheck(CharSequence cs, int start, int count) {
|
||
|
switch(mAlgorithm.checkRtl(cs, start, count)) {
|
||
|
case STATE_TRUE:
|
||
|
return true;
|
||
|
case STATE_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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Interface for an algorithm to guess the direction of a paragraph of text.
|
||
|
*/
|
||
|
private static interface TextDirectionAlgorithm {
|
||
|
/**
|
||
|
* Returns whether the range of text is RTL according to the algorithm.
|
||
|
*/
|
||
|
int checkRtl(CharSequence cs, int start, int count);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Algorithm that uses the first strong directional character to determine the paragraph
|
||
|
* direction. This is the standard Unicode Bidirectional Algorithm (steps P2 and P3), with the
|
||
|
* exception that if no strong character is found, UNKNOWN is returned.
|
||
|
*/
|
||
|
private static class FirstStrong implements TextDirectionAlgorithm {
|
||
|
@Override
|
||
|
public int checkRtl(CharSequence cs, int start, int count) {
|
||
|
int result = STATE_UNKNOWN;
|
||
|
int openIsolateCount = 0;
|
||
|
for (int cp, i = start, end = start + count;
|
||
|
i < end && result == STATE_UNKNOWN;
|
||
|
i += Character.charCount(cp)) {
|
||
|
cp = Character.codePointAt(cs, i);
|
||
|
if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates
|
||
|
openIsolateCount += 1;
|
||
|
} else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI)
|
||
|
if (openIsolateCount > 0) openIsolateCount -= 1;
|
||
|
} else if (openIsolateCount == 0) {
|
||
|
// Only consider the characters outside isolate pairs
|
||
|
result = isRtlCodePoint(cp);
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private FirstStrong() {
|
||
|
}
|
||
|
|
||
|
public static final FirstStrong INSTANCE = new FirstStrong();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Algorithm that uses the presence of any strong directional character of the type indicated
|
||
|
* in the constructor parameter to determine the direction of text.
|
||
|
*
|
||
|
* Characters inside isolate pairs are skipped.
|
||
|
*/
|
||
|
private static class AnyStrong implements TextDirectionAlgorithm {
|
||
|
private final boolean mLookForRtl;
|
||
|
|
||
|
@Override
|
||
|
public int checkRtl(CharSequence cs, int start, int count) {
|
||
|
boolean haveUnlookedFor = false;
|
||
|
int openIsolateCount = 0;
|
||
|
for (int cp, i = start, end = start + count; i < end; i += Character.charCount(cp)) {
|
||
|
cp = Character.codePointAt(cs, i);
|
||
|
if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates
|
||
|
openIsolateCount += 1;
|
||
|
} else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI)
|
||
|
if (openIsolateCount > 0) openIsolateCount -= 1;
|
||
|
} else if (openIsolateCount == 0) {
|
||
|
// Only consider the characters outside isolate pairs
|
||
|
switch (isRtlCodePoint(cp)) {
|
||
|
case STATE_TRUE:
|
||
|
if (mLookForRtl) {
|
||
|
return STATE_TRUE;
|
||
|
}
|
||
|
haveUnlookedFor = true;
|
||
|
break;
|
||
|
case STATE_FALSE:
|
||
|
if (!mLookForRtl) {
|
||
|
return STATE_FALSE;
|
||
|
}
|
||
|
haveUnlookedFor = true;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (haveUnlookedFor) {
|
||
|
return mLookForRtl ? STATE_FALSE : STATE_TRUE;
|
||
|
}
|
||
|
return STATE_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 Locale direction to force the direction of a paragraph.
|
||
|
*/
|
||
|
private static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl {
|
||
|
|
||
|
public TextDirectionHeuristicLocale() {
|
||
|
super(null);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected boolean defaultIsRtl() {
|
||
|
final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault());
|
||
|
return (dir == View.LAYOUT_DIRECTION_RTL);
|
||
|
}
|
||
|
|
||
|
public static final TextDirectionHeuristicLocale INSTANCE =
|
||
|
new TextDirectionHeuristicLocale();
|
||
|
}
|
||
|
}
|