Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5536c4fd authored by Roozbeh Pournader's avatar Roozbeh Pournader
Browse files

Use bidi heuristics correctly in BoringLayout#isBoring()

in order to determine if the text is RTL, the previous code ran
chunks of the text (in code units) through the TextDirectionHeuristic
instead of the whole text, which could result in misdetection as RTL
in various cases, or missing some cases due to RTL characters getting
split across chunks. Now we look at the whole text instead.

This also refactors the code to make it clearer to understand and
removes an unused signature for isBoring.

Bug: 27702584
Change-Id: I8d98614a0af28c0d4e61af5ab4a27a8a3ab8c9dc
parent 185037ec
Loading
Loading
Loading
Loading
+58 −67
Original line number Diff line number Diff line
@@ -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)
@@ -240,24 +230,18 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
    }

    /**
     * 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)
     * if boring.
     * @hide
     * Returns true if the text contains any RTL characters, bidi format characters, or surrogate
     * code units.
     */
    public static Metrics isBoring(CharSequence text, TextPaint paint,
            TextDirectionHeuristic textDir, Metrics metrics) {
    private static boolean hasAnyInterestingChars(CharSequence text, int textLength) {
        final int MAX_BUF_LEN = 500;
        final char[] buffer = TextUtils.obtain(MAX_BUF_LEN);
        final int textLength = text.length();
        boolean boring = true;

        outer:
        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 reject surrogate code units
            // as non-boring as soon we see one.
                // 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;
@@ -266,6 +250,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback

                    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
@@ -273,30 +258,39 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
                            (c >= 0xFB1D && c <= 0xFDFF) ||  // Hebrew and Arabic presentation forms
                            (c >= 0xFE70 && c <= 0xFEFE) // Arabic presentation forms
                       ) {
                    boring = false;
                    break outer;
                        return true;
                    }
                }

            // 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;
            }
            }

            return false;
        } finally {
            TextUtils.recycle(buffer);
        }
    }

        if (boring && text instanceof Spanned) {
    /**
     * 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)
     * if boring.
     * @hide
     */
    public static Metrics isBoring(CharSequence text, TextPaint paint,
            TextDirectionHeuristic textDir, Metrics metrics) {
        final int textLength = text.length();
        if (hasAnyInterestingChars(text, textLength)) {
           return null;  // There are some interesting characters. Not boring.
        }
        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();
@@ -311,9 +305,6 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
        TextLine.recycle(line);

        return fm;
        } else {
            return null;
        }
    }

    @Override