471 lines
18 KiB
Java
471 lines
18 KiB
Java
package androidx.core.text;
|
|
|
|
import android.text.SpannableStringBuilder;
|
|
import java.util.Locale;
|
|
public final class BidiFormatter {
|
|
private static final int DEFAULT_FLAGS = 2;
|
|
public static final BidiFormatter DEFAULT_LTR_INSTANCE;
|
|
public static final BidiFormatter DEFAULT_RTL_INSTANCE;
|
|
public static final TextDirectionHeuristicCompat DEFAULT_TEXT_DIRECTION_HEURISTIC;
|
|
private static final int DIR_LTR = -1;
|
|
private static final int DIR_RTL = 1;
|
|
private static final int DIR_UNKNOWN = 0;
|
|
private static final String EMPTY_STRING = "";
|
|
private static final int FLAG_STEREO_RESET = 2;
|
|
private static final char LRE = 8234;
|
|
private static final char LRM = 8206;
|
|
private static final String LRM_STRING = Character.toString(8206);
|
|
private static final char PDF = 8236;
|
|
private static final char RLE = 8235;
|
|
private static final char RLM = 8207;
|
|
private static final String RLM_STRING = Character.toString(8207);
|
|
private final TextDirectionHeuristicCompat mDefaultTextDirectionHeuristicCompat;
|
|
private final int mFlags;
|
|
private final boolean mIsRtlContext;
|
|
|
|
public static final class Builder {
|
|
private int mFlags;
|
|
private boolean mIsRtlContext;
|
|
private TextDirectionHeuristicCompat mTextDirectionHeuristicCompat;
|
|
|
|
public Builder() {
|
|
initialize(BidiFormatter.isRtlLocale(Locale.getDefault()));
|
|
}
|
|
|
|
public Builder(Locale locale) {
|
|
initialize(BidiFormatter.isRtlLocale(locale));
|
|
}
|
|
|
|
public Builder(boolean z2) {
|
|
initialize(z2);
|
|
}
|
|
|
|
private static BidiFormatter getDefaultInstanceFromContext(boolean z2) {
|
|
return z2 ? BidiFormatter.DEFAULT_RTL_INSTANCE : BidiFormatter.DEFAULT_LTR_INSTANCE;
|
|
}
|
|
|
|
private void initialize(boolean z2) {
|
|
this.mIsRtlContext = z2;
|
|
this.mTextDirectionHeuristicCompat = BidiFormatter.DEFAULT_TEXT_DIRECTION_HEURISTIC;
|
|
this.mFlags = 2;
|
|
}
|
|
|
|
public BidiFormatter build() {
|
|
return (this.mFlags == 2 && this.mTextDirectionHeuristicCompat == BidiFormatter.DEFAULT_TEXT_DIRECTION_HEURISTIC) ? getDefaultInstanceFromContext(this.mIsRtlContext) : new BidiFormatter(this.mIsRtlContext, this.mFlags, this.mTextDirectionHeuristicCompat);
|
|
}
|
|
|
|
public Builder setTextDirectionHeuristic(TextDirectionHeuristicCompat textDirectionHeuristicCompat) {
|
|
this.mTextDirectionHeuristicCompat = textDirectionHeuristicCompat;
|
|
return this;
|
|
}
|
|
|
|
public Builder stereoReset(boolean z2) {
|
|
if (z2) {
|
|
this.mFlags |= 2;
|
|
} else {
|
|
this.mFlags &= -3;
|
|
}
|
|
return this;
|
|
}
|
|
}
|
|
|
|
public static class DirectionalityEstimator {
|
|
private static final byte[] DIR_TYPE_CACHE = new byte[1792];
|
|
private static final int DIR_TYPE_CACHE_SIZE = 1792;
|
|
private int charIndex;
|
|
private final boolean isHtml;
|
|
private char lastChar;
|
|
private final int length;
|
|
private final CharSequence text;
|
|
|
|
static {
|
|
for (int i = 0; i < 1792; i++) {
|
|
DIR_TYPE_CACHE[i] = Character.getDirectionality(i);
|
|
}
|
|
}
|
|
|
|
public DirectionalityEstimator(CharSequence charSequence, boolean z2) {
|
|
this.text = charSequence;
|
|
this.isHtml = z2;
|
|
this.length = charSequence.length();
|
|
}
|
|
|
|
private static byte getCachedDirectionality(char c2) {
|
|
return c2 < 1792 ? DIR_TYPE_CACHE[c2] : Character.getDirectionality(c2);
|
|
}
|
|
|
|
private byte skipEntityBackward() {
|
|
char charAt;
|
|
int i = this.charIndex;
|
|
do {
|
|
int i2 = this.charIndex;
|
|
if (i2 <= 0) {
|
|
break;
|
|
}
|
|
CharSequence charSequence = this.text;
|
|
int i3 = i2 - 1;
|
|
this.charIndex = i3;
|
|
charAt = charSequence.charAt(i3);
|
|
this.lastChar = charAt;
|
|
if (charAt == '&') {
|
|
return 12;
|
|
}
|
|
} while (charAt != ';');
|
|
this.charIndex = i;
|
|
this.lastChar = ';';
|
|
return 13;
|
|
}
|
|
|
|
private byte skipEntityForward() {
|
|
char charAt;
|
|
do {
|
|
int i = this.charIndex;
|
|
if (i >= this.length) {
|
|
return 12;
|
|
}
|
|
CharSequence charSequence = this.text;
|
|
this.charIndex = i + 1;
|
|
charAt = charSequence.charAt(i);
|
|
this.lastChar = charAt;
|
|
} while (charAt != ';');
|
|
return 12;
|
|
}
|
|
|
|
private byte skipTagBackward() {
|
|
char charAt;
|
|
int i = this.charIndex;
|
|
while (true) {
|
|
int i2 = this.charIndex;
|
|
if (i2 <= 0) {
|
|
break;
|
|
}
|
|
CharSequence charSequence = this.text;
|
|
int i3 = i2 - 1;
|
|
this.charIndex = i3;
|
|
char charAt2 = charSequence.charAt(i3);
|
|
this.lastChar = charAt2;
|
|
if (charAt2 == '<') {
|
|
return 12;
|
|
}
|
|
if (charAt2 == '>') {
|
|
break;
|
|
} else if (charAt2 == '\"' || charAt2 == '\'') {
|
|
do {
|
|
int i4 = this.charIndex;
|
|
if (i4 <= 0) {
|
|
break;
|
|
}
|
|
CharSequence charSequence2 = this.text;
|
|
int i5 = i4 - 1;
|
|
this.charIndex = i5;
|
|
charAt = charSequence2.charAt(i5);
|
|
this.lastChar = charAt;
|
|
} while (charAt != charAt2);
|
|
}
|
|
}
|
|
this.charIndex = i;
|
|
this.lastChar = '>';
|
|
return 13;
|
|
}
|
|
|
|
private byte skipTagForward() {
|
|
char charAt;
|
|
int i = this.charIndex;
|
|
while (true) {
|
|
int i2 = this.charIndex;
|
|
if (i2 < this.length) {
|
|
CharSequence charSequence = this.text;
|
|
this.charIndex = i2 + 1;
|
|
char charAt2 = charSequence.charAt(i2);
|
|
this.lastChar = charAt2;
|
|
if (charAt2 == '>') {
|
|
return 12;
|
|
}
|
|
if (charAt2 == '\"' || charAt2 == '\'') {
|
|
do {
|
|
int i3 = this.charIndex;
|
|
if (i3 >= this.length) {
|
|
break;
|
|
}
|
|
CharSequence charSequence2 = this.text;
|
|
this.charIndex = i3 + 1;
|
|
charAt = charSequence2.charAt(i3);
|
|
this.lastChar = charAt;
|
|
} while (charAt != charAt2);
|
|
}
|
|
} else {
|
|
this.charIndex = i;
|
|
this.lastChar = '<';
|
|
return 13;
|
|
}
|
|
}
|
|
}
|
|
|
|
public byte dirTypeBackward() {
|
|
char charAt = this.text.charAt(this.charIndex - 1);
|
|
this.lastChar = charAt;
|
|
if (Character.isLowSurrogate(charAt)) {
|
|
int codePointBefore = Character.codePointBefore(this.text, this.charIndex);
|
|
this.charIndex -= Character.charCount(codePointBefore);
|
|
return Character.getDirectionality(codePointBefore);
|
|
}
|
|
this.charIndex--;
|
|
byte cachedDirectionality = getCachedDirectionality(this.lastChar);
|
|
if (!this.isHtml) {
|
|
return cachedDirectionality;
|
|
}
|
|
char c2 = this.lastChar;
|
|
return c2 == '>' ? skipTagBackward() : c2 == ';' ? skipEntityBackward() : cachedDirectionality;
|
|
}
|
|
|
|
public byte dirTypeForward() {
|
|
char charAt = this.text.charAt(this.charIndex);
|
|
this.lastChar = charAt;
|
|
if (Character.isHighSurrogate(charAt)) {
|
|
int codePointAt = Character.codePointAt(this.text, this.charIndex);
|
|
this.charIndex = Character.charCount(codePointAt) + this.charIndex;
|
|
return Character.getDirectionality(codePointAt);
|
|
}
|
|
this.charIndex++;
|
|
byte cachedDirectionality = getCachedDirectionality(this.lastChar);
|
|
if (!this.isHtml) {
|
|
return cachedDirectionality;
|
|
}
|
|
char c2 = this.lastChar;
|
|
return c2 == '<' ? skipTagForward() : c2 == '&' ? skipEntityForward() : cachedDirectionality;
|
|
}
|
|
|
|
public int getEntryDir() {
|
|
this.charIndex = 0;
|
|
int i = 0;
|
|
int i2 = 0;
|
|
int i3 = 0;
|
|
while (this.charIndex < this.length && i == 0) {
|
|
byte dirTypeForward = dirTypeForward();
|
|
if (dirTypeForward != 0) {
|
|
if (dirTypeForward == 1 || dirTypeForward == 2) {
|
|
if (i3 == 0) {
|
|
return 1;
|
|
}
|
|
} else if (dirTypeForward != 9) {
|
|
switch (dirTypeForward) {
|
|
case 14:
|
|
case 15:
|
|
i3++;
|
|
i2 = -1;
|
|
break;
|
|
case 16:
|
|
case 17:
|
|
i3++;
|
|
i2 = 1;
|
|
break;
|
|
case 18:
|
|
i3--;
|
|
i2 = 0;
|
|
break;
|
|
}
|
|
}
|
|
} else if (i3 == 0) {
|
|
return -1;
|
|
}
|
|
i = i3;
|
|
}
|
|
if (i == 0) {
|
|
return 0;
|
|
}
|
|
if (i2 != 0) {
|
|
return i2;
|
|
}
|
|
while (this.charIndex > 0) {
|
|
switch (dirTypeBackward()) {
|
|
case 14:
|
|
case 15:
|
|
if (i == i3) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case 16:
|
|
case 17:
|
|
if (i == i3) {
|
|
return 1;
|
|
}
|
|
break;
|
|
case 18:
|
|
i3++;
|
|
continue;
|
|
}
|
|
i3--;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public int getExitDir() {
|
|
this.charIndex = this.length;
|
|
int i = 0;
|
|
int i2 = 0;
|
|
while (this.charIndex > 0) {
|
|
byte dirTypeBackward = dirTypeBackward();
|
|
if (dirTypeBackward != 0) {
|
|
if (dirTypeBackward == 1 || dirTypeBackward == 2) {
|
|
if (i == 0) {
|
|
return 1;
|
|
}
|
|
if (i2 == 0) {
|
|
}
|
|
} else if (dirTypeBackward != 9) {
|
|
switch (dirTypeBackward) {
|
|
case 14:
|
|
case 15:
|
|
if (i2 == i) {
|
|
return -1;
|
|
}
|
|
i--;
|
|
break;
|
|
case 16:
|
|
case 17:
|
|
if (i2 == i) {
|
|
return 1;
|
|
}
|
|
i--;
|
|
break;
|
|
case 18:
|
|
i++;
|
|
break;
|
|
default:
|
|
if (i2 != 0) {
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
} else if (i == 0) {
|
|
return -1;
|
|
} else {
|
|
if (i2 == 0) {
|
|
}
|
|
}
|
|
i2 = i;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static {
|
|
TextDirectionHeuristicCompat textDirectionHeuristicCompat = TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR;
|
|
DEFAULT_TEXT_DIRECTION_HEURISTIC = textDirectionHeuristicCompat;
|
|
DEFAULT_LTR_INSTANCE = new BidiFormatter(false, 2, textDirectionHeuristicCompat);
|
|
DEFAULT_RTL_INSTANCE = new BidiFormatter(true, 2, textDirectionHeuristicCompat);
|
|
}
|
|
|
|
public BidiFormatter(boolean z2, int i, TextDirectionHeuristicCompat textDirectionHeuristicCompat) {
|
|
this.mIsRtlContext = z2;
|
|
this.mFlags = i;
|
|
this.mDefaultTextDirectionHeuristicCompat = textDirectionHeuristicCompat;
|
|
}
|
|
|
|
private static int getEntryDir(CharSequence charSequence) {
|
|
return new DirectionalityEstimator(charSequence, false).getEntryDir();
|
|
}
|
|
|
|
private static int getExitDir(CharSequence charSequence) {
|
|
return new DirectionalityEstimator(charSequence, false).getExitDir();
|
|
}
|
|
|
|
public static BidiFormatter getInstance() {
|
|
return new Builder().build();
|
|
}
|
|
|
|
public static BidiFormatter getInstance(Locale locale) {
|
|
return new Builder(locale).build();
|
|
}
|
|
|
|
public static BidiFormatter getInstance(boolean z2) {
|
|
return new Builder(z2).build();
|
|
}
|
|
|
|
public static boolean isRtlLocale(Locale locale) {
|
|
return TextUtilsCompat.getLayoutDirectionFromLocale(locale) == 1;
|
|
}
|
|
|
|
private String markAfter(CharSequence charSequence, TextDirectionHeuristicCompat textDirectionHeuristicCompat) {
|
|
boolean isRtl = textDirectionHeuristicCompat.isRtl(charSequence, 0, charSequence.length());
|
|
return (this.mIsRtlContext || (!isRtl && getExitDir(charSequence) != 1)) ? this.mIsRtlContext ? (!isRtl || getExitDir(charSequence) == -1) ? RLM_STRING : "" : "" : LRM_STRING;
|
|
}
|
|
|
|
private String markBefore(CharSequence charSequence, TextDirectionHeuristicCompat textDirectionHeuristicCompat) {
|
|
boolean isRtl = textDirectionHeuristicCompat.isRtl(charSequence, 0, charSequence.length());
|
|
return (this.mIsRtlContext || (!isRtl && getEntryDir(charSequence) != 1)) ? this.mIsRtlContext ? (!isRtl || getEntryDir(charSequence) == -1) ? RLM_STRING : "" : "" : LRM_STRING;
|
|
}
|
|
|
|
public boolean getStereoReset() {
|
|
return (this.mFlags & 2) != 0;
|
|
}
|
|
|
|
public boolean isRtl(CharSequence charSequence) {
|
|
return this.mDefaultTextDirectionHeuristicCompat.isRtl(charSequence, 0, charSequence.length());
|
|
}
|
|
|
|
public boolean isRtl(String str) {
|
|
return isRtl((CharSequence) str);
|
|
}
|
|
|
|
public boolean isRtlContext() {
|
|
return this.mIsRtlContext;
|
|
}
|
|
|
|
public CharSequence unicodeWrap(CharSequence charSequence) {
|
|
return unicodeWrap(charSequence, this.mDefaultTextDirectionHeuristicCompat, true);
|
|
}
|
|
|
|
public CharSequence unicodeWrap(CharSequence charSequence, TextDirectionHeuristicCompat textDirectionHeuristicCompat) {
|
|
return unicodeWrap(charSequence, textDirectionHeuristicCompat, true);
|
|
}
|
|
|
|
public CharSequence unicodeWrap(CharSequence charSequence, TextDirectionHeuristicCompat textDirectionHeuristicCompat, boolean z2) {
|
|
if (charSequence == null) {
|
|
return null;
|
|
}
|
|
boolean isRtl = textDirectionHeuristicCompat.isRtl(charSequence, 0, charSequence.length());
|
|
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
|
|
if (getStereoReset() && z2) {
|
|
spannableStringBuilder.append((CharSequence) markBefore(charSequence, isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
|
|
}
|
|
if (isRtl != this.mIsRtlContext) {
|
|
spannableStringBuilder.append(isRtl ? (char) 8235 : 8234);
|
|
spannableStringBuilder.append(charSequence);
|
|
spannableStringBuilder.append((char) 8236);
|
|
} else {
|
|
spannableStringBuilder.append(charSequence);
|
|
}
|
|
if (z2) {
|
|
spannableStringBuilder.append((CharSequence) markAfter(charSequence, isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
|
|
}
|
|
return spannableStringBuilder;
|
|
}
|
|
|
|
public CharSequence unicodeWrap(CharSequence charSequence, boolean z2) {
|
|
return unicodeWrap(charSequence, this.mDefaultTextDirectionHeuristicCompat, z2);
|
|
}
|
|
|
|
public String unicodeWrap(String str) {
|
|
return unicodeWrap(str, this.mDefaultTextDirectionHeuristicCompat, true);
|
|
}
|
|
|
|
public String unicodeWrap(String str, TextDirectionHeuristicCompat textDirectionHeuristicCompat) {
|
|
return unicodeWrap(str, textDirectionHeuristicCompat, true);
|
|
}
|
|
|
|
public String unicodeWrap(String str, TextDirectionHeuristicCompat textDirectionHeuristicCompat, boolean z2) {
|
|
if (str == null) {
|
|
return null;
|
|
}
|
|
return unicodeWrap((CharSequence) str, textDirectionHeuristicCompat, z2).toString();
|
|
}
|
|
|
|
public String unicodeWrap(String str, boolean z2) {
|
|
return unicodeWrap(str, this.mDefaultTextDirectionHeuristicCompat, z2);
|
|
}
|
|
}
|