Merge "Use bidi heuristics correctly in BoringLayout#isBoring()"

This commit is contained in:
Roozbeh Pournader
2016-06-02 18:19:41 +00:00
committed by Android (Google) Code Review

View File

@@ -220,16 +220,6 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
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);
}
/**
* 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)
@@ -239,6 +229,45 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics);
}
/**
* Returns true if the text contains any RTL characters, bidi format characters, or surrogate
* code units.
*/
private static boolean hasAnyInterestingChars(CharSequence text, int textLength) {
final int MAX_BUF_LEN = 500;
final char[] buffer = TextUtils.obtain(MAX_BUF_LEN);
try {
for (int start = 0; start < textLength; start += MAX_BUF_LEN) {
final int end = Math.min(start + MAX_BUF_LEN, textLength);
// No need to worry about getting half codepoints, since we consider surrogate code
// units "interesting" as soon we see one.
TextUtils.getChars(text, start, end, buffer, 0);
final int len = end - start;
for (int i = 0; i < len; i++) {
final char c = buffer[i];
if (c == '\n' || c == '\t' ||
(c >= 0x0590 && c <= 0x08FF) || // RTL scripts
c == 0x200E || // Bidi format character
c == 0x200F || // Bidi format character
(c >= 0x202A && c <= 0x202E) || // Bidi format characters
(c >= 0x2066 && c <= 0x2069) || // Bidi format characters
(c >= 0xD800 && c <= 0xDFFF) || // surrogate pairs
(c >= 0xFB1D && c <= 0xFDFF) || // Hebrew and Arabic presentation forms
(c >= 0xFE70 && c <= 0xFEFE) // Arabic presentation forms
) {
return true;
}
}
}
return false;
} finally {
TextUtils.recycle(buffer);
}
}
/**
* 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)
@@ -247,73 +276,35 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
*/
public static Metrics isBoring(CharSequence text, TextPaint paint,
TextDirectionHeuristic textDir, Metrics metrics) {
final int MAX_BUF_LEN = 500;
final char[] buffer = TextUtils.obtain(MAX_BUF_LEN);
final int textLength = text.length();
boolean boring = true;
outer:
for (int start = 0; start < textLength; start += MAX_BUF_LEN) {
final int end = Math.min(start + MAX_BUF_LEN, textLength);
// No need to worry about getting half codepoints, since we reject surrogate code units
// as non-boring as soon we see one.
TextUtils.getChars(text, start, end, buffer, 0);
final int len = end - start;
for (int i = 0; i < len; i++) {
final char c = buffer[i];
if (c == '\n' || c == '\t' ||
(c >= 0x0590 && c <= 0x08FF) || // RTL scripts
c == 0x200F || // Bidi format character
(c >= 0x202A && c <= 0x202E) || // Bidi format characters
(c >= 0x2066 && c <= 0x2069) || // Bidi format characters
(c >= 0xD800 && c <= 0xDFFF) || // surrogate pairs
(c >= 0xFB1D && c <= 0xFDFF) || // Hebrew and Arabic presentation forms
(c >= 0xFE70 && c <= 0xFEFE) // Arabic presentation forms
) {
boring = false;
break outer;
}
}
// TODO: This looks a little suspicious, and in some cases can result in O(n^2)
// run time. Consider moving outside the loop.
if (textDir != null && textDir.isRtl(buffer, 0, len)) {
boring = false;
break outer;
}
if (hasAnyInterestingChars(text, textLength)) {
return null; // There are some interesting characters. Not boring.
}
TextUtils.recycle(buffer);
if (boring && text instanceof Spanned) {
if (textDir != null && textDir.isRtl(text, 0, textLength)) {
return null; // The heuristic considers the whole text RTL. Not boring.
}
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
if (styles.length > 0) {
boring = false;
return null; // There are some PargraphStyle spans. Not boring.
}
}
if (boring) {
Metrics fm = metrics;
if (fm == null) {
fm = new Metrics();
} else {
fm.reset();
}
TextLine line = TextLine.obtain();
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
fm.width = (int) Math.ceil(line.metrics(fm));
TextLine.recycle(line);
return fm;
Metrics fm = metrics;
if (fm == null) {
fm = new Metrics();
} else {
return null;
fm.reset();
}
TextLine line = TextLine.obtain();
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
fm.width = (int) Math.ceil(line.metrics(fm));
TextLine.recycle(line);
return fm;
}
@Override